chromium/chromeos/ash/services/assistant/service.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/service.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/controller/assistant_alarm_timer_controller.h"
#include "ash/public/cpp/assistant/controller/assistant_controller.h"
#include "ash/public/cpp/assistant/controller/assistant_notification_controller.h"
#include "ash/public/cpp/session/session_controller.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/buildflag.h"
#include "chromeos/ash/components/assistant/buildflags.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/ash/components/dbus/dbus_thread_manager.h"
#include "chromeos/ash/services/assistant/assistant_interaction_logger.h"
#include "chromeos/ash/services/assistant/assistant_manager_service.h"
#include "chromeos/ash/services/assistant/assistant_manager_service_impl.h"
#include "chromeos/ash/services/assistant/public/cpp/assistant_browser_delegate.h"
#include "chromeos/ash/services/assistant/public/cpp/assistant_prefs.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_context.h"
#include "chromeos/ash/services/libassistant/public/cpp/libassistant_loader.h"
#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/access_token_fetcher.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"

#if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chromeos/ash/services/libassistant/constants.h"
#endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)

namespace ash::assistant {

namespace {

constexpr char kScopeAssistant[] =
    "https://www.googleapis.com/auth/assistant-sdk-prototype";

constexpr char kServiceStateHistogram[] = "Assistant.ServiceState";

constexpr base::TimeDelta kMinTokenRefreshDelay = base::Milliseconds(1000);
constexpr base::TimeDelta kMaxTokenRefreshDelay = base::Milliseconds(60 * 1000);

// Testing override for the URI used to contact the s3 server.
const char* g_s3_server_uri_override = nullptr;
// Testing override for the device-id used by Libassistant to identify this
// device.
const char* g_device_id_override = nullptr;

#if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
base::TaskTraits GetTaskTraits() {
  return {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
          base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
}
#endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)

// The max number of tries to start service.
// We decide whether to start service based on two counters:
// 1. the backoff `failure_count`, and
// 2. the pref value `kAssistantNumFailuresSinceLastServiceRun`.
//
// 1.   Will not restart service if the `failure_count` is larger than
//      `kMaxStartServiceRetries`. Note that the `failure_count` will change:
// 1.a. Increment by 1 for every service disconnected.
// 1.b. Reset to 0 when explicitly re-enable the Assistant from the Settings.
// 1.c. Reset to 0 when re-login the device.
// 1.d. Decrement by 1 when it has been `kAutoRecoverTime`.
//
// 2.   Will not restart service if the pref value
//      `kAssistantNumFailuresSinceLastServiceRun` is larger than
//      `kMaxStartServiceRetries`, unless `failure_count` is 0, e.g. the first
//      time login. Note that the `kAssistantNumFailuresSinceLastServiceRun`
//      will change:
// 2.a. Increment by 1 for every service disconnected.
// 2.b. Reset to 0 when every service running.
constexpr int kMaxStartServiceRetries = 1;

// An interval used to gradually reduce the failure_count so that we could
// restart.
constexpr base::TimeDelta kAutoRecoverTime = base::Hours(24);

constexpr net::BackoffEntry::Policy kRetryStartServiceBackoffPolicy = {
    0,          // Number of initial errors to ignore.
    1000,       // Initial delay in ms.
    2.0,        // Factor by which the waiting time will be multiplied.
    0.2,        // Fuzzing percentage.
    60 * 1000,  // Maximum delay in ms.
    -1,         // Never discard the entry.
    true,       // Use initial delay.
};

AssistantStatus ToAssistantStatus(AssistantManagerService::State state) {
  using State = AssistantManagerService::State;

  switch (state) {
    case State::STOPPED:
    case State::STOPPING:
    case State::STARTING:
    case State::STARTED:
    case State::DISCONNECTED:
      return AssistantStatus::NOT_READY;
    case State::RUNNING:
      return AssistantStatus::READY;
  }
}

std::optional<std::string> GetS3ServerUriOverride() {
  if (g_s3_server_uri_override)
    return g_s3_server_uri_override;
  return std::nullopt;
}

std::optional<std::string> GetDeviceIdOverride() {
  if (g_device_id_override)
    return g_device_id_override;
  return std::nullopt;
}

// In the signed-out mode, we are going to run Assistant service without
// using user's signed in account information.
bool IsSignedOutMode() {
  // One example of using fake gaia login is in our automation tests, i.e.
  // Assistant Tast tests.
  return base::CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kDisableGaiaServices);
}

void RecordServiceState(AssistantManagerService::State state) {
  base::UmaHistogramEnumeration(kServiceStateHistogram, state);
}

}  // namespace

// Scoped observer that will subscribe |Service| as an Ash session observer,
// and will unsubscribe in its destructor.
class ScopedAshSessionObserver {
 public:
  ScopedAshSessionObserver(SessionActivationObserver* observer,
                           const AccountId& account_id)
      : observer_(observer), account_id_(account_id) {
    DCHECK(account_id_.is_valid());
    DCHECK(controller());
    controller()->AddSessionActivationObserverForAccountId(account_id_,
                                                           observer_);
  }

  ~ScopedAshSessionObserver() {
    if (controller())
      controller()->RemoveSessionActivationObserverForAccountId(account_id_,
                                                                observer_);
  }

 private:
  SessionController* controller() const { return SessionController::Get(); }

  const raw_ptr<SessionActivationObserver> observer_;
  const AccountId account_id_;
};

class Service::Context : public ServiceContext {
 public:
  explicit Context(Service* parent) : parent_(parent) {}

  Context(const Context&) = delete;
  Context& operator=(const Context&) = delete;

  ~Context() override = default;

  // ServiceContext:
  AssistantAlarmTimerController* assistant_alarm_timer_controller() override {
    return AssistantAlarmTimerController::Get();
  }

  AssistantController* assistant_controller() override {
    return AssistantController::Get();
  }

  AssistantNotificationController* assistant_notification_controller()
      override {
    return AssistantNotificationController::Get();
  }

  AssistantScreenContextController* assistant_screen_context_controller()
      override {
    return AssistantScreenContextController::Get();
  }

  AssistantStateBase* assistant_state() override {
    return AssistantState::Get();
  }

  CrasAudioHandler* cras_audio_handler() override {
    return CrasAudioHandler::Get();
  }

  DeviceActions* device_actions() override { return DeviceActions::Get(); }

  scoped_refptr<base::SequencedTaskRunner> main_task_runner() override {
    return parent_->main_task_runner_;
  }

  chromeos::PowerManagerClient* power_manager_client() override {
    return chromeos::PowerManagerClient::Get();
  }

  std::string primary_account_gaia_id() override {
    return parent_->RetrievePrimaryAccountInfo().gaia;
  }

 private:
  const raw_ptr<Service> parent_;  // |this| is owned by |parent_|.
};

Service::Service(std::unique_ptr<network::PendingSharedURLLoaderFactory>
                     pending_url_loader_factory,
                 signin::IdentityManager* identity_manager,
                 PrefService* pref_service)
    : context_(std::make_unique<Context>(this)),
      identity_manager_(identity_manager),
      pref_service_(pref_service),
      token_refresh_timer_(std::make_unique<base::OneShotTimer>()),
      main_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
      pending_url_loader_factory_(std::move(pending_url_loader_factory)),
      start_service_retry_backoff_(&kRetryStartServiceBackoffPolicy),
      auto_service_recover_timer_(std::make_unique<base::OneShotTimer>()) {
  DCHECK(identity_manager_);
  chromeos::PowerManagerClient* power_manager_client =
      context_->power_manager_client();
  power_manager_observation_.Observe(power_manager_client);
  power_manager_client->RequestStatusUpdate();
}

Service::~Service() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  AssistantState::Get()->RemoveObserver(this);
  AssistantController::Get()->SetAssistant(nullptr);
}

// static
void Service::OverrideS3ServerUriForTesting(const char* uri) {
  g_s3_server_uri_override = uri;
}

// static
void Service::OverrideDeviceIdForTesting(const char* device_id) {
  g_device_id_override = device_id;
}

void Service::SetAssistantManagerServiceForTesting(
    std::unique_ptr<AssistantManagerService> assistant_manager_service) {
  DCHECK(assistant_manager_service_ == nullptr);
  assistant_manager_service_for_testing_ = std::move(assistant_manager_service);
}

void Service::Init() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  AssistantState::Get()->AddObserver(this);

  DCHECK(!assistant_manager_service_);

  RequestAccessToken();
  LoadLibassistant();
}

void Service::Shutdown() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (assistant_manager_service_)
    StopAssistantManagerService();
}

Assistant* Service::GetAssistant() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(assistant_manager_service_);
  return assistant_manager_service_.get();
}

void Service::PowerChanged(const power_manager::PowerSupplyProperties& prop) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const bool power_source_connected =
      prop.external_power() == power_manager::PowerSupplyProperties::AC;
  if (power_source_connected == power_source_connected_)
    return;

  power_source_connected_ = power_source_connected;
  UpdateAssistantManagerState();
}

void Service::SuspendDone(base::TimeDelta sleep_duration) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // |token_refresh_timer_| may become stale during sleeping, so we immediately
  // request a new token to make sure it is fresh.
  if (token_refresh_timer_->IsRunning()) {
    token_refresh_timer_->AbandonAndStop();
    RequestAccessToken();
  }
}

void Service::OnSessionActivated(bool activated) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  session_active_ = activated;

  AssistantBrowserDelegate::Get()->OnAssistantStatusChanged(
      ToAssistantStatus(assistant_manager_service_->GetState()));
  UpdateListeningState();
}

void Service::OnLockStateChanged(bool locked) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  locked_ = locked;
  UpdateListeningState();
}

void Service::OnAssistantConsentStatusChanged(int consent_status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Notify device apps status when user accepts activity control.
  if (assistant_manager_service_ &&
      assistant_manager_service_->GetState() ==
          AssistantManagerService::State::RUNNING) {
    assistant_manager_service_->SyncDeviceAppsStatus();
  }
}

void Service::OnAssistantContextEnabled(bool enabled) {
  UpdateAssistantManagerState();
}

void Service::OnAssistantHotwordAlwaysOn(bool hotword_always_on) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // No need to update hotword status if power source is connected.
  if (power_source_connected_)
    return;

  UpdateAssistantManagerState();
}

void Service::OnAssistantSettingsEnabled(bool enabled) {
  // Reset the failure count and backoff delay when the Settings is re-enabled.
  start_service_retry_backoff_.Reset();
  UpdateAssistantManagerState();
}

void Service::OnAssistantHotwordEnabled(bool enabled) {
  UpdateAssistantManagerState();
}

void Service::OnLocaleChanged(const std::string& locale) {
  UpdateAssistantManagerState();
}

void Service::OnArcPlayStoreEnabledChanged(bool enabled) {
  UpdateAssistantManagerState();
}

void Service::OnLockedFullScreenStateChanged(bool enabled) {
  UpdateListeningState();
}

void Service::OnAuthenticationError() {
  RequestAccessToken();
}

void Service::OnStateChanged(AssistantManagerService::State new_state) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  switch (new_state) {
    case AssistantManagerService::State::STARTED:
      FinalizeAssistantManagerService();
      break;
    case AssistantManagerService::State::RUNNING:
      OnLibassistantServiceRunning();
      break;
    case AssistantManagerService::State::STOPPED:
      OnLibassistantServiceStopped();
      break;
    case AssistantManagerService::State::DISCONNECTED:
      OnLibassistantServiceDisconnected();
      break;
    case AssistantManagerService::State::STARTING:
    case AssistantManagerService::State::STOPPING:
      // No action.
      break;
  }

  RecordServiceState(new_state);
  AssistantBrowserDelegate::Get()->OnAssistantStatusChanged(
      ToAssistantStatus(new_state));

  UpdateListeningState();
}

void Service::UpdateAssistantManagerState() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto* assistant_state = AssistantState::Get();

  if (!assistant_state->hotword_enabled().has_value() ||
      !assistant_state->settings_enabled().has_value() ||
      !assistant_state->locale().has_value() ||
      (!access_token_.has_value() && !IsSignedOutMode()) ||
      !assistant_state->arc_play_store_enabled().has_value() ||
      !libassistant_loaded_ || is_deleting_data_) {
    // Assistant state has not finished initialization, let's wait.
    return;
  }

  if (IsSignedOutMode()) {
    // Clear |access_token_| in signed-out mode to keep it synced with what we
    // will pass to the |assistant_manager_service_|.
    access_token_ = std::nullopt;
  }

  if (!assistant_manager_service_)
    CreateAssistantManagerService();

  auto state = assistant_manager_service_->GetState();
  switch (state) {
    case AssistantManagerService::State::STOPPED:
    case AssistantManagerService::State::DISCONNECTED:
      if (!CanStartService()) {
        return;
      }

      if (assistant_state->settings_enabled().value()) {
        assistant_manager_service_->Start(GetUserInfo(), ShouldEnableHotword());

        // Re-add observers every time when starting.
        assistant_manager_service_->AddAuthenticationStateObserver(this);
        assistant_manager_service_->AddAndFireStateObserver(this);

        if (AssistantInteractionLogger::IsLoggingEnabled()) {
          interaction_logger_ = std::make_unique<AssistantInteractionLogger>();
          assistant_manager_service_->AddAssistantInteractionSubscriber(
              interaction_logger_.get());
        }

        DVLOG(1) << "Request Assistant start";
      }
      break;
    case AssistantManagerService::State::STARTING:
    case AssistantManagerService::State::STARTED:
      // If the Assistant is disabled by domain policy, the libassistant will
      // never becomes ready. Stop waiting for the state change and stop the
      // service.
      if (assistant_state->allowed_state() ==
          AssistantAllowedState::DISALLOWED_BY_POLICY) {
        StopAssistantManagerService();
        return;
      }
      // Wait if |assistant_manager_service_| is not at a stable state.
      ScheduleUpdateAssistantManagerState(/*should_backoff=*/false);
      break;
    case AssistantManagerService::State::STOPPING:
      ScheduleUpdateAssistantManagerState(/*should_backoff=*/false);
      break;
    case AssistantManagerService::State::RUNNING:
      if (assistant_state->settings_enabled().value()) {
        assistant_manager_service_->SetUser(GetUserInfo());
        assistant_manager_service_->EnableHotword(ShouldEnableHotword());
        assistant_manager_service_->SetArcPlayStoreEnabled(
            assistant_state->arc_play_store_enabled().value());
        assistant_manager_service_->SetAssistantContextEnabled(
            assistant_state->IsScreenContextAllowed());
      } else {
        StopAssistantManagerService();
      }
      break;
  }
}

void Service::ScheduleUpdateAssistantManagerState(bool should_backoff) {
  update_assistant_manager_callback_.Cancel();
  update_assistant_manager_callback_.Reset(base::BindOnce(
      &Service::UpdateAssistantManagerState, weak_ptr_factory_.GetWeakPtr()));

  base::TimeDelta delay =
      should_backoff ? start_service_retry_backoff_.GetTimeUntilRelease()
                     : kUpdateAssistantManagerDelay;
  main_task_runner_->PostDelayedTask(
      FROM_HERE, update_assistant_manager_callback_.callback(), delay);
}

CoreAccountInfo Service::RetrievePrimaryAccountInfo() const {
  CoreAccountInfo account_info =
      identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
  CHECK(!account_info.account_id.empty());
  CHECK(!account_info.gaia.empty());
  return account_info;
}

void Service::RequestAccessToken() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Bypass access token fetching when service is running in signed-out mode.
  if (IsSignedOutMode()) {
    VLOG(1) << "Signed out mode detected, bypass access token fetching.";
    return;
  }

  if (access_token_fetcher_) {
    LOG(WARNING) << "Access token already requested.";
    return;
  }

  VLOG(1) << "Start requesting access token.";
  CoreAccountInfo account_info = RetrievePrimaryAccountInfo();
  if (!identity_manager_->HasAccountWithRefreshToken(account_info.account_id)) {
    LOG(ERROR) << "Failed to retrieve primary account info. Retrying.";
    RetryRefreshToken();
    return;
  }

  signin::ScopeSet scopes;
  scopes.insert(kScopeAssistant);
  scopes.insert(GaiaConstants::kGCMGroupServerOAuth2Scope);

  access_token_fetcher_ = identity_manager_->CreateAccessTokenFetcherForAccount(
      account_info.account_id, "cros_assistant", scopes,
      base::BindOnce(&Service::GetAccessTokenCallback, base::Unretained(this)),
      signin::AccessTokenFetcher::Mode::kImmediate);
}

void Service::GetAccessTokenCallback(
    GoogleServiceAuthError error,
    signin::AccessTokenInfo access_token_info) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // It's safe to delete AccessTokenFetcher from inside its own callback.
  access_token_fetcher_.reset();

  if (error.state() != GoogleServiceAuthError::NONE) {
    LOG(ERROR) << "Failed to retrieve token, error: " << error.ToString();
    RetryRefreshToken();
    return;
  }

  access_token_ = access_token_info.token;
  UpdateAssistantManagerState();
  token_refresh_timer_->Start(
      FROM_HERE, access_token_info.expiration_time - base::Time::Now(), this,
      &Service::RequestAccessToken);
}

void Service::RetryRefreshToken() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  base::TimeDelta backoff_delay =
      std::min(kMinTokenRefreshDelay *
                   (1 << (token_refresh_error_backoff_factor - 1)),
               kMaxTokenRefreshDelay) +
      base::RandDouble() * kMinTokenRefreshDelay;
  if (backoff_delay < kMaxTokenRefreshDelay)
    ++token_refresh_error_backoff_factor;
  token_refresh_timer_->Start(FROM_HERE, backoff_delay, this,
                              &Service::RequestAccessToken);
}

void Service::CreateAssistantManagerService() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  assistant_manager_service_ = CreateAndReturnAssistantManagerService();
}

std::unique_ptr<AssistantManagerService>
Service::CreateAndReturnAssistantManagerService() {
  if (assistant_manager_service_for_testing_)
    return std::move(assistant_manager_service_for_testing_);

  // |assistant_manager_service_| is only created once.
  DCHECK(pending_url_loader_factory_);
  return std::make_unique<AssistantManagerServiceImpl>(
      context(), std::move(pending_url_loader_factory_),
      GetS3ServerUriOverride(), GetDeviceIdOverride());
}

void Service::FinalizeAssistantManagerService() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(assistant_manager_service_->GetState() ==
             AssistantManagerService::STARTED ||
         assistant_manager_service_->GetState() ==
             AssistantManagerService::RUNNING);

  // Ensure one-time mojom initialization.
  if (is_assistant_manager_service_finalized_)
    return;
  is_assistant_manager_service_finalized_ = true;

  AddAshSessionObserver();
  AssistantController::Get()->SetAssistant(assistant_manager_service_.get());
}

void Service::StopAssistantManagerService() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  assistant_manager_service_->Stop();
  weak_ptr_factory_.InvalidateWeakPtrs();
}

void Service::OnLibassistantServiceRunning() {
  DVLOG(1) << "Assistant is running";
  pref_service_->SetInteger(prefs::kAssistantNumFailuresSinceLastServiceRun, 0);
}

void Service::OnLibassistantServiceStopped() {
  ClearAfterStop();
}

void Service::OnLibassistantServiceDisconnected() {
  ClearAfterStop();

  if (auto_service_recover_timer_->IsRunning()) {
    auto_service_recover_timer_->Stop();
  }

  // Increase the failure count for both the backoff and pref.
  start_service_retry_backoff_.InformOfRequest(/*succeeded=*/false);
  int num_failures = pref_service_->GetInteger(
      prefs::kAssistantNumFailuresSinceLastServiceRun);
  pref_service_->SetInteger(prefs::kAssistantNumFailuresSinceLastServiceRun,
                            num_failures + 1);
  if (CanStartService()) {
    LOG(WARNING) << "LibAssistant service disconnected. Re-starting...";

    // Restarts LibassistantService.
    ScheduleUpdateAssistantManagerState(/*should_backoff=*/true);
  } else {
    // Start auto recover timer.
    auto delay = GetAutoRecoverTime();
    auto_service_recover_timer_->Start(FROM_HERE, delay, this,
                                       &Service::DecreaseStartServiceBackoff);
    LOG(ERROR)
        << "LibAssistant service keeps disconnected. All retries attempted.";
  }
}

void Service::AddAshSessionObserver() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // No session controller in unittest.
  if (SessionController::Get()) {
    // Note that this account can either be a regular account using real gaia,
    // or a fake gaia account.
    CoreAccountInfo account_info = RetrievePrimaryAccountInfo();
    AccountId account_id = AccountId::FromNonCanonicalEmail(
        account_info.email, account_info.gaia, AccountType::GOOGLE);
    scoped_ash_session_observer_ =
        std::make_unique<ScopedAshSessionObserver>(this, account_id);
  }
}

void Service::UpdateListeningState() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!assistant_manager_service_) {
    return;
  }

  bool should_listen =
      !locked_ &&
      !AssistantState::Get()->locked_full_screen_enabled().value_or(false) &&
      session_active_;
  DVLOG(1) << "Update assistant listening state: " << should_listen;
  assistant_manager_service_->EnableListening(should_listen);
  assistant_manager_service_->EnableHotword(should_listen &&
                                            ShouldEnableHotword());
}

std::optional<AssistantManagerService::UserInfo> Service::GetUserInfo() const {
  if (access_token_) {
    return AssistantManagerService::UserInfo(RetrievePrimaryAccountInfo().gaia,
                                             access_token_.value());
  }
  return std::nullopt;
}

bool Service::ShouldEnableHotword() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  bool dsp_available = context()->cras_audio_handler()->HasHotwordDevice();
  auto* assistant_state = AssistantState::Get();

  // Disable hotword if hotword is not set to always on and power source is not
  // connected.
  if (!dsp_available && !assistant_state->hotword_always_on().value_or(false) &&
      !power_source_connected_) {
    return false;
  }

  return assistant_state->hotword_enabled().value();
}

void Service::LoadLibassistant() {
  libassistant::LibassistantLoader::Load(base::BindOnce(
      &Service::OnLibassistantLoaded, weak_ptr_factory_.GetWeakPtr()));
}

void Service::OnLibassistantLoaded(bool success) {
  libassistant_loaded_ = success;

  if (success) {
    UpdateAssistantManagerState();
  }
}

void Service::ClearAfterStop() {
  is_assistant_manager_service_finalized_ = false;
  scoped_ash_session_observer_.reset();

#if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
  // When user disables the Assistant, we also delete all data.
  if (!AssistantState::Get()->settings_enabled().value()) {
    is_deleting_data_ = true;
    base::ThreadPool::CreateSequencedTaskRunner(GetTaskTraits())
        ->PostTaskAndReply(
            FROM_HERE, base::BindOnce([]() {
              base::DeletePathRecursively(base::FilePath(
                  FILE_PATH_LITERAL(libassistant::kAssistantBaseDirPath)));
            }),
            base::BindOnce(&Service::OnDataDeleted,
                           weak_ptr_factory_.GetWeakPtr()));
  }
#endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)

  ResetAuthenticationStateObserver();
}

void Service::DecreaseStartServiceBackoff() {
  // Reduce the failure_count by one to allow restart.
  start_service_retry_backoff_.InformOfRequest(/*succeeded=*/true);

  // It is ok to try to reset service if the service is running.
  ScheduleUpdateAssistantManagerState(/*should_backoff=*/true);

  // Start auto recover timer.
  if (start_service_retry_backoff_.failure_count() > 0) {
    auto delay = GetAutoRecoverTime();
    auto_service_recover_timer_->Start(FROM_HERE, delay, this,
                                       &Service::DecreaseStartServiceBackoff);
  }
}

base::TimeDelta Service::GetAutoRecoverTime() {
  if (!auto_recover_time_for_testing_.is_zero()) {
    return auto_recover_time_for_testing_;
  }
  return kAutoRecoverTime;
}

bool Service::CanStartService() const {
  // Please see comments on `kMaxStartServiceRetries`.
  // We can start service if the failure count is zero:
  // 1.b. Reset to 0 when explicitly re-enable the Assistant from the Settings.
  // 1.c. Reset to 0 when re-login the device.
  // 1.d. Decrement by 1 when it has been `kAutoRecoverTime`.
  if (start_service_retry_backoff_.failure_count() == 0) {
    return true;
  }

  // Do not start service if it has retried `kMaxStartServiceRetries` times in
  // one chrome session or since the last time enable in Settings.
  if (start_service_retry_backoff_.failure_count() > kMaxStartServiceRetries) {
    return false;
  }

  // Do not start service if `kAssistantNumFailuresSinceLastServiceRun` failed
  // `kMaxStartServiceRetries` times.
  int num_failures_since_last_service_run = pref_service_->GetInteger(
      prefs::kAssistantNumFailuresSinceLastServiceRun);
  if (num_failures_since_last_service_run > kMaxStartServiceRetries) {
    return false;
  }

  return true;
}

void Service::OnDataDeleted() {
  is_deleting_data_ = false;
  UpdateAssistantManagerState();
}

}  // namespace ash::assistant