in shell/platform/android/io/flutter/view/AccessibilityBridge.java [950:1171]
public boolean performAction(
int virtualViewId, int accessibilityAction, @Nullable Bundle arguments) {
if (virtualViewId >= MIN_ENGINE_GENERATED_NODE_ID) {
// The node is in the engine generated range, and is handled by the accessibility view
// embedder.
boolean didPerform =
accessibilityViewEmbedder.performAction(virtualViewId, accessibilityAction, arguments);
if (didPerform
&& accessibilityAction == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
embeddedAccessibilityFocusedNodeId = null;
}
return didPerform;
}
SemanticsNode semanticsNode = flutterSemanticsTree.get(virtualViewId);
if (semanticsNode == null) {
return false;
}
switch (accessibilityAction) {
case AccessibilityNodeInfo.ACTION_CLICK:
{
// Note: TalkBack prior to Oreo doesn't use this handler and instead simulates a
// click event at the center of the SemanticsNode. Other a11y services might go
// through this handler though.
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.TAP);
return true;
}
case AccessibilityNodeInfo.ACTION_LONG_CLICK:
{
// Note: TalkBack doesn't use this handler and instead simulates a long click event
// at the center of the SemanticsNode. Other a11y services might go through this
// handler though.
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.LONG_PRESS);
return true;
}
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
{
if (semanticsNode.hasAction(Action.SCROLL_UP)) {
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.SCROLL_UP);
} else if (semanticsNode.hasAction(Action.SCROLL_LEFT)) {
// TODO(ianh): bidi support using textDirection
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.SCROLL_LEFT);
} else if (semanticsNode.hasAction(Action.INCREASE)) {
semanticsNode.value = semanticsNode.increasedValue;
semanticsNode.valueAttributes = semanticsNode.increasedValueAttributes;
// Event causes Android to read out the updated value.
sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.INCREASE);
} else {
return false;
}
return true;
}
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
{
if (semanticsNode.hasAction(Action.SCROLL_DOWN)) {
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.SCROLL_DOWN);
} else if (semanticsNode.hasAction(Action.SCROLL_RIGHT)) {
// TODO(ianh): bidi support using textDirection
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.SCROLL_RIGHT);
} else if (semanticsNode.hasAction(Action.DECREASE)) {
semanticsNode.value = semanticsNode.decreasedValue;
semanticsNode.valueAttributes = semanticsNode.decreasedValueAttributes;
// Event causes Android to read out the updated value.
sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.DECREASE);
} else {
return false;
}
return true;
}
case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
{
// Text selection APIs aren't available until API 18. We can't handle the case here so
// return false
// instead. It's extremely unlikely that this case would ever be triggered in the first
// place in API <
// 18.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return false;
}
return performCursorMoveAction(semanticsNode, virtualViewId, arguments, false);
}
case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
{
// Text selection APIs aren't available until API 18. We can't handle the case here so
// return false
// instead. It's extremely unlikely that this case would ever be triggered in the first
// place in API <
// 18.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return false;
}
return performCursorMoveAction(semanticsNode, virtualViewId, arguments, true);
}
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
{
// Focused semantics node must be reset before sending the
// TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED event. Otherwise,
// TalkBack may think the node is still focused.
if (accessibilityFocusedSemanticsNode != null
&& accessibilityFocusedSemanticsNode.id == virtualViewId) {
accessibilityFocusedSemanticsNode = null;
}
if (embeddedAccessibilityFocusedNodeId != null
&& embeddedAccessibilityFocusedNodeId == virtualViewId) {
embeddedAccessibilityFocusedNodeId = null;
}
accessibilityChannel.dispatchSemanticsAction(
virtualViewId, Action.DID_LOSE_ACCESSIBILITY_FOCUS);
sendAccessibilityEvent(
virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
return true;
}
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
{
if (accessibilityFocusedSemanticsNode == null) {
// When Android focuses a node, it doesn't invalidate the view.
// (It does when it sends ACTION_CLEAR_ACCESSIBILITY_FOCUS, so
// we only have to worry about this when the focused node is null.)
rootAccessibilityView.invalidate();
}
// Focused semantics node must be set before sending the TYPE_VIEW_ACCESSIBILITY_FOCUSED
// event. Otherwise, TalkBack may think the node is not focused yet.
accessibilityFocusedSemanticsNode = semanticsNode;
accessibilityChannel.dispatchSemanticsAction(
virtualViewId, Action.DID_GAIN_ACCESSIBILITY_FOCUS);
sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
if (semanticsNode.hasAction(Action.INCREASE)
|| semanticsNode.hasAction(Action.DECREASE)) {
// SeekBars only announce themselves after this event.
sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
}
return true;
}
case ACTION_SHOW_ON_SCREEN:
{
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.SHOW_ON_SCREEN);
return true;
}
case AccessibilityNodeInfo.ACTION_SET_SELECTION:
{
// Text selection APIs aren't available until API 18. We can't handle the case here so
// return false
// instead. It's extremely unlikely that this case would ever be triggered in the first
// place in API <
// 18.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return false;
}
final Map<String, Integer> selection = new HashMap<>();
final boolean hasSelection =
arguments != null
&& arguments.containsKey(
AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT)
&& arguments.containsKey(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT);
if (hasSelection) {
selection.put(
"base",
arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT));
selection.put(
"extent",
arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT));
} else {
// Clear the selection
selection.put("base", semanticsNode.textSelectionExtent);
selection.put("extent", semanticsNode.textSelectionExtent);
}
accessibilityChannel.dispatchSemanticsAction(
virtualViewId, Action.SET_SELECTION, selection);
// The voice access expects the semantics node to update immediately. We update the
// semantics node based on prediction. If the result is incorrect, it will be updated in
// the next frame.
SemanticsNode node = flutterSemanticsTree.get(virtualViewId);
node.textSelectionBase = selection.get("base");
node.textSelectionExtent = selection.get("extent");
return true;
}
case AccessibilityNodeInfo.ACTION_COPY:
{
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.COPY);
return true;
}
case AccessibilityNodeInfo.ACTION_CUT:
{
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.CUT);
return true;
}
case AccessibilityNodeInfo.ACTION_PASTE:
{
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.PASTE);
return true;
}
case AccessibilityNodeInfo.ACTION_DISMISS:
{
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.DISMISS);
return true;
}
case AccessibilityNodeInfo.ACTION_SET_TEXT:
{
// Set text APIs aren't available until API 21. We can't handle the case here so
// return false instead. It's extremely unlikely that this case would ever be
// triggered in the first place in API < 21.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return false;
}
return performSetText(semanticsNode, virtualViewId, arguments);
}
default:
// might be a custom accessibility accessibilityAction.
final int flutterId = accessibilityAction - FIRST_RESOURCE_ID;
CustomAccessibilityAction contextAction = customAccessibilityActions.get(flutterId);
if (contextAction != null) {
accessibilityChannel.dispatchSemanticsAction(
virtualViewId, Action.CUSTOM_ACTION, contextAction.id);
return true;
}
}
return false;
}