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

#include "content/browser/renderer_host/media/audio_service_listener.h"

#include <utility>

#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/default_tick_clock.h"
#include "content/browser/media/audio_log_factory.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/child_process_termination_info.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "services/audio/public/mojom/constants.mojom.h"
#include "services/audio/public/mojom/log_factory_manager.mojom.h"

namespace content {

AudioServiceListener::Metrics::Metrics(const base::TickClock* clock)
    : clock_(clock), initial_downtime_start_(clock_->NowTicks()) {}

AudioServiceListener::Metrics::~Metrics() = default;

void AudioServiceListener::Metrics::ServiceAlreadyRunning() {
  LogServiceStartStatus(ServiceStartStatus::kAlreadyStarted);
  started_ = clock_->NowTicks();
  initial_downtime_start_ = base::TimeTicks();
}

void AudioServiceListener::Metrics::ServiceCreated() {
  DCHECK(created_.is_null());
  created_ = clock_->NowTicks();
}

void AudioServiceListener::Metrics::ServiceStarted() {
  started_ = clock_->NowTicks();

  // |created_| is uninitialized if OnServiceCreated() was called before the
  // listener is initialized with OnInit() call.
  if (!created_.is_null()) {
    LogServiceStartStatus(ServiceStartStatus::kSuccess);
    UMA_HISTOGRAM_TIMES("Media.AudioService.ObservedStartupTime",
                        started_ - created_);
    created_ = base::TimeTicks();
  }

  if (!initial_downtime_start_.is_null()) {
    UMA_HISTOGRAM_CUSTOM_TIMES("Media.AudioService.ObservedInitialDowntime",
                               started_ - initial_downtime_start_,
                               base::TimeDelta(), base::TimeDelta::FromDays(7),
                               50);
    initial_downtime_start_ = base::TimeTicks();
  }

  if (!stopped_.is_null()) {
    UMA_HISTOGRAM_CUSTOM_TIMES("Media.AudioService.ObservedDowntime2",
                               started_ - stopped_, base::TimeDelta(),
                               base::TimeDelta::FromDays(7), 50);
    stopped_ = base::TimeTicks();
  }
}

void AudioServiceListener::Metrics::ServiceFailedToStart() {
  LogServiceStartStatus(ServiceStartStatus::kFailure);
  created_ = base::TimeTicks();
}

void AudioServiceListener::Metrics::ServiceStopped() {
  stopped_ = clock_->NowTicks();

  DCHECK(!started_.is_null());
  UMA_HISTOGRAM_CUSTOM_TIMES("Media.AudioService.ObservedUptime",
                             stopped_ - started_, base::TimeDelta(),
                             base::TimeDelta::FromDays(7), 50);
  started_ = base::TimeTicks();
}

void AudioServiceListener::Metrics::ServiceProcessTerminated(
    Metrics::ServiceProcessTerminationStatus status) {
  UMA_HISTOGRAM_ENUMERATION(
      "Media.AudioService.ObservedProcessTerminationStatus", status);
}

void AudioServiceListener::Metrics::LogServiceStartStatus(
    Metrics::ServiceStartStatus status) {
  UMA_HISTOGRAM_ENUMERATION("Media.AudioService.ObservedStartStatus", status);
}

AudioServiceListener::AudioServiceListener(
    std::unique_ptr<service_manager::Connector> connector)
    : binding_(this),
      connector_(std::move(connector)),
      metrics_(base::DefaultTickClock::GetInstance()) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  if (!connector_)
    return;  // Happens in unittests.

  service_manager::mojom::ServiceManagerPtr service_manager;
  connector_->BindInterface(service_manager::mojom::kServiceName,
                            &service_manager);
  service_manager::mojom::ServiceManagerListenerPtr listener;
  service_manager::mojom::ServiceManagerListenerRequest request(
      mojo::MakeRequest(&listener));
  service_manager->AddListener(std::move(listener));
  binding_.Bind(std::move(request));
  BrowserChildProcessObserver::Add(this);
}

AudioServiceListener::~AudioServiceListener() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  BrowserChildProcessObserver::Remove(this);
}

base::ProcessId AudioServiceListener::GetProcessId() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  return process_id_;
}

void AudioServiceListener::OnInit(
    std::vector<service_manager::mojom::RunningServiceInfoPtr>
        running_services) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  for (const service_manager::mojom::RunningServiceInfoPtr& instance :
       running_services) {
    if (instance->identity.name() == audio::mojom::kServiceName) {
      process_id_ = instance->pid;
      metrics_.ServiceAlreadyRunning();
      MaybeSetLogFactory();
      break;
    }
  }
}

void AudioServiceListener::OnServiceCreated(
    service_manager::mojom::RunningServiceInfoPtr service) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  if (service->identity.name() != audio::mojom::kServiceName)
    return;
  metrics_.ServiceCreated();
  MaybeSetLogFactory();
}

void AudioServiceListener::OnServiceStarted(
    const ::service_manager::Identity& identity,
    uint32_t pid) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  if (identity.name() != audio::mojom::kServiceName)
    return;
  process_id_ = pid;
  metrics_.ServiceStarted();
}

void AudioServiceListener::OnServicePIDReceived(
    const ::service_manager::Identity& identity,
    uint32_t pid) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  if (identity.name() != audio::mojom::kServiceName)
    return;
  process_id_ = pid;
}

void AudioServiceListener::OnServiceFailedToStart(
    const ::service_manager::Identity& identity) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  if (identity.name() != audio::mojom::kServiceName)
    return;
  metrics_.ServiceFailedToStart();
}

void AudioServiceListener::OnServiceStopped(
    const ::service_manager::Identity& identity) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  if (identity.name() != audio::mojom::kServiceName)
    return;
  metrics_.ServiceStopped();
  log_factory_is_set_ = false;
}

void AudioServiceListener::BrowserChildProcessHostDisconnected(
    const ChildProcessData& data) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  if (base::GetProcId(data.GetHandle()) != process_id_)
    return;
  process_id_ = base::kNullProcessId;
  metrics_.ServiceProcessTerminated(
      Metrics::ServiceProcessTerminationStatus::kDisconnect);
}

void AudioServiceListener::BrowserChildProcessCrashed(
    const ChildProcessData& data,
    const ChildProcessTerminationInfo& info) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  if (base::GetProcId(data.GetHandle()) != process_id_)
    return;
  process_id_ = base::kNullProcessId;
  metrics_.ServiceProcessTerminated(
      Metrics::ServiceProcessTerminationStatus::kCrash);
}

void AudioServiceListener::BrowserChildProcessKilled(
    const ChildProcessData& data,
    const ChildProcessTerminationInfo& info) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  if (base::GetProcId(data.GetHandle()) != process_id_)
    return;
  process_id_ = base::kNullProcessId;
  metrics_.ServiceProcessTerminated(
      Metrics::ServiceProcessTerminationStatus::kKill);
}

void AudioServiceListener::MaybeSetLogFactory() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
  if (!base::FeatureList::IsEnabled(features::kAudioServiceOutOfProcess) ||
      !connector_ || log_factory_is_set_)
    return;

  media::mojom::AudioLogFactoryPtr audio_log_factory_ptr;
  mojo::MakeStrongBinding(std::make_unique<AudioLogFactory>(),
                          mojo::MakeRequest(&audio_log_factory_ptr));
  audio::mojom::LogFactoryManagerPtr log_factory_manager_ptr;
  connector_->BindInterface(audio::mojom::kServiceName,
                            mojo::MakeRequest(&log_factory_manager_ptr));
  log_factory_manager_ptr->SetLogFactory(std::move(audio_log_factory_ptr));
  log_factory_is_set_ = true;
}

}  // namespace content
