910e62b5创建于 1月15日历史提交
// 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 "services/media_session/audio_focus_manager.h"

#include <iterator>
#include <utility>
#include <vector>

#include "base/containers/adapters.h"
#include "base/functional/bind.h"
#include "base/notimplemented.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_observer.h"
#include "base/unguessable_token.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/media_session/audio_focus_request.h"
#include "services/media_session/public/cpp/features.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h"

namespace media_session {

namespace {

mojom::EnforcementMode GetDefaultEnforcementMode() {
  if (base::FeatureList::IsEnabled(features::kAudioFocusEnforcement)) {
    if (base::FeatureList::IsEnabled(features::kAudioFocusSessionGrouping))
      return mojom::EnforcementMode::kSingleGroup;
    return mojom::EnforcementMode::kSingleSession;
  }

  return mojom::EnforcementMode::kNone;
}

}  // namespace

// MediaPowerDelegate will pause all playback if the device is suspended.
class MediaPowerDelegate : public base::PowerSuspendObserver {
 public:
  explicit MediaPowerDelegate(base::WeakPtr<AudioFocusManager> owner)
      : owner_(owner) {
    base::PowerMonitor::GetInstance()->AddPowerSuspendObserver(this);
  }

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

  ~MediaPowerDelegate() override {
    base::PowerMonitor::GetInstance()->RemovePowerSuspendObserver(this);
  }

  // base::PowerSuspendObserver:
  void OnSuspend() override {
    DCHECK(owner_);
    owner_->SuspendAllSessions();
  }

 private:
  const base::WeakPtr<AudioFocusManager> owner_;
};

class AudioFocusManager::SourceObserverHolder {
 public:
  SourceObserverHolder(AudioFocusManager* owner,
                       const base::UnguessableToken& source_id,
                       mojo::PendingRemote<mojom::AudioFocusObserver> observer)
      : identity_(source_id), observer_(std::move(observer)) {
    // Set a connection error handler so that we will remove observers that have
    // had an error / been closed.
    observer_.set_disconnect_handler(base::BindOnce(
        &AudioFocusManager::CleanupSourceObservers, base::Unretained(owner)));
  }

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

  ~SourceObserverHolder() = default;

  bool is_valid() const { return observer_.is_connected(); }

  const base::UnguessableToken& identity() const { return identity_; }

  void OnFocusGained(mojom::AudioFocusRequestStatePtr session) {
    observer_->OnFocusGained(std::move(session));
  }

  void OnFocusLost(mojom::AudioFocusRequestStatePtr session) {
    observer_->OnFocusLost(std::move(session));
  }

  void OnRequestIdReleased(const base::UnguessableToken& request_id) {
    observer_->OnRequestIdReleased(request_id);
  }

 private:
  const base::UnguessableToken identity_;
  mojo::Remote<mojom::AudioFocusObserver> observer_;
};

void AudioFocusManager::RequestAudioFocus(
    mojo::PendingReceiver<mojom::AudioFocusRequestClient> receiver,
    mojo::PendingRemote<mojom::MediaSession> session,
    mojom::MediaSessionInfoPtr session_info,
    mojom::AudioFocusType type,
    RequestAudioFocusCallback callback) {
  auto request_id = base::UnguessableToken::Create();

  RequestAudioFocusInternal(
      std::make_unique<AudioFocusRequest>(
          weak_ptr_factory_.GetWeakPtr(), std::move(receiver),
          std::move(session), std::move(session_info), type, request_id,
          GetBindingSourceName(), base::UnguessableToken::Create(),
          GetBindingIdentity()),
      type);

  std::move(callback).Run(request_id);
}

void AudioFocusManager::RequestGroupedAudioFocus(
    const base::UnguessableToken& request_id,
    mojo::PendingReceiver<mojom::AudioFocusRequestClient> receiver,
    mojo::PendingRemote<mojom::MediaSession> session,
    mojom::MediaSessionInfoPtr session_info,
    mojom::AudioFocusType type,
    const base::UnguessableToken& group_id,
    RequestGroupedAudioFocusCallback callback) {
  if (IsFocusEntryPresent(request_id)) {
    std::move(callback).Run(false /* success */);
    return;
  }

  RequestAudioFocusInternal(
      std::make_unique<AudioFocusRequest>(
          weak_ptr_factory_.GetWeakPtr(), std::move(receiver),
          std::move(session), std::move(session_info), type, request_id,
          GetBindingSourceName(), group_id, GetBindingIdentity()),
      type);

  std::move(callback).Run(true /* success */);
}

void AudioFocusManager::GetFocusRequests(GetFocusRequestsCallback callback) {
  std::vector<mojom::AudioFocusRequestStatePtr> requests;

  for (const auto& row : audio_focus_stack_)
    requests.push_back(row->ToAudioFocusRequestState());

  std::move(callback).Run(std::move(requests));
}

void AudioFocusManager::GetDebugInfoForRequest(
    const RequestId& request_id,
    GetDebugInfoForRequestCallback callback) {
  for (auto& row : audio_focus_stack_) {
    if (row->id() != request_id)
      continue;

    row->ipc()->GetDebugInfo(base::BindOnce(
        [](const base::UnguessableToken& group_id,
           const base::UnguessableToken& identity,
           GetDebugInfoForRequestCallback callback,
           mojom::MediaSessionDebugInfoPtr info) {
          // Inject the |group_id| into the state string. This is because in
          // some cases the group id is automatically generated by the media
          // session service so the session is unaware of it.
          if (!info->state.empty())
            info->state += " ";
          info->state += "GroupId=" + group_id.ToString();

          // Inject the identity into the state string.
          info->state += " Identity=" + identity.ToString();

          std::move(callback).Run(std::move(info));
        },
        row->group_id(), row->identity(), std::move(callback)));
    return;
  }

  std::move(callback).Run(mojom::MediaSessionDebugInfo::New());
}

void AudioFocusManager::AbandonAudioFocusInternal(RequestId id) {
  if (audio_focus_stack_.empty())
    return;

  bool was_top_most_session = audio_focus_stack_.back()->id() == id;

  auto row = RemoveFocusEntryIfPresent(id);
  if (!row)
    return;

  EnforceAudioFocus();
  MaybeUpdateActiveSession();

  // Notify observers that we lost audio focus.
  mojom::AudioFocusRequestStatePtr session_state =
      row->ToAudioFocusRequestState();

  for (const auto& observer : observers_)
    observer->OnFocusLost(session_state.Clone());

  for (auto& holder : source_observers_) {
    if (holder->identity() == row->identity())
      holder->OnFocusLost(session_state.Clone());
  }

  if (!was_top_most_session || audio_focus_stack_.empty())
    return;

  // Notify observers that the session on top gained focus.
  AudioFocusRequest* new_session = audio_focus_stack_.back().get();

  for (const auto& observer : observers_)
    observer->OnFocusGained(new_session->ToAudioFocusRequestState());

  for (auto& holder : source_observers_) {
    if (holder->identity() == new_session->identity())
      holder->OnFocusGained(new_session->ToAudioFocusRequestState());
  }
}

void AudioFocusManager::AddObserver(
    mojo::PendingRemote<mojom::AudioFocusObserver> observer) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  observers_.Add(std::move(observer));
}

void AudioFocusManager::SetSource(const base::UnguessableToken& identity,
                                  const std::string& name) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  auto& context = receivers_.current_context();
  context->identity = identity;
  context->source_name = name;
}

void AudioFocusManager::SetEnforcementMode(mojom::EnforcementMode mode) {
  if (mode == mojom::EnforcementMode::kDefault)
    mode = GetDefaultEnforcementMode();

  if (mode == enforcement_mode_)
    return;

  enforcement_mode_ = mode;

  if (audio_focus_stack_.empty())
    return;

  EnforceAudioFocus();
}

void AudioFocusManager::AddSourceObserver(
    const base::UnguessableToken& source_id,
    mojo::PendingRemote<mojom::AudioFocusObserver> observer) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  source_observers_.push_back(std::make_unique<SourceObserverHolder>(
      this, source_id, std::move(observer)));
}

void AudioFocusManager::GetSourceFocusRequests(
    const base::UnguessableToken& source_id,
    GetFocusRequestsCallback callback) {
  std::vector<mojom::AudioFocusRequestStatePtr> requests;

  for (const auto& row : audio_focus_stack_) {
    if (row->identity() == source_id)
      requests.push_back(row->ToAudioFocusRequestState());
  }

  std::move(callback).Run(std::move(requests));
}

void AudioFocusManager::RequestIdReleased(
    const base::UnguessableToken& request_id) {
  for (const auto& observer : observers_)
    observer->OnRequestIdReleased(request_id);

  const base::UnguessableToken& source_id = GetBindingIdentity();
  for (auto& holder : source_observers_) {
    if (holder->identity() == source_id)
      holder->OnRequestIdReleased(request_id);
  }
}

void AudioFocusManager::StartDuckingAllAudio(
    const std::optional<base::UnguessableToken>& exempted_request_id) {
  ducking_all_audio_ = true;
  ducking_exempted_request_id_ = exempted_request_id;
  EnforceAudioFocus();
}

void AudioFocusManager::StopDuckingAllAudio() {
  ducking_all_audio_ = false;
  EnforceAudioFocus();
}

void AudioFocusManager::FlushForTesting(FlushForTestingCallback callback) {
  for (auto& row : audio_focus_stack_) {
    row->FlushForTesting();  // IN-TEST
  }
  observers_.FlushForTesting();  // IN-TEST
  std::move(callback).Run();
}

void AudioFocusManager::CreateActiveMediaController(
    mojo::PendingReceiver<mojom::MediaController> receiver) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  active_media_controller_.BindToInterface(std::move(receiver));
}

void AudioFocusManager::CreateMediaControllerForSession(
    mojo::PendingReceiver<mojom::MediaController> receiver,
    const base::UnguessableToken& receiver_id) {
  for (auto& row : audio_focus_stack_) {
    if (row->id() != receiver_id)
      continue;

    row->BindToMediaController(std::move(receiver));
    break;
  }
}

void AudioFocusManager::SuspendAllSessions() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  for (auto& row : audio_focus_stack_)
    row->ipc()->Suspend(mojom::MediaSession::SuspendType::kUI);
}

void AudioFocusManager::BindToInterface(
    mojo::PendingReceiver<mojom::AudioFocusManager> receiver) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  receivers_.Add(this, std::move(receiver),
                 std::make_unique<ReceiverContext>());
}

void AudioFocusManager::BindToDebugInterface(
    mojo::PendingReceiver<mojom::AudioFocusManagerDebug> receiver) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  debug_receivers_.Add(this, std::move(receiver));
}

void AudioFocusManager::BindToControllerManagerInterface(
    mojo::PendingReceiver<mojom::MediaControllerManager> receiver) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  controller_receivers_.Add(this, std::move(receiver));
}

void AudioFocusManager::RequestAudioFocusInternal(
    std::unique_ptr<AudioFocusRequest> row,
    mojom::AudioFocusType type) {
  const auto& identity = row->identity();
  row->set_audio_focus_type(type);
  audio_focus_stack_.push_back(std::move(row));

  EnforceAudioFocus();
  MaybeUpdateActiveSession();

  // Notify observers that we were gained audio focus.
  mojom::AudioFocusRequestStatePtr session_state =
      audio_focus_stack_.back()->ToAudioFocusRequestState();
  for (const auto& observer : observers_)
    observer->OnFocusGained(session_state.Clone());

  for (auto& holder : source_observers_) {
    if (holder->identity() == identity)
      holder->OnFocusGained(session_state.Clone());
  }
}

void AudioFocusManager::EnforceAudioFocus() {
  DCHECK_NE(mojom::EnforcementMode::kDefault, enforcement_mode_);
  if (audio_focus_stack_.empty())
    return;

  EnforcementState state;

  for (auto& session : base::Reversed(audio_focus_stack_)) {
    EnforceSingleSession(session.get(), state);

    // Update the flags based on the audio focus type of this session. If the
    // session is suspended then any transient audio focus type should not have
    // an effect.
    switch (session->audio_focus_type()) {
      case mojom::AudioFocusType::kGain:
        state.should_stop = true;
        break;
      case mojom::AudioFocusType::kGainTransient:
        if (!session->IsSuspended())
          state.should_suspend = true;
        break;
      case mojom::AudioFocusType::kGainTransientMayDuck:
        if (!session->IsSuspended())
          state.should_duck = true;
        break;
      case mojom::AudioFocusType::kAmbient:
        break;
    }
  }
}

void AudioFocusManager::MaybeUpdateActiveSession() {
  AudioFocusRequest* active = nullptr;

  for (auto& row : base::Reversed(audio_focus_stack_)) {
    if (!row->info()->is_controllable ||
        row->info()->ignore_for_active_session) {
      continue;
    }

    active = row.get();
    break;
  }

  if (active) {
    active_media_controller_.SetMediaSession(active);
  } else {
    active_media_controller_.ClearMediaSession();
  }
}

AudioFocusManager::AudioFocusManager()
    : enforcement_mode_(GetDefaultEnforcementMode()) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  power_delegate_ =
      std::make_unique<MediaPowerDelegate>(weak_ptr_factory_.GetWeakPtr());
}

AudioFocusManager::~AudioFocusManager() = default;

std::unique_ptr<AudioFocusRequest> AudioFocusManager::RemoveFocusEntryIfPresent(
    RequestId id) {
  std::unique_ptr<AudioFocusRequest> row;

  for (auto iter = audio_focus_stack_.begin(); iter != audio_focus_stack_.end();
       ++iter) {
    if ((*iter)->id() == id) {
      row.swap((*iter));
      audio_focus_stack_.erase(iter);
      break;
    }
  }

  return row;
}

bool AudioFocusManager::IsFocusEntryPresent(
    const base::UnguessableToken& id) const {
  for (auto& row : audio_focus_stack_) {
    if (row->id() == id)
      return true;
  }

  return false;
}

const std::string& AudioFocusManager::GetBindingSourceName() const {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  return receivers_.current_context()->source_name;
}

const base::UnguessableToken& AudioFocusManager::GetBindingIdentity() const {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  return receivers_.current_context()->identity;
}

bool AudioFocusManager::IsSessionOnTopOfAudioFocusStack(
    RequestId id,
    mojom::AudioFocusType type) const {
  return !audio_focus_stack_.empty() && audio_focus_stack_.back()->id() == id &&
         audio_focus_stack_.back()->audio_focus_type() == type;
}

bool AudioFocusManager::ShouldSessionBeSuspended(
    const AudioFocusRequest* session,
    const EnforcementState& state) const {
  bool should_suspend_any = state.should_stop || state.should_suspend;

  switch (enforcement_mode_) {
    case mojom::EnforcementMode::kSingleSession:
      return should_suspend_any;
    case mojom::EnforcementMode::kSingleGroup:
      return should_suspend_any &&
             session->group_id() != audio_focus_stack_.back()->group_id();
    case mojom::EnforcementMode::kNone:
      return false;
    case mojom::EnforcementMode::kDefault:
      NOTIMPLEMENTED();
      return false;
  }
}

bool AudioFocusManager::ShouldSessionBeDucked(
    const AudioFocusRequest* session,
    const EnforcementState& state) const {
  if (ducking_all_audio_ && (!ducking_exempted_request_id_.has_value() ||
                             *ducking_exempted_request_id_ != session->id())) {
    return true;
  }

  switch (enforcement_mode_) {
    case mojom::EnforcementMode::kSingleSession:
    case mojom::EnforcementMode::kSingleGroup:
      if (session->info()->force_duck)
        return state.should_duck || ShouldSessionBeSuspended(session, state);
      return state.should_duck;
    case mojom::EnforcementMode::kNone:
      return false;
    case mojom::EnforcementMode::kDefault:
      NOTIMPLEMENTED();
      return false;
  }
}

void AudioFocusManager::EnforceSingleSession(AudioFocusRequest* session,
                                             const EnforcementState& state) {
  if (ShouldSessionBeDucked(session, state)) {
    session->ipc()->StartDucking();
  } else {
    session->ipc()->StopDucking();
  }

  // If the session wants to be ducked instead of suspended we should stop now.
  if (session->info()->force_duck)
    return;

  if (ShouldSessionBeSuspended(session, state)) {
    session->Suspend(state);
  } else {
    session->ReleaseTransientHold();
  }
}

void AudioFocusManager::CleanupSourceObservers() {
  std::erase_if(source_observers_,
                [](const auto& holder) { return !holder->is_valid(); });
}

}  // namespace media_session