// 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 "content/browser/media/session/media_session_impl.h"

#include <memory>

#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/media/session/media_session_player_observer.h"
#include "content/browser/media/session/mock_media_session_player_observer.h"
#include "content/browser/media/session/mock_media_session_service_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/media_session_service.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_media_session_client.h"
#include "content/public/test/test_renderer_host.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_web_contents.h"
#include "media/base/media_content_type.h"
#include "media/base/media_switches.h"
#include "media/base/picture_in_picture_events_info.h"
#include "services/media_session/public/cpp/features.h"
#include "services/media_session/public/cpp/test/audio_focus_test_util.h"
#include "services/media_session/public/cpp/test/mock_media_session.h"
#include "services/media_session/public/mojom/audio_focus.mojom.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "third_party/blink/public/common/features.h"

using ::testing::_;
using testing::Return;

namespace content {

using media_session::mojom::AudioFocusType;
using media_session::mojom::MediaPlaybackState;
using media_session::mojom::MediaSessionAction;
using media_session::mojom::MediaSessionInfo;
using media_session::mojom::MediaSessionInfoPtr;
using media_session::test::MockMediaSessionMojoObserver;
using media_session::test::TestAudioFocusObserver;

namespace {

class MockAudioFocusDelegate : public AudioFocusDelegate {
 public:
  MockAudioFocusDelegate() = default;

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

  ~MockAudioFocusDelegate() override = default;

  void AbandonAudioFocus() override {}

  AudioFocusResult RequestAudioFocus(AudioFocusType type) override {
    request_audio_focus_count_++;
    last_requested_focus_type_ = type;
    return AudioFocusResult::kSuccess;
  }

  std::optional<AudioFocusType> GetCurrentFocusType() const override {
    return AudioFocusType::kGain;
  }

  void MediaSessionInfoChanged(
      const MediaSessionInfoPtr& session_info) override {
    session_info_ = session_info.Clone();
  }

  MOCK_CONST_METHOD0(request_id, const base::UnguessableToken&());

  MOCK_METHOD(void, ReleaseRequestId, (), (override));

  MediaSessionInfo::SessionState GetState() const {
    DCHECK(!session_info_.is_null());
    return session_info_->state;
  }

  int request_audio_focus_count() const { return request_audio_focus_count_; }

  const std::optional<AudioFocusType>& GetLastRequestedFocusType() {
    return last_requested_focus_type_;
  }

 private:
  int request_audio_focus_count_ = 0;
  std::optional<AudioFocusType> last_requested_focus_type_;

  MediaSessionInfoPtr session_info_;
};

// A mock WebContentsDelegate which listens to |ActivateContents()| calls.
class MockWebContentsDelegate : public content::WebContentsDelegate {
 public:
  // content::WebContentsDelegate:
  MOCK_METHOD(void, ActivateContents, (content::WebContents*), (override));
};

class MockContentBrowserClient : public TestContentBrowserClient {
 public:
  MOCK_METHOD(media::PictureInPictureEventsInfo::AutoPipInfo,
              GetAutoPipInfo,
              (const WebContents& web_contents),
              (const, override));
};

}  // anonymous namespace

MATCHER_P(EnterPictureInPictureReasonEquals,
          expected_reason,
          "Matches a MediaSessionActionDetails with a specific "
          "EnterPictureInPicture reason.") {
  if (!arg) {
    return false;
  }

  if (!arg->is_enter_picture_in_picture()) {
    return false;
  }

  return arg->get_enter_picture_in_picture()->reason == expected_reason;
}

class MediaSessionImplTest : public RenderViewHostTestHarness {
 public:
  MediaSessionImplTest()
      : RenderViewHostTestHarness(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
    default_actions_.insert(MediaSessionAction::kPlay);
    default_actions_.insert(MediaSessionAction::kPause);
    default_actions_.insert(MediaSessionAction::kStop);
    default_actions_.insert(MediaSessionAction::kSeekTo);
    default_actions_.insert(MediaSessionAction::kScrubTo);
    default_actions_.insert(MediaSessionAction::kSeekForward);
    default_actions_.insert(MediaSessionAction::kSeekBackward);
  }

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

  void SetUp() override {
    scoped_feature_list_.InitWithFeatures(
        {media_session::features::kMediaSessionService,
         media_session::features::kAudioFocusEnforcement,
         blink::features::kBrowserInitiatedAutomaticPictureInPicture},
        {});

    RenderViewHostTestHarness::SetUp();

    player_observer_ = std::make_unique<MockMediaSessionPlayerObserver>(
        main_rfh(), media::MediaContentType::kPersistent);
    mock_media_session_service_ =
        std::make_unique<testing::NiceMock<MockMediaSessionServiceImpl>>(
            main_rfh());

    // Connect to the Media Session service and bind |audio_focus_remote_| to
    // it.
    GetMediaSessionService().BindAudioFocusManager(
        audio_focus_remote_.BindNewPipeAndPassReceiver());
    audio_focus_remote_->SetEnforcementMode(
        media_session::mojom::EnforcementMode::kDefault);
  }

  void TearDown() override {
    mock_media_session_service_.reset();

    scoped_feature_list_.Reset();
    audio_focus_remote_->SetEnforcementMode(
        media_session::mojom::EnforcementMode::kDefault);

    RenderViewHostTestHarness::TearDown();
  }

  void RequestAudioFocus(MediaSessionImpl* session,
                         AudioFocusType audio_focus_type) {
    session->RequestSystemAudioFocus(audio_focus_type);
  }

  void AbandonAudioFocus(MediaSessionImpl* session) {
    session->AbandonSystemAudioFocusIfNeeded();
  }

  bool GetForceDuck(MediaSessionImpl* session) {
    return media_session::test::GetMediaSessionInfoSync(session)->force_duck;
  }

  MediaSessionInfo::SessionState GetState(MediaSessionImpl* session) {
    return media_session::test::GetMediaSessionInfoSync(session)->state;
  }

  void ClearObservers(MediaSessionImpl* session) {
    session->observers_.Clear();
  }

  bool HasObservers(MediaSessionImpl* session) {
    return !session->observers_.empty();
  }

  void FlushForTesting(MediaSessionImpl* session) {
    session->FlushForTesting();
  }

  std::unique_ptr<TestAudioFocusObserver> CreateAudioFocusObserver() {
    std::unique_ptr<TestAudioFocusObserver> observer =
        std::make_unique<TestAudioFocusObserver>();

    audio_focus_remote_->AddObserver(observer->BindNewPipeAndPassRemote());
    audio_focus_remote_.FlushForTesting();

    return observer;
  }

  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer_;

  void SetDelegateForTests(MediaSessionImpl* session,
                           std::unique_ptr<AudioFocusDelegate> delegate) {
    session->SetDelegateForTests(std::move(delegate));
  }

  MockMediaSessionServiceImpl& mock_media_session_service() const {
    return *mock_media_session_service_.get();
  }

  MediaSessionImpl* GetMediaSession() {
    return MediaSessionImpl::Get(web_contents());
  }

  // Returns the player ID.
  int StartNewPlayer() {
    int player_id;
    GetMediaSession()->AddPlayer(
        player_observer_.get(), player_id = player_observer_->StartNewPlayer());
    return player_id;
  }

  MockMediaSessionPlayerObserver* player_observer() {
    return player_observer_.get();
  }

  const std::set<MediaSessionAction>& default_actions() const {
    return default_actions_;
  }

  void OnVideoVisibilityChanged() {
    GetMediaSession()->OnVideoVisibilityChanged();
  }

  bool HasSufficientlyVisibleVideo() {
    return GetMediaSession()->HasSufficientlyVisibleVideo();
  }

 private:
  std::set<MediaSessionAction> default_actions_;

  base::test::ScopedFeatureList scoped_feature_list_;

  std::unique_ptr<MockMediaSessionServiceImpl> mock_media_session_service_;

  mojo::Remote<media_session::mojom::AudioFocusManager> audio_focus_remote_;
};

TEST_F(MediaSessionImplTest, SessionInfoState) {
  EXPECT_EQ(MediaSessionInfo::SessionState::kInactive,
            GetState(GetMediaSession()));

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    RequestAudioFocus(GetMediaSession(), AudioFocusType::kGain);
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);

    EXPECT_TRUE(observer.session_info().Equals(
        media_session::test::GetMediaSessionInfoSync(GetMediaSession())));
  }

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    GetMediaSession()->StartDucking();
    observer.WaitForState(MediaSessionInfo::SessionState::kDucking);
  }

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    GetMediaSession()->StopDucking();
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);

    EXPECT_TRUE(observer.session_info().Equals(
        media_session::test::GetMediaSessionInfoSync(GetMediaSession())));
  }

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
    observer.WaitForState(MediaSessionInfo::SessionState::kSuspended);

    EXPECT_TRUE(observer.session_info().Equals(
        media_session::test::GetMediaSessionInfoSync(GetMediaSession())));
  }

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    GetMediaSession()->Resume(MediaSession::SuspendType::kSystem);
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);

    EXPECT_TRUE(observer.session_info().Equals(
        media_session::test::GetMediaSessionInfoSync(GetMediaSession())));
  }

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    AbandonAudioFocus(GetMediaSession());
    observer.WaitForState(MediaSessionInfo::SessionState::kInactive);

    EXPECT_TRUE(observer.session_info().Equals(
        media_session::test::GetMediaSessionInfoSync(GetMediaSession())));
  }
}

TEST_F(MediaSessionImplTest, NotifyDelegateOnStateChange) {
  auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
  MockAudioFocusDelegate* delegate = delegate_unique.get();
  SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));

  RequestAudioFocus(GetMediaSession(), AudioFocusType::kGain);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(MediaSessionInfo::SessionState::kActive, delegate->GetState());

  GetMediaSession()->StartDucking();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking, delegate->GetState());

  GetMediaSession()->StopDucking();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(MediaSessionInfo::SessionState::kActive, delegate->GetState());

  GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(MediaSessionInfo::SessionState::kSuspended, delegate->GetState());

  GetMediaSession()->Resume(MediaSession::SuspendType::kSystem);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(MediaSessionInfo::SessionState::kActive, delegate->GetState());

  AbandonAudioFocus(GetMediaSession());
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(MediaSessionInfo::SessionState::kInactive, delegate->GetState());
}

TEST_F(MediaSessionImplTest, RegisterObserver) {
  // There is no way to get the number of mojo observers so we should just
  // remove them all and check if the mojo observers interface ptr set is
  // empty or not.
  ClearObservers(GetMediaSession());
  EXPECT_FALSE(HasObservers(GetMediaSession()));

  MockMediaSessionMojoObserver observer(*GetMediaSession());
  FlushForTesting(GetMediaSession());

  EXPECT_TRUE(HasObservers(GetMediaSession()));
}

TEST_F(MediaSessionImplTest, SessionInfo_PlaybackState) {
  EXPECT_EQ(MediaPlaybackState::kPaused,
            media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                ->playback_state);

  int player_id = player_observer_->StartNewPlayer();

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    GetMediaSession()->AddPlayer(player_observer_.get(), player_id);
    observer.WaitForPlaybackState(MediaPlaybackState::kPlaying);
  }

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    GetMediaSession()->OnPlayerPaused(player_observer_.get(), player_id);
    observer.WaitForPlaybackState(MediaPlaybackState::kPaused);
  }
}

TEST_F(MediaSessionImplTest, OnPlayerPaused_MultipleNormalPlayers) {
  MediaSessionImpl* session = GetMediaSession();
  static const base::UnguessableToken test_token =
      base::UnguessableToken::Create();
  auto mock_delegate = std::make_unique<MockAudioFocusDelegate>();
  EXPECT_CALL(*mock_delegate, request_id())
      .WillRepeatedly(testing::ReturnRef(test_token));
  EXPECT_CALL(*mock_delegate, ReleaseRequestId()).Times(testing::AnyNumber());
  SetDelegateForTests(session, std::move(mock_delegate));
  int player1 = StartNewPlayer();
  int player2 = StartNewPlayer();
  ASSERT_TRUE(session->IsActive());
  ASSERT_TRUE(session->IsControllable());
  session->OnPlayerPaused(player_observer(), player1);
  EXPECT_TRUE(session->IsActive());
  EXPECT_TRUE(session->IsControllable());
  player_observer()->SetPlaying(player2, false);
  session->OnPlayerPaused(player_observer(), player2);
  EXPECT_TRUE(session->IsSuspended());
  session->Resume(MediaSession::SuspendType::kUI);
  EXPECT_TRUE(session->IsActive());
}

TEST_F(MediaSessionImplTest, SuspendUI) {
  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(MediaSessionAction::kPause, _))
      .Times(0);

  StartNewPlayer();

  GetMediaSession()->Suspend(MediaSession::SuspendType::kUI);
  mock_media_session_service().FlushForTesting();

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForExpectedActions(default_actions());
}

TEST_F(MediaSessionImplTest, SuspendContent_WithAction) {
  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(MediaSessionAction::kPause, _))
      .Times(0);

  StartNewPlayer();
  mock_media_session_service().EnableAction(MediaSessionAction::kPause);

  GetMediaSession()->Suspend(MediaSession::SuspendType::kContent);
  mock_media_session_service().FlushForTesting();

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForExpectedActions(default_actions());
}

TEST_F(MediaSessionImplTest, SuspendSystem_WithAction) {
  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(MediaSessionAction::kPause, _))
      .Times(0);

  StartNewPlayer();
  mock_media_session_service().EnableAction(MediaSessionAction::kPause);

  GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
  mock_media_session_service().FlushForTesting();

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForExpectedActions(default_actions());
}

TEST_F(MediaSessionImplTest, SuspendUI_WithAction) {
  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(MediaSessionAction::kPause, _));

  StartNewPlayer();
  mock_media_session_service().EnableAction(MediaSessionAction::kPause);

  GetMediaSession()->Suspend(MediaSession::SuspendType::kUI);
  mock_media_session_service().FlushForTesting();

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForExpectedActions(default_actions());
}

TEST_F(MediaSessionImplTest, ResumeUI) {
  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(MediaSessionAction::kPlay, _))
      .Times(0);

  StartNewPlayer();

  GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
  GetMediaSession()->Resume(MediaSession::SuspendType::kUI);
  mock_media_session_service().FlushForTesting();

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForExpectedActions(default_actions());
}

TEST_F(MediaSessionImplTest, ResumeContent_WithAction) {
  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(MediaSessionAction::kPlay, _))
      .Times(0);

  StartNewPlayer();
  mock_media_session_service().EnableAction(MediaSessionAction::kPlay);

  GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
  GetMediaSession()->Resume(MediaSession::SuspendType::kContent);
  mock_media_session_service().FlushForTesting();

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForExpectedActions(default_actions());
}

TEST_F(MediaSessionImplTest, ResumeSystem_WithAction) {
  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(MediaSessionAction::kPlay, _))
      .Times(0);

  StartNewPlayer();
  mock_media_session_service().EnableAction(MediaSessionAction::kPlay);

  GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
  GetMediaSession()->Resume(MediaSession::SuspendType::kSystem);
  mock_media_session_service().FlushForTesting();

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForExpectedActions(default_actions());
}

TEST_F(MediaSessionImplTest, ResumeUI_WithAction) {
  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(MediaSessionAction::kPlay, _));

  StartNewPlayer();
  mock_media_session_service().EnableAction(MediaSessionAction::kPlay);

  GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
  GetMediaSession()->Resume(MediaSession::SuspendType::kUI);
  mock_media_session_service().FlushForTesting();

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForExpectedActions(default_actions());
}

#if !BUILDFLAG(IS_ANDROID)

TEST_F(MediaSessionImplTest, WebContentsDestroyed_ReleasesFocus) {
  std::unique_ptr<WebContents> web_contents(CreateTestWebContents());
  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

  {
    std::unique_ptr<TestAudioFocusObserver> observer =
        CreateAudioFocusObserver();
    RequestAudioFocus(media_session, AudioFocusType::kGain);
    observer->WaitForGainedEvent();
  }

  {
    MockMediaSessionMojoObserver observer(*media_session);
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  }

  {
    std::unique_ptr<TestAudioFocusObserver> observer =
        CreateAudioFocusObserver();
    web_contents.reset();
    observer->WaitForLostEvent();
  }
}

TEST_F(MediaSessionImplTest, WebContentsDestroyed_ReleasesTransients) {
  std::unique_ptr<WebContents> web_contents(CreateTestWebContents());
  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());

  {
    std::unique_ptr<TestAudioFocusObserver> observer =
        CreateAudioFocusObserver();
    RequestAudioFocus(media_session, AudioFocusType::kGainTransientMayDuck);
    observer->WaitForGainedEvent();
  }

  {
    MockMediaSessionMojoObserver observer(*media_session);
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  }

  {
    std::unique_ptr<TestAudioFocusObserver> observer =
        CreateAudioFocusObserver();
    web_contents.reset();
    observer->WaitForLostEvent();
  }
}

TEST_F(MediaSessionImplTest, WebContentsDestroyed_StopsDucking) {
  std::unique_ptr<WebContents> web_contents_1(CreateTestWebContents());
  MediaSessionImpl* media_session_1 =
      MediaSessionImpl::Get(web_contents_1.get());

  std::unique_ptr<WebContents> web_contents_2(CreateTestWebContents());
  MediaSessionImpl* media_session_2 =
      MediaSessionImpl::Get(web_contents_2.get());

  {
    std::unique_ptr<TestAudioFocusObserver> observer =
        CreateAudioFocusObserver();
    RequestAudioFocus(media_session_1, AudioFocusType::kGain);
    observer->WaitForGainedEvent();
  }

  {
    MockMediaSessionMojoObserver observer(*media_session_1);
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  }

  {
    std::unique_ptr<TestAudioFocusObserver> observer =
        CreateAudioFocusObserver();
    RequestAudioFocus(media_session_2, AudioFocusType::kGainTransientMayDuck);
    observer->WaitForGainedEvent();
  }

  {
    MockMediaSessionMojoObserver observer(*media_session_1);
    observer.WaitForState(MediaSessionInfo::SessionState::kDucking);
  }

  {
    std::unique_ptr<TestAudioFocusObserver> observer =
        CreateAudioFocusObserver();
    web_contents_2.reset();
    observer->WaitForLostEvent();
  }

  {
    MockMediaSessionMojoObserver observer(*media_session_1);
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  }
}

#if BUILDFLAG(IS_MAC)

TEST_F(MediaSessionImplTest, TabFocusDoesNotCauseAudioFocus) {
  auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
  MockAudioFocusDelegate* delegate = delegate_unique.get();
  SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    RequestAudioFocus(GetMediaSession(), AudioFocusType::kGain);
    FlushForTesting(GetMediaSession());
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  }

  EXPECT_EQ(1, delegate->request_audio_focus_count());
  GetMediaSession()->OnWebContentsFocused(nullptr);
  EXPECT_EQ(1, delegate->request_audio_focus_count());
}

#else  // BUILDFLAG(IS_MAC)

TEST_F(MediaSessionImplTest, RequestAudioFocus_OnFocus_Active) {
  auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
  MockAudioFocusDelegate* delegate = delegate_unique.get();
  SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    RequestAudioFocus(GetMediaSession(), AudioFocusType::kGain);
    FlushForTesting(GetMediaSession());
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  }

  EXPECT_EQ(1, delegate->request_audio_focus_count());
  GetMediaSession()->OnWebContentsFocused(nullptr);
  EXPECT_EQ(2, delegate->request_audio_focus_count());
}

TEST_F(MediaSessionImplTest, RequestAudioFocus_OnFocus_Inactive) {
  auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
  MockAudioFocusDelegate* delegate = delegate_unique.get();
  SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));
  EXPECT_EQ(MediaSessionInfo::SessionState::kInactive,
            GetState(GetMediaSession()));

  EXPECT_EQ(0, delegate->request_audio_focus_count());
  GetMediaSession()->OnWebContentsFocused(nullptr);
  EXPECT_EQ(0, delegate->request_audio_focus_count());
}

TEST_F(MediaSessionImplTest, RequestAudioFocus_OnFocus_Suspended) {
  auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
  MockAudioFocusDelegate* delegate = delegate_unique.get();
  SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    RequestAudioFocus(GetMediaSession(), AudioFocusType::kGain);
    FlushForTesting(GetMediaSession());
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  }

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
    observer.WaitForState(MediaSessionInfo::SessionState::kSuspended);
  }

  EXPECT_EQ(1, delegate->request_audio_focus_count());
  GetMediaSession()->OnWebContentsFocused(nullptr);
  EXPECT_EQ(1, delegate->request_audio_focus_count());
}

#endif  // BUILDFLAG(IS_MAC)

#endif  // !BUILDFLAG(IS_ANDROID)

TEST_F(MediaSessionImplTest, SourceId_SameBrowserContext) {
  auto other_contents = TestWebContents::Create(browser_context(), nullptr);
  MediaSessionImpl* other_session = MediaSessionImpl::Get(other_contents.get());

  EXPECT_EQ(GetMediaSession()->GetSourceId(), other_session->GetSourceId());
}

TEST_F(MediaSessionImplTest, SourceId_DifferentBrowserContext) {
  auto other_context = CreateBrowserContext();
  auto other_contents = TestWebContents::Create(other_context.get(), nullptr);
  MediaSessionImpl* other_session = MediaSessionImpl::Get(other_contents.get());

  EXPECT_NE(GetMediaSession()->GetSourceId(), other_session->GetSourceId());
}


TEST_F(MediaSessionImplTest, SessionInfoPictureInPicture) {
  WebContentsImpl* web_contents_impl =
      static_cast<WebContentsImpl*>(web_contents());

  EXPECT_EQ(
      media_session::test::GetMediaSessionInfoSync(GetMediaSession())
          ->picture_in_picture_state,
      media_session::mojom::MediaPictureInPictureState::kNotInPictureInPicture);

  web_contents_impl->SetHasPictureInPictureVideo(true);
  EXPECT_EQ(
      media_session::test::GetMediaSessionInfoSync(GetMediaSession())
          ->picture_in_picture_state,
      media_session::mojom::MediaPictureInPictureState::kInPictureInPicture);

  web_contents_impl->SetHasPictureInPictureVideo(false);
  EXPECT_EQ(
      media_session::test::GetMediaSessionInfoSync(GetMediaSession())
          ->picture_in_picture_state,
      media_session::mojom::MediaPictureInPictureState::kNotInPictureInPicture);
}

TEST_F(MediaSessionImplTest, SessionInfoAudioSink) {
  // When the session is created it should be using the default audio device.
  // When the default audio device is in use, the |audio_sink_id| attribute
  // should be unset.
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                   ->audio_sink_id.has_value());
  int player1 = player_observer_->StartNewPlayer();
  int player2 = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player1);
  GetMediaSession()->AddPlayer(player_observer_.get(), player2);
  player_observer_->SetAudioSinkId(player1, "1");
  player_observer_->SetAudioSinkId(player2, "1");

  auto info = media_session::test::GetMediaSessionInfoSync(GetMediaSession());
  ASSERT_TRUE(info->audio_sink_id.has_value());
  EXPECT_EQ(info->audio_sink_id.value(), "1");

  // If multiple audio devices are being used the audio sink id attribute should
  // be unset.
  player_observer_->SetAudioSinkId(player2, "2");
  info = media_session::test::GetMediaSessionInfoSync(GetMediaSession());
  EXPECT_FALSE(info->audio_sink_id.has_value());
}

TEST_F(MediaSessionImplTest, SessionInfoPresentation) {
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                   ->has_presentation);

  GetMediaSession()->OnPresentationsChanged(true);
  EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                  ->has_presentation);

  GetMediaSession()->OnPresentationsChanged(false);
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                   ->has_presentation);
}

TEST_F(MediaSessionImplTest, SessionInfoRemotePlaybackMetadata) {
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                   ->remote_playback_metadata);

  int player1 = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player1);
  GetMediaSession()->SetRemotePlaybackMetadata(
      media_session::mojom::RemotePlaybackMetadata::New(
          "video_codec", "audio_codec", false, true, "device_friendly_name",
          false));
  EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                  ->remote_playback_metadata);

  int player2 = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player2);

  EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                  ->remote_playback_metadata->remote_playback_disabled);
}

TEST_F(MediaSessionImplTest, RaiseActivatesWebContents) {
  MockWebContentsDelegate delegate;
  web_contents()->SetDelegate(&delegate);

  // When the WebContents has a delegate, |Raise()| should activate the
  // WebContents.
  EXPECT_CALL(delegate, ActivateContents(web_contents()));
  GetMediaSession()->Raise();
  testing::Mock::VerifyAndClearExpectations(&delegate);

  // When the WebContents does not have a delegate, |Raise()| should not crash.
  web_contents()->SetDelegate(nullptr);
  GetMediaSession()->Raise();
}

TEST_F(MediaSessionImplTest,
       RegisteredEnterPictureInPictureExposesAutoPictureInPicture) {
  // When the website has registered for 'enterpictureinpicture',
  // MediaSessionImpl should expose the kEnterPictureInPicture,
  // kEnterAutoPictureInPicture, and kExitPictureInPicture to the internal
  // MediaSession service.
  StartNewPlayer();
  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  mock_media_session_service().EnableAction(
      MediaSessionAction::kEnterPictureInPicture);
  mock_media_session_service().FlushForTesting();

  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterPictureInPicture));
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kExitPictureInPicture));
}

TEST_F(MediaSessionImplTest, WebContentsHasPictureInPictureVideo) {
  WebContentsImpl* web_contents_impl =
      static_cast<WebContentsImpl*>(web_contents());
  web_contents_impl->SetHasPictureInPictureVideo(true);

  StartNewPlayer();
  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  mock_media_session_service().EnableAction(MediaSessionAction::kPause);
  mock_media_session_service().FlushForTesting();

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterPictureInPicture));
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kExitPictureInPicture));
}

TEST_F(MediaSessionImplTest, WebContentsHasPictureInPictureDocument) {
  WebContentsImpl* web_contents_impl =
      static_cast<WebContentsImpl*>(web_contents());
  web_contents_impl->SetHasPictureInPictureDocument(true);

  StartNewPlayer();
  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  mock_media_session_service().EnableAction(MediaSessionAction::kPause);
  mock_media_session_service().FlushForTesting();

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterPictureInPicture));
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kExitPictureInPicture));
}

TEST_F(MediaSessionImplTest, SufficientlyVisibleVideo_NoPlayer) {
  OnVideoVisibilityChanged();

  EXPECT_FALSE(HasSufficientlyVisibleVideo());
}

TEST_F(MediaSessionImplTest, SufficientlyVisibleVideo_MultiplePlayers) {
  // Start with a single player with a video reporting as sufficiently visible.
  int player1 = player_observer_->StartNewPlayer();
  player_observer_->SetHasSufficientlyVisibleVideo(player1, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player1);

  OnVideoVisibilityChanged();
  EXPECT_TRUE(HasSufficientlyVisibleVideo());

  // Add a second player with with a video reporting as sufficiently visible,
  // and make player1 report that its video is not sufficiently visible.
  int player2 = player_observer_->StartNewPlayer();
  player_observer_->SetHasSufficientlyVisibleVideo(player2, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player2);

  player_observer_->SetHasSufficientlyVisibleVideo(player1, false);

  OnVideoVisibilityChanged();
  EXPECT_TRUE(HasSufficientlyVisibleVideo());

  // Make player2 report that its video is not sufficiently visible.
  player_observer_->SetHasSufficientlyVisibleVideo(player2, false);

  OnVideoVisibilityChanged();
  EXPECT_FALSE(HasSufficientlyVisibleVideo());
}

TEST_F(MediaSessionImplTest, SessionInfoDontHideMetadataByDefault) {
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                   ->hide_metadata);
}

TEST_F(MediaSessionImplTest, PausedPlayersDoNotRequestFocus) {
  // If a player is paused when it's added, it should be controllable but should
  // not request audio focus.
  auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
  MockAudioFocusDelegate* delegate = delegate_unique.get();
  SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));
  int player_id = StartNewPlayer();
  EXPECT_TRUE(GetMediaSession()->IsActive());
  EXPECT_TRUE(GetMediaSession()->IsControllable());
  EXPECT_EQ(delegate->request_audio_focus_count(), 1);
  player_observer()->SetPlaying(player_id, false);
  // Remember that the player still has the audio focus.  Re-adding the paused
  // player should neither lose the focus, nor re-request it.
  GetMediaSession()->AddPlayer(player_observer(), player_id);
  EXPECT_EQ(delegate->request_audio_focus_count(), 1);
  EXPECT_TRUE(GetMediaSession()->IsActive());

  // Give up audio focus.
  GetMediaSession()->RemovePlayer(player_observer(), player_id);
  EXPECT_FALSE(GetMediaSession()->IsActive());
  EXPECT_FALSE(GetMediaSession()->IsControllable());

  // Adding should now result in an inactive session that is controllable,
  // without requesting focus again.
  GetMediaSession()->AddPlayer(player_observer(), player_id);
  EXPECT_EQ(delegate->request_audio_focus_count(), 1);
  EXPECT_FALSE(GetMediaSession()->IsActive());
  EXPECT_TRUE(GetMediaSession()->IsControllable());
}

TEST_F(MediaSessionImplTest, SeekingAndScrubbingNotAllowedWithMaxDuration) {
  MockMediaSessionMojoObserver observer(*GetMediaSession());
  int player_id = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player_id);

  media_session::MediaPosition pos;
  pos = media_session::MediaPosition(
      /*playback_rate=*/1.0,
      /*duration=*/base::TimeDelta::Max(),
      /*position=*/base::TimeDelta(), /*end_of_media=*/false);

  player_observer_->SetPosition(player_id, pos);
  GetMediaSession()->RebuildAndNotifyMediaPositionChanged();
  FlushForTesting(GetMediaSession());

  // With a max duration, we should be considered live media and should not
  // allow seeking and scrubbing actions by default.
  EXPECT_FALSE(base::Contains(observer.actions(), MediaSessionAction::kSeekTo));
  EXPECT_FALSE(
      base::Contains(observer.actions(), MediaSessionAction::kScrubTo));
  EXPECT_FALSE(
      base::Contains(observer.actions(), MediaSessionAction::kSeekForward));
  EXPECT_FALSE(
      base::Contains(observer.actions(), MediaSessionAction::kSeekBackward));

  // However, if the website explicitly supports the action, then we will still
  // route it.
  mock_media_session_service().EnableAction(MediaSessionAction::kSeekTo);
  FlushForTesting(GetMediaSession());

  EXPECT_TRUE(base::Contains(observer.actions(), MediaSessionAction::kSeekTo));
  EXPECT_FALSE(
      base::Contains(observer.actions(), MediaSessionAction::kScrubTo));
  EXPECT_FALSE(
      base::Contains(observer.actions(), MediaSessionAction::kSeekForward));
  EXPECT_FALSE(
      base::Contains(observer.actions(), MediaSessionAction::kSeekBackward));
}

TEST_F(MediaSessionImplTest, AmbientPlayerFocusRequest) {
  auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
  MockAudioFocusDelegate* delegate = delegate_unique.get();
  SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));

  int player_id = player_observer_->StartNewPlayer();

  player_observer_->SetMediaContentType(media::MediaContentType::kAmbient);
  MockMediaSessionMojoObserver observer(*GetMediaSession());
  GetMediaSession()->AddPlayer(player_observer_.get(), player_id);

#if BUILDFLAG(IS_ANDROID)
  // On Android, ambient players should not request audio focus.
  observer.WaitForState(MediaSessionInfo::SessionState::kInactive);
  EXPECT_FALSE(delegate->GetLastRequestedFocusType().has_value());
#else
  // On other platforms, an ambient player should request ambient focus.
  observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  EXPECT_TRUE(delegate->GetLastRequestedFocusType().has_value());
  EXPECT_EQ(AudioFocusType::kAmbient, *delegate->GetLastRequestedFocusType());

  // Ambient players should also receive volume multiplier updates.
  constexpr float kDuckingMultiplier = 0.1;
  EXPECT_EQ(player_observer_->GetVolumeMultiplier(player_id), 1.0);
  GetMediaSession()->SetDuckingVolumeMultiplier(kDuckingMultiplier);
  GetMediaSession()->StartDucking();
  EXPECT_EQ(player_observer_->GetVolumeMultiplier(player_id),
            kDuckingMultiplier);
  GetMediaSession()->StopDucking();
  EXPECT_EQ(player_observer_->GetVolumeMultiplier(player_id), 1.0);
#endif  // BUILDFLAG(IS_ANDROID)
}

TEST_F(MediaSessionImplTest, AmbientPlayerDoesNotRequestFocusWhenSuspended) {
  auto delegate_unique = std::make_unique<MockAudioFocusDelegate>();
  SetDelegateForTests(GetMediaSession(), std::move(delegate_unique));

  int persistent_player_id = player_observer_->StartNewPlayer();

  MockMediaSessionPlayerObserver ambient_player_observer(
      main_rfh(), media::MediaContentType::kAmbient);
  int ambient_player_id = ambient_player_observer.StartNewPlayer();

  // Add a persistent player to get audio focus.
  MockMediaSessionMojoObserver observer(*GetMediaSession());
  GetMediaSession()->AddPlayer(player_observer_.get(), persistent_player_id);
  observer.WaitForState(MediaSessionInfo::SessionState::kActive);

  // Suspend the persistent player.
  GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem);
  observer.WaitForState(MediaSessionInfo::SessionState::kSuspended);

  // Adding an ambient player should not change the audio focus state.
  GetMediaSession()->AddPlayer(&ambient_player_observer, ambient_player_id);
  observer.WaitForState(MediaSessionInfo::SessionState::kSuspended);
}

TEST_F(MediaSessionImplTest, AutoPictureInPictureInfoChanged) {
  EXPECT_EQ(
      0,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  mock_media_session_service().EnableAction(
      MediaSessionAction::kEnterPictureInPicture);
  mock_media_session_service().FlushForTesting();

  int player = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player);
  GetMediaSession()->EnterAutoPictureInPicture();

  EXPECT_EQ(
      1,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());
}

TEST_F(MediaSessionImplTest,
       DoesNotEntersBrowserInitiatedAutoPip_MoreThanOnePlayer) {
  MockMediaSessionMojoObserver observer(*GetMediaSession());
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());

  int player1 = player_observer_->StartNewPlayer();
  player_observer_->SetIsPictureInPictureAvailable(player1, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player1);
  observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  FlushForTesting(GetMediaSession());

  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));

  int player2 = player_observer_->StartNewPlayer();
  player_observer_->SetIsPictureInPictureAvailable(player2, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player2);
  observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(GetMediaSession()->ShouldRouteAction(
      MediaSessionAction::kEnterPictureInPicture));
  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));

  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
}

TEST_F(MediaSessionImplTest,
       DoesNotEntersBrowserInitiatedAutoPip_SinglePlayerNotPlaying) {
  MockMediaSessionMojoObserver observer(*GetMediaSession());
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());

  int player = player_observer_->StartNewPlayer(/*is_playing=*/false);
  player_observer_->SetIsPictureInPictureAvailable(player, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player);
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(GetMediaSession()->ShouldRouteAction(
      MediaSessionAction::kEnterPictureInPicture));
  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));

  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
}

TEST_F(MediaSessionImplTest,
       DoesNotEnterBrowserInitiatedAutoPip_PlayerInSubframe) {
  // Create a subframe.
  auto* parent_host_tester = content::RenderFrameHostTester::For(main_rfh());
  parent_host_tester->InitializeRenderFrameIfNeeded();
  content::RenderFrameHost* sub_frame =
      parent_host_tester->AppendChild("child");
  ASSERT_TRUE(sub_frame);

  // Create a player observer associated with the subframe.
  player_observer_ = std::make_unique<MockMediaSessionPlayerObserver>(
      sub_frame, media::MediaContentType::kPersistent);

  // Start a playing player with picture-in-picture available.
  int player_id = player_observer_->StartNewPlayer(/*is_playing=*/true);
  player_observer_->SetIsPictureInPictureAvailable(player_id, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player_id);

  // Verify that kEnterAutoPictureInPicture action is not available.
  MockMediaSessionMojoObserver observer(*GetMediaSession());
  FlushForTesting(GetMediaSession());
  EXPECT_FALSE(GetMediaSession()->ShouldRouteAction(
      MediaSessionAction::kEnterPictureInPicture));
  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));

  // Attempt to enter automatic picture-in-picture and verify that
  // OnEnterPictureInPicture was not called.
  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
}

TEST_F(MediaSessionImplTest,
       EntersBrowserInitiatedAutoPip_SinglePlayerPlaying) {
  MockMediaSessionMojoObserver observer(*GetMediaSession());
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());

  int player = player_observer_->StartNewPlayer();
  player_observer_->SetIsPictureInPictureAvailable(player, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player);
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(GetMediaSession()->ShouldRouteAction(
      MediaSessionAction::kEnterPictureInPicture));
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));

  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(1, player_observer_->received_enter_picture_in_picture_calls());
}

TEST_F(MediaSessionImplTest,
       DoesNotEnterBrowserInitiatedAutoPip_WhenUsingCamera) {
  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());

  int player = player_observer_->StartNewPlayer();
  player_observer_->SetIsPictureInPictureAvailable(player, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player);
  FlushForTesting(GetMediaSession());

  // With no camera/microphone usage, auto-pip should be possible.
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));

  // Set camera state to `kTurnedOn`.
  mock_media_session_service().SetCameraState(
      media_session::mojom::CameraState::kTurnedOn);
  FlushForTesting(GetMediaSession());

  // Auto-pip should not be possible.
  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());

  // Set camera state back to Unknown.
  mock_media_session_service().SetCameraState(
      media_session::mojom::CameraState::kUnknown);
  FlushForTesting(GetMediaSession());

  // Auto-pip should be possible.
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));
  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(1, player_observer_->received_enter_picture_in_picture_calls());
}

TEST_F(MediaSessionImplTest,
       DoesNotEnterBrowserInitiatedAutoPip_WhenUsingMicrophone) {
  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());

  int player = player_observer_->StartNewPlayer();
  player_observer_->SetIsPictureInPictureAvailable(player, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player);
  FlushForTesting(GetMediaSession());

  // With no camera/microphone usage, auto-pip should be possible.
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));

  // Set microphone state to `kUnmuted`.
  mock_media_session_service().SetMicrophoneState(
      media_session::mojom::MicrophoneState::kUnmuted);
  FlushForTesting(GetMediaSession());

  // Auto-pip should not be possible.
  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());

  // Set microphone state back to `kUnknown`.
  mock_media_session_service().SetMicrophoneState(
      media_session::mojom::MicrophoneState::kUnknown);
  FlushForTesting(GetMediaSession());

  // Auto-pip should be possible.
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));
  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(1, player_observer_->received_enter_picture_in_picture_calls());
}

TEST_F(MediaSessionImplTest,
       DoesNotEnterBrowserInitiatedAutoPip_WhenUsingCameraAndMicrophone) {
  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());

  int player = player_observer_->StartNewPlayer();
  player_observer_->SetIsPictureInPictureAvailable(player, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player);
  FlushForTesting(GetMediaSession());

  // With no camera/microphone usage, auto-pip should be possible.
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));

  // Set camera and microphone state to `kTurnedOn`/`kUnmuted`.
  mock_media_session_service().SetCameraState(
      media_session::mojom::CameraState::kTurnedOn);
  mock_media_session_service().SetMicrophoneState(
      media_session::mojom::MicrophoneState::kUnmuted);
  FlushForTesting(GetMediaSession());

  // Auto-pip should not be possible.
  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());

  // Set camera and microphone state back to `kUnknown`.
  mock_media_session_service().SetCameraState(
      media_session::mojom::CameraState::kUnknown);
  mock_media_session_service().SetMicrophoneState(
      media_session::mojom::MicrophoneState::kUnknown);
  FlushForTesting(GetMediaSession());

  // Auto-pip should be possible.
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));
  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(1, player_observer_->received_enter_picture_in_picture_calls());
}

TEST_F(MediaSessionImplTest,
       DoesNotEnterBrowserInitiatedAutoPip_WhenPictureInPictureNotAvailable) {
  MockMediaSessionMojoObserver observer(*GetMediaSession());
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                   ->can_enter_browser_initiated_autopip);

  int player = player_observer_->StartNewPlayer();
  player_observer_->SetIsPictureInPictureAvailable(player, false);
  GetMediaSession()->AddPlayer(player_observer_.get(), player);
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                   ->can_enter_browser_initiated_autopip);

  GetMediaSession()->EnterAutoPictureInPicture();
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
}

TEST_F(
    MediaSessionImplTest,
    CanEnterBrowserInitiatedAutoPipIsFalse_WhenEnterPictureInPictureIsPresent) {
  MockMediaSessionMojoObserver observer(*GetMediaSession());
  FlushForTesting(GetMediaSession());

  EXPECT_FALSE(base::Contains(observer.actions(),
                              MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_EQ(0, player_observer_->received_enter_picture_in_picture_calls());
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                   ->can_enter_browser_initiated_autopip);

  int player = player_observer_->StartNewPlayer();
  player_observer_->SetIsPictureInPictureAvailable(player, true);
  GetMediaSession()->AddPlayer(player_observer_.get(), player);
  FlushForTesting(GetMediaSession());

  // With no action handler, `can_enter_browser_initiated_autopip` should be
  // true.
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                  ->can_enter_browser_initiated_autopip);

  // Enable the enterpictureinpicture action handler.
  mock_media_session_service().EnableAction(
      MediaSessionAction::kEnterPictureInPicture);
  FlushForTesting(GetMediaSession());

  // With the enterpictureinpicture action handler enabled,
  // `can_enter_browser_initiated_autopip` should be false.
  EXPECT_TRUE(base::Contains(observer.actions(),
                             MediaSessionAction::kEnterAutoPictureInPicture));
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                   ->can_enter_browser_initiated_autopip);
}

TEST_F(MediaSessionImplTest,
       ReportAutoPictureInPictureInfoChanged_Deduplicates) {
  int player = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player);

  MockContentBrowserClient mock_client;
  ContentBrowserClient* old_client = SetBrowserClientForTesting(&mock_client);

  media::PictureInPictureEventsInfo::AutoPipInfo info;
  info.auto_pip_reason =
      media::PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback;
  EXPECT_CALL(mock_client, GetAutoPipInfo(_)).WillRepeatedly(Return(info));

  // The first call should notify.
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      1,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // A second call with the same info should not.
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      1,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // A call with different info should notify.
  info.is_playing = true;
  EXPECT_CALL(mock_client, GetAutoPipInfo(_)).WillRepeatedly(Return(info));
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      2,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // A call with the same info again should not.
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      2,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  SetBrowserClientForTesting(old_client);
}

TEST_F(MediaSessionImplTest,
       ReportAutoPictureInPictureInfoChanged_ClearedOnCrossDocumentNavigate) {
  int player = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player);

  MockContentBrowserClient mock_client;
  ContentBrowserClient* old_client = SetBrowserClientForTesting(&mock_client);

  media::PictureInPictureEventsInfo::AutoPipInfo info;
  info.auto_pip_reason =
      media::PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback;
  EXPECT_CALL(mock_client, GetAutoPipInfo(_)).WillRepeatedly(Return(info));

  // The first call should notify.
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      1,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // Navigate to a new document, which should clear the cached info.
  NavigateAndCommit(GURL("https://example.com/foo"));

  // The info is the same, but since we navigated, it should be sent again.
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      2,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  SetBrowserClientForTesting(old_client);
}

TEST_F(MediaSessionImplTest,
       ReportAutoPictureInPictureInfoChanged_NotClearedOnSameDocumentNavigate) {
  // Navigate to an initial page.
  NavigateAndCommit(GURL("https://example.com/"));

  int player = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player);

  MockContentBrowserClient mock_client;
  ContentBrowserClient* old_client = SetBrowserClientForTesting(&mock_client);

  media::PictureInPictureEventsInfo::AutoPipInfo info;
  info.auto_pip_reason =
      media::PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback;
  EXPECT_CALL(mock_client, GetAutoPipInfo(_)).WillRepeatedly(Return(info));

  // The first call should notify.
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      1,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // Navigate to a same-document fragment, which should not clear the cached
  // info.
  NavigateAndCommit(GURL("https://example.com/#foo"));

  // The info is the same, and since we did a same-document navigation, it
  // should not be sent again.
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      1,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  SetBrowserClientForTesting(old_client);
}

TEST_F(MediaSessionImplTest, NewPlayerReceivesAutoPictureInPictureInfo) {
  // Add a player. It should not receive any info yet.
  int player1 = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player1);
  EXPECT_EQ(
      0,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // Set up the mock client to return some auto-pip info.
  MockContentBrowserClient mock_client;
  ContentBrowserClient* old_client = SetBrowserClientForTesting(&mock_client);

  media::PictureInPictureEventsInfo::AutoPipInfo info;
  info.auto_pip_reason =
      media::PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback;
  EXPECT_CALL(mock_client, GetAutoPipInfo(_)).WillRepeatedly(Return(info));

  // Report the info change. The first player should be notified.
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      1,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // Add a second player. It should be notified immediately with the cached
  // info.
  int player2 = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player2);
  EXPECT_EQ(
      2,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  SetBrowserClientForTesting(old_client);
}

TEST_F(MediaSessionImplTest, NewPlayerReceivesAutoPictureInPictureInfoOnce) {
  MockContentBrowserClient mock_client;
  ContentBrowserClient* old_client = SetBrowserClientForTesting(&mock_client);

  media::PictureInPictureEventsInfo::AutoPipInfo info;
  info.auto_pip_reason =
      media::PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback;
  EXPECT_CALL(mock_client, GetAutoPipInfo(_)).WillRepeatedly(Return(info));

  // Report an info change. There are no players, so nothing should happen.
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      0,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // Add a player. It should be notified immediately with the cached info.
  int player1 = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player1);
  EXPECT_EQ(
      1,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // Add the same player again. It should not be notified again.
  GetMediaSession()->AddPlayer(player_observer_.get(), player1);
  EXPECT_EQ(
      1,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // Report a change. The existing player should be notified.
  info.is_playing = true;
  EXPECT_CALL(mock_client, GetAutoPipInfo(_)).WillRepeatedly(Return(info));
  GetMediaSession()->ReportAutoPictureInPictureInfoChanged();
  EXPECT_EQ(
      2,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  // Remove the player and add it back. It should be notified again.
  GetMediaSession()->RemovePlayer(player_observer_.get(), player1);
  GetMediaSession()->AddPlayer(player_observer_.get(), player1);
  EXPECT_EQ(
      3,
      player_observer_->received_auto_picture_in_picture_info_changed_calls());

  SetBrowserClientForTesting(old_client);
}

TEST_F(MediaSessionImplTest, EnterPictureInPictureWithReason) {
  StartNewPlayer();
  mock_media_session_service().EnableAction(
      MediaSessionAction::kEnterPictureInPicture);

  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(
                  MediaSessionAction::kEnterPictureInPicture,
                  EnterPictureInPictureReasonEquals(
                      blink::mojom::MediaSessionEnterPictureInPictureReason::
                          kUserAction)));

  GetMediaSession()->EnterPictureInPicture();
  mock_media_session_service().FlushForTesting();
}

TEST_F(MediaSessionImplTest, EnterAutoPictureInPictureWithReason) {
  StartNewPlayer();
  mock_media_session_service().EnableAction(
      MediaSessionAction::kEnterPictureInPicture);

  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(
                  MediaSessionAction::kEnterPictureInPicture,
                  EnterPictureInPictureReasonEquals(
                      blink::mojom::MediaSessionEnterPictureInPictureReason::
                          kContentOccluded)));

  GetMediaSession()->EnterAutoPictureInPicture();
  mock_media_session_service().FlushForTesting();
}

class MediaSessionImplWithMediaSessionClientTest : public MediaSessionImplTest {
 protected:
  TestMediaSessionClient client_;
};

TEST_F(MediaSessionImplWithMediaSessionClientTest,
       SessionInfoDontHideMetadata) {
  client_.SetShouldHideMetadata(false);
  EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                   ->hide_metadata);
}

TEST_F(MediaSessionImplWithMediaSessionClientTest, SessionInfoHideMetadata) {
  client_.SetShouldHideMetadata(true);
  EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(GetMediaSession())
                  ->hide_metadata);
}

// Tests for throttling duration updates.
// TODO (jazzhsu): Remove these tests once media session supports livestream.
class MediaSessionImplDurationThrottleTest : public MediaSessionImplTest {
 public:
  void SetUp() override {
    MediaSessionImplTest::SetUp();
    GetMediaSession()->SetShouldThrottleDurationUpdateForTest(true);
  }

  void FastForwardBy(base::TimeDelta delay) {
    task_environment()->FastForwardBy(delay);
  }

  int GetDurationUpdateMaxAllowance() {
    return MediaSessionImpl::kDurationUpdateMaxAllowance;
  }

  base::TimeDelta GetDurationUpdateAllowanceIncreaseInterval() {
    return MediaSessionImpl::kDurationUpdateAllowanceIncreaseInterval;
  }
};

TEST_F(MediaSessionImplDurationThrottleTest, ThrottleDurationUpdate) {
  MockMediaSessionMojoObserver observer(*GetMediaSession());
  int player_id = player_observer_->StartNewPlayer();
  GetMediaSession()->AddPlayer(player_observer_.get(), player_id);

  media_session::MediaPosition pos;
  for (int duration = 0; duration <= GetDurationUpdateMaxAllowance();
       ++duration) {
    pos = media_session::MediaPosition(
        /*playback_rate=*/0.0,
        /*duration=*/base::Seconds(duration),
        /*position=*/base::TimeDelta(), /*end_of_media=*/false);

    player_observer_->SetPosition(player_id, pos);
    GetMediaSession()->RebuildAndNotifyMediaPositionChanged();
    FlushForTesting(GetMediaSession());

    if (duration == GetDurationUpdateMaxAllowance()) {
      // Last update should be throttle and marked as +INF duration.
      EXPECT_EQ(**observer.session_position(),
                media_session::MediaPosition(
                    /*playback_rate=*/0.0,
                    /*duration=*/base::TimeDelta::Max(),
                    /*position=*/base::TimeDelta(), /*end_of_media=*/false));

      // Since we're now considered live, the seeking and scrubbing actions
      // should no longer be available.
      EXPECT_FALSE(
          base::Contains(observer.actions(), MediaSessionAction::kSeekTo));
      EXPECT_FALSE(
          base::Contains(observer.actions(), MediaSessionAction::kScrubTo));
      EXPECT_FALSE(
          base::Contains(observer.actions(), MediaSessionAction::kSeekForward));
      EXPECT_FALSE(base::Contains(observer.actions(),
                                  MediaSessionAction::kSeekBackward));
    } else {
      EXPECT_EQ(**observer.session_position(), pos);

      // If we're not considered live, then the seeking and scrubbing actions
      // should still be available.
      EXPECT_TRUE(
          base::Contains(observer.actions(), MediaSessionAction::kSeekTo));
      EXPECT_TRUE(
          base::Contains(observer.actions(), MediaSessionAction::kScrubTo));
      EXPECT_TRUE(
          base::Contains(observer.actions(), MediaSessionAction::kSeekForward));
      EXPECT_TRUE(base::Contains(observer.actions(),
                                 MediaSessionAction::kSeekBackward));
    }
  }

  // Media session should send last throttled duration update after certain
  // delay.
  FastForwardBy(GetDurationUpdateAllowanceIncreaseInterval());
  FlushForTesting(GetMediaSession());
  EXPECT_EQ(**observer.session_position(), pos);
}

TEST_F(MediaSessionImplDurationThrottleTest, ThrottleResetOnPlayerChange) {
  MockMediaSessionMojoObserver observer(*GetMediaSession());

  // Duration updates caused by player switch should not be throttled.
  media_session::MediaPosition pos;
  for (int duration = 0; duration <= GetDurationUpdateMaxAllowance();
       ++duration) {
    int player_id = player_observer_->StartNewPlayer();
    GetMediaSession()->AddPlayer(player_observer_.get(), player_id);

    pos = media_session::MediaPosition(
        /*playback_rate=*/0.0,
        /*duration=*/base::Seconds(duration),
        /*position=*/base::TimeDelta(), /*end_of_media=*/false);

    player_observer_->SetPosition(player_id, pos);
    GetMediaSession()->RebuildAndNotifyMediaPositionChanged();
    FlushForTesting(GetMediaSession());
    EXPECT_EQ(**observer.session_position(), pos);

    GetMediaSession()->RemovePlayer(player_observer_.get(), player_id);
  }
}

}  // namespace content