diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index c542da80f1f8a..a8d1918a39460 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -190,6 +190,14 @@ public class AccessibilityBridge extends AccessibilityNodeProvider { @Nullable private SemanticsNode inputFocusedSemanticsNode; + // Keeps track of the last semantics node that had the input focus. + // + // This is used to determine if the input focus has changed since the last time the + // {@code inputFocusSemanticsNode} has been set, so that we can send a {@code TYPE_VIEW_FOCUSED} + // event when it changes. + @Nullable + private SemanticsNode lastInputFocusedSemanticsNode; + // The widget within Flutter that currently sits beneath a cursor, e.g, // beneath a stylus or mouse cursor. @Nullable @@ -1377,6 +1385,20 @@ void updateSemantics(@NonNull ByteBuffer buffer, @NonNull String[] strings) { event.getText().add(object.label); sendAccessibilityEvent(event); } + + // If the object is the input-focused node, then tell the reader about it, but only if + // it has changed since the last update. + if (inputFocusedSemanticsNode != null && inputFocusedSemanticsNode.id == object.id && + (lastInputFocusedSemanticsNode == null || lastInputFocusedSemanticsNode.id != inputFocusedSemanticsNode.id)) { + lastInputFocusedSemanticsNode = inputFocusedSemanticsNode; + sendAccessibilityEvent(obtainAccessibilityEvent(object.id, AccessibilityEvent.TYPE_VIEW_FOCUSED)); + } else if (inputFocusedSemanticsNode == null) { + // There's no TYPE_VIEW_CLEAR_FOCUSED event, so if the current input focus becomes + // null, then we just set the last one to null too, so that it sends the event again + // when something regains focus. + lastInputFocusedSemanticsNode = null; + } + if (inputFocusedSemanticsNode != null && inputFocusedSemanticsNode.id == object.id && object.hadFlag(Flag.IS_TEXT_FIELD) && object.hasFlag(Flag.IS_TEXT_FIELD) // If we have a TextField that has InputFocus, we should avoid announcing it if something