| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/accessibility/fake_service_client.h" |
| |
| #include "base/functional/callback_forward.h" |
| #include "base/notreached.h" |
| #include "mojo/public/cpp/bindings/pending_associated_remote.h" |
| #include "ui/accessibility/ax_tree_id.h" |
| |
| namespace ax { |
| FakeServiceClient::FakeServiceClient(mojom::AccessibilityService* service) |
| : service_(service) { |
| desktop_tree_id_ = ui::AXTreeID::CreateNewAXTreeID(); |
| } |
| |
| FakeServiceClient::~FakeServiceClient() = default; |
| |
| void FakeServiceClient::BindAutomation( |
| mojo::PendingAssociatedRemote<ax::mojom::Automation> automation) { |
| automation_remotes_.Add(std::move(automation)); |
| if (automation_bound_closure_) { |
| std::move(automation_bound_closure_).Run(); |
| } |
| } |
| |
| void FakeServiceClient::BindAutomationClient( |
| mojo::PendingReceiver<ax::mojom::AutomationClient> automation_client) { |
| automation_client_receivers_.Add(this, std::move(automation_client)); |
| } |
| |
| void FakeServiceClient::Enable(EnableCallback callback) { |
| std::move(callback).Run(desktop_tree_id_); |
| } |
| |
| void FakeServiceClient::Disable() {} |
| |
| void FakeServiceClient::EnableTree(const ui::AXTreeID& tree_id) {} |
| |
| void FakeServiceClient::PerformAction(const ui::AXActionData& data) {} |
| |
| #if BUILDFLAG(SUPPORTS_OS_ACCESSIBILITY_SERVICE) |
| void FakeServiceClient::BindAutoclickClient( |
| mojo::PendingReceiver<ax::mojom::AutoclickClient> |
| autoclick_client_reciever) { |
| autoclick_client_recievers_.Add(this, std::move(autoclick_client_reciever)); |
| } |
| void FakeServiceClient::BindSpeechRecognition( |
| mojo::PendingReceiver<ax::mojom::SpeechRecognition> sr_receiver) { |
| sr_receivers_.Add(this, std::move(sr_receiver)); |
| } |
| |
| void FakeServiceClient::BindTts( |
| mojo::PendingReceiver<ax::mojom::Tts> tts_receiver) { |
| tts_receivers_.Add(this, std::move(tts_receiver)); |
| } |
| |
| void FakeServiceClient::BindUserInput( |
| mojo::PendingReceiver<mojom::UserInput> ui_receiver) { |
| ui_receivers_.Add(this, std::move(ui_receiver)); |
| } |
| |
| void FakeServiceClient::BindUserInterface( |
| mojo::PendingReceiver<mojom::UserInterface> ux_receiver) { |
| ux_receivers_.Add(this, std::move(ux_receiver)); |
| } |
| |
| void FakeServiceClient::HandleScrollableBoundsForPointFound( |
| const gfx::Rect& bounds) { |
| if (scrollable_bounds_for_point_callback_) { |
| scrollable_bounds_for_point_callback_.Run(bounds); |
| } |
| } |
| |
| void FakeServiceClient::BindAutoclick(BindAutoclickCallback callback) { |
| std::move(callback).Run(autoclick_remote_.BindNewPipeAndPassReceiver()); |
| } |
| |
| void FakeServiceClient::Start(ax::mojom::StartOptionsPtr options, |
| StartCallback callback) { |
| auto info = mojom::SpeechRecognitionStartInfo::New(); |
| info->type = mojom::SpeechRecognitionType::kNetwork; |
| if (speech_recognition_start_error_.has_value()) { |
| info->observer_or_error = mojom::ObserverOrError::NewError( |
| speech_recognition_start_error_.value()); |
| } else { |
| info->observer_or_error = mojom::ObserverOrError::NewObserver( |
| sr_event_observer_.BindNewPipeAndPassReceiver()); |
| } |
| |
| std::move(callback).Run(std::move(info)); |
| if (speech_recognition_start_callback_) { |
| speech_recognition_start_callback_.Run(); |
| } |
| } |
| |
| void FakeServiceClient::Stop(ax::mojom::StopOptionsPtr options, |
| StopCallback callback) { |
| std::move(callback).Run(speech_recognition_stop_error_); |
| } |
| |
| void FakeServiceClient::BindAccessibilityFileLoader( |
| mojo::PendingReceiver<ax::mojom::AccessibilityFileLoader> |
| file_loader_receiver) { |
| DCHECK(!file_loader_.is_bound()); |
| file_loader_.Bind(std::move(file_loader_receiver)); |
| } |
| |
| void FakeServiceClient::Load(const base::FilePath& path, |
| LoadCallback callback) { |
| // TODO(crbug.com/1493546): Implement file loading for |
| // FakeAccessibilityServiceClient. |
| } |
| |
| void FakeServiceClient::Speak(const std::string& utterance, |
| ax::mojom::TtsOptionsPtr options, |
| SpeakCallback callback) { |
| auto result = mojom::TtsSpeakResult::New(); |
| result->error = mojom::TtsError::kNoError; |
| result->utterance_client = tts_utterance_client_.BindNewPipeAndPassReceiver(); |
| std::move(callback).Run(std::move(result)); |
| if (tts_speak_callback_) { |
| tts_speak_callback_.Run(utterance, std::move(options)); |
| } |
| } |
| |
| void FakeServiceClient::Stop() { |
| if (!tts_utterance_client_.is_bound()) { |
| return; |
| } |
| auto event = mojom::TtsEvent::New(); |
| event->type = mojom::TtsEventType::kInterrupted; |
| tts_utterance_client_->OnEvent(std::move(event)); |
| tts_utterance_client_.reset(); |
| } |
| |
| void FakeServiceClient::Pause() { |
| if (!tts_utterance_client_.is_bound()) { |
| return; |
| } |
| auto event = mojom::TtsEvent::New(); |
| event->type = mojom::TtsEventType::kPause; |
| tts_utterance_client_->OnEvent(std::move(event)); |
| } |
| |
| void FakeServiceClient::Resume() { |
| if (!tts_utterance_client_.is_bound()) { |
| return; |
| } |
| auto event = mojom::TtsEvent::New(); |
| event->type = mojom::TtsEventType::kResume; |
| tts_utterance_client_->OnEvent(std::move(event)); |
| } |
| |
| void FakeServiceClient::IsSpeaking(IsSpeakingCallback callback) { |
| std::move(callback).Run(tts_utterance_client_.is_bound()); |
| } |
| |
| void FakeServiceClient::GetVoices(GetVoicesCallback callback) { |
| std::vector<ax::mojom::TtsVoicePtr> voices; |
| |
| // Create a voice with all event types. |
| auto first_voice = ax::mojom::TtsVoice::New(); |
| first_voice->voice_name = "Lyra"; |
| first_voice->lang = "en-US", first_voice->remote = false; |
| first_voice->engine_id = "us_toddler"; |
| first_voice->event_types = std::vector<mojom::TtsEventType>(); |
| for (int i = static_cast<int>(mojom::TtsEventType::kMinValue); |
| i <= static_cast<int>(mojom::TtsEventType::kMaxValue); i++) { |
| first_voice->event_types->emplace_back(static_cast<mojom::TtsEventType>(i)); |
| } |
| |
| // Create a voice with just two event types/ |
| auto second_voice = ax::mojom::TtsVoice::New(); |
| second_voice->voice_name = "Juno"; |
| second_voice->lang = "en-GB", second_voice->remote = true; |
| second_voice->engine_id = "us_baby"; |
| second_voice->event_types = std::vector<mojom::TtsEventType>(); |
| second_voice->event_types->emplace_back(mojom::TtsEventType::kStart); |
| second_voice->event_types->emplace_back(mojom::TtsEventType::kEnd); |
| |
| voices.emplace_back(std::move(first_voice)); |
| voices.emplace_back(std::move(second_voice)); |
| std::move(callback).Run(std::move(voices)); |
| } |
| |
| void FakeServiceClient::SendSyntheticKeyEventForShortcutOrNavigation( |
| mojom::SyntheticKeyEventPtr key_event) { |
| key_events_.emplace_back(std::move(key_event)); |
| if (synthetic_key_event_callback_) { |
| synthetic_key_event_callback_.Run(); |
| } |
| } |
| |
| void FakeServiceClient::SendSyntheticMouseEvent( |
| mojom::SyntheticMouseEventPtr mouse_event) { |
| mouse_events_.emplace_back(mouse_event.Clone()); |
| if (synthetic_mouse_event_callback_) { |
| synthetic_mouse_event_callback_.Run(); |
| } |
| } |
| |
| void FakeServiceClient::DarkenScreen(bool darken) { |
| if (darken_screen_callback_) { |
| darken_screen_callback_.Run(darken); |
| } |
| } |
| |
| void FakeServiceClient::OpenSettingsSubpage(const std::string& subpage) { |
| if (open_settings_subpage_callback_) { |
| open_settings_subpage_callback_.Run(subpage); |
| } |
| } |
| |
| void FakeServiceClient::ShowConfirmationDialog( |
| const std::string& title, |
| const std::string& description, |
| const std::optional<std::string>& cancel_name, |
| ShowConfirmationDialogCallback callback) { |
| std::move(callback).Run(true); |
| } |
| |
| void FakeServiceClient::SetFocusRings( |
| std::vector<mojom::FocusRingInfoPtr> focus_rings, |
| mojom::AssistiveTechnologyType at_type) { |
| focus_rings_for_type_[at_type] = std::move(focus_rings); |
| if (focus_rings_callback_) { |
| focus_rings_callback_.Run(); |
| } |
| } |
| |
| void FakeServiceClient::SetHighlights(const std::vector<gfx::Rect>& rects, |
| SkColor color) { |
| if (highlights_callback_) { |
| highlights_callback_.Run(rects, color); |
| } |
| } |
| |
| void FakeServiceClient::SetVirtualKeyboardVisible(bool is_visible) { |
| if (virtual_keyboard_visible_callback_) { |
| virtual_keyboard_visible_callback_.Run(is_visible); |
| } |
| } |
| #endif // BUILDFLAG(SUPPORTS_OS_ACCESSIBILITY_SERVICE) |
| |
| void FakeServiceClient::BindAccessibilityServiceClientForTest() { |
| if (service_) { |
| service_->BindAccessibilityServiceClient( |
| a11y_client_receiver_.BindNewPipeAndPassRemote()); |
| } |
| } |
| |
| void FakeServiceClient::SetAutomationBoundClosure(base::OnceClosure closure) { |
| automation_bound_closure_ = std::move(closure); |
| } |
| |
| bool FakeServiceClient::AutomationIsBound() const { |
| return automation_client_receivers_.size() && automation_remotes_.size(); |
| } |
| |
| #if BUILDFLAG(SUPPORTS_OS_ACCESSIBILITY_SERVICE) |
| void FakeServiceClient::RequestScrollableBoundsForPoint( |
| const gfx::Point& point) { |
| autoclick_remote_->RequestScrollableBoundsForPoint(point); |
| } |
| |
| void FakeServiceClient::SetScrollableBoundsForPointFoundCallback( |
| base::RepeatingCallback<void(const gfx::Rect&)> callback) { |
| scrollable_bounds_for_point_callback_ = std::move(callback); |
| } |
| |
| void FakeServiceClient::SetSpeechRecognitionStartCallback( |
| base::RepeatingCallback<void()> callback) { |
| speech_recognition_start_callback_ = std::move(callback); |
| } |
| |
| void FakeServiceClient::SendSpeechRecognitionStopEvent() { |
| sr_event_observer_->OnStop(); |
| } |
| |
| void FakeServiceClient::SendSpeechRecognitionResultEvent() { |
| auto result = ax::mojom::SpeechRecognitionResultEvent::New(); |
| result->transcript = "Hello world"; |
| result->is_final = true; |
| sr_event_observer_->OnResult(std::move(result)); |
| } |
| |
| void FakeServiceClient::SendSpeechRecognitionErrorEvent() { |
| auto event = ax::mojom::SpeechRecognitionErrorEvent::New(); |
| event->message = "Goodnight world"; |
| sr_event_observer_->OnError(std::move(event)); |
| } |
| |
| void FakeServiceClient::SetSpeechRecognitionStartError( |
| const std::string& error) { |
| speech_recognition_start_error_ = error; |
| } |
| |
| void FakeServiceClient::SetSpeechRecognitionStopError( |
| const std::string& error) { |
| speech_recognition_stop_error_ = error; |
| } |
| |
| void FakeServiceClient::SetTtsSpeakCallback( |
| base::RepeatingCallback<void(const std::string&, mojom::TtsOptionsPtr)> |
| callback) { |
| tts_speak_callback_ = std::move(callback); |
| } |
| |
| void FakeServiceClient::SendTtsUtteranceEvent(mojom::TtsEventPtr tts_event) { |
| CHECK(tts_utterance_client_.is_bound()); |
| tts_utterance_client_->OnEvent(std::move(tts_event)); |
| } |
| |
| void FakeServiceClient::SetSyntheticKeyEventCallback( |
| base::RepeatingCallback<void()> callback) { |
| synthetic_key_event_callback_ = std::move(callback); |
| } |
| |
| void FakeServiceClient::SetSyntheticMouseEventCallback( |
| base::RepeatingCallback<void()> callback) { |
| synthetic_mouse_event_callback_ = std::move(callback); |
| } |
| |
| const std::vector<mojom::SyntheticKeyEventPtr>& |
| FakeServiceClient::GetKeyEvents() const { |
| return key_events_; |
| } |
| |
| const std::vector<mojom::SyntheticMouseEventPtr>& |
| FakeServiceClient::GetMouseEvents() const { |
| return mouse_events_; |
| } |
| |
| void FakeServiceClient::SetDarkenScreenCallback( |
| base::RepeatingCallback<void(bool darken)> callback) { |
| darken_screen_callback_ = std::move(callback); |
| } |
| |
| void FakeServiceClient::SetOpenSettingsSubpageCallback( |
| base::RepeatingCallback<void(const std::string& subpage)> callback) { |
| open_settings_subpage_callback_ = std::move(callback); |
| } |
| |
| void FakeServiceClient::SetFocusRingsCallback( |
| base::RepeatingCallback<void()> callback) { |
| focus_rings_callback_ = std::move(callback); |
| } |
| |
| void FakeServiceClient::SetHighlightsCallback( |
| base::RepeatingCallback<void(const std::vector<gfx::Rect>& rects, |
| SkColor color)> callback) { |
| highlights_callback_ = callback; |
| } |
| |
| void FakeServiceClient::SetVirtualKeyboardVisibleCallback( |
| base::RepeatingCallback<void(bool is_visible)> callback) { |
| virtual_keyboard_visible_callback_ = std::move(callback); |
| } |
| |
| bool FakeServiceClient::UserInterfaceIsBound() const { |
| return ux_receivers_.size(); |
| } |
| |
| const std::vector<mojom::FocusRingInfoPtr>& |
| FakeServiceClient::GetFocusRingsForType( |
| mojom::AssistiveTechnologyType type) const { |
| return focus_rings_for_type_.at(type); |
| } |
| |
| void FakeServiceClient::SendAccessibilityEvents( |
| const ui::AXTreeID& tree_id, |
| const std::vector<ui::AXTreeUpdate>& updates, |
| const gfx::Point& mouse_location, |
| const std::vector<ui::AXEvent>& events) { |
| for (auto& remote : automation_remotes_) { |
| remote->DispatchAccessibilityEvents(tree_id, updates, mouse_location, |
| events); |
| } |
| } |
| |
| #endif // BUILDFLAG(SUPPORTS_OS_ACCESSIBILITY_SERVICE) |
| |
| bool FakeServiceClient::AccessibilityServiceClientIsBound() const { |
| return a11y_client_receiver_.is_bound(); |
| } |
| |
| } // namespace ax |