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

#include "content/browser/media/session/media_session_controller.h"

#include <memory>
#include <optional>
#include <tuple>

#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "content/browser/media/media_web_contents_observer.h"
#include "content/browser/media/session/audio_focus_delegate.h"
#include "content/browser/media/session/media_session_impl.h"
#include "content/public/browser/media_device_id.h"
#include "content/test/mock_agent_scheduling_group_host.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "media/audio/audio_device_description.h"
#include "media/base/picture_in_picture_events_info.h"
#include "media/mojo/mojom/media_player.mojom.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"

namespace content {

class FakeAudioFocusDelegate : public content::AudioFocusDelegate {
 public:
  void set_audio_focus_result(AudioFocusResult result) {
    audio_focus_result_ = result;
  }

  // content::AudioFocusDelegate:
  AudioFocusResult RequestAudioFocus(
      media_session::mojom::AudioFocusType audio_focus_type) override {
    audio_focus_type_ = audio_focus_type;
    return audio_focus_result_;
  }
  void AbandonAudioFocus() override { audio_focus_type_.reset(); }
  std::optional<media_session::mojom::AudioFocusType> GetCurrentFocusType()
      const override {
    return audio_focus_type_;
  }
  void MediaSessionInfoChanged(
      const media_session::mojom::MediaSessionInfoPtr&) override {}
  const base::UnguessableToken& request_id() const override {
    return base::UnguessableToken::Null();
  }
  void ReleaseRequestId() override {}

 private:
  std::optional<media_session::mojom::AudioFocusType> audio_focus_type_;
  AudioFocusResult audio_focus_result_ = AudioFocusResult::kSuccess;
};

// Helper class that provides an implementation of the media::mojom::MediaPlayer
// mojo interface to allow checking that messages sent over mojo are received
// with the right values in the other end.
//
// Note this relies on MediaSessionController::BindMediaPlayer() to provide the
// MediaSessionController instance owned by the test with a valid mojo remote,
// that will be bound to the mojo receiver provided by this class instead of the
// real one used in production which would be owned by HTMLMediaElement instead.
class TestMediaPlayer : public media::mojom::MediaPlayer {
 public:
  enum class PauseRequestType {
    kNone,
    kTriggeredByUser,
    kNotTriggeredByUser,
  };

  TestMediaPlayer(MediaWebContentsObserver* media_web_contents_observer,
                  const MediaPlayerId& player_id) {
    // Bind the remote to the receiver, so that we can intercept incoming
    // MediaPlayer messages sent via the different methods that use the remote.
    // MediaPlayerObserver messages will not be used.
    mojo::AssociatedRemote<media::mojom::MediaPlayerHost> player_host;
    media_web_contents_observer->BindMediaPlayerHost(
        player_id.frame_routing_id,
        player_host.BindNewEndpointAndPassDedicatedReceiver());

    mojo::PendingAssociatedRemote<media::mojom::MediaPlayer> player;
    receiver_.Bind(player.InitWithNewEndpointAndPassReceiver());

    mojo::PendingAssociatedRemote<media::mojom::MediaPlayerObserver>
        dummy_player_observer;
    player_host->OnMediaPlayerAdded(
        std::move(player),
        dummy_player_observer.InitWithNewEndpointAndPassReceiver(),
        player_id.player_id);
    player_host.FlushForTesting();
  }

  // Needs to be called from tests after invoking a method related playback
  // from the MediaPlayer mojo interface, so that we have enough time to
  // process the message.
  void WaitUntilReceivedMessage() {
    run_loop_ = std::make_unique<base::RunLoop>();
    run_loop_->Run();
    run_loop_.reset();
  }

  // Needs to be called from tests after invoking SetVolumeMultiplier method
  // from the MediaPlayer mojo interface, so that we have enough time to
  // process the message.
  void WaitUntilVolumeChanged() {
    run_loop_for_volume_ = std::make_unique<base::RunLoop>();
    run_loop_for_volume_->Run();
    run_loop_for_volume_.reset();
  }

  // media::mojom::MediaPlayer implementation.
  void RequestPlay(bool triggered_by_user) override {
    received_play_ = true;
    received_play_triggered_by_user_ = triggered_by_user;
    run_loop_->Quit();
  }

  void RequestPause(bool triggered_by_user) override {
    received_pause_type_ = triggered_by_user
                               ? PauseRequestType::kTriggeredByUser
                               : PauseRequestType::kNotTriggeredByUser;
    run_loop_->Quit();
  }

  void RequestSeekForward(base::TimeDelta seek_time) override {
    received_seek_forward_time_ = seek_time;
    run_loop_->Quit();
  }

  void RequestSeekBackward(base::TimeDelta seek_time) override {
    received_seek_backward_time_ = seek_time;
    run_loop_->Quit();
  }

  void RequestSeekTo(base::TimeDelta seek_time) override {
    received_seek_to_time_ = seek_time;
    run_loop_->Quit();
  }

  void RequestEnterPictureInPicture(
      const std::optional<gfx::Size>& min_size) override {}

  void RequestMute(bool mute) override {}

  void SetVolumeMultiplier(double multiplier) override {
    received_volume_multiplier_ = multiplier;
    if (run_loop_for_volume_)
      run_loop_for_volume_->Quit();
  }

  void SetPersistentState(bool persistent) override {}

  void SetPowerExperimentState(bool enabled) override {}

  void SetAudioSinkId(const std::string& sink_id) override {
    received_set_audio_sink_id_ = sink_id;
    run_loop_->Quit();
  }

  void SuspendForFrameClosed() override {}

  void RequestMediaRemoting() override {}
  void RequestSaveVideoFrame() override {}

  void RequestVisibility(
      RequestVisibilityCallback request_visibility_callback) override {
    std::move(request_visibility_callback).Run(expected_visibility_);
    if (run_loop_) {
      run_loop_->Quit();
    }
  }

  // Helper method to set expected video visibility, which is later used as an
  // argument for the `RequestVisibility` method callback
  // (`RequestVisibilityCallback`).
  void SetExpectedVisibility(bool expected_visibility) {
    expected_visibility_ = expected_visibility;
  }

  void RecordAutoPictureInPictureInfo(
      const media::PictureInPictureEventsInfo::AutoPipInfo&
          auto_picture_in_picture_info) override {
    auto_picture_in_picture_info_ = auto_picture_in_picture_info;
    run_loop_->Quit();
  }

  // Getters used from MediaSessionControllerTest.
  bool received_play() const { return received_play_; }
  bool received_play_triggered_by_user() const {
    return received_play_triggered_by_user_;
  }

  PauseRequestType received_pause() const { return received_pause_type_; }

  const base::TimeDelta& received_seek_forward_time() const {
    return received_seek_forward_time_;
  }

  const base::TimeDelta& received_seek_backward_time() const {
    return received_seek_backward_time_;
  }

  const base::TimeDelta& received_seek_to_time() const {
    return received_seek_to_time_;
  }

  double received_volume_multiplier() const {
    return received_volume_multiplier_;
  }

  const std::string& received_set_audio_sink_id() const {
    return received_set_audio_sink_id_;
  }

  const media::PictureInPictureEventsInfo::AutoPipInfo&
  received_auto_picture_in_picture_info() const {
    return auto_picture_in_picture_info_;
  }

 private:
  std::unique_ptr<base::RunLoop> run_loop_;
  std::unique_ptr<base::RunLoop> run_loop_for_volume_;
  mojo::AssociatedReceiver<media::mojom::MediaPlayer> receiver_{this};

  bool received_play_{false};
  bool received_play_triggered_by_user_{false};
  double received_volume_multiplier_{0};
  PauseRequestType received_pause_type_{PauseRequestType::kNone};
  base::TimeDelta received_seek_forward_time_;
  base::TimeDelta received_seek_backward_time_;
  base::TimeDelta received_seek_to_time_;
  std::string received_set_audio_sink_id_;
  bool expected_visibility_ = false;
  media::PictureInPictureEventsInfo::AutoPipInfo auto_picture_in_picture_info_;
};

// Helper class to mock `RequestVisibility` callbacks.
class RequestVisibilityWaiter {
 public:
  RequestVisibilityWaiter() = default;

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

  TestMediaPlayer::RequestVisibilityCallback VisibilityCallback() {
    meets_visibility_ = std::nullopt;
    // base::Unretained() is safe since no further tasks can run after
    // RunLoop::Run() returns.
    return base::BindOnce(&RequestVisibilityWaiter::RequestVisibility,
                          base::Unretained(this));
  }

  void WaitUntilDone() {
    if (meets_visibility_) {
      return;
    }
    run_loop_ = std::make_unique<base::RunLoop>();
    run_loop_->Run();
  }

  bool MeetsVisibility() {
    DCHECK(meets_visibility_);
    return meets_visibility_.value();
  }

 private:
  void RequestVisibility(bool meets_visibility) {
    meets_visibility_ = meets_visibility;
    if (run_loop_) {
      run_loop_->Quit();
    }
  }

  std::unique_ptr<base::RunLoop> run_loop_;
  std::optional<bool> meets_visibility_;
};

class MediaSessionControllerTest : public RenderViewHostImplTestHarness {
 public:
  void SetUp() override {
    RenderViewHostImplTestHarness::SetUp();

    id_ = MediaPlayerId(contents()->GetPrimaryMainFrame()->GetGlobalId(), 0);
    controller_ = CreateController();
    media_player_ = CreateMediaPlayer(controller_.get());

    auto delegate = std::make_unique<FakeAudioFocusDelegate>();
    audio_focus_delegate_ = delegate.get();
    media_session()->SetDelegateForTests(std::move(delegate));
  }

  void TearDown() override {
    audio_focus_delegate_ = nullptr;
    // Destruct the controller prior to any other teardown to avoid out of order
    // destruction relative to the MediaSession instance.
    controller_.reset();
    media_player_.reset();

    RenderViewHostImplTestHarness::TearDown();
  }

 protected:
  std::unique_ptr<MediaSessionController> CreateController() {
    return std::make_unique<MediaSessionController>(id_, contents());
  }

  std::unique_ptr<TestMediaPlayer> CreateMediaPlayer(
      MediaSessionController* controller) {
    MediaWebContentsObserver* media_web_contents_observer =
        contents()->media_web_contents_observer();
    DCHECK(media_web_contents_observer);
    return std::make_unique<TestMediaPlayer>(media_web_contents_observer, id_);
  }

  MediaSessionImpl* media_session() {
    return MediaSessionImpl::Get(contents());
  }

  void Suspend(bool triggered_by_user = true) {
    controller_->OnSuspend(controller_->get_player_id_for_testing(),
                           triggered_by_user);
    media_player_->WaitUntilReceivedMessage();
  }

  void Resume(bool triggered_by_user = true) {
    controller_->OnResume(controller_->get_player_id_for_testing(),
                          triggered_by_user);
    media_player_->WaitUntilReceivedMessage();
  }

  void SeekForward(base::TimeDelta seek_time) {
    controller_->OnSeekForward(controller_->get_player_id_for_testing(),
                               seek_time);
    media_player_->WaitUntilReceivedMessage();
  }

  void SeekBackward(base::TimeDelta seek_time) {
    controller_->OnSeekBackward(controller_->get_player_id_for_testing(),
                                seek_time);
    media_player_->WaitUntilReceivedMessage();
  }

  void SeekTo(base::TimeDelta seek_time) {
    controller_->OnSeekTo(controller_->get_player_id_for_testing(), seek_time);
    media_player_->WaitUntilReceivedMessage();
  }

  void SetVolumeMultiplier(double multiplier) {
    controller_->OnSetVolumeMultiplier(controller_->get_player_id_for_testing(),
                                       multiplier);
    media_player_->WaitUntilVolumeChanged();
  }

  void RequestVisibility(
      TestMediaPlayer::RequestVisibilityCallback request_visibility_callback) {
    controller_->OnRequestVisibility(controller_->get_player_id_for_testing(),
                                     std::move(request_visibility_callback));
    media_player_->WaitUntilReceivedMessage();
  }

  // Helpers to check the results of using the basic controls.
  bool ReceivedMessagePlay(bool triggered_by_user) {
    return media_player_->received_play() &&
           media_player_->received_play_triggered_by_user() ==
               triggered_by_user;
  }

  bool ReceivedMessagePause(bool triggered_by_user) {
    TestMediaPlayer::PauseRequestType expected_pause_request =
        triggered_by_user
            ? TestMediaPlayer::PauseRequestType::kTriggeredByUser
            : TestMediaPlayer::PauseRequestType::kNotTriggeredByUser;
    return media_player_->received_pause() == expected_pause_request;
  }

  bool ReceivedMessageSeekForward(base::TimeDelta expected_seek_time) {
    return expected_seek_time == media_player_->received_seek_forward_time();
  }

  bool ReceivedMessageSeekBackward(base::TimeDelta expected_seek_time) {
    return expected_seek_time == media_player_->received_seek_backward_time();
  }

  bool ReceivedMessageSeekTo(base::TimeDelta expected_seek_time) {
    return expected_seek_time == media_player_->received_seek_to_time();
  }

  bool ReceivedMessageVolume(double expected_volume_multiplier) {
    return expected_volume_multiplier ==
           media_player_->received_volume_multiplier();
  }

  // Helper method to set the expected visibility that will be used by the
  // `RequestVisibilityCallback`.
  void SetExpectedVisibility(bool expected_visibility) {
    media_player_->SetExpectedVisibility(expected_visibility);
  }

  MediaPlayerId id_ = MediaPlayerId::CreateMediaPlayerIdForTests();
  std::unique_ptr<MediaSessionController> controller_;
  std::unique_ptr<TestMediaPlayer> media_player_;
  raw_ptr<FakeAudioFocusDelegate> audio_focus_delegate_ = nullptr;
};

TEST_F(MediaSessionControllerTest, NoAudioNoSession) {
  controller_->SetMetadata(false, true, media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  EXPECT_FALSE(media_session()->IsActive());
  EXPECT_FALSE(media_session()->IsControllable());
}

TEST_F(MediaSessionControllerTest, TransientNoControllableSession) {
  controller_->SetMetadata(true, false, media::MediaContentType::kTransient);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  EXPECT_TRUE(media_session()->IsActive());
  EXPECT_FALSE(media_session()->IsControllable());
}

TEST_F(MediaSessionControllerTest, BasicControls) {
  controller_->SetMetadata(true, false, media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  EXPECT_TRUE(media_session()->IsActive());
  EXPECT_TRUE(media_session()->IsControllable());

  // Verify suspend notifies the renderer and maintains its session.
  Suspend();
  EXPECT_TRUE(ReceivedMessagePause(/*triggered_by_user=*/true));

  // Likewise verify the resume behavior.
  Resume();
  EXPECT_TRUE(ReceivedMessagePlay(/*triggered_by_user=*/true));

  // ...as well as the seek behavior.
  const base::TimeDelta kTestSeekForwardTime = base::Seconds(1);
  SeekForward(kTestSeekForwardTime);
  EXPECT_TRUE(ReceivedMessageSeekForward(kTestSeekForwardTime));
  const base::TimeDelta kTestSeekBackwardTime = base::Seconds(2);
  SeekBackward(kTestSeekBackwardTime);
  EXPECT_TRUE(ReceivedMessageSeekBackward(kTestSeekBackwardTime));
  const base::TimeDelta kTestSeekToTime = base::Seconds(3);
  SeekTo(kTestSeekToTime);
  EXPECT_TRUE(ReceivedMessageSeekTo(kTestSeekToTime));

  // Verify destruction of the controller removes its session.
  controller_.reset();
  EXPECT_FALSE(media_session()->IsActive());
  EXPECT_FALSE(media_session()->IsControllable());
}

TEST_F(MediaSessionControllerTest, VolumeMultiplier) {
  controller_->SetMetadata(true, false, media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  EXPECT_TRUE(media_session()->IsActive());
  EXPECT_TRUE(media_session()->IsControllable());

  // Upon creation of the MediaSession the default multiplier will be sent.
  media_player_->WaitUntilVolumeChanged();
  EXPECT_TRUE(ReceivedMessageVolume(1.0));

  // Verify a different volume multiplier is sent.
  const double kTestMultiplier = 0.5;
  SetVolumeMultiplier(kTestMultiplier);
  EXPECT_TRUE(ReceivedMessageVolume(kTestMultiplier));
}

TEST_F(MediaSessionControllerTest, ControllerSidePause) {
  controller_->SetMetadata(true, false, media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  EXPECT_TRUE(media_session()->IsActive());
  EXPECT_TRUE(media_session()->IsControllable());

  // Verify pause behavior.
  controller_->OnPlaybackPaused(false);
  EXPECT_FALSE(media_session()->IsActive());
  EXPECT_TRUE(media_session()->IsControllable());

  // Verify the next OnPlaybackStarted() call restores the session.
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  EXPECT_TRUE(media_session()->IsActive());
  EXPECT_TRUE(media_session()->IsControllable());
}

TEST_F(MediaSessionControllerTest, Reinitialize) {
  controller_->SetMetadata(false, true, media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  EXPECT_FALSE(media_session()->IsActive());
  EXPECT_FALSE(media_session()->IsControllable());

  // Create a transient type session.
  controller_->SetMetadata(true, false, media::MediaContentType::kTransient);
  EXPECT_TRUE(media_session()->IsActive());
  EXPECT_FALSE(media_session()->IsControllable());
  const int current_player_id = controller_->get_player_id_for_testing();

  // Reinitialize the session as a content type.
  controller_->SetMetadata(true, false, media::MediaContentType::kPersistent);
  EXPECT_TRUE(media_session()->IsActive());
  EXPECT_TRUE(media_session()->IsControllable());
  // Player id should not change when there's an active session.
  EXPECT_EQ(current_player_id, controller_->get_player_id_for_testing());

  // Verify suspend notifies the renderer and maintains its session.
  Suspend();
  EXPECT_TRUE(ReceivedMessagePause(/*triggered_by_user=*/true));

  // Likewise verify the resume behavior.
  Resume();
  EXPECT_TRUE(ReceivedMessagePlay(/*triggered_by_user=*/true));

  Suspend();
  EXPECT_TRUE(ReceivedMessagePause(/*triggered_by_user=*/true));

  // Verify the system resume behavior.
  Resume(/*triggered_by_user=*/false);
  EXPECT_TRUE(ReceivedMessagePlay(/*triggered_by_user=*/false));

  // Verify the system suspend behavior.
  Suspend(/*triggered_by_user=*/false);
  EXPECT_TRUE(ReceivedMessagePause(/*triggered_by_user=*/false));
}

TEST_F(MediaSessionControllerTest, PositionState) {
  media_session::MediaPosition expected_position(
      /*playback_rate=*/0.0, /*duration=*/base::Seconds(10),
      /*position=*/base::TimeDelta(), /*end_of_media=*/false);

  controller_->OnMediaPositionStateChanged(expected_position);

  EXPECT_EQ(expected_position,
            controller_->GetPosition(controller_->get_player_id_for_testing()));
}

TEST_F(MediaSessionControllerTest, RemovePlayerIfSessionReset) {
  controller_->SetMetadata(true, false, media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  EXPECT_TRUE(media_session()->IsActive());

  controller_.reset();
  EXPECT_FALSE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest, PictureInPictureAvailability) {
  EXPECT_FALSE(controller_->IsPictureInPictureAvailable(
      controller_->get_player_id_for_testing()));

  controller_->OnPictureInPictureAvailabilityChanged(true);
  EXPECT_TRUE(controller_->IsPictureInPictureAvailable(
      controller_->get_player_id_for_testing()));
}

TEST_F(MediaSessionControllerTest, SufficientlyVisibleVideo) {
  EXPECT_FALSE(controller_->HasSufficientlyVisibleVideo(
      controller_->get_player_id_for_testing()));

  controller_->OnVideoVisibilityChanged(true);
  EXPECT_TRUE(controller_->HasSufficientlyVisibleVideo(
      controller_->get_player_id_for_testing()));
}

TEST_F(MediaSessionControllerTest, RequestVisibility) {
  media_player_->SetExpectedVisibility(true);
  RequestVisibilityWaiter request_visibility_waiter;
  RequestVisibility(request_visibility_waiter.VisibilityCallback());
  request_visibility_waiter.WaitUntilDone();
  EXPECT_TRUE(request_visibility_waiter.MeetsVisibility());

  media_player_->SetExpectedVisibility(false);
  RequestVisibility(request_visibility_waiter.VisibilityCallback());
  request_visibility_waiter.WaitUntilDone();
  EXPECT_FALSE(request_visibility_waiter.MeetsVisibility());
}

TEST_F(MediaSessionControllerTest, AudioOutputSinkIdChange) {
  EXPECT_EQ(controller_->GetAudioOutputSinkId(
                controller_->get_player_id_for_testing()),
            media::AudioDeviceDescription::kDefaultDeviceId);

  controller_->OnAudioOutputSinkChanged("1");
  EXPECT_EQ(controller_->GetAudioOutputSinkId(
                controller_->get_player_id_for_testing()),
            "1");
}

TEST_F(MediaSessionControllerTest, AddPlayerWhenUnmuted) {
  contents()->SetAudioMuted(true);

  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ false,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  ASSERT_FALSE(media_session()->IsActive());

  contents()->SetAudioMuted(false);
  controller_->WebContentsMutedStateChanged(false);
  EXPECT_TRUE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest, RemovePlayerWhenMuted) {
  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ false,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  ASSERT_TRUE(media_session()->IsActive());

  contents()->SetAudioMuted(true);
  controller_->WebContentsMutedStateChanged(true);
  EXPECT_FALSE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest, EnterLeavePictureInPictureMuted) {
  contents()->SetAudioMuted(true);

  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ false,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  ASSERT_FALSE(media_session()->IsActive());

  // Entering PictureInPicture means the user expects to control the media, so
  // this should activate the session.
  contents()->SetHasPictureInPictureVideo(true);
  controller_->PictureInPictureStateChanged(true);
  EXPECT_TRUE(media_session()->IsActive());

  contents()->SetHasPictureInPictureVideo(false);
  controller_->PictureInPictureStateChanged(false);
  EXPECT_FALSE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest, MuteWithPictureInPicture) {
  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ false,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  contents()->SetHasPictureInPictureVideo(true);
  controller_->PictureInPictureStateChanged(true);
  ASSERT_TRUE(media_session()->IsActive());

  contents()->SetAudioMuted(true);
  controller_->WebContentsMutedStateChanged(true);
  EXPECT_TRUE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest, LeavePictureInPictureUnmuted) {
  contents()->SetAudioMuted(true);

  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ false,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  ASSERT_FALSE(media_session()->IsActive());

  contents()->SetAudioMuted(false);
  controller_->WebContentsMutedStateChanged(false);
  contents()->SetHasPictureInPictureVideo(true);
  controller_->PictureInPictureStateChanged(true);

  // Media was unmuted, so we now have audio focus, which should keep the
  // session active.
  contents()->SetHasPictureInPictureVideo(false);
  controller_->PictureInPictureStateChanged(false);
  EXPECT_TRUE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest, AddPlayerWhenAddingAudio) {
  controller_->SetMetadata(
      /* has_audio = */ false, /* has_video = */ true,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  ASSERT_FALSE(media_session()->IsActive());

  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ false,
      media::MediaContentType::kPersistent);
  EXPECT_TRUE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest,
       AddPlayerWhenEnteringPictureInPictureWithNoAudio) {
  controller_->SetMetadata(
      /* has_audio = */ false, /* has_video = */ true,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  ASSERT_FALSE(media_session()->IsActive());

  contents()->SetHasPictureInPictureVideo(true);
  controller_->PictureInPictureStateChanged(true);
  EXPECT_TRUE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest,
       AddPlayerWhenEnteringPictureInPicturePaused) {
  controller_->SetMetadata(
      /*has_audio=*/false, /*has_video=*/true,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  controller_->OnPlaybackPaused(/*reached_end_of_stream=*/false);
  ASSERT_FALSE(media_session()->IsActive());

  contents()->SetHasPictureInPictureVideo(true);
  controller_->PictureInPictureStateChanged(true);
  EXPECT_FALSE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest,
       AddPlayerInitiallyPictureInPictureWithNoAudio) {
  contents()->SetHasPictureInPictureVideo(true);

  controller_->SetMetadata(
      /* has_audio = */ false, /* has_video = */ true,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  EXPECT_TRUE(media_session()->IsActive());

  contents()->SetHasPictureInPictureVideo(false);
  controller_->PictureInPictureStateChanged(false);

  EXPECT_FALSE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest,
       AddPlayerWhenStartingRemotePlaybackWithNoAudio) {
  controller_->SetMetadata(
      /* has_audio */ false, /* has_video */ true,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  ASSERT_FALSE(media_session()->IsActive());

  controller_->OnRemotePlaybackMetadataChanged(
      media_session::mojom::RemotePlaybackMetadata::New(
          "video_codec", "audio_codec",
          /* is_remote_playback_disabled */ false,
          /* is_remote_rendering */ true, "device_friendly_name",
          /* is_encrypted_media */ false));
  EXPECT_TRUE(media_session()->IsActive());
}

TEST_F(MediaSessionControllerTest, EndOfPlaybackWithInPictureInPicture) {
  contents()->SetHasPictureInPictureVideo(true);
  controller_->PictureInPictureStateChanged(true);

  controller_->SetMetadata(
      /*has_audio=*/true, /*has_video=*/false,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  EXPECT_TRUE(media_session()->IsActive());
  EXPECT_TRUE(media_session()->IsControllable());

  // Keeping the PiP window open should keep the session controllable.
  controller_->OnPlaybackPaused(/*reached_end_of_stream=*/true);
  EXPECT_FALSE(media_session()->IsActive());
  EXPECT_TRUE(media_session()->IsControllable());

  contents()->SetHasPictureInPictureVideo(false);
  controller_->PictureInPictureStateChanged(false);
  EXPECT_FALSE(media_session()->IsActive());
  EXPECT_FALSE(media_session()->IsControllable());

  // Re-opening the PiP window makes the session controllable again.
  contents()->SetHasPictureInPictureVideo(true);
  controller_->PictureInPictureStateChanged(true);
  EXPECT_FALSE(media_session()->IsActive());
  EXPECT_TRUE(media_session()->IsControllable());
}

TEST_F(MediaSessionControllerTest, HasVideo_True) {
  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ true,
      media::MediaContentType::kPersistent);
  EXPECT_TRUE(controller_->HasVideo(controller_->get_player_id_for_testing()));
}

TEST_F(MediaSessionControllerTest, HasVideo_False) {
  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ false,
      media::MediaContentType::kPersistent);
  EXPECT_FALSE(controller_->HasVideo(controller_->get_player_id_for_testing()));
}

TEST_F(MediaSessionControllerTest, AudioFocusRequestFailure) {
  // Start playback with the audio track only.
  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ false,
      media::MediaContentType::kPersistent);
  ASSERT_TRUE(controller_->OnPlaybackStarted());
  ASSERT_TRUE(media_session()->IsActive());

  // Add a video track while audio focus cannot be obtained.
  audio_focus_delegate_->set_audio_focus_result(
      AudioFocusDelegate::AudioFocusResult::kFailed);
  media_session()->Suspend(MediaSession::SuspendType::kSystem);
  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ true,
      media::MediaContentType::kPersistent);
  EXPECT_FALSE(media_session()->IsActive());

  // Have a one-shot player re-activate the session, then discard it.
  audio_focus_delegate_->set_audio_focus_result(
      AudioFocusDelegate::AudioFocusResult::kSuccess);
  auto transient_controller = CreateController();
  transient_controller->SetMetadata(
      /* has_audio = */ true, /* has_video = */ true,
      media::MediaContentType::kOneShot);
  ASSERT_TRUE(transient_controller->OnPlaybackStarted());
  EXPECT_TRUE(media_session()->IsActive());
  transient_controller->OnPlaybackPaused(false);

  // Activate the first player.
  controller_->SetMetadata(
      /* has_audio = */ true, /* has_video = */ true,
      media::MediaContentType::kPersistent);
  EXPECT_TRUE(media_session()->IsActive());

  // Remove the controller's session and make sure position updates are simply
  // ignored (no active player).
  controller_.reset();
  EXPECT_FALSE(media_session()->IsActive());
  media_session()->RebuildAndNotifyMediaPositionChanged();
}

TEST_F(MediaSessionControllerTest, SetAudioSinkId) {
  // No sink ID has been set.
  EXPECT_TRUE(media_player_->received_set_audio_sink_id().empty());

  // Set a non-default device ID.
  const std::string new_sink_id = "new sink id";
  controller_->OnSetAudioSinkId(controller_->get_player_id_for_testing(),
                                new_sink_id);
  media_player_->WaitUntilReceivedMessage();

  // The media player receives a hashed version of `new_sink_id`, which must
  // follow a specific format.
  EXPECT_FALSE(media_player_->received_set_audio_sink_id().empty());
  EXPECT_TRUE(IsValidDeviceId(media_player_->received_set_audio_sink_id()));
  EXPECT_NE(media_player_->received_set_audio_sink_id(),
            media::AudioDeviceDescription::kDefaultDeviceId);

  // Set the default device ID.
  controller_->OnSetAudioSinkId(
      controller_->get_player_id_for_testing(),
      media::AudioDeviceDescription::kDefaultDeviceId);
  media_player_->WaitUntilReceivedMessage();
  // The hashed version of the default device ID equals the unhashed version.
  EXPECT_EQ(media_player_->received_set_audio_sink_id(),
            media::AudioDeviceDescription::kDefaultDeviceId);
}

TEST_F(MediaSessionControllerTest, AutoPictureInPictureInfoChanged) {
  auto received_info = media_player_->received_auto_picture_in_picture_info();
  EXPECT_EQ(received_info.auto_pip_reason,
            media::PictureInPictureEventsInfo::AutoPipReason::kUnknown);
  EXPECT_FALSE(received_info.has_audio_focus);
  EXPECT_FALSE(received_info.is_playing);
  EXPECT_FALSE(received_info.was_recently_audible);
  EXPECT_FALSE(received_info.has_safe_url);
  EXPECT_FALSE(received_info.meets_media_engagement_conditions);
  EXPECT_FALSE(received_info.blocked_due_to_content_setting);

  const media::PictureInPictureEventsInfo::AutoPipInfo
      auto_picture_in_picture_info{
          .auto_pip_reason =
              media::PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback,
          .has_audio_focus = true,
          .is_playing = true,
          .was_recently_audible = true,
          .has_safe_url = true,
          .meets_media_engagement_conditions = true,
          .blocked_due_to_content_setting = true,
      };
  controller_->OnAutoPictureInPictureInfoChanged(
      controller_->get_player_id_for_testing(), auto_picture_in_picture_info);
  media_player_->WaitUntilReceivedMessage();
  received_info = media_player_->received_auto_picture_in_picture_info();

  EXPECT_EQ(received_info.auto_pip_reason,
            auto_picture_in_picture_info.auto_pip_reason);
  EXPECT_EQ(received_info.has_audio_focus,
            auto_picture_in_picture_info.has_audio_focus);
  EXPECT_EQ(received_info.is_playing, auto_picture_in_picture_info.is_playing);
  EXPECT_EQ(received_info.was_recently_audible,
            auto_picture_in_picture_info.was_recently_audible);
  EXPECT_EQ(received_info.has_safe_url,
            auto_picture_in_picture_info.has_safe_url);
  EXPECT_EQ(received_info.meets_media_engagement_conditions,
            auto_picture_in_picture_info.meets_media_engagement_conditions);
  EXPECT_EQ(received_info.blocked_due_to_content_setting,
            auto_picture_in_picture_info.blocked_due_to_content_setting);
}

}  // namespace content
