chromium/chrome/browser/ash/extensions/speech/speech_recognition_private_manager.cc

// Copyright 2021 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/extensions/speech/speech_recognition_private_manager.h"

#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/extensions/speech/speech_recognition_private_recognizer.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
#include "chrome/common/extensions/api/speech_recognition_private.h"
#include "content/public/browser/browser_context.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/extension_event_histogram_value.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extensions_browser_client.h"

namespace extensions {

namespace {

std::string GetExtensionIdFromKey(const std::string& key) {
  std::size_t pos = key.find('.');
  if (pos != std::string::npos)
    return key.substr(0, pos);

  // If we couldn't find a ".", then the key is the extension ID.
  return key;
}

std::optional<int> GetClientIdFromKey(const std::string& key) {
  int client_id = -1;
  std::optional<int> result;
  std::size_t pos = key.find('.');
  if (pos != std::string::npos) {
    // Extract the number to the right of the "."
    base::StringToInt(key.substr(pos + 1), &client_id);
    result = client_id;
  }

  return result;
}

// Factory to get or create an instance of SpeechRecognitionPrivateManager from
// a browser context.
class SpeechRecognitionPrivateManagerFactory
    : public ProfileKeyedServiceFactory {
 public:
  SpeechRecognitionPrivateManagerFactory(
      const SpeechRecognitionPrivateManagerFactory&) = delete;
  SpeechRecognitionPrivateManagerFactory& operator=(
      const SpeechRecognitionPrivateManagerFactory&) = delete;

  static SpeechRecognitionPrivateManager* GetForBrowserContext(
      content::BrowserContext* context);

  static SpeechRecognitionPrivateManagerFactory* GetInstance();

 private:
  friend class base::NoDestructor<SpeechRecognitionPrivateManagerFactory>;

  SpeechRecognitionPrivateManagerFactory();
  ~SpeechRecognitionPrivateManagerFactory() override = default;

  // BrowserContextKeyedServiceFactory:
  KeyedService* BuildServiceInstanceFor(
      content::BrowserContext* context) const override;
};

// static
SpeechRecognitionPrivateManager*
SpeechRecognitionPrivateManagerFactory::GetForBrowserContext(
    content::BrowserContext* context) {
  return static_cast<SpeechRecognitionPrivateManager*>(
      GetInstance()->GetServiceForBrowserContext(context, true));
}

// static
SpeechRecognitionPrivateManagerFactory*
SpeechRecognitionPrivateManagerFactory::GetInstance() {
  static base::NoDestructor<SpeechRecognitionPrivateManagerFactory> instance;
  return instance.get();
}

SpeechRecognitionPrivateManagerFactory::SpeechRecognitionPrivateManagerFactory()
    : ProfileKeyedServiceFactory(
          "SpeechRecognitionApiManager",
          // Incognito profiles should use their own instance of the browser
          // context.
          ProfileSelections::Builder()
              .WithRegular(ProfileSelection::kOwnInstance)
              // TODO(crbug.com/40257657): Check if this service is needed in
              // Guest mode.
              .WithGuest(ProfileSelection::kOwnInstance)
              // TODO(crbug.com/41488885): Check if this service is needed for
              // Ash Internals.
              .WithAshInternals(ProfileSelection::kOwnInstance)
              .Build()) {
  DependsOn(EventRouterFactory::GetInstance());
}

KeyedService* SpeechRecognitionPrivateManagerFactory::BuildServiceInstanceFor(
    content::BrowserContext* context) const {
  return new SpeechRecognitionPrivateManager(context);
}

}  // namespace

SpeechRecognitionPrivateManager::SpeechRecognitionPrivateManager(
    content::BrowserContext* context)
    : context_(context) {}

SpeechRecognitionPrivateManager::~SpeechRecognitionPrivateManager() = default;

// static
SpeechRecognitionPrivateManager* SpeechRecognitionPrivateManager::Get(
    content::BrowserContext* context) {
  return static_cast<SpeechRecognitionPrivateManagerFactory*>(GetFactory())
      ->GetForBrowserContext(context);
}

// static
BrowserContextKeyedServiceFactory*
SpeechRecognitionPrivateManager::GetFactory() {
  return SpeechRecognitionPrivateManagerFactory::GetInstance();
}

void SpeechRecognitionPrivateManager::HandleStart(
    const std::string& key,
    std::optional<std::string> locale,
    std::optional<bool> interim_results,
    OnStartCallback callback) {
  GetSpeechRecognizer(key)->HandleStart(locale, interim_results,
                                        std::move(callback));
}

void SpeechRecognitionPrivateManager::HandleStop(const std::string& key,
                                                 OnStopCallback callback) {
  GetSpeechRecognizer(key)->HandleStop(std::move(callback));
}

void SpeechRecognitionPrivateManager::HandleSpeechRecognitionStopped(
    const std::string& key) {
  std::string extension_id = GetExtensionIdFromKey(key);
  std::optional<int> client_id = GetClientIdFromKey(key);
  EventRouter* event_router = EventRouter::Get(context_);

  base::Value::Dict return_dict;
  if (client_id.has_value())
    return_dict.Set("clientId", client_id.value());

  base::Value::List event_args;
  event_args.Append(std::move(return_dict));
  std::unique_ptr<Event> event = std::make_unique<Event>(
      events::SPEECH_RECOGNITION_PRIVATE_ON_STOP,
      api::speech_recognition_private::OnStop::kEventName,
      std::move(event_args));

  event_router->DispatchEventToExtension(extension_id, std::move(event));
}

void SpeechRecognitionPrivateManager::HandleSpeechRecognitionResult(
    const std::string& key,
    const std::u16string& transcript,
    bool is_final) {
  std::string extension_id = GetExtensionIdFromKey(key);
  std::optional<int> client_id = GetClientIdFromKey(key);
  EventRouter* event_router = EventRouter::Get(context_);

  api::speech_recognition_private::SpeechRecognitionResultEvent event;
  event.transcript = base::UTF16ToUTF8(transcript);
  event.is_final = is_final;
  event.client_id = client_id;

  auto event_args = api::speech_recognition_private::OnResult::Create(event);
  std::unique_ptr<Event> event_ptr = std::make_unique<Event>(
      events::SPEECH_RECOGNITION_PRIVATE_ON_RESULT,
      api::speech_recognition_private::OnResult::kEventName,
      std::move(event_args));

  event_router->DispatchEventToExtension(extension_id, std::move(event_ptr));
}

void SpeechRecognitionPrivateManager::HandleSpeechRecognitionError(
    const std::string& key,
    const std::string& message) {
  std::string extension_id = GetExtensionIdFromKey(key);
  std::optional<int> client_id = GetClientIdFromKey(key);
  EventRouter* event_router = EventRouter::Get(context_);

  api::speech_recognition_private::SpeechRecognitionErrorEvent event;
  event.message = message;
  event.client_id = client_id;

  auto event_args = api::speech_recognition_private::OnError::Create(event);
  std::unique_ptr<Event> event_ptr = std::make_unique<Event>(
      events::SPEECH_RECOGNITION_PRIVATE_ON_ERROR,
      api::speech_recognition_private::OnError::kEventName,
      std::move(event_args));

  event_router->DispatchEventToExtension(extension_id, std::move(event_ptr));
}

std::string SpeechRecognitionPrivateManager::CreateKey(
    const std::string& extension_id,
    std::optional<int> client_id) {
  if (!client_id.has_value())
    return extension_id;

  return base::StringPrintf("%s.%d", extension_id.c_str(), *client_id);
}

SpeechRecognitionPrivateRecognizer*
SpeechRecognitionPrivateManager::GetSpeechRecognizer(const std::string& key) {
  auto& recognizer = recognition_data_[key];
  if (!recognizer)
    recognizer = std::make_unique<SpeechRecognitionPrivateRecognizer>(
        this, context_, key);

  return recognizer.get();
}

// static
void SpeechRecognitionPrivateManager::EnsureFactoryBuilt() {
  GetFactory();
}

}  // namespace extensions