5package io.flutter.view;
7import static io.flutter.Build.API_LEVELS;
9import android.annotation.SuppressLint;
10import android.annotation.TargetApi;
12import android.content.ContentResolver;
14import android.content.res.Configuration;
15import android.database.ContentObserver;
22import android.provider.Settings;
23import android.text.SpannableString;
25import android.text.style.LocaleSpan;
26import android.text.style.TtsSpan;
29import android.view.WindowInsets;
30import android.view.WindowManager;
31import android.view.accessibility.AccessibilityEvent;
32import android.view.accessibility.AccessibilityManager;
33import android.view.accessibility.AccessibilityNodeInfo;
34import android.view.accessibility.AccessibilityNodeProvider;
35import androidx.annotation.NonNull;
36import androidx.annotation.Nullable;
37import androidx.annotation.RequiresApi;
38import androidx.annotation.VisibleForTesting;
39import io.flutter.BuildConfig;
41import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
42import io.flutter.plugin.platform.PlatformViewsAccessibilityDelegate;
43import io.flutter.util.Predicate;
44import io.flutter.util.ViewUtils;
45import java.nio.ByteBuffer;
46import java.nio.ByteOrder;
47import java.nio.charset.Charset;
49import java.util.regex.Matcher;
50import java.util.regex.Pattern;
81 private static final String TAG =
"AccessibilityBridge";
86 private static final int ACTION_SHOW_ON_SCREEN = 16908342;
88 private static final float SCROLL_EXTENT_FOR_INFINITY = 100000.0f;
89 private static final float SCROLL_POSITION_CAP_FOR_INFINITY = 70000.0f;
90 private static final int ROOT_NODE_ID = 0;
91 private static final int SCROLLABLE_ACTIONS =
92 Action.SCROLL_RIGHT.value
93 | Action.SCROLL_LEFT.value
94 | Action.SCROLL_UP.value
95 |
Action.SCROLL_DOWN.value;
97 private static final int FOCUSABLE_FLAGS =
98 Flag.HAS_CHECKED_STATE.value
99 | Flag.IS_CHECKED.value
100 | Flag.IS_SELECTED.value
101 | Flag.IS_TEXT_FIELD.value
102 | Flag.IS_FOCUSED.value
103 | Flag.HAS_ENABLED_STATE.value
104 | Flag.IS_ENABLED.value
105 | Flag.IS_IN_MUTUALLY_EXCLUSIVE_GROUP.value
106 | Flag.HAS_TOGGLED_STATE.value
107 | Flag.IS_TOGGLED.value
108 | Flag.IS_FOCUSABLE.value
109 | Flag.IS_SLIDER.value;
124 private static final int MIN_ENGINE_GENERATED_NODE_ID = 1 << 16;
127 private static final int BOLD_TEXT_WEIGHT_ADJUSTMENT = 300;
130 private static int FIRST_RESOURCE_ID = 267386881;
133 @NonNull
private final View rootAccessibilityView;
141 @NonNull
private final AccessibilityManager accessibilityManager;
153 @NonNull
private final ContentResolver contentResolver;
167 @NonNull
private final Map<Integer, SemanticsNode> flutterSemanticsTree =
new HashMap<>();
194 private final Map<Integer, CustomAccessibilityAction> customAccessibilityActions =
201 @Nullable
private SemanticsNode accessibilityFocusedSemanticsNode;
208 private Integer embeddedAccessibilityFocusedNodeId;
215 private Integer embeddedInputFocusedNodeId;
219 private int accessibilityFeatureFlags = 0;
231 @Nullable
private SemanticsNode inputFocusedSemanticsNode;
238 @Nullable
private SemanticsNode lastInputFocusedSemanticsNode;
242 @Nullable
private SemanticsNode hoveredObject;
246 return hoveredObject.id;
254 @NonNull
private final List<Integer> flutterNavigationStack =
new ArrayList<>();
257 private int previousRouteId = ROOT_NODE_ID;
262 @NonNull
private Integer lastLeftFrameInset = 0;
264 @Nullable
private OnAccessibilityChangeListener onAccessibilityChangeListener;
272 return accessibleNavigation;
275 private boolean accessibleNavigation =
false;
277 private void setAccessibleNavigation(
boolean value) {
278 if (accessibleNavigation ==
value) {
281 accessibleNavigation =
value;
282 if (accessibleNavigation) {
283 accessibilityFeatureFlags |= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
285 accessibilityFeatureFlags &= ~AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
287 sendLatestAccessibilityFlagsToFlutter();
291 private boolean isReleased =
false;
294 private final AccessibilityChannel.AccessibilityMessageHandler accessibilityMessageHandler =
295 new AccessibilityChannel.AccessibilityMessageHandler() {
298 public void announce(@NonNull String
message) {
299 rootAccessibilityView.announceForAccessibility(
message);
304 public void onTap(
int nodeId) {
305 sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_CLICKED);
310 public void onLongPress(
int nodeId) {
311 sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
316 public void onFocus(
int nodeId) {
317 sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_FOCUSED);
322 public void onTooltip(@NonNull String
message) {
329 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
332 AccessibilityEvent
e =
333 obtainAccessibilityEvent(ROOT_NODE_ID, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
335 sendAccessibilityEvent(
e);
340 public void updateCustomAccessibilityActions(ByteBuffer
buffer, String[] strings) {
341 buffer.order(ByteOrder.LITTLE_ENDIAN);
347 public void updateSemantics(
348 ByteBuffer
buffer, String[] strings, ByteBuffer[] stringAttributeArgs) {
349 buffer.order(ByteOrder.LITTLE_ENDIAN);
350 for (ByteBuffer
args : stringAttributeArgs) {
351 args.order(ByteOrder.LITTLE_ENDIAN);
358 private final AccessibilityManager.AccessibilityStateChangeListener
359 accessibilityStateChangeListener =
360 new AccessibilityManager.AccessibilityStateChangeListener() {
362 public void onAccessibilityStateChanged(
boolean accessibilityEnabled) {
366 if (accessibilityEnabled) {
367 accessibilityChannel.setAccessibilityMessageHandler(accessibilityMessageHandler);
368 accessibilityChannel.onAndroidAccessibilityEnabled();
370 setAccessibleNavigation(
false);
371 accessibilityChannel.setAccessibilityMessageHandler(
null);
372 accessibilityChannel.onAndroidAccessibilityDisabled();
375 if (onAccessibilityChangeListener !=
null) {
376 onAccessibilityChangeListener.onAccessibilityChanged(
377 accessibilityEnabled, accessibilityManager.isTouchExplorationEnabled());
384 private final AccessibilityManager.TouchExplorationStateChangeListener
385 touchExplorationStateChangeListener;
389 private final ContentObserver animationScaleObserver =
390 new ContentObserver(
new Handler()) {
392 public void onChange(
boolean selfChange) {
393 this.onChange(selfChange,
null);
397 public void onChange(
boolean selfChange, Uri uri) {
403 Settings.Global.getString(
404 contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE);
406 boolean shouldAnimationsBeDisabled =
value !=
null &&
value.equals(
"0");
407 if (shouldAnimationsBeDisabled) {
408 accessibilityFeatureFlags |= AccessibilityFeature.DISABLE_ANIMATIONS.value;
410 accessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value;
412 sendLatestAccessibilityFlagsToFlutter();
417 @NonNull View rootAccessibilityView,
419 @NonNull AccessibilityManager accessibilityManager,
420 @NonNull ContentResolver contentResolver,
423 rootAccessibilityView,
424 accessibilityChannel,
425 accessibilityManager,
428 platformViewsAccessibilityDelegate);
433 @NonNull View rootAccessibilityView,
435 @NonNull AccessibilityManager accessibilityManager,
436 @NonNull ContentResolver contentResolver,
439 this.rootAccessibilityView = rootAccessibilityView;
440 this.accessibilityChannel = accessibilityChannel;
441 this.accessibilityManager = accessibilityManager;
442 this.contentResolver = contentResolver;
443 this.accessibilityViewEmbedder = accessibilityViewEmbedder;
444 this.platformViewsAccessibilityDelegate = platformViewsAccessibilityDelegate;
447 accessibilityStateChangeListener.onAccessibilityStateChanged(accessibilityManager.isEnabled());
448 this.accessibilityManager.addAccessibilityStateChangeListener(accessibilityStateChangeListener);
452 touchExplorationStateChangeListener =
453 new AccessibilityManager.TouchExplorationStateChangeListener() {
455 public void onTouchExplorationStateChanged(
boolean isTouchExplorationEnabled) {
459 if (!isTouchExplorationEnabled) {
460 setAccessibleNavigation(
false);
461 onTouchExplorationExit();
464 if (onAccessibilityChangeListener !=
null) {
465 onAccessibilityChangeListener.onAccessibilityChanged(
466 accessibilityManager.isEnabled(), isTouchExplorationEnabled);
470 touchExplorationStateChangeListener.onTouchExplorationStateChanged(
471 accessibilityManager.isTouchExplorationEnabled());
472 this.accessibilityManager.addTouchExplorationStateChangeListener(
473 touchExplorationStateChangeListener);
477 animationScaleObserver.onChange(
false);
478 Uri transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE);
479 this.contentResolver.registerContentObserver(transitionUri,
false, animationScaleObserver);
487 platformViewsAccessibilityDelegate.attachAccessibilityBridge(
this);
497 public void release() {
499 platformViewsAccessibilityDelegate.detachAccessibilityBridge();
500 setOnAccessibilityChangeListener(
null);
501 accessibilityManager.removeAccessibilityStateChangeListener(accessibilityStateChangeListener);
502 accessibilityManager.removeTouchExplorationStateChangeListener(
503 touchExplorationStateChangeListener);
504 contentResolver.unregisterContentObserver(animationScaleObserver);
505 accessibilityChannel.setAccessibilityMessageHandler(
null);
509 public boolean isAccessibilityEnabled() {
510 return accessibilityManager.isEnabled();
514 public boolean isTouchExplorationEnabled() {
515 return accessibilityManager.isTouchExplorationEnabled();
522 public void setOnAccessibilityChangeListener(@Nullable OnAccessibilityChangeListener listener) {
523 this.onAccessibilityChangeListener = listener;
527 private void sendLatestAccessibilityFlagsToFlutter() {
528 accessibilityChannel.setAccessibilityFeatures(accessibilityFeatureFlags);
531 private boolean shouldSetCollectionInfo(
final SemanticsNode semanticsNode) {
541 return semanticsNode.scrollChildren > 0
542 && (SemanticsNode.nullableHasAncestor(
543 accessibilityFocusedSemanticsNode, o -> o == semanticsNode)
544 || !SemanticsNode.nullableHasAncestor(
545 accessibilityFocusedSemanticsNode, o -> o.hasFlag(Flag.HAS_IMPLICIT_SCROLLING)));
548 @TargetApi(API_LEVELS.API_31)
549 @RequiresApi(API_LEVELS.API_31)
550 private
void setBoldTextFlag() {
551 if (rootAccessibilityView ==
null || rootAccessibilityView.getResources() ==
null) {
554 int fontWeightAdjustment =
555 rootAccessibilityView.getResources().getConfiguration().fontWeightAdjustment;
557 fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED
558 && fontWeightAdjustment >= BOLD_TEXT_WEIGHT_ADJUSTMENT;
561 accessibilityFeatureFlags |= AccessibilityFeature.BOLD_TEXT.value;
563 accessibilityFeatureFlags &= AccessibilityFeature.BOLD_TEXT.value;
565 sendLatestAccessibilityFlagsToFlutter();
569 public AccessibilityNodeInfo obtainAccessibilityNodeInfo(View rootView) {
570 return AccessibilityNodeInfo.obtain(rootView);
574 public AccessibilityNodeInfo obtainAccessibilityNodeInfo(View rootView,
int virtualViewId) {
575 return AccessibilityNodeInfo.obtain(rootView, virtualViewId);
602 @SuppressWarnings(
"deprecation")
604 @SuppressLint(
"NewApi")
605 public AccessibilityNodeInfo createAccessibilityNodeInfo(
int virtualViewId) {
606 setAccessibleNavigation(
true);
607 if (virtualViewId >= MIN_ENGINE_GENERATED_NODE_ID) {
610 return accessibilityViewEmbedder.createAccessibilityNodeInfo(virtualViewId);
613 if (virtualViewId == View.NO_ID) {
614 AccessibilityNodeInfo
result = obtainAccessibilityNodeInfo(rootAccessibilityView);
615 rootAccessibilityView.onInitializeAccessibilityNodeInfo(
result);
618 if (flutterSemanticsTree.containsKey(ROOT_NODE_ID)) {
619 result.addChild(rootAccessibilityView, ROOT_NODE_ID);
621 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_24) {
622 result.setImportantForAccessibility(
false);
627 SemanticsNode semanticsNode = flutterSemanticsTree.get(virtualViewId);
628 if (semanticsNode ==
null) {
640 if (semanticsNode.platformViewId != -1) {
641 if (platformViewsAccessibilityDelegate.usesVirtualDisplay(semanticsNode.platformViewId)) {
643 platformViewsAccessibilityDelegate.getPlatformViewById(semanticsNode.platformViewId);
644 if (embeddedView ==
null) {
648 return accessibilityViewEmbedder.getRootNode(embeddedView, semanticsNode.id,
bounds);
652 AccessibilityNodeInfo
result =
653 obtainAccessibilityNodeInfo(rootAccessibilityView, virtualViewId);
657 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_24) {
658 result.setImportantForAccessibility(isImportant(semanticsNode));
662 result.setViewIdResourceName(
"");
663 if (semanticsNode.identifier !=
null) {
664 result.setViewIdResourceName(semanticsNode.identifier);
666 result.setPackageName(rootAccessibilityView.getContext().getPackageName());
667 result.setClassName(
"android.view.View");
668 result.setSource(rootAccessibilityView, virtualViewId);
669 result.setFocusable(semanticsNode.isFocusable());
670 if (inputFocusedSemanticsNode !=
null) {
671 result.setFocused(inputFocusedSemanticsNode.id == virtualViewId);
674 if (accessibilityFocusedSemanticsNode !=
null) {
675 result.setAccessibilityFocused(accessibilityFocusedSemanticsNode.id == virtualViewId);
678 if (semanticsNode.hasFlag(Flag.IS_TEXT_FIELD)) {
679 result.setPassword(semanticsNode.hasFlag(Flag.IS_OBSCURED));
680 if (!semanticsNode.hasFlag(Flag.IS_READ_ONLY)) {
681 result.setClassName(
"android.widget.EditText");
683 result.setEditable(!semanticsNode.hasFlag(Flag.IS_READ_ONLY));
684 if (semanticsNode.textSelectionBase != -1 && semanticsNode.textSelectionExtent != -1) {
685 result.setTextSelection(semanticsNode.textSelectionBase, semanticsNode.textSelectionExtent);
690 if (accessibilityFocusedSemanticsNode !=
null
691 && accessibilityFocusedSemanticsNode.id == virtualViewId) {
692 result.setLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
696 int granularities = 0;
697 if (semanticsNode.hasAction(
Action.MOVE_CURSOR_FORWARD_BY_CHARACTER)) {
698 result.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
699 granularities |= AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER;
701 if (semanticsNode.hasAction(
Action.MOVE_CURSOR_BACKWARD_BY_CHARACTER)) {
702 result.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
703 granularities |= AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER;
705 if (semanticsNode.hasAction(
Action.MOVE_CURSOR_FORWARD_BY_WORD)) {
706 result.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
707 granularities |= AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD;
709 if (semanticsNode.hasAction(
Action.MOVE_CURSOR_BACKWARD_BY_WORD)) {
710 result.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
711 granularities |= AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD;
713 result.setMovementGranularities(granularities);
714 if (semanticsNode.maxValueLength >= 0) {
717 final int length = semanticsNode.value ==
null ? 0 : semanticsNode.value.length();
718 int a =
length - semanticsNode.currentValueLength + semanticsNode.maxValueLength;
720 length - semanticsNode.currentValueLength + semanticsNode.maxValueLength);
726 if (semanticsNode.hasAction(
Action.SET_SELECTION)) {
727 result.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
729 if (semanticsNode.hasAction(
Action.COPY)) {
730 result.addAction(AccessibilityNodeInfo.ACTION_COPY);
732 if (semanticsNode.hasAction(
Action.CUT)) {
733 result.addAction(AccessibilityNodeInfo.ACTION_CUT);
735 if (semanticsNode.hasAction(
Action.PASTE)) {
736 result.addAction(AccessibilityNodeInfo.ACTION_PASTE);
739 if (semanticsNode.hasAction(
Action.SET_TEXT)) {
740 result.addAction(AccessibilityNodeInfo.ACTION_SET_TEXT);
743 if (semanticsNode.hasFlag(Flag.IS_BUTTON) || semanticsNode.hasFlag(Flag.IS_LINK)) {
744 result.setClassName(
"android.widget.Button");
746 if (semanticsNode.hasFlag(Flag.IS_IMAGE)) {
747 result.setClassName(
"android.widget.ImageView");
751 if (semanticsNode.hasAction(
Action.DISMISS)) {
752 result.setDismissable(
true);
753 result.addAction(AccessibilityNodeInfo.ACTION_DISMISS);
756 if (semanticsNode.parent !=
null) {
757 if (BuildConfig.DEBUG && semanticsNode.id <= ROOT_NODE_ID) {
758 Log.e(TAG,
"Semantics node id is not > ROOT_NODE_ID.");
760 result.setParent(rootAccessibilityView, semanticsNode.parent.id);
762 if (BuildConfig.DEBUG && semanticsNode.id != ROOT_NODE_ID) {
763 Log.e(TAG,
"Semantics node id does not equal ROOT_NODE_ID.");
765 result.setParent(rootAccessibilityView);
768 if (semanticsNode.previousNodeId != -1 &&
Build.VERSION.SDK_INT >= API_LEVELS.API_22) {
769 result.setTraversalAfter(rootAccessibilityView, semanticsNode.previousNodeId);
773 if (semanticsNode.parent !=
null) {
774 Rect parentBounds = semanticsNode.parent.getGlobalRect();
776 boundsInParent.offset(-parentBounds.left, -parentBounds.top);
777 result.setBoundsInParent(boundsInParent);
781 final Rect boundsInScreen = getBoundsInScreen(
bounds);
782 result.setBoundsInScreen(boundsInScreen);
783 result.setVisibleToUser(
true);
785 !semanticsNode.hasFlag(Flag.HAS_ENABLED_STATE) || semanticsNode.hasFlag(Flag.IS_ENABLED));
787 if (semanticsNode.hasAction(
Action.TAP)) {
788 if (semanticsNode.onTapOverride !=
null) {
790 new AccessibilityNodeInfo.AccessibilityAction(
791 AccessibilityNodeInfo.ACTION_CLICK, semanticsNode.onTapOverride.hint));
792 result.setClickable(
true);
794 result.addAction(AccessibilityNodeInfo.ACTION_CLICK);
795 result.setClickable(
true);
798 if (semanticsNode.hasAction(
Action.LONG_PRESS)) {
799 if (semanticsNode.onLongPressOverride !=
null) {
801 new AccessibilityNodeInfo.AccessibilityAction(
802 AccessibilityNodeInfo.ACTION_LONG_CLICK, semanticsNode.onLongPressOverride.hint));
803 result.setLongClickable(
true);
805 result.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
806 result.setLongClickable(
true);
809 if (semanticsNode.hasAction(
Action.SCROLL_LEFT)
810 || semanticsNode.hasAction(
Action.SCROLL_UP)
811 || semanticsNode.hasAction(
Action.SCROLL_RIGHT)
812 || semanticsNode.hasAction(
Action.SCROLL_DOWN)) {
813 result.setScrollable(
true);
829 if (semanticsNode.hasFlag(Flag.HAS_IMPLICIT_SCROLLING)) {
830 if (semanticsNode.hasAction(
Action.SCROLL_LEFT)
831 || semanticsNode.hasAction(
Action.SCROLL_RIGHT)) {
832 if (shouldSetCollectionInfo(semanticsNode)) {
834 AccessibilityNodeInfo.CollectionInfo.obtain(
836 semanticsNode.scrollChildren,
840 result.setClassName(
"android.widget.HorizontalScrollView");
843 if (shouldSetCollectionInfo(semanticsNode)) {
845 AccessibilityNodeInfo.CollectionInfo.obtain(
846 semanticsNode.scrollChildren,
851 result.setClassName(
"android.widget.ScrollView");
858 if (semanticsNode.hasAction(
Action.SCROLL_LEFT)
859 || semanticsNode.hasAction(
Action.SCROLL_UP)) {
860 result.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
862 if (semanticsNode.hasAction(
Action.SCROLL_RIGHT)
863 || semanticsNode.hasAction(
Action.SCROLL_DOWN)) {
864 result.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
867 if (semanticsNode.hasAction(
Action.INCREASE) || semanticsNode.hasAction(
Action.DECREASE)) {
870 result.setClassName(
"android.widget.SeekBar");
871 if (semanticsNode.hasAction(
Action.INCREASE)) {
872 result.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
874 if (semanticsNode.hasAction(
Action.DECREASE)) {
875 result.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
878 if (semanticsNode.hasFlag(Flag.IS_LIVE_REGION)) {
879 result.setLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
884 if (semanticsNode.hasFlag(Flag.IS_TEXT_FIELD)) {
885 result.setText(semanticsNode.getValue());
886 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
887 result.setHintText(semanticsNode.getTextFieldHint());
889 }
else if (!semanticsNode.hasFlag(Flag.SCOPES_ROUTE)) {
890 CharSequence
content = semanticsNode.getValueLabelHint();
891 if (
Build.VERSION.SDK_INT < API_LEVELS.API_28) {
892 if (semanticsNode.tooltip !=
null) {
904 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
905 if (semanticsNode.tooltip !=
null) {
906 result.setTooltipText(semanticsNode.tooltip);
910 boolean hasCheckedState = semanticsNode.hasFlag(Flag.HAS_CHECKED_STATE);
911 boolean hasToggledState = semanticsNode.hasFlag(Flag.HAS_TOGGLED_STATE);
912 if (BuildConfig.DEBUG && (hasCheckedState && hasToggledState)) {
913 Log.e(TAG,
"Expected semanticsNode to have checked state and toggled state.");
915 result.setCheckable(hasCheckedState || hasToggledState);
916 if (hasCheckedState) {
917 result.setChecked(semanticsNode.hasFlag(Flag.IS_CHECKED));
918 if (semanticsNode.hasFlag(Flag.IS_IN_MUTUALLY_EXCLUSIVE_GROUP)) {
919 result.setClassName(
"android.widget.RadioButton");
921 result.setClassName(
"android.widget.CheckBox");
923 }
else if (hasToggledState) {
924 result.setChecked(semanticsNode.hasFlag(Flag.IS_TOGGLED));
925 result.setClassName(
"android.widget.Switch");
927 result.setSelected(semanticsNode.hasFlag(Flag.IS_SELECTED));
930 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
931 result.setHeading(semanticsNode.hasFlag(Flag.IS_HEADER));
935 if (accessibilityFocusedSemanticsNode !=
null
936 && accessibilityFocusedSemanticsNode.id == virtualViewId) {
937 result.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
939 result.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
943 if (semanticsNode.customAccessibilityActions !=
null) {
944 for (CustomAccessibilityAction
action : semanticsNode.customAccessibilityActions) {
946 new AccessibilityNodeInfo.AccessibilityAction(
action.resourceId,
action.label));
950 for (SemanticsNode child : semanticsNode.childrenInTraversalOrder) {
951 if (child.hasFlag(Flag.IS_HIDDEN)) {
954 if (child.platformViewId != -1) {
956 platformViewsAccessibilityDelegate.getPlatformViewById(child.platformViewId);
965 if (!platformViewsAccessibilityDelegate.usesVirtualDisplay(child.platformViewId)) {
966 result.addChild(embeddedView);
970 result.addChild(rootAccessibilityView, child.id);
975 private boolean isImportant(SemanticsNode node) {
976 if (node.hasFlag(Flag.SCOPES_ROUTE)) {
980 if (node.getValueLabelHint() !=
null) {
985 return (node.actions & ~systemAction) != 0;
996 int[] locationOnScreen =
new int[2];
997 rootAccessibilityView.getLocationOnScreen(locationOnScreen);
998 boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
999 return boundsInScreen;
1016 public boolean performAction(
1017 int virtualViewId,
int accessibilityAction, @Nullable Bundle arguments) {
1018 if (virtualViewId >= MIN_ENGINE_GENERATED_NODE_ID) {
1021 boolean didPerform =
1022 accessibilityViewEmbedder.performAction(virtualViewId, accessibilityAction, arguments);
1024 && accessibilityAction == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
1025 embeddedAccessibilityFocusedNodeId =
null;
1029 SemanticsNode semanticsNode = flutterSemanticsTree.get(virtualViewId);
1030 if (semanticsNode ==
null) {
1033 switch (accessibilityAction) {
1034 case AccessibilityNodeInfo.ACTION_CLICK:
1039 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.TAP);
1042 case AccessibilityNodeInfo.ACTION_LONG_CLICK:
1047 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.LONG_PRESS);
1050 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1052 if (semanticsNode.hasAction(
Action.SCROLL_UP)) {
1053 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.SCROLL_UP);
1054 }
else if (semanticsNode.hasAction(
Action.SCROLL_LEFT)) {
1056 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.SCROLL_LEFT);
1057 }
else if (semanticsNode.hasAction(
Action.INCREASE)) {
1058 semanticsNode.value = semanticsNode.increasedValue;
1059 semanticsNode.valueAttributes = semanticsNode.increasedValueAttributes;
1061 sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
1062 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.INCREASE);
1068 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
1070 if (semanticsNode.hasAction(
Action.SCROLL_DOWN)) {
1071 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.SCROLL_DOWN);
1072 }
else if (semanticsNode.hasAction(
Action.SCROLL_RIGHT)) {
1074 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.SCROLL_RIGHT);
1075 }
else if (semanticsNode.hasAction(
Action.DECREASE)) {
1076 semanticsNode.value = semanticsNode.decreasedValue;
1077 semanticsNode.valueAttributes = semanticsNode.decreasedValueAttributes;
1079 sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
1080 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.DECREASE);
1086 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
1088 return performCursorMoveAction(semanticsNode, virtualViewId, arguments,
false);
1090 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
1092 return performCursorMoveAction(semanticsNode, virtualViewId, arguments,
true);
1094 case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
1099 if (accessibilityFocusedSemanticsNode !=
null
1100 && accessibilityFocusedSemanticsNode.id == virtualViewId) {
1101 accessibilityFocusedSemanticsNode =
null;
1103 if (embeddedAccessibilityFocusedNodeId !=
null
1104 && embeddedAccessibilityFocusedNodeId == virtualViewId) {
1105 embeddedAccessibilityFocusedNodeId =
null;
1107 accessibilityChannel.dispatchSemanticsAction(
1108 virtualViewId,
Action.DID_LOSE_ACCESSIBILITY_FOCUS);
1109 sendAccessibilityEvent(
1110 virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
1113 case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
1115 if (accessibilityFocusedSemanticsNode ==
null) {
1119 rootAccessibilityView.invalidate();
1123 accessibilityFocusedSemanticsNode = semanticsNode;
1125 accessibilityChannel.dispatchSemanticsAction(
1126 virtualViewId,
Action.DID_GAIN_ACCESSIBILITY_FOCUS);
1128 HashMap<String, Object>
message =
new HashMap<>();
1129 message.put(
"type",
"didGainFocus");
1130 message.put(
"nodeId", semanticsNode.id);
1131 accessibilityChannel.channel.send(
message);
1133 sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
1135 if (semanticsNode.hasAction(
Action.INCREASE)
1136 || semanticsNode.hasAction(
Action.DECREASE)) {
1138 sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
1143 case ACTION_SHOW_ON_SCREEN:
1145 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.SHOW_ON_SCREEN);
1148 case AccessibilityNodeInfo.ACTION_SET_SELECTION:
1150 final Map<String, Integer> selection =
new HashMap<>();
1151 final boolean hasSelection =
1153 && arguments.containsKey(
1154 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT)
1155 && arguments.containsKey(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT);
1159 arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT));
1162 arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT));
1165 selection.put(
"base", semanticsNode.textSelectionExtent);
1166 selection.put(
"extent", semanticsNode.textSelectionExtent);
1168 accessibilityChannel.dispatchSemanticsAction(
1169 virtualViewId,
Action.SET_SELECTION, selection);
1173 SemanticsNode node = flutterSemanticsTree.get(virtualViewId);
1174 node.textSelectionBase = selection.get(
"base");
1175 node.textSelectionExtent = selection.get(
"extent");
1178 case AccessibilityNodeInfo.ACTION_COPY:
1180 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.COPY);
1183 case AccessibilityNodeInfo.ACTION_CUT:
1185 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.CUT);
1188 case AccessibilityNodeInfo.ACTION_PASTE:
1190 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.PASTE);
1193 case AccessibilityNodeInfo.ACTION_DISMISS:
1195 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.DISMISS);
1198 case AccessibilityNodeInfo.ACTION_SET_TEXT:
1200 return performSetText(semanticsNode, virtualViewId, arguments);
1204 final int flutterId = accessibilityAction - FIRST_RESOURCE_ID;
1205 CustomAccessibilityAction contextAction = customAccessibilityActions.get(flutterId);
1206 if (contextAction !=
null) {
1207 accessibilityChannel.dispatchSemanticsAction(
1208 virtualViewId,
Action.CUSTOM_ACTION, contextAction.id);
1219 private boolean performCursorMoveAction(
1220 @NonNull SemanticsNode semanticsNode,
1222 @NonNull Bundle arguments,
1224 final int granularity =
1225 arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
1226 final boolean extendSelection =
1227 arguments.getBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
1230 final int previousTextSelectionBase = semanticsNode.textSelectionBase;
1231 final int previousTextSelectionExtent = semanticsNode.textSelectionExtent;
1232 predictCursorMovement(semanticsNode, granularity, forward, extendSelection);
1234 if (previousTextSelectionBase != semanticsNode.textSelectionBase
1235 || previousTextSelectionExtent != semanticsNode.textSelectionExtent) {
1236 final String
value = semanticsNode.value !=
null ? semanticsNode.value :
"";
1237 final AccessibilityEvent selectionEvent =
1238 obtainAccessibilityEvent(
1239 semanticsNode.id, AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
1240 selectionEvent.getText().add(
value);
1241 selectionEvent.setFromIndex(semanticsNode.textSelectionBase);
1242 selectionEvent.setToIndex(semanticsNode.textSelectionExtent);
1243 selectionEvent.setItemCount(
value.length());
1244 sendAccessibilityEvent(selectionEvent);
1247 switch (granularity) {
1248 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER:
1250 if (forward && semanticsNode.hasAction(
Action.MOVE_CURSOR_FORWARD_BY_CHARACTER)) {
1251 accessibilityChannel.dispatchSemanticsAction(
1252 virtualViewId,
Action.MOVE_CURSOR_FORWARD_BY_CHARACTER, extendSelection);
1255 if (!forward && semanticsNode.hasAction(
Action.MOVE_CURSOR_BACKWARD_BY_CHARACTER)) {
1256 accessibilityChannel.dispatchSemanticsAction(
1257 virtualViewId,
Action.MOVE_CURSOR_BACKWARD_BY_CHARACTER, extendSelection);
1262 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD:
1263 if (forward && semanticsNode.hasAction(
Action.MOVE_CURSOR_FORWARD_BY_WORD)) {
1264 accessibilityChannel.dispatchSemanticsAction(
1265 virtualViewId,
Action.MOVE_CURSOR_FORWARD_BY_WORD, extendSelection);
1268 if (!forward && semanticsNode.hasAction(
Action.MOVE_CURSOR_BACKWARD_BY_WORD)) {
1269 accessibilityChannel.dispatchSemanticsAction(
1270 virtualViewId,
Action.MOVE_CURSOR_BACKWARD_BY_WORD, extendSelection);
1274 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE:
1275 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH:
1276 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE:
1282 private void predictCursorMovement(
1283 @NonNull SemanticsNode node,
int granularity,
boolean forward,
boolean extendSelection) {
1284 if (node.textSelectionExtent < 0 || node.textSelectionBase < 0) {
1288 switch (granularity) {
1289 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER:
1290 if (forward && node.textSelectionExtent < node.value.length()) {
1291 node.textSelectionExtent += 1;
1292 }
else if (!forward && node.textSelectionExtent > 0) {
1293 node.textSelectionExtent -= 1;
1296 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD:
1297 if (forward && node.textSelectionExtent < node.value.length()) {
1298 Pattern pattern = Pattern.compile(
"\\p{L}(\\b)");
1299 Matcher
result = pattern.matcher(node.value.substring(node.textSelectionExtent));
1303 node.textSelectionExtent +=
result.start(1);
1305 node.textSelectionExtent = node.value.length();
1307 }
else if (!forward && node.textSelectionExtent > 0) {
1309 Pattern pattern = Pattern.compile(
"(?s:.*)(\\b)\\p{L}");
1310 Matcher
result = pattern.matcher(node.value.substring(0, node.textSelectionExtent));
1312 node.textSelectionExtent =
result.start(1);
1316 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE:
1317 if (forward && node.textSelectionExtent < node.value.length()) {
1319 Pattern pattern = Pattern.compile(
"(?!^)(\\n)");
1320 Matcher
result = pattern.matcher(node.value.substring(node.textSelectionExtent));
1322 node.textSelectionExtent +=
result.start(1);
1324 node.textSelectionExtent = node.value.length();
1326 }
else if (!forward && node.textSelectionExtent > 0) {
1328 Pattern pattern = Pattern.compile(
"(?s:.*)(\\n)");
1329 Matcher
result = pattern.matcher(node.value.substring(0, node.textSelectionExtent));
1331 node.textSelectionExtent =
result.start(1);
1333 node.textSelectionExtent = 0;
1337 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH:
1338 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE:
1340 node.textSelectionExtent = node.value.length();
1342 node.textSelectionExtent = 0;
1346 if (!extendSelection) {
1347 node.textSelectionBase = node.textSelectionExtent;
1355 private boolean performSetText(SemanticsNode node,
int virtualViewId, @NonNull Bundle arguments) {
1356 String newText =
"";
1357 if (arguments !=
null
1358 && arguments.containsKey(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE)) {
1359 newText = arguments.getString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
1361 accessibilityChannel.dispatchSemanticsAction(virtualViewId,
Action.SET_TEXT, newText);
1364 node.value = newText;
1365 node.valueAttributes =
null;
1385 public AccessibilityNodeInfo findFocus(
int focus) {
1387 case AccessibilityNodeInfo.FOCUS_INPUT:
1389 if (inputFocusedSemanticsNode !=
null) {
1390 return createAccessibilityNodeInfo(inputFocusedSemanticsNode.id);
1392 if (embeddedInputFocusedNodeId !=
null) {
1393 return createAccessibilityNodeInfo(embeddedInputFocusedNodeId);
1397 case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY:
1399 if (accessibilityFocusedSemanticsNode !=
null) {
1400 return createAccessibilityNodeInfo(accessibilityFocusedSemanticsNode.id);
1402 if (embeddedAccessibilityFocusedNodeId !=
null) {
1403 return createAccessibilityNodeInfo(embeddedAccessibilityFocusedNodeId);
1411 private SemanticsNode getRootSemanticsNode() {
1412 if (BuildConfig.DEBUG && !flutterSemanticsTree.containsKey(0)) {
1413 Log.e(TAG,
"Attempted to getRootSemanticsNode without a root semantics node.");
1415 return flutterSemanticsTree.get(0);
1428 private SemanticsNode getOrCreateSemanticsNode(
int id) {
1429 SemanticsNode semanticsNode = flutterSemanticsTree.get(
id);
1430 if (semanticsNode ==
null) {
1431 semanticsNode =
new SemanticsNode(
this);
1432 semanticsNode.id =
id;
1433 flutterSemanticsTree.put(
id, semanticsNode);
1435 return semanticsNode;
1449 private CustomAccessibilityAction getOrCreateAccessibilityAction(
int id) {
1450 CustomAccessibilityAction
action = customAccessibilityActions.get(
id);
1452 action =
new CustomAccessibilityAction();
1454 action.resourceId =
id + FIRST_RESOURCE_ID;
1455 customAccessibilityActions.put(
id,
action);
1470 public boolean onAccessibilityHoverEvent(MotionEvent
event) {
1471 return onAccessibilityHoverEvent(
event,
false);
1488 public boolean onAccessibilityHoverEvent(MotionEvent
event,
boolean ignorePlatformViews) {
1489 if (!accessibilityManager.isTouchExplorationEnabled()) {
1492 if (flutterSemanticsTree.isEmpty()) {
1496 SemanticsNode semanticsNodeUnderCursor =
1497 getRootSemanticsNode()
1498 .hitTest(
new float[] {
event.getX(),
event.getY(), 0, 1}, ignorePlatformViews);
1501 if (semanticsNodeUnderCursor !=
null && semanticsNodeUnderCursor.platformViewId != -1) {
1502 if (ignorePlatformViews) {
1505 return accessibilityViewEmbedder.onAccessibilityHoverEvent(
1506 semanticsNodeUnderCursor.id,
event);
1509 if (
event.getAction() == MotionEvent.ACTION_HOVER_ENTER
1510 ||
event.getAction() == MotionEvent.ACTION_HOVER_MOVE) {
1511 handleTouchExploration(
event.getX(),
event.getY(), ignorePlatformViews);
1512 }
else if (
event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
1513 onTouchExplorationExit();
1515 Log.d(
"flutter",
"unexpected accessibility hover event: " +
event);
1528 private void onTouchExplorationExit() {
1529 if (hoveredObject !=
null) {
1530 sendAccessibilityEvent(hoveredObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
1531 hoveredObject =
null;
1544 private void handleTouchExploration(
float x,
float y,
boolean ignorePlatformViews) {
1545 if (flutterSemanticsTree.isEmpty()) {
1548 SemanticsNode semanticsNodeUnderCursor =
1549 getRootSemanticsNode().hitTest(
new float[] {
x,
y, 0, 1}, ignorePlatformViews);
1550 if (semanticsNodeUnderCursor != hoveredObject) {
1552 if (semanticsNodeUnderCursor !=
null) {
1553 sendAccessibilityEvent(
1554 semanticsNodeUnderCursor.id, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
1556 if (hoveredObject !=
null) {
1557 sendAccessibilityEvent(hoveredObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
1559 hoveredObject = semanticsNodeUnderCursor;
1572 void updateCustomAccessibilityActions(@NonNull ByteBuffer
buffer, @NonNull String[] strings) {
1573 while (
buffer.hasRemaining()) {
1574 int id =
buffer.getInt();
1575 CustomAccessibilityAction
action = getOrCreateAccessibilityAction(
id);
1576 action.overrideId =
buffer.getInt();
1577 int stringIndex =
buffer.getInt();
1578 action.label = stringIndex == -1 ? null : strings[stringIndex];
1579 stringIndex =
buffer.getInt();
1580 action.hint = stringIndex == -1 ? null : strings[stringIndex];
1591 void updateSemantics(
1592 @NonNull ByteBuffer
buffer,
1593 @NonNull String[] strings,
1594 @NonNull ByteBuffer[] stringAttributeArgs) {
1595 ArrayList<SemanticsNode> updated =
new ArrayList<>();
1596 while (
buffer.hasRemaining()) {
1597 int id =
buffer.getInt();
1598 SemanticsNode semanticsNode = getOrCreateSemanticsNode(
id);
1599 semanticsNode.updateWith(
buffer, strings, stringAttributeArgs);
1600 if (semanticsNode.hasFlag(Flag.IS_HIDDEN)) {
1603 if (semanticsNode.hasFlag(Flag.IS_FOCUSED)) {
1604 inputFocusedSemanticsNode = semanticsNode;
1606 if (semanticsNode.hadPreviousConfig) {
1607 updated.add(semanticsNode);
1609 if (semanticsNode.platformViewId != -1
1610 && !platformViewsAccessibilityDelegate.usesVirtualDisplay(semanticsNode.platformViewId)) {
1612 platformViewsAccessibilityDelegate.getPlatformViewById(semanticsNode.platformViewId);
1613 if (embeddedView !=
null) {
1614 embeddedView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
1619 Set<SemanticsNode> visitedObjects =
new HashSet<>();
1620 SemanticsNode rootObject = getRootSemanticsNode();
1622 if (rootObject !=
null) {
1623 final float[]
identity =
new float[16];
1628 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_23) {
1629 boolean needsToApplyLeftCutoutInset =
true;
1634 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
1635 needsToApplyLeftCutoutInset = doesLayoutInDisplayCutoutModeRequireLeftInset();
1638 if (needsToApplyLeftCutoutInset) {
1639 WindowInsets insets = rootAccessibilityView.getRootWindowInsets();
1640 if (insets !=
null) {
1641 if (!lastLeftFrameInset.equals(insets.getSystemWindowInsetLeft())) {
1642 rootObject.globalGeometryDirty =
true;
1643 rootObject.inverseTransformDirty =
true;
1645 lastLeftFrameInset = insets.getSystemWindowInsetLeft();
1650 rootObject.updateRecursively(
identity, visitedObjects,
false);
1651 rootObject.collectRoutes(newRoutes);
1658 SemanticsNode lastAdded =
null;
1659 for (SemanticsNode semanticsNode : newRoutes) {
1660 if (!flutterNavigationStack.contains(semanticsNode.id)) {
1661 lastAdded = semanticsNode;
1666 if (lastAdded ==
null && newRoutes.size() > 0) {
1667 lastAdded = newRoutes.get(newRoutes.size() - 1);
1677 if (lastAdded !=
null
1678 && (lastAdded.id != previousRouteId || newRoutes.size() != flutterNavigationStack.size())) {
1679 previousRouteId = lastAdded.id;
1680 onWindowNameChange(lastAdded);
1682 flutterNavigationStack.clear();
1683 for (SemanticsNode semanticsNode : newRoutes) {
1684 flutterNavigationStack.add(semanticsNode.id);
1687 Iterator<Map.Entry<Integer, SemanticsNode>> it = flutterSemanticsTree.entrySet().iterator();
1688 while (it.hasNext()) {
1689 Map.Entry<Integer, SemanticsNode> entry = it.next();
1690 SemanticsNode
object = entry.getValue();
1691 if (!visitedObjects.contains(
object)) {
1692 willRemoveSemanticsNode(
object);
1699 sendWindowContentChangeEvent(0);
1701 for (SemanticsNode
object : updated) {
1702 if (
object.didScroll()) {
1703 AccessibilityEvent
event =
1704 obtainAccessibilityEvent(
object.
id, AccessibilityEvent.TYPE_VIEW_SCROLLED);
1708 float position =
object.scrollPosition;
1709 float max =
object.scrollExtentMax;
1710 if (Float.isInfinite(
object.scrollExtentMax)) {
1711 max = SCROLL_EXTENT_FOR_INFINITY;
1712 if (position > SCROLL_POSITION_CAP_FOR_INFINITY) {
1713 position = SCROLL_POSITION_CAP_FOR_INFINITY;
1716 if (Float.isInfinite(
object.scrollExtentMin)) {
1717 max += SCROLL_EXTENT_FOR_INFINITY;
1718 if (position < -SCROLL_POSITION_CAP_FOR_INFINITY) {
1719 position = -SCROLL_POSITION_CAP_FOR_INFINITY;
1721 position += SCROLL_EXTENT_FOR_INFINITY;
1723 max -=
object.scrollExtentMin;
1724 position -=
object.scrollExtentMin;
1727 if (
object.hadAction(
Action.SCROLL_UP) ||
object.hadAction(
Action.SCROLL_DOWN)) {
1728 event.setScrollY((
int) position);
1729 event.setMaxScrollY((
int)
max);
1730 }
else if (
object.hadAction(
Action.SCROLL_LEFT) ||
object.hadAction(
Action.SCROLL_RIGHT)) {
1731 event.setScrollX((
int) position);
1732 event.setMaxScrollX((
int)
max);
1734 if (
object.scrollChildren > 0) {
1736 event.setItemCount(
object.scrollChildren);
1737 event.setFromIndex(
object.scrollIndex);
1738 int visibleChildren = 0;
1740 for (SemanticsNode child :
object.childrenInHitTestOrder) {
1741 if (!child.hasFlag(Flag.IS_HIDDEN)) {
1742 visibleChildren += 1;
1745 if (BuildConfig.DEBUG) {
1746 if (
object.scrollIndex + visibleChildren >
object.scrollChildren) {
1747 Log.e(TAG,
"Scroll index is out of bounds.");
1750 if (
object.childrenInHitTestOrder.isEmpty()) {
1751 Log.e(TAG,
"Had scrollChildren but no childrenInHitTestOrder");
1762 event.setToIndex(
object.scrollIndex + visibleChildren - 1);
1764 sendAccessibilityEvent(
event);
1766 if (
object.hasFlag(Flag.IS_LIVE_REGION) &&
object.didChangeLabel()) {
1767 sendWindowContentChangeEvent(
object.
id);
1769 if (accessibilityFocusedSemanticsNode !=
null
1770 && accessibilityFocusedSemanticsNode.id ==
object.id
1771 && !
object.hadFlag(Flag.IS_SELECTED)
1772 &&
object.hasFlag(Flag.IS_SELECTED)) {
1773 AccessibilityEvent
event =
1774 obtainAccessibilityEvent(
object.
id, AccessibilityEvent.TYPE_VIEW_SELECTED);
1775 event.getText().add(
object.label);
1776 sendAccessibilityEvent(
event);
1781 if (inputFocusedSemanticsNode !=
null
1782 && inputFocusedSemanticsNode.id ==
object.id
1783 && (lastInputFocusedSemanticsNode ==
null
1784 || lastInputFocusedSemanticsNode.id != inputFocusedSemanticsNode.id)) {
1785 lastInputFocusedSemanticsNode = inputFocusedSemanticsNode;
1786 sendAccessibilityEvent(
1787 obtainAccessibilityEvent(
object.
id, AccessibilityEvent.TYPE_VIEW_FOCUSED));
1788 }
else if (inputFocusedSemanticsNode ==
null) {
1792 lastInputFocusedSemanticsNode =
null;
1795 if (inputFocusedSemanticsNode !=
null
1796 && inputFocusedSemanticsNode.id ==
object.id
1797 &&
object.hadFlag(Flag.IS_TEXT_FIELD)
1798 &&
object.hasFlag(Flag.IS_TEXT_FIELD)
1803 && (accessibilityFocusedSemanticsNode ==
null
1804 || (accessibilityFocusedSemanticsNode.id == inputFocusedSemanticsNode.id))) {
1805 String oldValue = object.previousValue !=
null ? object.previousValue :
"";
1806 String newValue = object.value !=
null ? object.value :
"";
1807 AccessibilityEvent
event = createTextChangedEvent(
object.
id, oldValue, newValue);
1808 if (
event !=
null) {
1809 sendAccessibilityEvent(
event);
1812 if (
object.previousTextSelectionBase !=
object.textSelectionBase
1813 ||
object.previousTextSelectionExtent !=
object.textSelectionExtent) {
1814 AccessibilityEvent selectionEvent =
1815 obtainAccessibilityEvent(
1816 object.
id, AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
1817 selectionEvent.getText().add(newValue);
1818 selectionEvent.setFromIndex(
object.textSelectionBase);
1819 selectionEvent.setToIndex(
object.textSelectionExtent);
1820 selectionEvent.setItemCount(newValue.length());
1821 sendAccessibilityEvent(selectionEvent);
1827 private AccessibilityEvent createTextChangedEvent(
int id, String oldValue, String newValue) {
1828 AccessibilityEvent
e = obtainAccessibilityEvent(
id, AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
1829 e.setBeforeText(oldValue);
1830 e.getText().add(newValue);
1833 for (
i = 0;
i < oldValue.length() &&
i < newValue.length(); ++
i) {
1834 if (oldValue.charAt(
i) != newValue.charAt(
i)) {
1838 if (
i >= oldValue.length() &&
i >= newValue.length()) {
1841 int firstDifference =
i;
1842 e.setFromIndex(firstDifference);
1844 int oldIndex = oldValue.length() - 1;
1845 int newIndex = newValue.length() - 1;
1846 while (oldIndex >= firstDifference && newIndex >= firstDifference) {
1847 if (oldValue.charAt(oldIndex) != newValue.charAt(newIndex)) {
1853 e.setRemovedCount(oldIndex - firstDifference + 1);
1854 e.setAddedCount(newIndex - firstDifference + 1);
1867 public void sendAccessibilityEvent(
int viewId,
int eventType) {
1868 if (!accessibilityManager.isEnabled()) {
1871 sendAccessibilityEvent(obtainAccessibilityEvent(viewId, eventType));
1881 private void sendAccessibilityEvent(@NonNull AccessibilityEvent
event) {
1882 if (!accessibilityManager.isEnabled()) {
1889 rootAccessibilityView.getParent().requestSendAccessibilityEvent(rootAccessibilityView,
event);
1903 private void onWindowNameChange(@NonNull SemanticsNode
route) {
1904 String routeName =
route.getRouteName();
1905 if (routeName ==
null) {
1916 if (
Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
1917 setAccessibilityPaneTitle(routeName);
1919 AccessibilityEvent
event =
1920 obtainAccessibilityEvent(
route.id, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
1921 event.getText().add(routeName);
1922 sendAccessibilityEvent(
event);
1926 @TargetApi(API_LEVELS.API_28)
1927 @RequiresApi(API_LEVELS.API_28)
1928 private
void setAccessibilityPaneTitle(String title) {
1929 rootAccessibilityView.setAccessibilityPaneTitle(title);
1942 private void sendWindowContentChangeEvent(
int virtualViewId) {
1943 AccessibilityEvent
event =
1944 obtainAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
1945 event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
1946 sendAccessibilityEvent(
event);
1957 private AccessibilityEvent obtainAccessibilityEvent(
int virtualViewId,
int eventType) {
1958 AccessibilityEvent
event = obtainAccessibilityEvent(eventType);
1959 event.setPackageName(rootAccessibilityView.getContext().getPackageName());
1960 event.setSource(rootAccessibilityView, virtualViewId);
1965 public AccessibilityEvent obtainAccessibilityEvent(
int eventType) {
1966 return AccessibilityEvent.obtain(eventType);
1975 @TargetApi(API_LEVELS.API_28)
1976 @RequiresApi(API_LEVELS.API_28)
1977 private
boolean doesLayoutInDisplayCutoutModeRequireLeftInset() {
1978 Context context = rootAccessibilityView.getContext();
1979 Activity activity = ViewUtils.getActivity(context);
1980 if (activity ==
null || activity.getWindow() ==
null) {
1985 int layoutInDisplayCutoutMode = activity.getWindow().getAttributes().layoutInDisplayCutoutMode;
1986 return layoutInDisplayCutoutMode
1987 == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
1988 || layoutInDisplayCutoutMode
1989 == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
1996 private void willRemoveSemanticsNode(SemanticsNode semanticsNodeToBeRemoved) {
1997 if (BuildConfig.DEBUG) {
1998 if (!flutterSemanticsTree.containsKey(semanticsNodeToBeRemoved.id)) {
1999 Log.e(TAG,
"Attempted to remove a node that is not in the tree.");
2001 if (flutterSemanticsTree.get(semanticsNodeToBeRemoved.id) != semanticsNodeToBeRemoved) {
2002 Log.e(TAG,
"Flutter semantics tree failed to get expected node when searching by id.");
2009 semanticsNodeToBeRemoved.parent =
null;
2011 if (semanticsNodeToBeRemoved.platformViewId != -1
2012 && embeddedAccessibilityFocusedNodeId !=
null
2013 && accessibilityViewEmbedder.platformViewOfNode(embeddedAccessibilityFocusedNodeId)
2014 == platformViewsAccessibilityDelegate.getPlatformViewById(
2015 semanticsNodeToBeRemoved.platformViewId)) {
2018 sendAccessibilityEvent(
2019 embeddedAccessibilityFocusedNodeId,
2020 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2021 embeddedAccessibilityFocusedNodeId =
null;
2024 if (semanticsNodeToBeRemoved.platformViewId != -1) {
2026 platformViewsAccessibilityDelegate.getPlatformViewById(
2027 semanticsNodeToBeRemoved.platformViewId);
2028 if (embeddedView !=
null) {
2029 embeddedView.setImportantForAccessibility(
2030 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
2034 if (accessibilityFocusedSemanticsNode == semanticsNodeToBeRemoved) {
2035 sendAccessibilityEvent(
2036 accessibilityFocusedSemanticsNode.id,
2037 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2038 accessibilityFocusedSemanticsNode =
null;
2041 if (inputFocusedSemanticsNode == semanticsNodeToBeRemoved) {
2042 inputFocusedSemanticsNode =
null;
2045 if (hoveredObject == semanticsNodeToBeRemoved) {
2046 hoveredObject =
null;
2061 public void reset() {
2062 flutterSemanticsTree.clear();
2063 if (accessibilityFocusedSemanticsNode !=
null) {
2064 sendAccessibilityEvent(
2065 accessibilityFocusedSemanticsNode.id,
2066 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
2068 accessibilityFocusedSemanticsNode =
null;
2069 hoveredObject =
null;
2070 sendWindowContentChangeEvent(0);
2077 public interface OnAccessibilityChangeListener {
2078 void onAccessibilityChanged(
boolean isAccessibilityEnabled,
boolean isTouchExplorationEnabled);
2086 SCROLL_LEFT(1 << 2),
2087 SCROLL_RIGHT(1 << 3),
2089 SCROLL_DOWN(1 << 5),
2092 SHOW_ON_SCREEN(1 << 8),
2093 MOVE_CURSOR_FORWARD_BY_CHARACTER(1 << 9),
2094 MOVE_CURSOR_BACKWARD_BY_CHARACTER(1 << 10),
2095 SET_SELECTION(1 << 11),
2099 DID_GAIN_ACCESSIBILITY_FOCUS(1 << 15),
2100 DID_LOSE_ACCESSIBILITY_FOCUS(1 << 16),
2101 CUSTOM_ACTION(1 << 17),
2103 MOVE_CURSOR_FORWARD_BY_WORD(1 << 19),
2104 MOVE_CURSOR_BACKWARD_BY_WORD(1 << 20),
2108 public final int value;
2118 static int systemAction =
2119 Action.DID_GAIN_ACCESSIBILITY_FOCUS.value
2120 & Action.DID_LOSE_ACCESSIBILITY_FOCUS.value
2121 &
Action.SHOW_ON_SCREEN.value;
2126 HAS_CHECKED_STATE(1 << 0),
2128 IS_SELECTED(1 << 2),
2130 IS_TEXT_FIELD(1 << 4),
2132 HAS_ENABLED_STATE(1 << 6),
2134 IS_IN_MUTUALLY_EXCLUSIVE_GROUP(1 << 8),
2136 IS_OBSCURED(1 << 10),
2137 SCOPES_ROUTE(1 << 11),
2138 NAMES_ROUTE(1 << 12),
2141 IS_LIVE_REGION(1 << 15),
2142 HAS_TOGGLED_STATE(1 << 16),
2143 IS_TOGGLED(1 << 17),
2144 HAS_IMPLICIT_SCROLLING(1 << 18),
2145 IS_MULTILINE(1 << 19),
2146 IS_READ_ONLY(1 << 20),
2147 IS_FOCUSABLE(1 << 21),
2150 IS_KEYBOARD_KEY(1 << 24),
2151 IS_CHECK_STATE_MIXED(1 << 25),
2152 HAS_EXPANDED_STATE(1 << 26),
2153 IS_EXPANDED(1 << 27);
2163 private enum AccessibilityFeature {
2164 ACCESSIBLE_NAVIGATION(1 << 0),
2165 INVERT_COLORS(1 << 1),
2166 DISABLE_ANIMATIONS(1 << 2),
2168 REDUCE_MOTION(1 << 4),
2169 HIGH_CONTRAST(1 << 5),
2170 ON_OFF_SWITCH_LABELS(1 << 6);
2174 AccessibilityFeature(
int value) {
2212 private static class CustomAccessibilityAction {
2213 CustomAccessibilityAction() {}
2218 private int resourceId = -1;
2223 private int id = -1;
2228 private int overrideId = -1;
2231 private String label;
2234 private String hint;
2254 private static class SpellOutStringAttribute
extends StringAttribute {}
2272 private static class SemanticsNode {
2273 private static boolean nullableHasAncestor(
2274 SemanticsNode
target, Predicate<SemanticsNode>
tester) {
2281 private int id = -1;
2284 private int actions;
2285 private int maxValueLength;
2286 private int currentValueLength;
2287 private int textSelectionBase;
2288 private int textSelectionExtent;
2289 private int platformViewId;
2290 private int scrollChildren;
2291 private int scrollIndex;
2292 private float scrollPosition;
2293 private float scrollExtentMax;
2294 private float scrollExtentMin;
2296 private String label;
2298 private String
value;
2300 private String increasedValue;
2302 private String decreasedValue;
2304 private String hint;
2311 @Nullable
private String tooltip;
2324 private int previousNodeId = -1;
2329 private boolean hadPreviousConfig =
false;
2330 private int previousFlags;
2331 private int previousActions;
2332 private int previousTextSelectionBase;
2333 private int previousTextSelectionExtent;
2334 private float previousScrollPosition;
2335 private float previousScrollExtentMax;
2336 private float previousScrollExtentMin;
2337 private String previousValue;
2338 private String previousLabel;
2342 private float right;
2343 private float bottom;
2346 private SemanticsNode parent;
2350 private CustomAccessibilityAction onTapOverride;
2351 private CustomAccessibilityAction onLongPressOverride;
2353 private boolean inverseTransformDirty =
true;
2354 private float[] inverseTransform;
2356 private boolean globalGeometryDirty =
true;
2357 private float[] globalTransform;
2358 private Rect globalRect;
2361 this.accessibilityBridge = accessibilityBridge;
2368 private SemanticsNode getAncestor(Predicate<SemanticsNode>
tester) {
2369 SemanticsNode nextAncestor = parent;
2370 while (nextAncestor !=
null) {
2371 if (
tester.test(nextAncestor)) {
2372 return nextAncestor;
2374 nextAncestor = nextAncestor.parent;
2386 return (actions &
action.value) != 0;
2394 return (previousActions &
action.value) != 0;
2397 private boolean hasFlag(@NonNull Flag
flag) {
2401 private boolean hadFlag(@NonNull Flag
flag) {
2402 if (BuildConfig.DEBUG && !hadPreviousConfig) {
2403 Log.e(TAG,
"Attempted to check hadFlag but had no previous config.");
2405 return (previousFlags &
flag.value) != 0;
2408 private boolean didScroll() {
2409 return !Float.isNaN(scrollPosition)
2410 && !Float.isNaN(previousScrollPosition)
2411 && previousScrollPosition != scrollPosition;
2414 private boolean didChangeLabel() {
2415 if (label ==
null && previousLabel ==
null) {
2418 return label ==
null || previousLabel ==
null || !label.equals(previousLabel);
2421 private void log(@NonNull String indent,
boolean recursive) {
2422 if (BuildConfig.DEBUG) {
2426 +
"SemanticsNode id="
2438 +
" +-- textDirection="
2442 +
" +-- rect.ltrb=("
2456 String childIndent = indent +
" ";
2457 for (SemanticsNode child : childrenInTraversalOrder) {
2458 child.log(childIndent, recursive);
2464 private void updateWith(
2465 @NonNull ByteBuffer
buffer,
2466 @NonNull String[] strings,
2467 @NonNull ByteBuffer[] stringAttributeArgs) {
2468 hadPreviousConfig =
true;
2469 previousValue =
value;
2470 previousLabel = label;
2471 previousFlags =
flags;
2472 previousActions = actions;
2473 previousTextSelectionBase = textSelectionBase;
2474 previousTextSelectionExtent = textSelectionExtent;
2475 previousScrollPosition = scrollPosition;
2476 previousScrollExtentMax = scrollExtentMax;
2477 previousScrollExtentMin = scrollExtentMin;
2480 actions =
buffer.getInt();
2481 maxValueLength =
buffer.getInt();
2482 currentValueLength =
buffer.getInt();
2483 textSelectionBase =
buffer.getInt();
2484 textSelectionExtent =
buffer.getInt();
2485 platformViewId =
buffer.getInt();
2486 scrollChildren =
buffer.getInt();
2487 scrollIndex =
buffer.getInt();
2488 scrollPosition =
buffer.getFloat();
2489 scrollExtentMax =
buffer.getFloat();
2490 scrollExtentMin =
buffer.getFloat();
2492 int stringIndex =
buffer.getInt();
2494 identifier = stringIndex == -1 ? null : strings[stringIndex];
2495 stringIndex =
buffer.getInt();
2497 label = stringIndex == -1 ? null : strings[stringIndex];
2499 labelAttributes = getStringAttributesFromBuffer(
buffer, stringAttributeArgs);
2501 stringIndex =
buffer.getInt();
2502 value = stringIndex == -1 ? null : strings[stringIndex];
2504 valueAttributes = getStringAttributesFromBuffer(
buffer, stringAttributeArgs);
2506 stringIndex =
buffer.getInt();
2507 increasedValue = stringIndex == -1 ? null : strings[stringIndex];
2509 increasedValueAttributes = getStringAttributesFromBuffer(
buffer, stringAttributeArgs);
2511 stringIndex =
buffer.getInt();
2512 decreasedValue = stringIndex == -1 ? null : strings[stringIndex];
2514 decreasedValueAttributes = getStringAttributesFromBuffer(
buffer, stringAttributeArgs);
2516 stringIndex =
buffer.getInt();
2517 hint = stringIndex == -1 ? null : strings[stringIndex];
2519 hintAttributes = getStringAttributesFromBuffer(
buffer, stringAttributeArgs);
2521 stringIndex =
buffer.getInt();
2522 tooltip = stringIndex == -1 ? null : strings[stringIndex];
2529 bottom =
buffer.getFloat();
2534 for (
int i = 0;
i < 16; ++
i) {
2537 inverseTransformDirty =
true;
2538 globalGeometryDirty =
true;
2540 final int childCount =
buffer.getInt();
2541 childrenInTraversalOrder.clear();
2542 childrenInHitTestOrder.clear();
2543 for (
int i = 0;
i < childCount; ++
i) {
2544 SemanticsNode child = accessibilityBridge.getOrCreateSemanticsNode(
buffer.getInt());
2545 child.parent =
this;
2546 childrenInTraversalOrder.
add(child);
2548 for (
int i = 0;
i < childCount; ++
i) {
2549 SemanticsNode child = accessibilityBridge.getOrCreateSemanticsNode(
buffer.getInt());
2550 child.parent =
this;
2551 childrenInHitTestOrder.
add(child);
2554 final int actionCount =
buffer.getInt();
2555 if (actionCount == 0) {
2556 customAccessibilityActions =
null;
2558 if (customAccessibilityActions ==
null)
2559 customAccessibilityActions =
new ArrayList<>(actionCount);
2560 else customAccessibilityActions.clear();
2562 for (
int i = 0;
i < actionCount;
i++) {
2563 CustomAccessibilityAction
action =
2564 accessibilityBridge.getOrCreateAccessibilityAction(
buffer.getInt());
2567 }
else if (
action.overrideId ==
Action.LONG_PRESS.value) {
2568 onLongPressOverride =
action;
2572 if (BuildConfig.DEBUG &&
action.overrideId != -1) {
2573 Log.e(TAG,
"Expected action.overrideId to be -1.");
2583 @NonNull ByteBuffer
buffer, @NonNull ByteBuffer[] stringAttributeArgs) {
2584 final int attributesCount =
buffer.getInt();
2585 if (attributesCount == -1) {
2589 for (
int i = 0;
i < attributesCount; ++
i) {
2598 SpellOutStringAttribute attribute =
new SpellOutStringAttribute();
2599 attribute.start =
start;
2600 attribute.end =
end;
2601 attribute.type =
type;
2607 final int argsIndex =
buffer.getInt();
2608 final ByteBuffer
args = stringAttributeArgs[argsIndex];
2609 LocaleStringAttribute attribute =
new LocaleStringAttribute();
2610 attribute.start =
start;
2611 attribute.end =
end;
2612 attribute.type =
type;
2613 attribute.locale = Charset.forName(
"UTF-8").decode(
args).toString();
2624 private void ensureInverseTransform() {
2625 if (!inverseTransformDirty) {
2628 inverseTransformDirty =
false;
2629 if (inverseTransform ==
null) {
2630 inverseTransform =
new float[16];
2633 Arrays.fill(inverseTransform, 0);
2637 private Rect getGlobalRect() {
2638 if (BuildConfig.DEBUG && globalGeometryDirty) {
2639 Log.e(TAG,
"Attempted to getGlobalRect with a dirty geometry.");
2652 private SemanticsNode hitTest(
float[] point,
boolean stopAtPlatformView) {
2653 final float w = point[3];
2654 final float x = point[0] /
w;
2655 final float y = point[1] /
w;
2656 if (x < left || x >= right || y < top || y >= bottom)
return null;
2657 final float[] transformedPoint =
new float[4];
2658 for (SemanticsNode child : childrenInHitTestOrder) {
2659 if (child.hasFlag(Flag.IS_HIDDEN)) {
2662 child.ensureInverseTransform();
2663 Matrix.multiplyMV(transformedPoint, 0, child.inverseTransform, 0, point, 0);
2664 final SemanticsNode
result = child.hitTest(transformedPoint, stopAtPlatformView);
2669 final boolean foundPlatformView = stopAtPlatformView && platformViewId != -1;
2670 return isFocusable() || foundPlatformView ? this :
null;
2675 private boolean isFocusable() {
2678 if (hasFlag(Flag.SCOPES_ROUTE)) {
2681 if (hasFlag(Flag.IS_FOCUSABLE)) {
2687 return (actions & ~SCROLLABLE_ACTIONS) != 0
2688 || (
flags & FOCUSABLE_FLAGS) != 0
2689 || (label !=
null && !label.isEmpty())
2691 || (hint !=
null && !hint.isEmpty());
2695 if (hasFlag(Flag.SCOPES_ROUTE)) {
2698 for (SemanticsNode child : childrenInTraversalOrder) {
2699 child.collectRoutes(edges);
2703 private String getRouteName() {
2706 if (hasFlag(Flag.NAMES_ROUTE)) {
2707 if (label !=
null && !label.isEmpty()) {
2711 for (SemanticsNode child : childrenInTraversalOrder) {
2712 String newName = child.getRouteName();
2713 if (newName !=
null && !newName.isEmpty()) {
2720 private void updateRecursively(
2721 float[] ancestorTransform, Set<SemanticsNode> visitedObjects,
boolean forceUpdate) {
2722 visitedObjects.add(
this);
2724 if (globalGeometryDirty) {
2729 if (globalTransform ==
null) {
2730 globalTransform =
new float[16];
2733 if (BuildConfig.DEBUG) {
2734 Log.e(TAG,
"transform has not been initialized for id = " +
id);
2735 accessibilityBridge.getRootSemanticsNode().log(
"Semantics tree:",
true);
2739 Matrix.multiplyMM(globalTransform, 0, ancestorTransform, 0,
transform, 0);
2741 final float[] sample =
new float[4];
2745 final float[] point1 =
new float[4];
2746 final float[] point2 =
new float[4];
2747 final float[] point3 =
new float[4];
2748 final float[] point4 =
new float[4];
2752 transformPoint(point1, globalTransform, sample);
2756 transformPoint(point2, globalTransform, sample);
2760 transformPoint(point3, globalTransform, sample);
2764 transformPoint(point4, globalTransform, sample);
2766 if (globalRect ==
null) globalRect =
new Rect();
2769 Math.round(
min(point1[0], point2[0], point3[0], point4[0])),
2770 Math.round(
min(point1[1], point2[1], point3[1], point4[1])),
2771 Math.round(
max(point1[0], point2[0], point3[0], point4[0])),
2772 Math.round(
max(point1[1], point2[1], point3[1], point4[1])));
2774 globalGeometryDirty =
false;
2777 if (BuildConfig.DEBUG) {
2778 if (globalTransform ==
null) {
2779 Log.e(TAG,
"Expected globalTransform to not be null.");
2781 if (globalRect ==
null) {
2782 Log.e(TAG,
"Expected globalRect to not be null.");
2786 int previousNodeId = -1;
2787 for (SemanticsNode child : childrenInTraversalOrder) {
2788 child.previousNodeId = previousNodeId;
2789 previousNodeId = child.id;
2790 child.updateRecursively(globalTransform, visitedObjects, forceUpdate);
2794 private void transformPoint(
float[]
result,
float[]
transform,
float[] point) {
2803 private float min(
float a,
float b,
float c,
float d) {
2804 return Math.min(
a, Math.min(
b, Math.min(c,
d)));
2807 private float max(
float a,
float b,
float c,
float d) {
2808 return Math.max(
a, Math.max(
b, Math.max(c,
d)));
2811 private CharSequence getValue() {
2812 return createSpannableString(
value, valueAttributes);
2815 private CharSequence getLabel() {
2816 return createSpannableString(label, labelAttributes);
2819 private CharSequence getHint() {
2820 return createSpannableString(hint, hintAttributes);
2823 private CharSequence getValueLabelHint() {
2824 CharSequence[] array =
new CharSequence[] {getValue(), getLabel(), getHint()};
2825 CharSequence
result =
null;
2826 for (CharSequence
word : array) {
2827 if (
word !=
null &&
word.length() > 0) {
2838 private CharSequence getTextFieldHint() {
2839 CharSequence[] array =
new CharSequence[] {getLabel(), getHint()};
2840 CharSequence
result =
null;
2841 for (CharSequence
word : array) {
2842 if (
word !=
null &&
word.length() > 0) {
2854 if (
string ==
null) {
2857 final SpannableString spannableString =
new SpannableString(
string);
2858 if (attributes !=
null) {
2860 switch (attribute.type) {
2863 final TtsSpan ttsSpan =
new TtsSpan.Builder<>(TtsSpan.TYPE_VERBATIM).
build();
2864 spannableString.setSpan(ttsSpan, attribute.start, attribute.end, 0);
2869 LocaleStringAttribute localeAttribute = (LocaleStringAttribute) attribute;
2870 Locale locale = Locale.forLanguageTag(localeAttribute.locale);
2871 final LocaleSpan localeSpan =
new LocaleSpan(locale);
2872 spannableString.setSpan(localeSpan, attribute.start, attribute.end, 0);
2878 return spannableString;
2899 @SuppressLint(
"SwitchIntDef")
2900 public
boolean externalViewRequestSendAccessibilityEvent(
2901 View embeddedView, View eventOrigin, AccessibilityEvent
event) {
2902 if (!accessibilityViewEmbedder.requestSendAccessibilityEvent(
2903 embeddedView, eventOrigin,
event)) {
2906 Integer virtualNodeId = accessibilityViewEmbedder.getRecordFlutterId(embeddedView,
event);
2907 if (virtualNodeId ==
null) {
2910 switch (
event.getEventType()) {
2911 case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
2912 hoveredObject =
null;
2914 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
2915 embeddedAccessibilityFocusedNodeId = virtualNodeId;
2916 accessibilityFocusedSemanticsNode =
null;
2918 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
2919 embeddedInputFocusedNodeId =
null;
2920 embeddedAccessibilityFocusedNodeId =
null;
2922 case AccessibilityEvent.TYPE_VIEW_FOCUSED:
2923 embeddedInputFocusedNodeId = virtualNodeId;
2924 inputFocusedSemanticsNode =
null;
void add(sk_sp< SkIDChangeListener > listener) SK_EXCLUDES(fMutex)
boolean getAccessibleNavigation()
AccessibilityBridge( @NonNull View rootAccessibilityView, @NonNull AccessibilityChannel accessibilityChannel, @NonNull AccessibilityManager accessibilityManager, @NonNull ContentResolver contentResolver, @NonNull PlatformViewsAccessibilityDelegate platformViewsAccessibilityDelegate)
AccessibilityBridge( @NonNull View rootAccessibilityView, @NonNull AccessibilityChannel accessibilityChannel, @NonNull AccessibilityManager accessibilityManager, @NonNull ContentResolver contentResolver, @NonNull AccessibilityViewEmbedder accessibilityViewEmbedder, @NonNull PlatformViewsAccessibilityDelegate platformViewsAccessibilityDelegate)
static SkString identifier(const FontFamilyDesc &family, const FontDesc &font)
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
FlutterSemanticsFlag flag
FlutterSemanticsFlag flags
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
static float max(float r, float g, float b)
static float min(float r, float g, float b)
union flutter::testing::@2836::KeyboardChange::@76 content
Optional< SkRect > bounds
void Log(const char *format,...) SK_PRINTF_LIKE(1
def Build(configs, env, options)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however route
SK_API sk_sp< PrecompileColorFilter > Matrix()
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)