From 7c34dfafc9acece1a9438f206bfbb0a9bedba3bf Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Thu, 25 Jan 2018 15:00:29 -0800 Subject: [PATCH] Adds a11y action for selecting text (and moving cursor) (#4589) See https://github.com/flutter/flutter/pull/14275 for framework side change. Also includes some minor clean-ups for consistency. Required for https://github.com/flutter/flutter/issues/13469. --- lib/ui/semantics.dart | 45 ++++++++++++++----- .../io/flutter/view/AccessibilityBridge.java | 37 ++++++++++++--- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/lib/ui/semantics.dart b/lib/ui/semantics.dart index b51ad19e4f..0fb40000d5 100644 --- a/lib/ui/semantics.dart +++ b/lib/ui/semantics.dart @@ -17,9 +17,10 @@ class SemanticsAction { static const int _kScrollDownIndex = 1 << 5; static const int _kIncreaseIndex = 1 << 6; static const int _kDecreaseIndex = 1 << 7; - static const int _kShowOnScreen = 1 << 8; - static const int _kMoveCursorForwardByCharacter = 1 << 9; - static const int _kMoveCursorBackwardByCharacter = 1 << 10; + static const int _kShowOnScreenIndex = 1 << 8; + static const int _kMoveCursorForwardByCharacterIndex = 1 << 9; + static const int _kMoveCursorBackwardByCharacterIndex = 1 << 10; + static const int _kSetSelectionIndex = 1 << 11; /// The numerical value for this action. /// @@ -76,18 +77,35 @@ class SemanticsAction { /// /// For example, this action might be send to a node in a scrollable list that /// is partially off screen to bring it on screen. - static const SemanticsAction showOnScreen = const SemanticsAction._(_kShowOnScreen); + static const SemanticsAction showOnScreen = const SemanticsAction._(_kShowOnScreenIndex); /// Move the cursor forward by one character. /// /// This is for example used by the cursor control in text fields. - static const SemanticsAction moveCursorForwardByCharacter = const SemanticsAction._(_kMoveCursorForwardByCharacter); + /// + /// The action includes a boolean argument, which indicates whether the cursor + /// movement should extend (or start) a selection. + static const SemanticsAction moveCursorForwardByCharacter = const SemanticsAction._(_kMoveCursorForwardByCharacterIndex); /// Move the cursor backward by one character. /// /// This is for example used by the cursor control in text fields. - static const SemanticsAction moveCursorBackwardByCharacter = const SemanticsAction._(_kMoveCursorBackwardByCharacter); + /// + /// The action includes a boolean argument, which indicates whether the cursor + /// movement should extend (or start) a selection. + static const SemanticsAction moveCursorBackwardByCharacter = const SemanticsAction._(_kMoveCursorBackwardByCharacterIndex); + + /// Set the text selection to the given range. + /// + /// The provided argument is a Map which includes the keys `base` + /// and `extent` indicating where the selection within the `value` of the + /// semantics node should start and where it should end. Values for both + /// keys can range from 0 to length of `value` (inclusive). + /// + /// Setting `base` and `extent` to the same value will move the cursor to + /// that position (without selecting anything). + static const SemanticsAction setSelection = const SemanticsAction._(_kSetSelectionIndex); /// The possible semantics actions. /// @@ -102,9 +120,10 @@ class SemanticsAction { _kScrollDownIndex: scrollDown, _kIncreaseIndex: increase, _kDecreaseIndex: decrease, - _kShowOnScreen: showOnScreen, - _kMoveCursorForwardByCharacter: moveCursorForwardByCharacter, - _kMoveCursorBackwardByCharacter: moveCursorBackwardByCharacter, + _kShowOnScreenIndex: showOnScreen, + _kMoveCursorForwardByCharacterIndex: moveCursorForwardByCharacter, + _kMoveCursorBackwardByCharacterIndex: moveCursorBackwardByCharacter, + _kSetSelectionIndex: setSelection, }; @override @@ -126,12 +145,14 @@ class SemanticsAction { return 'SemanticsAction.increase'; case _kDecreaseIndex: return 'SemanticsAction.decrease'; - case _kShowOnScreen: + case _kShowOnScreenIndex: return 'SemanticsAction.showOnScreen'; - case _kMoveCursorForwardByCharacter: + case _kMoveCursorForwardByCharacterIndex: return 'SemanticsAction.moveCursorForwardByCharacter'; - case _kMoveCursorBackwardByCharacter: + case _kMoveCursorBackwardByCharacterIndex: return 'SemanticsAction.moveCursorBackwardByCharacter'; + case _kSetSelectionIndex: + return 'SemanticsAction.setSelection'; } return null; } diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index 972e72ad9e..dd56adfec6 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -31,6 +31,11 @@ import java.util.Set; class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMessageChannel.MessageHandler { private static final String TAG = "FlutterView"; + // Constants from higher API levels. + // TODO(goderbauer): Get these from Android Support Library when + // https://github.com/flutter/flutter/issues/11099 is resolved. + public static final int ACTION_SHOW_ON_SCREEN = 16908342; // API level 23 + private Map mObjects; private final FlutterView mOwner; private boolean mAccessibilityEnabled = false; @@ -51,7 +56,8 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess DECREASE(1 << 7), SHOW_ON_SCREEN(1 << 8), MOVE_CURSOR_FORWARD_BY_CHARACTER(1 << 9), - MOVE_CURSOR_BACKWARD_BY_CHARACTER(1 << 10); + MOVE_CURSOR_BACKWARD_BY_CHARACTER(1 << 10), + SET_SELECTION(1 << 11); Action(int value) { this.value = value; @@ -142,6 +148,9 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess } result.setMovementGranularities(granularities); } + if (object.hasAction(Action.SET_SELECTION)) { + result.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); + } if (object.hasFlag(Flag.IS_BUTTON)) { result.setClassName("android.widget.Button"); @@ -317,12 +326,30 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess return true; } - // TODO(goderbauer): Use ACTION_SHOW_ON_SCREEN from Android Support Library after - // https://github.com/flutter/flutter/issues/11099 is resolved. - case 16908342: { // ACTION_SHOW_ON_SCREEN, added in API level 23 + case ACTION_SHOW_ON_SCREEN: { mOwner.dispatchSemanticsAction(virtualViewId, Action.SHOW_ON_SCREEN); return true; } + case AccessibilityNodeInfo.ACTION_SET_SELECTION: { + final Map 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", object.textSelectionExtent); + selection.put("extent", object.textSelectionExtent); + } + mOwner.dispatchSemanticsAction(virtualViewId, Action.SET_SELECTION, selection); + return true; + } } return false; } @@ -467,7 +494,7 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess object.id, AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); selectionEvent.getText().add(newValue); selectionEvent.setFromIndex(object.textSelectionBase); - selectionEvent.setToIndex(object.previousTextSelectionExtent); + selectionEvent.setToIndex(object.textSelectionExtent); selectionEvent.setItemCount(newValue.length()); sendAccessibilityEvent(selectionEvent); } -- GitLab