[go: up one dir, main page]

Media Keys: Hide the SMTC on the lock screen

This CL adds logic to hide the System Media Transport Controls on the
lock screen unless the media is currently playing. If media is playing
on the lock screen and is then paused, we want to hide the SMTC after 5
seconds.

(cherry picked from commit 79e62ddd9964287cec778684995f086c2e3f4877)

Bug: 970353
Change-Id: I9fae652594381de05338ba0947fd18eed6ff7573
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1643733
Commit-Queue: Tommy Steimel <steimel@chromium.org>
Reviewed-by: Mounir Lamouri <mlamouri@chromium.org>
Reviewed-by: Chrome Cunningham <chcunningham@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#666544}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1653870
Reviewed-by: Tommy Steimel <steimel@chromium.org>
Cr-Commit-Position: refs/branch-heads/3809@{#235}
Cr-Branched-From: d82dec1a818f378c464ba307ddd9c92133eac355-refs/heads/master@{#665002}
diff --git a/content/browser/media/media_keys_listener_manager_impl.cc b/content/browser/media/media_keys_listener_manager_impl.cc
index 6802e42e..2c6ae1b 100644
--- a/content/browser/media/media_keys_listener_manager_impl.cc
+++ b/content/browser/media/media_keys_listener_manager_impl.cc
@@ -130,7 +130,7 @@
   // We should never receive an accelerator that was never registered.
   DCHECK(delegate_map_.contains(accelerator.key_code()));
 
-#if defined(OS_WIN) || defined(OS_MACOSX)
+#if defined(OS_MACOSX)
   // For privacy, we don't want to handle media keys when the system is locked.
   // On Windows and Mac OS X, this will happen unless we explicitly prevent it.
   // TODO(steimel): Consider adding an idle monitor instead and disabling the
diff --git a/content/browser/media/system_media_controls_notifier.cc b/content/browser/media/system_media_controls_notifier.cc
index c86ab6d5..c5fdb38 100644
--- a/content/browser/media/system_media_controls_notifier.cc
+++ b/content/browser/media/system_media_controls_notifier.cc
@@ -7,10 +7,13 @@
 #include <memory>
 #include <utility>
 
+#include "base/bind.h"
+#include "base/time/time.h"
 #include "content/public/browser/content_browser_client.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"
+#include "ui/base/idle/idle.h"
 #include "ui/base/win/system_media_controls/system_media_controls_service.h"
 
 namespace content {
@@ -20,6 +23,12 @@
 const int kMinImageSize = 71;
 const int kDesiredImageSize = 150;
 
+constexpr base::TimeDelta kScreenLockPollInterval =
+    base::TimeDelta::FromSeconds(1);
+constexpr int kHideSmtcDelaySeconds = 5;
+constexpr base::TimeDelta kHideSmtcDelay =
+    base::TimeDelta::FromSeconds(kHideSmtcDelaySeconds);
+
 SystemMediaControlsNotifier::SystemMediaControlsNotifier(
     service_manager::Connector* connector)
     : connector_(connector) {}
@@ -27,6 +36,8 @@
 SystemMediaControlsNotifier::~SystemMediaControlsNotifier() = default;
 
 void SystemMediaControlsNotifier::Initialize() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   // |service_| can be set in tests.
   if (!service_)
     service_ = system_media_controls::SystemMediaControlsService::GetInstance();
@@ -36,6 +47,11 @@
   if (!service_)
     return;
 
+  lock_polling_timer_.Start(
+      FROM_HERE, kScreenLockPollInterval,
+      base::BindRepeating(&SystemMediaControlsNotifier::CheckLockState,
+                          base::Unretained(this)));
+
   // |connector_| can be null in tests.
   if (!connector_)
     return;
@@ -65,14 +81,77 @@
       kDesiredImageSize, std::move(image_observer_ptr));
 }
 
+void SystemMediaControlsNotifier::CheckLockState() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  bool new_state = ui::CheckIdleStateIsLocked();
+  if (screen_locked_ == new_state)
+    return;
+
+  screen_locked_ = new_state;
+  if (screen_locked_)
+    OnScreenLocked();
+  else
+    OnScreenUnlocked();
+}
+
+void SystemMediaControlsNotifier::OnScreenLocked() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(service_);
+
+  // If media is currently playing, don't hide the SMTC.
+  if (session_info_ptr_ &&
+      session_info_ptr_->playback_state ==
+          media_session::mojom::MediaPlaybackState::kPlaying) {
+    return;
+  }
+
+  // Otherwise, hide them.
+  service_->SetEnabled(false);
+}
+
+void SystemMediaControlsNotifier::OnScreenUnlocked() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(service_);
+
+  StopHideSmtcTimer();
+  service_->SetEnabled(true);
+}
+
+void SystemMediaControlsNotifier::StartHideSmtcTimer() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  hide_smtc_timer_.Start(
+      FROM_HERE, kHideSmtcDelay,
+      base::BindOnce(&SystemMediaControlsNotifier::HideSmtcTimerFired,
+                     base::Unretained(this)));
+}
+
+void SystemMediaControlsNotifier::StopHideSmtcTimer() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  hide_smtc_timer_.Stop();
+}
+
+void SystemMediaControlsNotifier::HideSmtcTimerFired() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(service_);
+
+  service_->SetEnabled(false);
+}
+
 void SystemMediaControlsNotifier::MediaSessionInfoChanged(
     media_session::mojom::MediaSessionInfoPtr session_info_ptr) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(service_);
 
+  bool is_playing = false;
+
   session_info_ptr_ = std::move(session_info_ptr);
   if (session_info_ptr_) {
     if (session_info_ptr_->playback_state ==
         media_session::mojom::MediaPlaybackState::kPlaying) {
+      is_playing = true;
       service_->SetPlaybackStatus(
           MediaPlaybackStatus::MediaPlaybackStatus_Playing);
     } else {
@@ -89,10 +168,18 @@
     // presented to the platform, and terminate these steps.
     service_->ClearMetadata();
   }
+
+  if (screen_locked_) {
+    if (is_playing)
+      StopHideSmtcTimer();
+    else if (!hide_smtc_timer_.IsRunning())
+      StartHideSmtcTimer();
+  }
 }
 
 void SystemMediaControlsNotifier::MediaSessionMetadataChanged(
     const base::Optional<media_session::MediaMetadata>& metadata) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(service_);
 
   if (metadata.has_value()) {
@@ -116,6 +203,7 @@
 void SystemMediaControlsNotifier::MediaControllerImageChanged(
     media_session::mojom::MediaSessionImageType type,
     const SkBitmap& bitmap) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(service_);
 
   if (!bitmap.empty()) {
diff --git a/content/browser/media/system_media_controls_notifier.h b/content/browser/media/system_media_controls_notifier.h
index 7f41acb..23aaf87 100644
--- a/content/browser/media/system_media_controls_notifier.h
+++ b/content/browser/media/system_media_controls_notifier.h
@@ -8,6 +8,8 @@
 #include <memory>
 #include <vector>
 
+#include "base/sequence_checker.h"
+#include "base/timer/timer.h"
 #include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/media_session/public/mojom/media_controller.mojom.h"
@@ -57,6 +59,27 @@
   }
 
  private:
+  friend class SystemMediaControlsNotifierTest;
+
+  // Polls the current idle state of the system.
+  void CheckLockState();
+
+  // Called when the idle state changes from unlocked to locked.
+  void OnScreenLocked();
+
+  // Called when the idle state changes from locked to unlocked.
+  void OnScreenUnlocked();
+
+  // Helper functions for dealing with the timer that hides the System Media
+  // Transport Controls on the lock screen 5 seconds after the user pauses.
+  void StartHideSmtcTimer();
+  void StopHideSmtcTimer();
+  void HideSmtcTimerFired();
+
+  bool screen_locked_ = false;
+  base::RepeatingTimer lock_polling_timer_;
+  base::OneShotTimer hide_smtc_timer_;
+
   // Our connection to Window's System Media Transport Controls.
   system_media_controls::SystemMediaControlsService* service_ = nullptr;
 
@@ -73,6 +96,8 @@
   mojo::Binding<media_session::mojom::MediaControllerImageObserver>
       media_controller_image_observer_binding_{this};
 
+  SEQUENCE_CHECKER(sequence_checker_);
+
   DISALLOW_COPY_AND_ASSIGN(SystemMediaControlsNotifier);
 };
 
diff --git a/content/browser/media/system_media_controls_notifier_unittest.cc b/content/browser/media/system_media_controls_notifier_unittest.cc
index e6c39a6..b3e39184 100644
--- a/content/browser/media/system_media_controls_notifier_unittest.cc
+++ b/content/browser/media/system_media_controls_notifier_unittest.cc
@@ -7,9 +7,11 @@
 #include <memory>
 #include <utility>
 
+#include "base/test/scoped_task_environment.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/idle/scoped_set_idle_state.h"
 #include "ui/base/win/system_media_controls/mock_system_media_controls_service.h"
 
 namespace content {
@@ -23,7 +25,9 @@
 
 class SystemMediaControlsNotifierTest : public testing::Test {
  public:
-  SystemMediaControlsNotifierTest() = default;
+  SystemMediaControlsNotifierTest()
+      : scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
   ~SystemMediaControlsNotifierTest() override = default;
 
   void SetUp() override {
@@ -78,7 +82,14 @@
     return mock_system_media_controls_service_;
   }
 
+  base::RepeatingTimer& lock_polling_timer() {
+    return notifier_->lock_polling_timer_;
+  }
+
+  base::OneShotTimer& hide_smtc_timer() { return notifier_->hide_smtc_timer_; }
+
  private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
   std::unique_ptr<SystemMediaControlsNotifier> notifier_;
   system_media_controls::testing::MockSystemMediaControlsService
       mock_system_media_controls_service_;
@@ -134,4 +145,83 @@
   SimulateImageChanged();
 }
 
+TEST_F(SystemMediaControlsNotifierTest, DisablesOnLockAndEnablesOnUnlock) {
+  EXPECT_CALL(mock_system_media_controls_service(), SetEnabled(false));
+
+  {
+    // Lock the screen.
+    ui::ScopedSetIdleState locked(ui::IDLE_STATE_LOCKED);
+
+    // Make sure that the lock polling timer is running and then force it to
+    // fire so that we don't need to wait. This should disable the service.
+    EXPECT_TRUE(lock_polling_timer().IsRunning());
+    lock_polling_timer().user_task().Run();
+  }
+
+  // Ensure that the service was disabled.
+  testing::Mock::VerifyAndClearExpectations(
+      &mock_system_media_controls_service());
+
+  // The service should be reenabled on unlock.
+  EXPECT_CALL(mock_system_media_controls_service(), SetEnabled(true));
+
+  {
+    // Unlock the screen.
+    ui::ScopedSetIdleState unlocked(ui::IDLE_STATE_ACTIVE);
+
+    // Make sure that the lock polling timer is running and then force it to
+    // fire so that we don't need to wait. This should enable the service.
+    EXPECT_TRUE(lock_polling_timer().IsRunning());
+    lock_polling_timer().user_task().Run();
+  }
+}
+
+TEST_F(SystemMediaControlsNotifierTest, DoesNotDisableOnLockWhenPlaying) {
+  EXPECT_CALL(mock_system_media_controls_service(), SetEnabled(_)).Times(0);
+
+  SimulatePlaying();
+
+  // Lock the screen.
+  ui::ScopedSetIdleState locked(ui::IDLE_STATE_LOCKED);
+
+  // Make sure that the lock polling timer is running and then force it to
+  // fire so that we don't need to wait. This should not disable the service.
+  EXPECT_TRUE(lock_polling_timer().IsRunning());
+  lock_polling_timer().user_task().Run();
+}
+
+TEST_F(SystemMediaControlsNotifierTest, DisablesAfterPausingOnLockScreen) {
+  Expectation playing = EXPECT_CALL(
+      mock_system_media_controls_service(),
+      SetPlaybackStatus(MediaPlaybackStatus::MediaPlaybackStatus_Playing));
+  Expectation paused =
+      EXPECT_CALL(
+          mock_system_media_controls_service(),
+          SetPlaybackStatus(MediaPlaybackStatus::MediaPlaybackStatus_Paused))
+          .After(playing);
+  EXPECT_CALL(mock_system_media_controls_service(), SetEnabled(false))
+      .After(paused);
+
+  SimulatePlaying();
+
+  // Lock the screen.
+  ui::ScopedSetIdleState locked(ui::IDLE_STATE_LOCKED);
+
+  // Make sure that the lock polling timer is running and then force it to
+  // fire so that we don't need to wait. This should not disable the service.
+  EXPECT_TRUE(lock_polling_timer().IsRunning());
+  lock_polling_timer().user_task().Run();
+
+  // Since we're playing, the timer to hide the SMTC should not be running.
+  EXPECT_FALSE(hide_smtc_timer().IsRunning());
+
+  SimulatePaused();
+
+  // Now that we're paused, the timer to hide the SMTC should be running.
+  EXPECT_TRUE(hide_smtc_timer().IsRunning());
+
+  // Force the timer to fire now. This should disable the service.
+  hide_smtc_timer().FireNow();
+}
+
 }  // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index d366959..08d057c 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2150,6 +2150,7 @@
       "//third_party/blink/public/common",
       "//third_party/blink/public/common:font_unique_name_table_proto",
       "//third_party/iaccessible2",
+      "//ui/base/idle:test_support",
       "//ui/base/win/system_media_controls:test_support",
     ]
     libs = [
diff --git a/ui/base/win/system_media_controls/mock_system_media_controls_service.h b/ui/base/win/system_media_controls/mock_system_media_controls_service.h
index 125a1ee..817b51f 100644
--- a/ui/base/win/system_media_controls/mock_system_media_controls_service.h
+++ b/ui/base/win/system_media_controls/mock_system_media_controls_service.h
@@ -25,6 +25,7 @@
   MOCK_METHOD1(AddObserver, void(SystemMediaControlsServiceObserver* observer));
   MOCK_METHOD1(RemoveObserver,
                void(SystemMediaControlsServiceObserver* observer));
+  MOCK_METHOD1(SetEnabled, void(bool enabled));
   MOCK_METHOD1(SetIsNextEnabled, void(bool value));
   MOCK_METHOD1(SetIsPreviousEnabled, void(bool value));
   MOCK_METHOD1(SetIsPlayEnabled, void(bool value));
diff --git a/ui/base/win/system_media_controls/system_media_controls_service.h b/ui/base/win/system_media_controls/system_media_controls_service.h
index 55e6850..8b5eba6 100644
--- a/ui/base/win/system_media_controls/system_media_controls_service.h
+++ b/ui/base/win/system_media_controls/system_media_controls_service.h
@@ -31,6 +31,9 @@
   virtual void AddObserver(SystemMediaControlsServiceObserver* observer) = 0;
   virtual void RemoveObserver(SystemMediaControlsServiceObserver* observer) = 0;
 
+  // Enables/disables the service.
+  virtual void SetEnabled(bool enabled) = 0;
+
   // TODO(steimel): Add other controls.
   // Enable or disable specific controls.
   virtual void SetIsNextEnabled(bool value) = 0;
diff --git a/ui/base/win/system_media_controls/system_media_controls_service_impl.cc b/ui/base/win/system_media_controls/system_media_controls_service_impl.cc
index 76fa9f4..7862e3c 100644
--- a/ui/base/win/system_media_controls/system_media_controls_service_impl.cc
+++ b/ui/base/win/system_media_controls/system_media_controls_service_impl.cc
@@ -118,6 +118,12 @@
   observers_.RemoveObserver(observer);
 }
 
+void SystemMediaControlsServiceImpl::SetEnabled(bool enabled) {
+  DCHECK(initialized_);
+  HRESULT hr = system_media_controls_->put_IsEnabled(enabled);
+  DCHECK(SUCCEEDED(hr));
+}
+
 void SystemMediaControlsServiceImpl::SetIsNextEnabled(bool value) {
   DCHECK(initialized_);
   HRESULT hr = system_media_controls_->put_IsNextEnabled(value);
diff --git a/ui/base/win/system_media_controls/system_media_controls_service_impl.h b/ui/base/win/system_media_controls/system_media_controls_service_impl.h
index 57e71e1..025ab92 100644
--- a/ui/base/win/system_media_controls/system_media_controls_service_impl.h
+++ b/ui/base/win/system_media_controls/system_media_controls_service_impl.h
@@ -37,6 +37,7 @@
   // SystemMediaControlsService implementation.
   void AddObserver(SystemMediaControlsServiceObserver* observer) override;
   void RemoveObserver(SystemMediaControlsServiceObserver* observer) override;
+  void SetEnabled(bool enabled) override;
   void SetIsNextEnabled(bool value) override;
   void SetIsPreviousEnabled(bool value) override;
   void SetIsPlayEnabled(bool value) override;