chromium/chromeos/ash/services/assistant/assistant_manager_service_impl.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/services/assistant/assistant_manager_service_impl.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <utility>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/assistant/assistant_state.h"
#include "ash/public/cpp/assistant/assistant_state_base.h"
#include "ash/public/cpp/assistant/controller/assistant_notification_controller.h"
#include "base/barrier_closure.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/memory/raw_ref.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "chromeos/ash/services/assistant/device_settings_host.h"
#include "chromeos/ash/services/assistant/libassistant_service_host_impl.h"
#include "chromeos/ash/services/assistant/media_host.h"
#include "chromeos/ash/services/assistant/platform/audio_input_host_impl.h"
#include "chromeos/ash/services/assistant/platform/audio_output_delegate_impl.h"
#include "chromeos/ash/services/assistant/platform/platform_delegate_impl.h"
#include "chromeos/ash/services/assistant/public/cpp/assistant_browser_delegate.h"
#include "chromeos/ash/services/assistant/public/cpp/assistant_enums.h"
#include "chromeos/ash/services/assistant/public/cpp/device_actions.h"
#include "chromeos/ash/services/assistant/public/cpp/features.h"
#include "chromeos/ash/services/assistant/service.h"
#include "chromeos/ash/services/assistant/service_context.h"
#include "chromeos/ash/services/assistant/timer_host.h"
#include "chromeos/ash/services/libassistant/public/mojom/android_app_info.mojom.h"
#include "chromeos/ash/services/libassistant/public/mojom/speech_recognition_observer.mojom.h"
#include "chromeos/services/assistant/public/shared/utils.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "chromeos/version/version_loader.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "ui/accessibility/mojom/ax_assistant_structure.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"

using media_session::mojom::MediaSessionAction;

namespace ash::assistant {
namespace {

static base::OnceCallback<void()> initialized_internal_callback_for_testing;
static bool is_first_init = true;

constexpr char kAndroidSettingsAppPackage[] = "com.android.settings";

std::vector<libassistant::mojom::AuthenticationTokenPtr> ToAuthenticationTokens(
    const std::optional<AssistantManagerService::UserInfo>& user) {
  std::vector<libassistant::mojom::AuthenticationTokenPtr> result;

  if (user.has_value()) {
    DCHECK(!user.value().gaia_id.empty());
    DCHECK(!user.value().access_token.empty());
    result.emplace_back(libassistant::mojom::AuthenticationToken::New(
        /*gaia_id=*/user.value().gaia_id,
        /*access_token=*/user.value().access_token));
  }

  return result;
}

libassistant::mojom::BootupConfigPtr CreateBootupConfig(
    const std::optional<std::string>& s3_server_uri_override,
    const std::optional<std::string>& device_id_override) {
  auto result = libassistant::mojom::BootupConfig::New();
  result->s3_server_uri_override = s3_server_uri_override;
  result->device_id_override = device_id_override;
  return result;
}

}  // namespace

// Observer that will receive all speech recognition related events,
// and forwards them to all |AssistantInteractionSubscriber|.
class SpeechRecognitionObserverWrapper
    : public libassistant::mojom::SpeechRecognitionObserver {
 public:
  explicit SpeechRecognitionObserverWrapper(
      const base::ObserverList<AssistantInteractionSubscriber>* observers)
      : interaction_subscribers_(*observers) {
    DCHECK(observers);
  }
  SpeechRecognitionObserverWrapper(const SpeechRecognitionObserverWrapper&) =
      delete;
  SpeechRecognitionObserverWrapper& operator=(
      const SpeechRecognitionObserverWrapper&) = delete;
  ~SpeechRecognitionObserverWrapper() override = default;

  mojo::PendingRemote<libassistant::mojom::SpeechRecognitionObserver>
  BindNewPipeAndPassRemote() {
    return receiver_.BindNewPipeAndPassRemote();
  }

  void Stop() { receiver_.reset(); }

  // libassistant::mojom::SpeechRecognitionObserver implementation:
  void OnSpeechLevelUpdated(float speech_level_in_decibels) override {
    for (auto& it : *interaction_subscribers_) {
      it.OnSpeechLevelUpdated(speech_level_in_decibels);
    }
  }

  void OnSpeechRecognitionStart() override {
    for (auto& it : *interaction_subscribers_) {
      it.OnSpeechRecognitionStarted();
    }
  }

  void OnIntermediateResult(const std::string& high_confidence_text,
                            const std::string& low_confidence_text) override {
    for (auto& it : *interaction_subscribers_) {
      it.OnSpeechRecognitionIntermediateResult(high_confidence_text,
                                               low_confidence_text);
    }
  }

  void OnSpeechRecognitionEnd() override {
    for (auto& it : *interaction_subscribers_) {
      it.OnSpeechRecognitionEndOfUtterance();
    }
  }

  void OnFinalResult(const std::string& recognized_text) override {
    for (auto& it : *interaction_subscribers_) {
      it.OnSpeechRecognitionFinalResult(recognized_text);
    }
  }

 private:
  // Owned by our parent, |AssistantManagerServiceImpl|.
  const raw_ref<const base::ObserverList<AssistantInteractionSubscriber>>
      interaction_subscribers_;

  mojo::Receiver<libassistant::mojom::SpeechRecognitionObserver> receiver_{
      this};
};

// static
void AssistantManagerServiceImpl::SetInitializedInternalCallbackForTesting(
    base::OnceCallback<void()> callback) {
  CHECK(initialized_internal_callback_for_testing.is_null());
  // We expect that the callback is set when AssistantStatus is NOT_READY to
  // confirm that AssistantStatus has changed from NOT_READY to READY. See more
  // details at a comment in AssistantManagerServiceImpl::OnDeviceAppsEnabled.
  CHECK(AssistantState::Get()->assistant_status() ==
        AssistantStatus::NOT_READY);
  initialized_internal_callback_for_testing = std::move(callback);
}

// static
void AssistantManagerServiceImpl::ResetIsFirstInitFlagForTesting() {
  is_first_init = true;
}

AssistantManagerServiceImpl::AssistantManagerServiceImpl(
    ServiceContext* context,
    std::unique_ptr<network::PendingSharedURLLoaderFactory>
        pending_url_loader_factory,
    std::optional<std::string> s3_server_uri_override,
    std::optional<std::string> device_id_override,
    std::unique_ptr<LibassistantServiceHost> libassistant_service_host)
    : assistant_settings_(std::make_unique<AssistantSettingsImpl>(context)),
      assistant_host_(std::make_unique<AssistantHost>(this)),
      platform_delegate_(std::make_unique<PlatformDelegateImpl>()),
      context_(context),
      device_settings_host_(std::make_unique<DeviceSettingsHost>(context)),
      media_host_(std::make_unique<MediaHost>(AssistantBrowserDelegate::Get(),
                                              &interaction_subscribers_)),
      timer_host_(std::make_unique<TimerHost>(context)),
      audio_output_delegate_(std::make_unique<AudioOutputDelegateImpl>(
          &media_host_->media_session())),
      speech_recognition_observer_(
          std::make_unique<SpeechRecognitionObserverWrapper>(
              &interaction_subscribers_)),
      bootup_config_(
          CreateBootupConfig(s3_server_uri_override, device_id_override)),
      url_loader_factory_(network::SharedURLLoaderFactory::Create(
          std::move(pending_url_loader_factory))),
      weak_factory_(this) {
  if (libassistant_service_host) {
    // During unittests a custom host is passed in, so we'll use that one.
    libassistant_service_host_ = std::move(libassistant_service_host);
  } else {
    // Use the default service host if none was provided.
    libassistant_service_host_ =
        std::make_unique<LibassistantServiceHostImpl>();
  }
}

AssistantManagerServiceImpl::~AssistantManagerServiceImpl() {
  // Destroy the Assistant Proxy first so the background thread is flushed
  // before any of the other objects are destroyed.
  assistant_host_ = nullptr;
}

void AssistantManagerServiceImpl::Start(const std::optional<UserInfo>& user,
                                        bool enable_hotword) {
  DCHECK(!IsServiceStarted());
  DCHECK(GetState() == State::STOPPED || GetState() == State::DISCONNECTED);

  Initialize();

  // Set the flag to avoid starting the service multiple times.
  SetStateAndInformObservers(State::STARTING);

  started_time_ = base::TimeTicks::Now();

  EnableHotword(enable_hotword);
  InitAssistant(user);
}

void AssistantManagerServiceImpl::Stop() {
  // We cannot cleanly stop the service if it is in the process of starting up.
  DCHECK_NE(GetState(), State::STARTING);

  // During shutdown, this could be called when the libassistant
  // service is not started.
  if (!IsServiceStarted()) {
    return;
  }

  weak_factory_.InvalidateWeakPtrs();
  SetStateAndInformObservers(State::STOPPING);

  // We stop observing media events before we destroy `AssistantManagerImpl`,
  // otherwise notification may happen any time after it is destroyed.
  media_host_->Stop();
  // For similar reason, stop observing app_list events.
  scoped_app_list_event_subscriber_.Reset();

  // When user disables the feature, we also delete all data.
  if (!assistant_state()->settings_enabled().value())
    service_controller().ResetAllDataAndStop();
  else
    service_controller().Stop();
}

AssistantManagerService::State AssistantManagerServiceImpl::GetState() const {
  return state_;
}

void AssistantManagerServiceImpl::SetUser(const std::optional<UserInfo>& user) {
  if (!IsServiceStarted())
    return;

  VLOG(1) << "Set user information (Gaia ID and access token).";
  settings_controller().SetAuthenticationTokens(ToAuthenticationTokens(user));
}

void AssistantManagerServiceImpl::EnableListening(bool enable) {
  // Could be called when the libassistant service is not started.
  if (!IsServiceStarted())
    return;

  settings_controller().SetListeningEnabled(enable);
}

void AssistantManagerServiceImpl::EnableHotword(bool enable) {
  // Could be called when the libassistant service is not started.
  if (!IsServiceStarted())
    return;

  audio_input_host_->OnHotwordEnabled(enable);
}

void AssistantManagerServiceImpl::SetArcPlayStoreEnabled(bool enable) {
  DCHECK(GetState() == State::RUNNING);
  if (assistant::features::IsAppSupportEnabled())
    display_controller().SetArcPlayStoreEnabled(enable);
}

void AssistantManagerServiceImpl::SetAssistantContextEnabled(bool enable) {
  DCHECK(GetState() == State::RUNNING);

  media_host_->SetRelatedInfoEnabled(enable);
  display_controller().SetRelatedInfoEnabled(enable);
}

AssistantSettings* AssistantManagerServiceImpl::GetAssistantSettings() {
  return assistant_settings_.get();
}

void AssistantManagerServiceImpl::AddAuthenticationStateObserver(
    AuthenticationStateObserver* observer) {
  DCHECK(observer);
  assistant_host_->AddAuthenticationStateObserver(
      observer->BindNewPipeAndPassRemote());
}

void AssistantManagerServiceImpl::AddAndFireStateObserver(
    AssistantManagerService::StateObserver* observer) {
  state_observers_.AddObserver(observer);
  observer->OnStateChanged(GetState());
}

void AssistantManagerServiceImpl::RemoveStateObserver(
    const AssistantManagerService::StateObserver* observer) {
  state_observers_.RemoveObserver(observer);
}

void AssistantManagerServiceImpl::SyncDeviceAppsStatus() {
  assistant_settings_->SyncDeviceAppsStatus(
      base::BindOnce(&AssistantManagerServiceImpl::OnDeviceAppsEnabled,
                     weak_factory_.GetWeakPtr()));
}

void AssistantManagerServiceImpl::UpdateInternalMediaPlayerStatus(
    MediaSessionAction action) {
  if (action == MediaSessionAction::kPause) {
    media_host_->PauseInternalMediaPlayer();
  } else if (action == MediaSessionAction::kPlay) {
    media_host_->ResumeInternalMediaPlayer();
  }
}

void AssistantManagerServiceImpl::StartVoiceInteraction() {
  DVLOG(1) << __func__;

  audio_input_host_->SetMicState(true);
  conversation_controller().StartVoiceInteraction();
}

void AssistantManagerServiceImpl::StopActiveInteraction(
    bool cancel_conversation) {
  DVLOG(1) << __func__;

  audio_input_host_->SetMicState(false);
  conversation_controller().StopActiveInteraction(cancel_conversation);
}

void AssistantManagerServiceImpl::StartEditReminderInteraction(
    const std::string& client_id) {
  conversation_controller().StartEditReminderInteraction(client_id);
}

void AssistantManagerServiceImpl::StartTextInteraction(
    const std::string& query,
    AssistantQuerySource source,
    bool allow_tts) {
  DVLOG(1) << __func__;

  conversation_controller().SendTextQuery(query, source, allow_tts);
}

void AssistantManagerServiceImpl::AddAssistantInteractionSubscriber(
    AssistantInteractionSubscriber* subscriber) {
  // For now, this function is handling events registration for both Mojom
  // side and Assistant service side. All native AssistantInteractionSubscriber
  // should be deprecated and replaced with |ConversationObserver| once the
  // migration is done.
  interaction_subscribers_.AddObserver(subscriber);
  AddRemoteConversationObserver(subscriber);
}

void AssistantManagerServiceImpl::RemoveAssistantInteractionSubscriber(
    AssistantInteractionSubscriber* subscriber) {
  interaction_subscribers_.RemoveObserver(subscriber);
}

void AssistantManagerServiceImpl::RetrieveNotification(
    const AssistantNotification& notification,
    int action_index) {
  conversation_controller().RetrieveNotification(notification, action_index);
}

void AssistantManagerServiceImpl::DismissNotification(
    const AssistantNotification& notification) {
  conversation_controller().DismissNotification(notification);
}

void AssistantManagerServiceImpl::OnInteractionStarted(
    const AssistantInteractionMetadata& metadata) {
  audio_input_host_->OnConversationTurnStarted();
}

void AssistantManagerServiceImpl::OnInteractionFinished(
    AssistantInteractionResolution resolution) {
  switch (resolution) {
    case AssistantInteractionResolution::kNormal:
      RecordQueryResponseTypeUMA();
      return;
    case AssistantInteractionResolution::kInterruption:
      if (HasReceivedQueryResponse())
        RecordQueryResponseTypeUMA();
      return;
    case AssistantInteractionResolution::kMicTimeout:
    case AssistantInteractionResolution::kError:
    case AssistantInteractionResolution::kMultiDeviceHotwordLoss:
      // No action needed.
      return;
  }
}

void AssistantManagerServiceImpl::OnHtmlResponse(const std::string& html,
                                                 const std::string& fallback) {
  receive_inline_response_ = true;
}

void AssistantManagerServiceImpl::OnTextResponse(const std::string& reponse) {
  receive_inline_response_ = true;
}

void AssistantManagerServiceImpl::OnOpenUrlResponse(const GURL& url,
                                                    bool in_background) {
  receive_url_response_ = url.spec();
}

void AssistantManagerServiceImpl::OnStateChanged(
    libassistant::mojom::ServiceState new_state) {
  using libassistant::mojom::ServiceState;

  DVLOG(1) << "Libassistant service state changed to " << new_state;
  switch (new_state) {
    case ServiceState::kStarted:
      OnServiceStarted();
      break;
    case ServiceState::kRunning:
      OnServiceRunning();
      break;
    case ServiceState::kDisconnected:
      OnServiceDisconnected();
      break;
    case ServiceState::kStopped:
      OnServiceStopped();
      break;
  }
}

void AssistantManagerServiceImpl::Initialize() {
  assistant_host_->StartLibassistantService(libassistant_service_host_.get());

  service_controller().AddAndFireStateObserver(
      state_observer_receiver_.BindNewPipeAndPassRemote());
  assistant_host_->AddSpeechRecognitionObserver(
      speech_recognition_observer_->BindNewPipeAndPassRemote());
  AddRemoteConversationObserver(this);

  audio_output_delegate_->Bind(assistant_host_->ExtractAudioOutputDelegate());
  platform_delegate_->Bind(assistant_host_->ExtractPlatformDelegate());
  audio_input_host_ = std::make_unique<AudioInputHostImpl>(
      assistant_host_->ExtractAudioInputController(),
      context_->cras_audio_handler(), context_->power_manager_client(),
      context_->assistant_state()->locale().value());

  assistant_settings_->Initialize(
      assistant_host_->ExtractSpeakerIdEnrollmentController(),
      &assistant_host_->settings_controller());

  media_host_->Initialize(&assistant_host_->media_controller(),
                          assistant_host_->ExtractMediaDelegate());
  timer_host_->Initialize(&assistant_host_->timer_controller(),
                          assistant_host_->ExtractTimerDelegate());

  device_settings_host_->Bind(assistant_host_->ExtractDeviceSettingsDelegate());
}

void AssistantManagerServiceImpl::InitAssistant(
    const std::optional<UserInfo>& user) {
  DCHECK(!IsServiceStarted());

  auto bootup_config = bootup_config_.Clone();
  bootup_config->authentication_tokens = ToAuthenticationTokens(user);
  bootup_config->hotword_enabled = assistant_state()->hotword_enabled().value();
  bootup_config->locale = assistant_state()->locale().value();
  bootup_config->spoken_feedback_enabled = spoken_feedback_enabled_;
  bootup_config->dark_mode_enabled = dark_mode_enabled_;

  service_controller().Initialize(std::move(bootup_config),
                                  BindURLLoaderFactory());
  service_controller().Start();
}

base::Thread& AssistantManagerServiceImpl::GetBackgroundThreadForTesting() {
  return background_thread();
}

void AssistantManagerServiceImpl::OnServiceStarted() {
  DCHECK_EQ(GetState(), State::STARTING);

  const base::TimeDelta time_since_started =
      base::TimeTicks::Now() - started_time_;
  UMA_HISTOGRAM_TIMES("Assistant.ServiceStartTime", time_since_started);

  SetStateAndInformObservers(State::STARTED);
}

bool AssistantManagerServiceImpl::IsServiceStarted() const {
  switch (state_) {
    case State::STOPPED:
    case State::STOPPING:
    case State::STARTING:
    case State::DISCONNECTED:
      return false;
    case State::STARTED:
    case State::RUNNING:
      return true;
  }
}

mojo::PendingRemote<network::mojom::URLLoaderFactory>
AssistantManagerServiceImpl::BindURLLoaderFactory() {
  mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote;
  url_loader_factory_->Clone(pending_remote.InitWithNewPipeAndPassReceiver());
  return pending_remote;
}

void AssistantManagerServiceImpl::OnServiceRunning() {
  // Try to avoid double run by checking |GetState()|.
  if (GetState() == State::RUNNING)
    return;

  SetStateAndInformObservers(State::RUNNING);

  if (is_first_init) {
    is_first_init = false;
    // Only sync status at the first init to prevent unexpected corner cases.
    if (assistant_state()->hotword_enabled().value())
      assistant_settings_->SyncSpeakerIdEnrollmentStatus();
  }

  const base::TimeDelta time_since_started =
      base::TimeTicks::Now() - started_time_;
  UMA_HISTOGRAM_TIMES("Assistant.ServiceReadyTime", time_since_started);

  SyncDeviceAppsStatus();

  SetAssistantContextEnabled(assistant_state()->IsScreenContextAllowed());

  if (assistant_state()->arc_play_store_enabled().has_value())
    SetArcPlayStoreEnabled(assistant_state()->arc_play_store_enabled().value());

  if (base::FeatureList::IsEnabled(assistant::features::kAssistantAppSupport))
    scoped_app_list_event_subscriber_.Observe(device_actions());
}

void AssistantManagerServiceImpl::OnServiceStopped() {
  // Valid `STOPPED` will only be called after `STOPPING`.
  // However, a false `STOPPED` may be received.
  // An ideal situation would be:
  // 1. `AddAndFireStateObserver()` is called.
  // 2. `ServiceController` sends the current state, e.g. STOPPED.
  // However, since it takes a longer time (through mojom to another thread),
  // the `state_` in this class could be temporary out of sync. For example:
  // 1. `AddAndFireStateObserver()` is called when the `state_` is STOPPED.
  // 2. `Start()`: sets `state_` to STARTING.
  // 3. `ServiceController` sends the current state inside it, which is STOPPED.
  // 4. `OnStateChanged()` receives STOPPED, but the `state_` is STARTING.
  // We will ignore the invalid STOPPED signal.
  if (GetState() != State::STOPPING)
    return;

  SetStateAndInformObservers(State::STOPPED);

  // Stop after the `assistant_manager` was destroyed.
  assistant_host_->StopLibassistantService();
  ClearAfterStop();
}

void AssistantManagerServiceImpl::OnServiceDisconnected() {
  SetStateAndInformObservers(State::DISCONNECTED);
  ClearAfterStop();
}

void AssistantManagerServiceImpl::OnAndroidAppListRefreshed(
    const std::vector<AndroidAppInfo>& apps_info) {
  DCHECK(GetState() == State::RUNNING);

  std::vector<AndroidAppInfo> filtered_apps_info;
  for (const auto& app_info : apps_info) {
    // TODO(b/146355799): Remove the special handling for Android settings app.
    if (app_info.package_name == kAndroidSettingsAppPackage)
      continue;

    filtered_apps_info.push_back(app_info);
  }
  display_controller().SetAndroidAppList(std::move(filtered_apps_info));
}

void AssistantManagerServiceImpl::OnAccessibilityStatusChanged(
    bool spoken_feedback_enabled) {
  if (spoken_feedback_enabled_ == spoken_feedback_enabled)
    return;

  spoken_feedback_enabled_ = spoken_feedback_enabled;

  // When |spoken_feedback_enabled_| changes we need to update our internal
  // options to turn on/off A11Y features in LibAssistant.
  if (IsServiceStarted())
    settings_controller().SetSpokenFeedbackEnabled(spoken_feedback_enabled_);
}

void AssistantManagerServiceImpl::OnColorModeChanged(bool dark_mode_enabled) {
  if (dark_mode_enabled_ == dark_mode_enabled)
    return;

  dark_mode_enabled_ = dark_mode_enabled;

  if (IsServiceStarted())
    settings_controller().SetDarkModeEnabled(dark_mode_enabled_);
}

void AssistantManagerServiceImpl::OnDeviceAppsEnabled(bool enabled) {
  // The device apps state sync should only be sent after service is running.
  // Check state here to prevent timing issue when the service is restarting.
  if (GetState() != State::RUNNING)
    return;

  display_controller().SetDeviceAppsEnabled(enabled);

  // You can set initialized_internal callback only when AssistantStatus is
  // NOT_READY. Also this line reaches only after GetState() becomes RUNNING
  // (i.e. READY). From that reason, test code can assume that status has
  // changed from NOT_READY to READY between those two points.
  //
  // Test code expects those things when Assistant gets initialized:
  //
  // - Status becomes READY.
  // - All necessary settings are passed to LibAssistant.
  //
  // We update necessary settings after status becomes READY. For now,
  // DeviceAppsEnabled is the only settings update which involves async call.
  // As other settings are sync, if this async call gets completed, we can also
  // assume that all necessary settings are passed to LibAssistant, i.e.
  // initialized.
  if (!initialized_internal_callback_for_testing.is_null()) {
    std::move(initialized_internal_callback_for_testing).Run();
  }
}

void AssistantManagerServiceImpl::AddTimeToTimer(const std::string& id,
                                                 base::TimeDelta duration) {
  timer_host_->AddTimeToTimer(id, duration);
}

void AssistantManagerServiceImpl::PauseTimer(const std::string& id) {
  timer_host_->PauseTimer(id);
}

void AssistantManagerServiceImpl::RemoveAlarmOrTimer(const std::string& id) {
  timer_host_->RemoveTimer(id);
}

void AssistantManagerServiceImpl::ResumeTimer(const std::string& id) {
  timer_host_->ResumeTimer(id);
}

void AssistantManagerServiceImpl::AddRemoteConversationObserver(
    ConversationObserver* observer) {
  conversation_controller().AddRemoteObserver(
      observer->BindNewPipeAndPassRemote());
}

mojo::PendingReceiver<libassistant::mojom::NotificationDelegate>
AssistantManagerServiceImpl::GetPendingNotificationDelegate() {
  return assistant_host_->ExtractNotificationDelegate();
}

void AssistantManagerServiceImpl::RecordQueryResponseTypeUMA() {
  AssistantQueryResponseType response_type = GetQueryResponseType();

  UMA_HISTOGRAM_ENUMERATION("Assistant.QueryResponseType", response_type);

  // Reset the flags.
  receive_url_response_.clear();
  receive_inline_response_ = false;
  device_settings_host_->reset_has_setting_changed();
}

bool AssistantManagerServiceImpl::HasReceivedQueryResponse() const {
  return GetQueryResponseType() != AssistantQueryResponseType::kUnspecified;
}

AssistantQueryResponseType AssistantManagerServiceImpl::GetQueryResponseType()
    const {
  if (device_settings_host_->has_setting_changed()) {
    return AssistantQueryResponseType::kDeviceAction;
  } else if (!receive_url_response_.empty()) {
    if (base::Contains(receive_url_response_, "www.google.com/search?")) {
      return AssistantQueryResponseType::kSearchFallback;
    } else {
      return AssistantQueryResponseType::kTargetedAction;
    }
  } else if (receive_inline_response_) {
    return AssistantQueryResponseType::kInlineElement;
  }

  return AssistantQueryResponseType::kUnspecified;
}

void AssistantManagerServiceImpl::SendAssistantFeedback(
    const AssistantFeedback& assistant_feedback) {
  conversation_controller().SendAssistantFeedback(assistant_feedback);
}

AssistantNotificationController*
AssistantManagerServiceImpl::assistant_notification_controller() {
  return context_->assistant_notification_controller();
}

AssistantStateBase* AssistantManagerServiceImpl::assistant_state() {
  return context_->assistant_state();
}

DeviceActions* AssistantManagerServiceImpl::device_actions() {
  return context_->device_actions();
}

scoped_refptr<base::SequencedTaskRunner>
AssistantManagerServiceImpl::main_task_runner() {
  return context_->main_task_runner();
}

libassistant::mojom::DisplayController&
AssistantManagerServiceImpl::display_controller() {
  return assistant_host_->display_controller();
}

libassistant::mojom::ServiceController&
AssistantManagerServiceImpl::service_controller() {
  return assistant_host_->service_controller();
}

void AssistantManagerServiceImpl::SetMicState(bool mic_open) {
  DCHECK(audio_input_host_);
  audio_input_host_->SetMicState(mic_open);
}

libassistant::mojom::ConversationController&
AssistantManagerServiceImpl::conversation_controller() {
  return assistant_host_->conversation_controller();
}

libassistant::mojom::SettingsController&
AssistantManagerServiceImpl::settings_controller() {
  return assistant_host_->settings_controller();
}

base::Thread& AssistantManagerServiceImpl::background_thread() {
  return assistant_host_->background_thread();
}

void AssistantManagerServiceImpl::SetStateAndInformObservers(State new_state) {
  state_ = new_state;

  for (auto& observer : state_observers_)
    observer.OnStateChanged(state_);
}

void AssistantManagerServiceImpl::ClearAfterStop() {
  weak_factory_.InvalidateWeakPtrs();

  state_observer_receiver_.reset();
  speech_recognition_observer_->Stop();
  audio_output_delegate_->Stop();
  platform_delegate_->Stop();
  audio_input_host_.reset();
  assistant_settings_->Stop();
  media_host_->Stop();
  timer_host_->Stop();
  device_settings_host_->Stop();

  scoped_app_list_event_subscriber_.Reset();
  interaction_subscribers_.Clear();
  state_observers_.Clear();

  is_first_init = true;
}

}  // namespace ash::assistant