Skip to content

Commit d559afb

Browse files
author
Jonah Williams
authored
Support customizing standard accessibility actions on Android. (#5823)
1 parent 228cecc commit d559afb

File tree

7 files changed

+94
-24
lines changed

7 files changed

+94
-24
lines changed

lib/ui/semantics.dart

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,9 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 {
585585
Float64List transform,
586586
Int32List childrenInTraversalOrder,
587587
Int32List childrenInHitTestOrder,
588+
@Deprecated('use additionalActions instead')
588589
Int32List customAcccessibilityActions,
590+
Int32List additionalActions,
589591
}) {
590592
if (transform.length != 16)
591593
throw new ArgumentError('transform argument must have 16 entries.');
@@ -611,7 +613,7 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 {
611613
transform,
612614
childrenInTraversalOrder,
613615
childrenInHitTestOrder,
614-
customAcccessibilityActions,
616+
additionalActions ?? customAcccessibilityActions,
615617
);
616618
}
617619
void _updateNode(
@@ -636,19 +638,30 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 {
636638
Float64List transform,
637639
Int32List childrenInTraversalOrder,
638640
Int32List childrenInHitTestOrder,
639-
Int32List customAcccessibilityActions,
641+
Int32List additionalActions,
640642
) native 'SemanticsUpdateBuilder_updateNode';
641643

642-
/// Update the custom accessibility action associated with the given `id`.
644+
/// Update the custom semantics action associated with the given `id`.
645+
///
646+
/// The name of the action exposed to the user is the `label`. For overriden
647+
/// standard actions this value is ignored.
648+
///
649+
/// The `hint` should describe what happens when an action occurs, not the
650+
/// manner in which a tap is accomplished. For example, use "delete" instead
651+
/// of "double tap to delete".
652+
///
653+
/// The text direction of the `hint` and `label` is the same as the global
654+
/// window.
643655
///
644-
/// The name of the action exposed to the user is the `label`. The text
645-
/// direction of this label is the same as the global window.
646-
void updateCustomAction({int id, String label}) {
656+
/// For overriden standard actions, `overrideId` corresponds with a
657+
/// [SemanticsAction.index] value. For custom actions this argument should not be
658+
/// provided.
659+
void updateCustomAction({int id, String label, String hint, int overrideId = -1}) {
647660
assert(id != null);
648-
assert(label != null && label != '');
649-
_updateCustomAction(id, label);
661+
assert(overrideId != null);
662+
_updateCustomAction(id, label, hint, overrideId);
650663
}
651-
void _updateCustomAction(int id, String label) native 'SemanticsUpdateBuilder_updateCustomAction';
664+
void _updateCustomAction(int id, String label, String hint, int overrideId) native 'SemanticsUpdateBuilder_updateCustomAction';
652665

653666
/// Creates a [SemanticsUpdate] object that encapsulates the updates recorded
654667
/// by this object.

lib/ui/semantics/custom_accessibility_action.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ struct CustomAccessibilityAction {
2020
~CustomAccessibilityAction();
2121

2222
int32_t id = 0;
23+
int32_t overrideId = -1;
2324
std::string label;
25+
std::string hint;
2426
};
2527

2628
// Contains custom accessibility actions that need to be updated.

lib/ui/semantics/semantics_update_builder.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,15 @@ void SemanticsUpdateBuilder::updateNode(
8888
nodes_[id] = node;
8989
}
9090

91-
void SemanticsUpdateBuilder::updateCustomAction(int id, std::string label) {
91+
void SemanticsUpdateBuilder::updateCustomAction(int id,
92+
std::string label,
93+
std::string hint,
94+
int overrideId) {
9295
CustomAccessibilityAction action;
9396
action.id = id;
97+
action.overrideId = overrideId;
9498
action.label = label;
99+
action.hint = hint;
95100
actions_[id] = action;
96101
}
97102

lib/ui/semantics/semantics_update_builder.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ class SemanticsUpdateBuilder
4747
const tonic::Int32List& childrenInHitTestOrder,
4848
const tonic::Int32List& customAccessibilityActions);
4949

50-
void updateCustomAction(int id, std::string label);
50+
void updateCustomAction(int id,
51+
std::string label,
52+
std::string hint,
53+
int overrideId);
5154

5255
fxl::RefPtr<SemanticsUpdate> build();
5356

shell/platform/android/io/flutter/view/AccessibilityBridge.java

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,24 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
223223
!object.hasFlag(Flag.HAS_ENABLED_STATE) || object.hasFlag(Flag.IS_ENABLED));
224224

225225
if (object.hasAction(Action.TAP)) {
226-
result.addAction(AccessibilityNodeInfo.ACTION_CLICK);
227-
result.setClickable(true);
226+
if (Build.VERSION.SDK_INT >= 21 && object.onTapOverride != null) {
227+
result.addAction(new AccessibilityNodeInfo.AccessibilityAction(
228+
AccessibilityNodeInfo.ACTION_CLICK, object.onTapOverride.hint));
229+
result.setClickable(true);
230+
} else {
231+
result.addAction(AccessibilityNodeInfo.ACTION_CLICK);
232+
result.setClickable(true);
233+
}
228234
}
229235
if (object.hasAction(Action.LONG_PRESS)) {
230-
result.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
231-
result.setLongClickable(true);
236+
if (Build.VERSION.SDK_INT >= 21 && object.onLongPressOverride != null) {
237+
result.addAction(new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_LONG_CLICK,
238+
object.onLongPressOverride.hint));
239+
result.setLongClickable(true);
240+
} else {
241+
result.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
242+
result.setLongClickable(true);
243+
}
232244
}
233245
if (object.hasAction(Action.SCROLL_LEFT) || object.hasAction(Action.SCROLL_UP)
234246
|| object.hasAction(Action.SCROLL_RIGHT) || object.hasAction(Action.SCROLL_DOWN)) {
@@ -288,8 +300,8 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
288300

289301
// Actions on the local context menu
290302
if (Build.VERSION.SDK_INT >= 21) {
291-
if (object.customAccessibilityAction != null) {
292-
for (CustomAccessibilityAction action : object.customAccessibilityAction) {
303+
if (object.customAccessibilityActions != null) {
304+
for (CustomAccessibilityAction action : object.customAccessibilityActions) {
293305
result.addAction(new AccessibilityNodeInfo.AccessibilityAction(
294306
action.resourceId, action.label));
295307
}
@@ -547,8 +559,11 @@ void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) {
547559
while (buffer.hasRemaining()) {
548560
int id = buffer.getInt();
549561
CustomAccessibilityAction action = getOrCreateAction(id);
562+
action.overrideId = buffer.getInt();
550563
int stringIndex = buffer.getInt();
551564
action.label = stringIndex == -1 ? null : strings[stringIndex];
565+
stringIndex = buffer.getInt();
566+
action.hint = stringIndex == -1 ? null : strings[stringIndex];
552567
}
553568
}
554569

@@ -851,9 +866,17 @@ private class CustomAccessibilityAction {
851866
/// does not collide with existing Android accessibility actions.
852867
int resourceId = -1;
853868
int id = -1;
869+
int overrideId = -1;
854870

855871
/// The label is the user presented value which is displayed in the local context menu.
856872
String label;
873+
874+
/// The hint is the text used in overriden standard actions.
875+
String hint;
876+
877+
boolean isStandardAction() {
878+
return overrideId != -1;
879+
}
857880
}
858881
/// Value is derived from ACTION_TYPE_MASK in AccessibilityNodeInfo.java
859882
static int firstResourceId = 267386881;
@@ -897,7 +920,9 @@ private class SemanticsObject {
897920
SemanticsObject parent;
898921
List<SemanticsObject> childrenInTraversalOrder;
899922
List<SemanticsObject> childrenInHitTestOrder;
900-
List<CustomAccessibilityAction> customAccessibilityAction;
923+
List<CustomAccessibilityAction> customAccessibilityActions;
924+
CustomAccessibilityAction onTapOverride;
925+
CustomAccessibilityAction onLongPressOverride;
901926

902927
private boolean inverseTransformDirty = true;
903928
private float[] inverseTransform;
@@ -1030,17 +1055,27 @@ void updateWith(ByteBuffer buffer, String[] strings) {
10301055
}
10311056
final int actionCount = buffer.getInt();
10321057
if (actionCount == 0) {
1033-
customAccessibilityAction = null;
1058+
customAccessibilityActions = null;
10341059
} else {
1035-
if (customAccessibilityAction == null)
1036-
customAccessibilityAction =
1060+
if (customAccessibilityActions == null)
1061+
customAccessibilityActions =
10371062
new ArrayList<CustomAccessibilityAction>(actionCount);
10381063
else
1039-
customAccessibilityAction.clear();
1064+
customAccessibilityActions.clear();
10401065

10411066
for (int i = 0; i < actionCount; i++) {
10421067
CustomAccessibilityAction action = getOrCreateAction(buffer.getInt());
1043-
customAccessibilityAction.add(action);
1068+
if (action.overrideId == Action.TAP.value) {
1069+
onTapOverride = action;
1070+
} else if (action.overrideId == Action.LONG_PRESS.value) {
1071+
onLongPressOverride = action;
1072+
} else {
1073+
// If we recieve a different overrideId it means that we were passed
1074+
// a standard action to override that we don't yet support.
1075+
assert action.overrideId == -1;
1076+
customAccessibilityActions.add(action);
1077+
}
1078+
customAccessibilityActions.add(action);
10441079
}
10451080
}
10461081
}

shell/platform/android/platform_view_android.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ void PlatformViewAndroid::UpdateSemantics(
183183
blink::CustomAccessibilityActionUpdates actions) {
184184
constexpr size_t kBytesPerNode = 36 * sizeof(int32_t);
185185
constexpr size_t kBytesPerChild = sizeof(int32_t);
186-
constexpr size_t kBytesPerAction = 2 * sizeof(int32_t);
186+
constexpr size_t kBytesPerAction = 4 * sizeof(int32_t);
187187

188188
JNIEnv* env = fml::jni::AttachCurrentThread();
189189
{
@@ -284,12 +284,19 @@ void PlatformViewAndroid::UpdateSemantics(
284284
// sending.
285285
const blink::CustomAccessibilityAction& action = value.second;
286286
actions_buffer_int32[actions_position++] = action.id;
287+
actions_buffer_int32[actions_position++] = action.overrideId;
287288
if (action.label.empty()) {
288289
actions_buffer_int32[actions_position++] = -1;
289290
} else {
290291
actions_buffer_int32[actions_position++] = action_strings.size();
291292
action_strings.push_back(action.label);
292293
}
294+
if (action.hint.empty()) {
295+
actions_buffer_int32[actions_position++] = -1;
296+
} else {
297+
actions_buffer_int32[actions_position++] = action_strings.size();
298+
action_strings.push_back(action.hint);
299+
}
293300
}
294301

295302
// Calling NewDirectByteBuffer in API level 22 and below with a size of zero

shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,11 @@ - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
542542
[[[NSMutableArray alloc] init] autorelease];
543543
for (int32_t action_id : node.customAccessibilityActions) {
544544
blink::CustomAccessibilityAction& action = actions_[action_id];
545+
if (action.overrideId != -1) {
546+
// iOS does not support overriding standard actions, so we ignore any
547+
// custom actions that have an override id provided.
548+
continue;
549+
}
545550
NSString* label = @(action.label.data());
546551
SEL selector = @selector(onCustomAccessibilityAction:);
547552
FlutterCustomAccessibilityAction* customAction =

0 commit comments

Comments
 (0)