blob: c8a162204623f92655ab2fb15105cbbe788450fb [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ui/a11y/bin/a11y_manager/app.h"
#include <fuchsia/accessibility/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/gtest/test_loop_fixture.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/vfs/cpp/pseudo_dir.h>
#include <gtest/gtest.h>
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_color_transform_handler.h"
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_focus_chain.h"
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_pointer_event_registry.h"
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_property_provider.h"
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_setui_accessibility.h"
#include "src/ui/a11y/bin/a11y_manager/tests/util/util.h"
#include "src/ui/a11y/lib/annotation/tests/mocks/mock_annotation_view.h"
#include "src/ui/a11y/lib/gesture_manager/recognizers/one_finger_n_tap_recognizer.h"
#include "src/ui/a11y/lib/magnifier/tests/mocks/mock_magnification_handler.h"
#include "src/ui/a11y/lib/semantics/tests/mocks/mock_semantic_listener.h"
#include "src/ui/a11y/lib/semantics/tests/mocks/mock_semantic_provider.h"
#include "src/ui/a11y/lib/semantics/tests/mocks/mock_semantics_event_manager.h"
#include "src/ui/a11y/lib/testing/input.h"
#include "src/ui/a11y/lib/util/util.h"
#include "src/ui/a11y/lib/view/tests/mocks/mock_view_semantics.h"
namespace accessibility_test {
namespace {
using fuchsia::accessibility::semantics::Node;
using fuchsia::accessibility::semantics::NodePtr;
using fuchsia::ui::input::accessibility::EventHandling;
using fuchsia::ui::input::accessibility::PointerEventListener;
using fuchsia::ui::input::accessibility::PointerEventListenerPtr;
class AppUnitTest : public gtest::TestLoopFixture {
public:
AppUnitTest()
: context_provider_(),
context_(context_provider_.context()),
mock_pointer_event_registry_(&context_provider_),
mock_color_transform_handler_(&context_provider_),
mock_setui_(&context_provider_),
mock_focus_chain_(&context_provider_),
mock_property_provider_(&context_provider_),
mock_annotation_view_factory_(new MockAnnotationViewFactory()),
mock_view_semantics_factory_(new MockViewSemanticsFactory()),
view_manager_(std::make_unique<a11y::SemanticTreeServiceFactory>(),
std::unique_ptr<MockViewSemanticsFactory>(mock_view_semantics_factory_),
std::unique_ptr<MockAnnotationViewFactory>(mock_annotation_view_factory_),
std::make_unique<MockSemanticsEventManager>(), context_provider_.context(),
context_->outgoing()->debug_dir()),
tts_manager_(context_),
color_transform_manager_(context_),
mock_semantic_provider_(&view_manager_),
app_(context_, &view_manager_, &tts_manager_, &color_transform_manager_,
&gesture_listener_registry_) {}
void SetUp() override {
TestLoopFixture::SetUp();
RunLoopUntilIdle();
// App is created, but is not fully-initialized. Make sure the fetch of settings only happens
// after it has been initialized.
EXPECT_EQ(0, mock_setui_.num_watch_called());
// Right now, obtaining the locale causes the app to be fully-initialized.
ASSERT_EQ(1, mock_property_provider_.get_profile_count());
mock_property_provider_.SetLocale("en");
mock_property_provider_.ReplyToGetProfile();
RunLoopUntilIdle();
ASSERT_EQ(1,
mock_property_provider_.get_profile_count()); // Stil 1, no changes in profile yet.
// Note: 2 here because as soon as we get a settings, we call Watch() again.
ASSERT_EQ(2, mock_setui_.num_watch_called());
}
void AddNodeToTree(uint32_t node_id, std::string label,
std::vector<uint32_t> child_ids = std::vector<uint32_t>()) {
std::vector<a11y::SemanticTree::TreeUpdate> node_updates;
auto node = CreateTestNode(node_id, label, child_ids);
node_updates.emplace_back(std::move(node));
ApplyNodeUpdates(std::move(node_updates));
}
void ApplyNodeUpdates(std::vector<a11y::SemanticTree::TreeUpdate> node_updates) {
auto mock_view_semantics = mock_view_semantics_factory_->GetViewSemantics();
ASSERT_TRUE(mock_view_semantics);
auto tree_ptr = mock_view_semantics->GetTree();
ASSERT_TRUE(tree_ptr);
ASSERT_TRUE(tree_ptr->Update(std::move(node_updates)));
RunLoopUntilIdle();
}
// Sends pointer events and returns the |handled| argument of the (last) resulting
// |OnStreamHandled| invocation.
//
// Yo dawg, I heard you like pointer event listener pointers, so I took a pointer to your pointer
// event listener pointer so you can receive events while you receive events (while honoring the
// C++ style guide).
std::optional<EventHandling> SendPointerEvents(PointerEventListenerPtr* listener,
const std::vector<PointerParams>& events) {
std::optional<EventHandling> event_handling;
listener->events().OnStreamHandled = [&event_handling](uint32_t /*unused*/, uint32_t /*unused*/,
EventHandling handled) {
event_handling = handled;
};
for (const auto& params : events) {
SendPointerEvent(listener->get(), params);
}
return event_handling;
}
void SendPointerEvent(PointerEventListener* listener, const PointerParams& params) {
listener->OnEvent(ToPointerEvent(params, input_event_time_++, mock_semantic_provider_.koid()));
// Simulate trivial passage of time (can expose edge cases with posted async tasks).
RunLoopUntilIdle();
}
// Sends a gesture that wouldn't be recognized by any accessibility feature, for testing arena
// configuration.
std::optional<EventHandling> SendUnrecognizedGesture(PointerEventListenerPtr* listener) {
return SendPointerEvents(listener, Zip({TapEvents(1, {}), TapEvents(2, {})}));
}
sys::testing::ComponentContextProvider context_provider_;
sys::ComponentContext* context_;
MockPointerEventRegistry mock_pointer_event_registry_;
MockColorTransformHandler mock_color_transform_handler_;
MockSetUIAccessibility mock_setui_;
MockFocusChain mock_focus_chain_;
MockPropertyProvider mock_property_provider_;
MockAnnotationViewFactory* mock_annotation_view_factory_;
MockViewSemanticsFactory* mock_view_semantics_factory_;
a11y::ViewManager view_manager_;
a11y::TtsManager tts_manager_;
a11y::ColorTransformManager color_transform_manager_;
a11y::GestureListenerRegistry gesture_listener_registry_;
MockSemanticProvider mock_semantic_provider_;
// App under test
a11y_manager::App app_;
private:
// We don't actually use these times. If we did, we'd want to more closely correlate them with
// fake time.
uint64_t input_event_time_ = 0;
};
// Test to make sure ViewManager Service is exposed by A11y.
// Test sends a node update to ViewManager and then compare the expected
// result using log file created by semantics manager.
TEST_F(AppUnitTest, UpdateNodeToSemanticsManager) {
// Turn on the screen reader.
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
mock_setui_.Set(std::move(settings), [](auto) {});
// Enble semantics and verify that they were enabled correctly.
view_manager_.SetSemanticsEnabled(true);
RunLoopUntilIdle();
EXPECT_TRUE(mock_semantic_provider_.GetSemanticsEnabled());
// Creating test node to update.
AddNodeToTree(0, "Label A");
// Check that the node is in the semantic tree
auto created_node = view_manager_.GetSemanticNode(mock_semantic_provider_.koid(), 0u);
EXPECT_TRUE(created_node);
EXPECT_EQ(created_node->attributes().label(), "Label A");
// Check that the committed node is present in the logs
vfs::PseudoDir* debug_dir = context_->outgoing()->debug_dir();
vfs::internal::Node* test_node;
EXPECT_EQ(ZX_OK, debug_dir->Lookup(std::to_string(mock_semantic_provider_.koid()), &test_node));
}
// This test makes sure that services implemented by the Tts manager are
// available.
TEST_F(AppUnitTest, OffersTtsManagerServices) {
fuchsia::accessibility::tts::TtsManagerPtr tts_manager;
context_provider_.ConnectToPublicService(tts_manager.NewRequest());
RunLoopUntilIdle();
EXPECT_TRUE(tts_manager.is_bound());
}
TEST_F(AppUnitTest, NoListenerInitially) {
mock_setui_.Set({}, [](auto) {});
RunLoopUntilIdle();
EXPECT_FALSE(mock_pointer_event_registry_.listener())
<< "No listener should be registered in the beginning, as there is no accessibility service "
"enabled.";
}
TEST_F(AppUnitTest, ListenerForScreenReader) {
EXPECT_FALSE(app_.state().screen_reader_enabled());
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
mock_setui_.Set(std::move(settings), [](auto) {});
RunLoopUntilIdle();
EXPECT_TRUE(app_.state().screen_reader_enabled());
ASSERT_TRUE(mock_pointer_event_registry_.listener());
EXPECT_EQ(SendUnrecognizedGesture(&mock_pointer_event_registry_.listener()),
EventHandling::CONSUMED);
}
TEST_F(AppUnitTest, ListenerForMagnifier) {
fuchsia::settings::AccessibilitySettings settings;
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
RunLoopUntilIdle();
EXPECT_TRUE(app_.state().magnifier_enabled());
ASSERT_TRUE(mock_pointer_event_registry_.listener());
EXPECT_EQ(SendUnrecognizedGesture(&mock_pointer_event_registry_.listener()),
EventHandling::REJECTED);
}
TEST_F(AppUnitTest, ListenerForAll) {
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
RunLoopUntilIdle();
ASSERT_TRUE(mock_pointer_event_registry_.listener());
EXPECT_EQ(SendUnrecognizedGesture(&mock_pointer_event_registry_.listener()),
EventHandling::CONSUMED);
}
TEST_F(AppUnitTest, NoListenerAfterAllRemoved) {
{
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
}
RunLoopUntilIdle();
{
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(false);
settings.set_enable_magnification(false);
mock_setui_.Set(std::move(settings), [](auto) {});
}
RunLoopUntilIdle();
EXPECT_FALSE(mock_pointer_event_registry_.listener());
}
// Covers a couple additional edge cases around removing listeners.
TEST_F(AppUnitTest, ListenerRemoveOneByOne) {
{
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
}
RunLoopUntilIdle();
{
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(false);
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
}
RunLoopUntilIdle();
EXPECT_EQ(app_.state().screen_reader_enabled(), false);
EXPECT_EQ(app_.state().magnifier_enabled(), true);
ASSERT_TRUE(mock_pointer_event_registry_.listener());
EXPECT_EQ(SendUnrecognizedGesture(&mock_pointer_event_registry_.listener()),
EventHandling::REJECTED);
{
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(false);
settings.set_enable_magnification(false);
mock_setui_.Set(std::move(settings), [](auto) {});
}
RunLoopUntilIdle();
EXPECT_EQ(app_.state().magnifier_enabled(), false);
EXPECT_FALSE(mock_pointer_event_registry_.listener());
}
// Makes sure gesture priorities are right. If they're not, screen reader would intercept this
// gesture.
TEST_F(AppUnitTest, MagnifierGestureWithScreenReader) {
MockMagnificationHandler mag_handler;
fidl::Binding<fuchsia::accessibility::MagnificationHandler> mag_handler_binding(&mag_handler);
{
fuchsia::accessibility::MagnifierPtr magnifier;
context_provider_.ConnectToPublicService(magnifier.NewRequest());
magnifier->RegisterHandler(mag_handler_binding.NewBinding());
}
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
RunLoopUntilIdle();
SendPointerEvents(&mock_pointer_event_registry_.listener(), 3 * TapEvents(1, {}));
RunLoopFor(a11y::Magnifier::kTransitionPeriod);
EXPECT_GT(mag_handler.transform().scale, 1);
}
TEST_F(AppUnitTest, ColorCorrectionApplied) {
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(false);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
EXPECT_EQ(fuchsia::accessibility::ColorCorrectionMode::DISABLED,
mock_color_transform_handler_.GetColorCorrectionMode());
// Turn on color correction.
fuchsia::settings::AccessibilitySettings newAccessibilitySettings;
newAccessibilitySettings.set_color_correction(
fuchsia::settings::ColorBlindnessType::DEUTERANOMALY);
mock_setui_.Set(std::move(newAccessibilitySettings), [](auto) {});
RunLoopUntilIdle();
// Verify that stuff changed
EXPECT_EQ(fuchsia::accessibility::ColorCorrectionMode::CORRECT_DEUTERANOMALY,
mock_color_transform_handler_.GetColorCorrectionMode());
}
TEST_F(AppUnitTest, ColorInversionApplied) {
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(false);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
EXPECT_FALSE(mock_color_transform_handler_.GetColorInversionEnabled());
// Turn on color correction.
fuchsia::settings::AccessibilitySettings newAccessibilitySettings;
newAccessibilitySettings.set_color_inversion(true);
mock_setui_.Set(std::move(newAccessibilitySettings), [](auto) {});
RunLoopUntilIdle();
// Verify that stuff changed
EXPECT_TRUE(mock_color_transform_handler_.GetColorInversionEnabled());
}
TEST_F(AppUnitTest, ScreenReaderOnAtStartup) {
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(true);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
// Verify that screen reader is on and the pointer event registry is wired up.
EXPECT_TRUE(app_.state().screen_reader_enabled());
ASSERT_TRUE(mock_pointer_event_registry_.listener());
EXPECT_EQ(SendUnrecognizedGesture(&mock_pointer_event_registry_.listener()),
EventHandling::CONSUMED);
}
TEST_F(AppUnitTest, InitializesFocusChain) {
// Ensures that when App is initialized, it connects to the Focus Chain different services.
RunLoopUntilIdle();
ASSERT_TRUE(mock_focus_chain_.listener());
ASSERT_TRUE(mock_focus_chain_.HasRegisteredFocuser());
}
// Makes sure FocusChain is wired up with the screen reader, when screen reader is enabled.
// This test uses explore action to make sure when a node is tapped, then screen reader can call
// RequestFocus() on FocusChain. This confirms that FocusChain is connected to ScreenReader.
TEST_F(AppUnitTest, FocusChainIsWiredToScreenReader) {
// Enable Screen Reader.
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(true);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
// Creating test node to update.
AddNodeToTree(0, "Label A");
auto created_node = view_manager_.GetSemanticNode(mock_semantic_provider_.koid(), 0u);
EXPECT_TRUE(created_node);
EXPECT_EQ(created_node->attributes().label(), "Label A");
// Set HitTest result which is required to know which node is being tapped.
mock_semantic_provider_.SetHitTestResult(0);
// Send Tap event for view_ref_. This should trigger explore action, which should then call
// FocusChain to set focus to the tapped view.
SendPointerEvents(&mock_pointer_event_registry_.listener(), TapEvents(1, {}));
RunLoopFor(a11y::OneFingerNTapRecognizer::kTapTimeout);
ASSERT_TRUE(mock_focus_chain_.IsRequestFocusCalled());
EXPECT_EQ(mock_semantic_provider_.koid(), mock_focus_chain_.GetFocusedViewKoid());
auto highlighted_view =
mock_annotation_view_factory_->GetAnnotationView(mock_semantic_provider_.koid());
EXPECT_TRUE(highlighted_view);
auto highlight = highlighted_view->GetCurrentHighlight();
EXPECT_TRUE(highlight.has_value());
}
TEST_F(AppUnitTest, FetchesLocaleInfoOnStartup) {
// App is initialized, so it should have requested once the locale.
ASSERT_EQ(1, mock_property_provider_.get_profile_count());
mock_property_provider_.SetLocale("en-US");
mock_property_provider_.SendOnChangeEvent();
RunLoopUntilIdle();
// The event causes GetProfile() to be invoked again from the a11y manager side. Check if the call
// happened through the mock.
ASSERT_EQ(2, mock_property_provider_.get_profile_count());
}
TEST_F(AppUnitTest, ScreenReaderReceivesLocaleWhenItChanges) {
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(true);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
EXPECT_TRUE(app_.state().screen_reader_enabled());
EXPECT_EQ(app_.screen_reader()->context()->locale_id(), "en");
mock_property_provider_.SetLocale("en-US");
mock_property_provider_.SendOnChangeEvent();
RunLoopUntilIdle();
// The event causes GetProfile() to be invoked again from the a11y manager side. Check if the call
// happened through the mock.
ASSERT_EQ(2, mock_property_provider_.get_profile_count());
// Sends a reply.
mock_property_provider_.ReplyToGetProfile();
RunLoopUntilIdle();
EXPECT_EQ(app_.screen_reader()->context()->locale_id(), "en-US");
}
TEST_F(AppUnitTest, ScreenReaderUsesDefaultLocaleIfPropertyProviderDisconnectsOrIsNotAvailable) {
EXPECT_FALSE(app_.state().screen_reader_enabled());
mock_property_provider_.CloseChannels();
RunLoopUntilIdle();
// Only one call to GetProfile happened, because the channel was closed.
ASSERT_EQ(1, mock_property_provider_.get_profile_count());
// Turns on the Screen Reader and checks that it picks up the default locale.
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(true);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
EXPECT_EQ(app_.screen_reader()->context()->locale_id(), "en-US");
}
// TODO(fxbug.dev/49924): Improve tests to cover what happens if services aren't available at
// startup.
} // namespace
} // namespace accessibility_test