From e24ec598e0c311061697494291307ad743fab0c3 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 4 Feb 2020 19:01:19 -0800 Subject: [PATCH] Fuchsia a11y actions (#16321) --- .../fuchsia/flutter/accessibility_bridge.cc | 55 +++++++++++++++- .../fuchsia/flutter/accessibility_bridge.h | 23 +++++-- .../flutter/accessibility_bridge_unittest.cc | 62 +++++++++++++++++++ .../platform/fuchsia/flutter/platform_view.cc | 7 +++ .../platform/fuchsia/flutter/platform_view.h | 4 ++ 5 files changed, 144 insertions(+), 7 deletions(-) diff --git a/shell/platform/fuchsia/flutter/accessibility_bridge.cc b/shell/platform/fuchsia/flutter/accessibility_bridge.cc index c75edc6e8a58..1ddb905b9bdb 100644 --- a/shell/platform/fuchsia/flutter/accessibility_bridge.cc +++ b/shell/platform/fuchsia/flutter/accessibility_bridge.cc @@ -281,12 +281,65 @@ void AccessibilityBridge::UpdateScreenRects( } } +std::optional +AccessibilityBridge::GetFlutterSemanticsAction( + fuchsia::accessibility::semantics::Action fuchsia_action, + uint32_t node_id) { + switch (fuchsia_action) { + // The default action associated with the element. + case fuchsia::accessibility::semantics::Action::DEFAULT: + return flutter::SemanticsAction::kTap; + // The secondary action associated with the element. This may correspond to + // a long press (touchscreens) or right click (mouse). + case fuchsia::accessibility::semantics::Action::SECONDARY: + return flutter::SemanticsAction::kLongPress; + // Set (input/non-accessibility) focus on this element. + case fuchsia::accessibility::semantics::Action::SET_FOCUS: + FML_DLOG(WARNING) + << "Unsupported action SET_FOCUS sent for accessibility node " + << node_id; + return {}; + // Set the element's value. + case fuchsia::accessibility::semantics::Action::SET_VALUE: + FML_DLOG(WARNING) + << "Unsupported action SET_VALUE sent for accessibility node " + << node_id; + return {}; + // Scroll node to make it visible. + case fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN: + return flutter::SemanticsAction::kShowOnScreen; + default: + FML_DLOG(WARNING) << "Unexpected action " + << static_cast(fuchsia_action) + << " sent for accessibility node " << node_id; + return {}; + } +} + // |fuchsia::accessibility::semantics::SemanticListener| void AccessibilityBridge::OnAccessibilityActionRequested( uint32_t node_id, fuchsia::accessibility::semantics::Action action, fuchsia::accessibility::semantics::SemanticListener:: - OnAccessibilityActionRequestedCallback callback) {} + OnAccessibilityActionRequestedCallback callback) { + if (nodes_.find(node_id) == nodes_.end()) { + FML_LOG(ERROR) << "Attempted to send accessibility action " + << static_cast(action) + << " to unkonwn node id: " << node_id; + callback(false); + return; + } + + std::optional flutter_action = + GetFlutterSemanticsAction(action, node_id); + if (!flutter_action.has_value()) { + callback(false); + return; + } + delegate_.DispatchSemanticsAction(static_cast(node_id), + flutter_action.value()); + callback(true); +} // |fuchsia::accessibility::semantics::SemanticListener| void AccessibilityBridge::HitTest( diff --git a/shell/platform/fuchsia/flutter/accessibility_bridge.h b/shell/platform/fuchsia/flutter/accessibility_bridge.h index 4072c9696ecf..cfa2252d92de 100644 --- a/shell/platform/fuchsia/flutter/accessibility_bridge.h +++ b/shell/platform/fuchsia/flutter/accessibility_bridge.h @@ -44,6 +44,8 @@ class AccessibilityBridge class Delegate { public: virtual void SetSemanticsEnabled(bool enabled) = 0; + virtual void DispatchSemanticsAction(int32_t node_id, + flutter::SemanticsAction action) = 0; }; // TODO(MI4-2531, FIDL-718): Remove this. We shouldn't be worried about @@ -91,6 +93,13 @@ class AccessibilityBridge fuchsia::accessibility::semantics::SemanticListener::HitTestCallback callback) override; + // |fuchsia::accessibility::semantics::SemanticListener| + void OnAccessibilityActionRequested( + uint32_t node_id, + fuchsia::accessibility::semantics::Action action, + fuchsia::accessibility::semantics::SemanticListener:: + OnAccessibilityActionRequestedCallback callback) override; + private: // Holds only the fields we need for hit testing. // In particular, it adds a screen_rect field to flutter::SemanticsNode. @@ -164,12 +173,14 @@ class AccessibilityBridge // Assumes that SemanticsNode::screen_rect is up to date. std::optional GetHitNode(int32_t node_id, float x, float y); - // |fuchsia::accessibility::semantics::SemanticListener| - void OnAccessibilityActionRequested( - uint32_t node_id, - fuchsia::accessibility::semantics::Action action, - fuchsia::accessibility::semantics::SemanticListener:: - OnAccessibilityActionRequestedCallback callback) override; + // Converts a fuchsia::accessibility::semantics::Action to a + // flutter::SemanticsAction. + // + // The node_id parameter is used for printing warnings about unsupported + // action types. + std::optional GetFlutterSemanticsAction( + fuchsia::accessibility::semantics::Action fuchsia_action, + uint32_t node_id); // |fuchsia::accessibility::semantics::SemanticListener| void OnSemanticsModeChanged(bool enabled, diff --git a/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc b/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc index ef2d61927166..320249b944a8 100644 --- a/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc +++ b/shell/platform/fuchsia/flutter/accessibility_bridge_unittest.cc @@ -23,7 +23,13 @@ class AccessibilityBridgeTestDelegate : public flutter_runner::AccessibilityBridge::Delegate { public: void SetSemanticsEnabled(bool enabled) override { enabled_ = enabled; } + void DispatchSemanticsAction(int32_t node_id, + flutter::SemanticsAction action) override { + actions.push_back(std::make_pair(node_id, action)); + } + bool enabled() { return enabled_; } + std::vector> actions; private: bool enabled_; @@ -50,6 +56,7 @@ class AccessibilityBridgeTest : public testing::Test { /*flags*/ 0u, &view_ref_control_.reference, &view_ref_.reference); EXPECT_EQ(status, ZX_OK); + accessibility_delegate_.actions.clear(); accessibility_bridge_ = std::make_unique( accessibility_delegate_, services_provider_.service_directory(), @@ -364,4 +371,59 @@ TEST_F(AccessibilityBridgeTest, HitTest) { accessibility_bridge_->HitTest({30, 30}, callback); EXPECT_EQ(hit_node_id, 4u); } + +TEST_F(AccessibilityBridgeTest, Actions) { + flutter::SemanticsNode node0; + node0.id = 0; + + flutter::SemanticsNode node1; + node1.id = 1; + + node0.childrenInTraversalOrder = {1}; + node0.childrenInHitTestOrder = {1}; + + accessibility_bridge_->AddSemanticsNodeUpdate({ + {0, node0}, + {1, node1}, + }); + RunLoopUntilIdle(); + + auto handled_callback = [](bool handled) { EXPECT_TRUE(handled); }; + auto unhandled_callback = [](bool handled) { EXPECT_FALSE(handled); }; + + accessibility_bridge_->OnAccessibilityActionRequested( + 0u, fuchsia::accessibility::semantics::Action::DEFAULT, handled_callback); + EXPECT_EQ(accessibility_delegate_.actions.size(), 1u); + EXPECT_EQ(accessibility_delegate_.actions.back(), + std::make_pair(0, flutter::SemanticsAction::kTap)); + + accessibility_bridge_->OnAccessibilityActionRequested( + 0u, fuchsia::accessibility::semantics::Action::SECONDARY, + handled_callback); + EXPECT_EQ(accessibility_delegate_.actions.size(), 2u); + EXPECT_EQ(accessibility_delegate_.actions.back(), + std::make_pair(0, flutter::SemanticsAction::kLongPress)); + + accessibility_bridge_->OnAccessibilityActionRequested( + 0u, fuchsia::accessibility::semantics::Action::SET_FOCUS, + unhandled_callback); + EXPECT_EQ(accessibility_delegate_.actions.size(), 2u); + + accessibility_bridge_->OnAccessibilityActionRequested( + 0u, fuchsia::accessibility::semantics::Action::SET_VALUE, + unhandled_callback); + EXPECT_EQ(accessibility_delegate_.actions.size(), 2u); + + accessibility_bridge_->OnAccessibilityActionRequested( + 0u, fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN, + handled_callback); + EXPECT_EQ(accessibility_delegate_.actions.size(), 3u); + EXPECT_EQ(accessibility_delegate_.actions.back(), + std::make_pair(0, flutter::SemanticsAction::kShowOnScreen)); + + accessibility_bridge_->OnAccessibilityActionRequested( + 2u, fuchsia::accessibility::semantics::Action::DEFAULT, + unhandled_callback); + EXPECT_EQ(accessibility_delegate_.actions.size(), 3u); +} } // namespace flutter_runner_test diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index e73b20e882e7..3795589ac6cf 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -602,6 +602,13 @@ void PlatformView::SetSemanticsEnabled(bool enabled) { } } +// |flutter::PlatformView| +// |flutter_runner::AccessibilityBridge::Delegate| +void PlatformView::DispatchSemanticsAction(int32_t node_id, + flutter::SemanticsAction action) { + flutter::PlatformView::DispatchSemanticsAction(node_id, action, {}); +} + // |flutter::PlatformView| void PlatformView::UpdateSemantics( flutter::SemanticsNodeUpdates update, diff --git a/shell/platform/fuchsia/flutter/platform_view.h b/shell/platform/fuchsia/flutter/platform_view.h index 4e0f434e97cb..a4b52028e7d3 100644 --- a/shell/platform/fuchsia/flutter/platform_view.h +++ b/shell/platform/fuchsia/flutter/platform_view.h @@ -69,6 +69,10 @@ class PlatformView final : public flutter::PlatformView, // |flutter_runner::AccessibilityBridge::Delegate| void SetSemanticsEnabled(bool enabled) override; + // |flutter_runner::AccessibilityBridge::Delegate| + void DispatchSemanticsAction(int32_t node_id, + flutter::SemanticsAction action) override; + // |PlatformView| flutter::PointerDataDispatcherMaker GetDispatcherMaker() override;