chromium/chrome/browser/ash/accessibility/service/fake_accessibility_service.cc

// 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 "chrome/browser/ash/accessibility/service/fake_accessibility_service.h"

#include <tuple>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/run_loop.h"
#include "mojo/public/cpp/bindings/clone_traits.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "services/accessibility/public/mojom/accessibility_service.mojom.h"
#include "services/accessibility/public/mojom/autoclick.mojom.h"
#include "services/accessibility/public/mojom/tts.mojom.h"
#include "services/accessibility/public/mojom/user_input.mojom.h"
#include "services/accessibility/public/mojom/user_interface.mojom.h"

namespace ash {

FakeAccessibilityService::FakeAccessibilityService() = default;
FakeAccessibilityService::~FakeAccessibilityService() = default;

void FakeAccessibilityService::BindAccessibilityServiceClient(
    mojo::PendingRemote<ax::mojom::AccessibilityServiceClient>
        accessibility_service_client) {
  accessibility_service_client_remote_.Bind(
      std::move(accessibility_service_client));
  accessibility_service_client_remote_->BindAccessibilityFileLoader(
      file_loader_remote_.BindNewPipeAndPassReceiver());
}

void FakeAccessibilityService::BindAnotherAutoclickClient() {
  mojo::PendingReceiver<ax::mojom::AutoclickClient> autoclick_client_receiver;
  autoclick_client_remotes_.Add(
      autoclick_client_receiver.InitWithNewPipeAndPassRemote());
  accessibility_service_client_remote_->BindAutoclickClient(
      std::move(autoclick_client_receiver));

  // Now connect the autoclick remote in the service back to the client in the
  // browser by getting a PendingReceiver<Autoclick> from the browser.
  for (auto& remote : autoclick_client_remotes_) {
    remote->BindAutoclick(
        base::BindOnce(&FakeAccessibilityService::OnAutoclickBoundCallback,
                       base::Unretained(this)));
  }
}

void FakeAccessibilityService::BindAnotherAutomation() {
  mojo::PendingAssociatedRemote<ax::mojom::Automation> automation_remote;
  automation_receivers_.Add(
      this, automation_remote.InitWithNewEndpointAndPassReceiver());
  accessibility_service_client_remote_->BindAutomation(
      std::move(automation_remote));
}

void FakeAccessibilityService::BindAnotherAutomationClient() {
  mojo::PendingReceiver<ax::mojom::AutomationClient> automation_client_receiver;
  automation_client_remotes_.Add(
      automation_client_receiver.InitWithNewPipeAndPassRemote());
}

void FakeAccessibilityService::BindAnotherSpeechRecognition() {
  mojo::PendingReceiver<ax::mojom::SpeechRecognition> receiver;
  sr_remotes_.Add(receiver.InitWithNewPipeAndPassRemote());
  accessibility_service_client_remote_->BindSpeechRecognition(
      std::move(receiver));
}

void FakeAccessibilityService::BindAnotherTts() {
  mojo::PendingReceiver<ax::mojom::Tts> tts_receiver;
  tts_remotes_.Add(tts_receiver.InitWithNewPipeAndPassRemote());
  accessibility_service_client_remote_->BindTts(std::move(tts_receiver));
}

void FakeAccessibilityService::BindAnotherUserInput() {
  mojo::PendingReceiver<ax::mojom::UserInput> ui_receiver;
  ui_remotes_.Add(ui_receiver.InitWithNewPipeAndPassRemote());
  accessibility_service_client_remote_->BindUserInput(std::move(ui_receiver));
}

void FakeAccessibilityService::BindAnotherUserInterface() {
  mojo::PendingReceiver<ax::mojom::UserInterface> ux_receiver;
  ux_remotes_.Add(ux_receiver.InitWithNewPipeAndPassRemote());
  accessibility_service_client_remote_->BindUserInterface(
      std::move(ux_receiver));
}

void FakeAccessibilityService::BindAssistiveTechnologyController(
    mojo::PendingReceiver<ax::mojom::AssistiveTechnologyController>
        at_controller_receiver,
    const std::vector<ax::mojom::AssistiveTechnologyType>& enabled_features) {
  at_controller_receivers_.Add(this, std::move(at_controller_receiver));
  EnableAssistiveTechnology(enabled_features);
}

void FakeAccessibilityService::ConnectDevToolsAgent(
    mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent,
    ax::mojom::AssistiveTechnologyType type) {
  auto it = connect_devtools_counts.find(type);
  if (it == connect_devtools_counts.end()) {
    connect_devtools_counts[type] = 0;
  }
  connect_devtools_counts[type]++;
}

void FakeAccessibilityService::DispatchTreeDestroyedEvent(
    const ui::AXTreeID& tree_id) {
  tree_destroyed_events_.emplace_back(tree_id);
  if (automation_events_closure_)
    std::move(automation_events_closure_).Run();
}

void FakeAccessibilityService::DispatchActionResult(
    const ui::AXActionData& data,
    bool result) {
  action_results_.emplace_back(std::make_tuple(data, result));
  if (automation_events_closure_)
    std::move(automation_events_closure_).Run();
}

void FakeAccessibilityService::DispatchAccessibilityEvents(
    const ui::AXTreeID& tree_id,
    const std::vector<ui::AXTreeUpdate>& updates,
    const gfx::Point& mouse_location,
    const std::vector<ui::AXEvent>& events) {
  accessibility_events_.emplace_back(tree_id);
  if (automation_events_closure_)
    std::move(automation_events_closure_).Run();
}

void FakeAccessibilityService::DispatchAccessibilityLocationChange(
    const ui::AXTreeID& tree_id,
    int node_id,
    const ui::AXRelativeBounds& bounds) {
  location_changes_.emplace_back(tree_id);
  if (automation_events_closure_)
    std::move(automation_events_closure_).Run();
}

void FakeAccessibilityService::DispatchGetTextLocationResult(
    const ui::AXActionData& data,
    const std::optional<gfx::Rect>& rect) {}

void FakeAccessibilityService::EnableAssistiveTechnology(
    const std::vector<ax::mojom::AssistiveTechnologyType>& enabled_features) {
  enabled_ATs_ = std::set(enabled_features.begin(), enabled_features.end());
  at_change_count_++;
  if (change_ATs_closure_ && at_change_count_ == expected_count_) {
    expected_count_ = 0;
    std::move(change_ATs_closure_).Run();
  }
}

void FakeAccessibilityService::RequestScrollableBoundsForPoint(
    const gfx::Point& point) {
  for (auto& remote : autoclick_client_remotes_) {
    remote->HandleScrollableBoundsForPointFound(autoclick_scrollable_bounds_);
  }
}

void FakeAccessibilityService::WaitForATChangeCount(int count) {
  if (count == at_change_count_) {
    return;
  }
  expected_count_ = count;
  base::RunLoop runner;
  change_ATs_closure_ = runner.QuitClosure();
  runner.Run();
}

int FakeAccessibilityService::GetDevtoolsConnectionCount(
    ax::mojom::AssistiveTechnologyType type) const {
  auto it = connect_devtools_counts.find(type);
  if (it == connect_devtools_counts.end()) {
    return 0;
  }
  return it->second;
}

bool FakeAccessibilityService::IsBound() const {
  return accessibility_service_client_remote_.is_bound();
}

void FakeAccessibilityService::AutomationClientEnable(bool enabled) {
  // TODO(crbug.com/1355633): Add once AutomationClient mojom is added.
  // for (auto& automation_client : automation_client_remotes_) {
  //   enabled ? automation_client->Enable() : automation_client->Disable();
  // }
}

void FakeAccessibilityService::WaitForAutomationEvents() {
  base::RunLoop runner;
  automation_events_closure_ = runner.QuitClosure();
  runner.Run();
}

void FakeAccessibilityService::RequestSpeechRecognitionStart(
    ax::mojom::StartOptionsPtr options,
    base::OnceCallback<void(ax::mojom::SpeechRecognitionStartInfoPtr)>
        callback) {
  CHECK_EQ(sr_remotes_.size(), 1u);
  for (auto& remote : sr_remotes_) {
    remote->Start(std::move(options), std::move(callback));
  }
}

void FakeAccessibilityService::RequestSpeechRecognitionStop(
    ax::mojom::StopOptionsPtr options,
    base::OnceCallback<void(const std::optional<std::string>&)> callback) {
  CHECK_EQ(sr_remotes_.size(), 1u);
  for (auto& remote : sr_remotes_) {
    remote->Stop(std::move(options), std::move(callback));
  }
}

void FakeAccessibilityService::RequestSpeak(
    const std::string& utterance,
    base::OnceCallback<void(ax::mojom::TtsSpeakResultPtr)> callback) {
  auto options = ax::mojom::TtsOptions::New();
  options->on_event = true;
  RequestSpeak(utterance, std::move(options), std::move(callback));
}

void FakeAccessibilityService::RequestSpeak(
    const std::string& utterance,
    ax::mojom::TtsOptionsPtr options,
    base::OnceCallback<void(ax::mojom::TtsSpeakResultPtr)> callback) {
  CHECK_EQ(tts_remotes_.size(), 1u);
  for (auto& tts_client : tts_remotes_) {
    tts_client->Speak(utterance, std::move(options), std::move(callback));
  }
}

void FakeAccessibilityService::RequestStop() {
  for (auto& tts_client : tts_remotes_) {
    tts_client->Stop();
  }
}

void FakeAccessibilityService::RequestPause() {
  for (auto& tts_client : tts_remotes_) {
    tts_client->Pause();
  }
}

void FakeAccessibilityService::RequestResume() {
  for (auto& tts_client : tts_remotes_) {
    tts_client->Resume();
  }
}

void FakeAccessibilityService::IsTtsSpeaking(
    base::OnceCallback<void(bool)> callback) {
  CHECK_EQ(tts_remotes_.size(), 1u);
  for (auto& tts_client : tts_remotes_) {
    tts_client->IsSpeaking(std::move(callback));
  }
}

void FakeAccessibilityService::RequestTtsVoices(
    ax::mojom::Tts::GetVoicesCallback callback) {
  CHECK_EQ(tts_remotes_.size(), 1u);
  for (auto& tts_client : tts_remotes_) {
    tts_client->GetVoices(std::move(callback));
  }
}

void FakeAccessibilityService::
    RequestSendSyntheticKeyEventForShortcutOrNavigation(
        ax::mojom::SyntheticKeyEventPtr key_event) {
  for (auto& ui_client : ui_remotes_) {
    ui_client->SendSyntheticKeyEventForShortcutOrNavigation(
        mojo::Clone(key_event));
  }
}

void FakeAccessibilityService::RequestSendSyntheticMouseEvent(
    ax::mojom::SyntheticMouseEventPtr mouse_event) {
  for (auto& ui_client : ui_remotes_) {
    ui_client->SendSyntheticMouseEvent(mojo::Clone(mouse_event));
  }
}

void FakeAccessibilityService::RequestDarkenScreen(bool darken) {
  for (auto& ux_client : ux_remotes_) {
    ux_client->DarkenScreen(darken);
  }
}

void FakeAccessibilityService::RequestOpenSettingsSubpage(
    const std::string& subpage) {
  for (auto& ux_client : ux_remotes_) {
    ux_client->OpenSettingsSubpage(subpage);
  }
}

void FakeAccessibilityService::RequestShowConfirmationDialog(
    const std::string& title,
    const std::string& description,
    const std::optional<std::string>& cancel_name,
    ax::mojom::UserInterface::ShowConfirmationDialogCallback callback) {
  for (auto& ux_client : ux_remotes_) {
    ux_client->ShowConfirmationDialog(title, description, cancel_name,
                                      std::move(callback));
  }
}

void FakeAccessibilityService::RequestSetFocusRings(
    std::vector<ax::mojom::FocusRingInfoPtr> focus_rings,
    ax::mojom::AssistiveTechnologyType at_type) {
  for (auto& ux_client : ux_remotes_) {
    ux_client->SetFocusRings(mojo::Clone(focus_rings), at_type);
  }
}

void FakeAccessibilityService::RequestSetHighlights(
    const std::vector<gfx::Rect>& rects,
    SkColor color) {
  for (auto& ux_client : ux_remotes_) {
    ux_client->SetHighlights(rects, color);
  }
}

void FakeAccessibilityService::RequestSetVirtualKeyboardVisible(
    bool is_visible) {
  for (auto& ux_client : ux_remotes_) {
    ux_client->SetVirtualKeyboardVisible(is_visible);
  }
}

void FakeAccessibilityService::RequestLoadFile(
    base::FilePath relative_path,
    ax::mojom::AccessibilityFileLoader::LoadCallback callback) {
  file_loader_remote_->Load(relative_path, std::move(callback));
}

void FakeAccessibilityService::OnAutoclickBoundCallback(
    mojo::PendingReceiver<ax::mojom::Autoclick> autoclick_receiver) {
  autoclick_receivers_.Add(this, std::move(autoclick_receiver));
}

}  // namespace ash