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

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

#include <memory>

#include "base/command_line.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/common/service_manager_connection.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_service_manager_context.h"
#include "media/base/media_content_type.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_request.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/constants.mojom.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "services/service_manager/public/cpp/connector.h"

namespace content {

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

namespace {

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

  void AbandonAudioFocus() override {}

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

  base::Optional<AudioFocusType> GetCurrentFocusType() const override {
    return AudioFocusType::kGain;
  }

  void MediaSessionInfoChanged(MediaSessionInfoPtr session_info) override {
    session_info_ = std::move(session_info);
  }

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

  int request_audio_focus_count() const { return request_audio_focus_count_; }

 private:
  int request_audio_focus_count_ = 0;

  MediaSessionInfoPtr session_info_;

  DISALLOW_COPY_AND_ASSIGN(MockAudioFocusDelegate);
};

}  // anonymous namespace

class MediaSessionImplTest : public RenderViewHostTestHarness {
 public:
  MediaSessionImplTest() {
    default_actions_.insert(media_session::mojom::MediaSessionAction::kPlay);
    default_actions_.insert(media_session::mojom::MediaSessionAction::kPause);
    default_actions_.insert(media_session::mojom::MediaSessionAction::kStop);
  }

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

    RenderViewHostTestHarness::SetUp();

    player_observer_.reset(new MockMediaSessionPlayerObserver(main_rfh()));
    mock_media_session_service_.reset(
        new testing::NiceMock<MockMediaSessionServiceImpl>(main_rfh()));

    // Connect to the Media Session service and bind |audio_focus_ptr_| to it.
    service_manager_context_ = std::make_unique<TestServiceManagerContext>();
    service_manager::Connector* connector =
        ServiceManagerConnection::GetForProcess()->GetConnector();
    connector->BindInterface(media_session::mojom::kServiceName,
                             mojo::MakeRequest(&audio_focus_ptr_));
  }

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

    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;
  }

  bool HasMojoObservers(MediaSessionImpl* session) {
    return !session->mojo_observers_.empty();
  }

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

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

    media_session::mojom::AudioFocusObserverPtr observer_ptr;
    observer->BindToMojoRequest(mojo::MakeRequest(&observer_ptr));
    audio_focus_ptr_->AddObserver(std::move(observer_ptr));
    audio_focus_ptr_.FlushForTesting();

    return observer;
  }

  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer_;

  void SetDelegateForTests(MediaSessionImpl* session,
                           AudioFocusDelegate* delegate) {
    session->SetDelegateForTests(base::WrapUnique(delegate));
  }

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

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

  void StartNewPlayer() {
    GetMediaSession()->AddPlayer(player_observer_.get(),
                                 player_observer_->StartNewPlayer(),
                                 media::MediaContentType::Persistent);
  }

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

 private:
  std::set<media_session::mojom::MediaSessionAction> default_actions_;

  base::test::ScopedFeatureList scoped_feature_list_;

  std::unique_ptr<MockMediaSessionServiceImpl> mock_media_session_service_;

  media_session::mojom::AudioFocusManagerPtr audio_focus_ptr_;

  std::unique_ptr<TestServiceManagerContext> service_manager_context_;

  DISALLOW_COPY_AND_ASSIGN(MediaSessionImplTest);
};

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

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

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

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

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

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

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

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

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

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

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

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

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

TEST_F(MediaSessionImplTest, NotifyDelegateOnStateChange) {
  MockAudioFocusDelegate* delegate = new MockAudioFocusDelegate();
  SetDelegateForTests(GetMediaSession(), delegate);

  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, PepperForcesDuckAndRequestsFocus) {
  int player_id = player_observer_->StartNewPlayer();

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    GetMediaSession()->AddPlayer(player_observer_.get(), player_id,
                                 media::MediaContentType::Pepper);
    observer.WaitForState(MediaSessionInfo::SessionState::kActive);
  }

  EXPECT_TRUE(GetForceDuck(GetMediaSession()));

  {
    MockMediaSessionMojoObserver observer(*GetMediaSession());
    GetMediaSession()->RemovePlayer(player_observer_.get(), player_id);
    observer.WaitForState(MediaSessionInfo::SessionState::kInactive);
  }

  EXPECT_FALSE(GetForceDuck(GetMediaSession()));
}

TEST_F(MediaSessionImplTest, RegisterMojoObserver) {
  EXPECT_FALSE(HasMojoObservers(GetMediaSession()));

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

  EXPECT_TRUE(HasMojoObservers(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,
                                 media::MediaContentType::Persistent);
    observer.WaitForPlaybackState(MediaPlaybackState::kPlaying);
  }

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

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

  StartNewPlayer();

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

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForActions();
  EXPECT_EQ(default_actions(), observer.actions_set());
}

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

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

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

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForActions();
  EXPECT_EQ(default_actions(), observer.actions_set());
}

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

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

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

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForActions();
  EXPECT_EQ(default_actions(), observer.actions_set());
}

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

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

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

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForActions();
  EXPECT_EQ(default_actions(), observer.actions_set());
}

TEST_F(MediaSessionImplTest, ResumeUI) {
  EXPECT_CALL(mock_media_session_service().mock_client(),
              DidReceiveAction(media_session::mojom::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.WaitForActions();
  EXPECT_EQ(default_actions(), observer.actions_set());
}

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

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

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

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForActions();
  EXPECT_EQ(default_actions(), observer.actions_set());
}

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

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

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

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForActions();
  EXPECT_EQ(default_actions(), observer.actions_set());
}

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

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

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

  media_session::test::MockMediaSessionMojoObserver observer(
      *GetMediaSession());
  observer.WaitForActions();
  EXPECT_EQ(default_actions(), observer.actions_set());
}

#if !defined(OS_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 defined(OS_MACOSX)

TEST_F(MediaSessionImplTest, TabFocusDoesNotCauseAudioFocus) {
  MockAudioFocusDelegate* delegate = new MockAudioFocusDelegate();
  SetDelegateForTests(GetMediaSession(), delegate);

  {
    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  // defined(OS_MACOSX)

TEST_F(MediaSessionImplTest, RequestAudioFocus_OnFocus_Active) {
  MockAudioFocusDelegate* delegate = new MockAudioFocusDelegate();
  SetDelegateForTests(GetMediaSession(), delegate);

  {
    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) {
  MockAudioFocusDelegate* delegate = new MockAudioFocusDelegate();
  SetDelegateForTests(GetMediaSession(), delegate);
  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) {
  MockAudioFocusDelegate* delegate = new MockAudioFocusDelegate();
  SetDelegateForTests(GetMediaSession(), delegate);

  {
    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  // defined(OS_MACOSX)

#endif  // !defined(OS_ANDROID)

}  // namespace content
