| // Copyright 2019 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/media_keys_listener_manager_impl.h" |
| |
| #include "base/containers/flat_set.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "content/browser/browser_main_loop.h" |
| #include "content/browser/media/hardware_key_media_controller.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "media/base/media_switches.h" |
| #include "services/media_session/public/cpp/test/test_media_controller.h" |
| #include "services/media_session/public/mojom/media_session.mojom.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/base/accelerators/media_keys_listener.h" |
| |
| namespace content { |
| |
| using media_session::mojom::MediaPlaybackState; |
| using media_session::mojom::MediaSessionAction; |
| using media_session::mojom::MediaSessionInfo; |
| using media_session::mojom::MediaSessionInfoPtr; |
| using media_session::test::TestMediaController; |
| |
| namespace { |
| |
| class MockMediaKeysListener : public ui::MediaKeysListener { |
| public: |
| explicit MockMediaKeysListener(ui::MediaKeysListener::Delegate* delegate) |
| : delegate_(delegate) {} |
| ~MockMediaKeysListener() override = default; |
| |
| // MediaKeysListener implementation. |
| bool StartWatchingMediaKey(ui::KeyboardCode key_code) override { |
| key_codes_.insert(key_code); |
| return true; |
| } |
| void StopWatchingMediaKey(ui::KeyboardCode key_code) override { |
| key_codes_.erase(key_code); |
| } |
| |
| void SimulateAccelerator(ui::Accelerator accelerator) { |
| if (IsWatching(accelerator.key_code())) |
| delegate_->OnMediaKeysAccelerator(accelerator); |
| } |
| |
| bool IsWatching(ui::KeyboardCode key_code) { |
| return key_codes_.contains(key_code); |
| } |
| |
| private: |
| ui::MediaKeysListener::Delegate* delegate_; |
| base::flat_set<ui::KeyboardCode> key_codes_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockMediaKeysListener); |
| }; |
| |
| class MockMediaKeysListenerDelegate : public ui::MediaKeysListener::Delegate { |
| public: |
| MockMediaKeysListenerDelegate() = default; |
| ~MockMediaKeysListenerDelegate() override = default; |
| |
| // MediaKeysListener::Delegate implementation. |
| void OnMediaKeysAccelerator(const ui::Accelerator& accelerator) override { |
| received_keys_.push_back(accelerator.key_code()); |
| } |
| |
| // Expect that we have received the correct number of key events. |
| void ExpectReceivedKeysCount(uint32_t count) { |
| EXPECT_EQ(count, received_keys_.size()); |
| } |
| |
| // Expect that the key event received at |index| has the specified key code. |
| void ExpectReceivedKey(uint32_t index, ui::KeyboardCode code) { |
| ASSERT_LT(index, received_keys_.size()); |
| EXPECT_EQ(code, received_keys_[index]); |
| } |
| |
| private: |
| std::vector<ui::KeyboardCode> received_keys_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockMediaKeysListenerDelegate); |
| }; |
| |
| } // anonymous namespace |
| |
| class MediaKeysListenerManagerImplTest : public ContentBrowserTest { |
| public: |
| MediaKeysListenerManagerImplTest() = default; |
| ~MediaKeysListenerManagerImplTest() override = default; |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ContentBrowserTest::SetUpCommandLine(command_line); |
| scoped_feature_list_.InitAndEnableFeature(media::kHardwareMediaKeyHandling); |
| } |
| |
| void SetUpOnMainThread() override { |
| media_keys_listener_manager_ = |
| BrowserMainLoop::GetInstance()->media_keys_listener_manager(); |
| |
| std::unique_ptr<MockMediaKeysListener> listener = |
| std::make_unique<MockMediaKeysListener>(media_keys_listener_manager_); |
| media_keys_listener_ = listener.get(); |
| media_keys_listener_manager_->SetMediaKeysListenerForTesting( |
| std::move(listener)); |
| |
| media_controller_ = std::make_unique<TestMediaController>(); |
| media_keys_listener_manager_->hardware_key_media_controller_for_testing() |
| ->SetMediaControllerForTesting( |
| media_controller_->CreateMediaControllerPtr()); |
| |
| ContentBrowserTest::SetUpOnMainThread(); |
| } |
| |
| void SetMediaSessionInfo(MediaSessionInfoPtr session_info) { |
| media_keys_listener_manager_->hardware_key_media_controller_for_testing() |
| ->MediaSessionInfoChanged(std::move(session_info)); |
| } |
| void SetSupportedMediaSessionActions( |
| const std::vector<MediaSessionAction>& actions) { |
| media_keys_listener_manager_->hardware_key_media_controller_for_testing() |
| ->MediaSessionActionsChanged(actions); |
| } |
| void FlushForTesting() { |
| media_keys_listener_manager_->hardware_key_media_controller_for_testing() |
| ->FlushForTesting(); |
| } |
| |
| MediaKeysListenerManagerImpl* media_keys_listener_manager() { |
| return media_keys_listener_manager_; |
| } |
| MockMediaKeysListener* media_keys_listener() { return media_keys_listener_; } |
| TestMediaController* media_controller() { return media_controller_.get(); } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| MediaKeysListenerManagerImpl* media_keys_listener_manager_; |
| MockMediaKeysListener* media_keys_listener_; |
| std::unique_ptr<TestMediaController> media_controller_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MediaKeysListenerManagerImplTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(MediaKeysListenerManagerImplTest, PressPlayPauseKey) { |
| // Tell the HardwareKeyMediaController that there is media playing that can be |
| // paused. |
| { |
| MediaSessionInfoPtr session_info(MediaSessionInfo::New()); |
| session_info->playback_state = MediaPlaybackState::kPlaying; |
| SetMediaSessionInfo(std::move(session_info)); |
| SetSupportedMediaSessionActions({MediaSessionAction::kPause}); |
| } |
| |
| // There should not have been any calls to the media controller yet. |
| EXPECT_EQ(0, media_controller()->suspend_count()); |
| EXPECT_EQ(0, media_controller()->resume_count()); |
| |
| // Press the play/pause media key. |
| media_keys_listener()->SimulateAccelerator( |
| ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE, 0)); |
| FlushForTesting(); |
| |
| // The media controller should have been told to pause. |
| EXPECT_EQ(1, media_controller()->suspend_count()); |
| EXPECT_EQ(0, media_controller()->resume_count()); |
| |
| // Tell the HardwareKeyMediaController that the media is now paused and can be |
| // played. |
| { |
| MediaSessionInfoPtr session_info(MediaSessionInfo::New()); |
| session_info->playback_state = MediaPlaybackState::kPaused; |
| SetMediaSessionInfo(std::move(session_info)); |
| SetSupportedMediaSessionActions({MediaSessionAction::kPlay}); |
| } |
| |
| // Press play/pause. |
| media_keys_listener()->SimulateAccelerator( |
| ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE, 0)); |
| FlushForTesting(); |
| |
| // The media controller should have been told to play. |
| EXPECT_EQ(1, media_controller()->suspend_count()); |
| EXPECT_EQ(1, media_controller()->resume_count()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MediaKeysListenerManagerImplTest, |
| ListensToTheCorrectMediaKeys) { |
| // Before any media session starts, we should not be listening for key input. |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PLAY_PAUSE)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_STOP)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_NEXT_TRACK)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PREV_TRACK)); |
| |
| // Tell the HardwareKeyMediaController that there is media playing that can be |
| // paused. |
| { |
| MediaSessionInfoPtr session_info(MediaSessionInfo::New()); |
| session_info->playback_state = MediaPlaybackState::kPlaying; |
| SetMediaSessionInfo(std::move(session_info)); |
| SetSupportedMediaSessionActions({MediaSessionAction::kPause}); |
| } |
| |
| // We should now be listening for the play/pause key, but no others. |
| EXPECT_TRUE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PLAY_PAUSE)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_STOP)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_NEXT_TRACK)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PREV_TRACK)); |
| |
| // Update the list of supported actions. |
| SetSupportedMediaSessionActions({MediaSessionAction::kPause, |
| MediaSessionAction::kStop, |
| MediaSessionAction::kNextTrack}); |
| |
| // We should now be listening for the correct media keys. |
| EXPECT_TRUE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PLAY_PAUSE)); |
| EXPECT_TRUE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_STOP)); |
| EXPECT_TRUE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_NEXT_TRACK)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PREV_TRACK)); |
| |
| // Update the list of supported actions. |
| SetSupportedMediaSessionActions({MediaSessionAction::kStop}); |
| |
| // We should now be listening for the correct media keys. |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PLAY_PAUSE)); |
| EXPECT_TRUE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_STOP)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_NEXT_TRACK)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PREV_TRACK)); |
| |
| // Disable media key handling for the HardwareKeyMediaController. |
| media_keys_listener_manager()->DisableInternalMediaKeyHandling(); |
| |
| // We should no longer be listening for key input. |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PLAY_PAUSE)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_STOP)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_NEXT_TRACK)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PREV_TRACK)); |
| |
| // Re-enable media key handling for the HardwareKeyMediaController. |
| media_keys_listener_manager()->EnableInternalMediaKeyHandling(); |
| |
| // We should now be listening for the correct media keys. |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PLAY_PAUSE)); |
| EXPECT_TRUE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_STOP)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_NEXT_TRACK)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PREV_TRACK)); |
| |
| // Have a different delegate besides the HardwareKeyMediaController request |
| // keys. |
| MockMediaKeysListenerDelegate delegate; |
| media_keys_listener_manager()->StartWatchingMediaKey( |
| ui::VKEY_MEDIA_PLAY_PAUSE, &delegate); |
| |
| // We should now be listening for only the new delegate's keys. |
| EXPECT_TRUE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PLAY_PAUSE)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_STOP)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_NEXT_TRACK)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PREV_TRACK)); |
| |
| // Unregister the delegate. |
| media_keys_listener_manager()->StopWatchingMediaKey(ui::VKEY_MEDIA_PLAY_PAUSE, |
| &delegate); |
| |
| // We should now be listening for the HardwareKeyMediaController's keys. |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PLAY_PAUSE)); |
| EXPECT_TRUE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_STOP)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_NEXT_TRACK)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PREV_TRACK)); |
| |
| // Tell the HardwareKeyMediaController there is no longer an active session. |
| SetMediaSessionInfo(nullptr); |
| SetSupportedMediaSessionActions({}); |
| |
| // We should no longer be listening for key input. |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PLAY_PAUSE)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_STOP)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_NEXT_TRACK)); |
| EXPECT_FALSE(media_keys_listener()->IsWatching(ui::VKEY_MEDIA_PREV_TRACK)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MediaKeysListenerManagerImplTest, |
| OtherDelegatesPreemptHardwareKeyMediaController) { |
| // Tell the HardwareKeyMediaController that there is media playing that can be |
| // paused or sent to the next track. |
| { |
| MediaSessionInfoPtr session_info(MediaSessionInfo::New()); |
| session_info->playback_state = MediaPlaybackState::kPlaying; |
| SetMediaSessionInfo(std::move(session_info)); |
| SetSupportedMediaSessionActions({MediaSessionAction::kPause}); |
| } |
| |
| // Set up a delegate that listens to Play/Pause. |
| MockMediaKeysListenerDelegate delegate; |
| media_keys_listener_manager()->StartWatchingMediaKey( |
| ui::VKEY_MEDIA_PLAY_PAUSE, &delegate); |
| |
| // There should not have been any calls to the media controller or the |
| // delegate yet. |
| EXPECT_EQ(0, media_controller()->suspend_count()); |
| EXPECT_EQ(0, media_controller()->next_track_count()); |
| delegate.ExpectReceivedKeysCount(0); |
| |
| // Press play/pause. |
| media_keys_listener()->SimulateAccelerator( |
| ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE, 0)); |
| FlushForTesting(); |
| |
| // The media controller should not have been told to pause. |
| EXPECT_EQ(0, media_controller()->suspend_count()); |
| EXPECT_EQ(0, media_controller()->next_track_count()); |
| |
| // The delegate should have received the event instead. |
| delegate.ExpectReceivedKeysCount(1); |
| delegate.ExpectReceivedKey(/*index=*/0, ui::VKEY_MEDIA_PLAY_PAUSE); |
| |
| // Unregister the delegate. |
| media_keys_listener_manager()->StopWatchingMediaKey(ui::VKEY_MEDIA_PLAY_PAUSE, |
| &delegate); |
| |
| // Press play/pause. |
| media_keys_listener()->SimulateAccelerator( |
| ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE, 0)); |
| FlushForTesting(); |
| |
| // The media controller should have been told to pause. |
| EXPECT_EQ(1, media_controller()->suspend_count()); |
| |
| // The delegate should not have been told to pause, since it was unregistered. |
| delegate.ExpectReceivedKeysCount(1); |
| } |
| |
| } // namespace content |