Commit 0ca767b6 authored by Noah Rose Ledesma's avatar Noah Rose Ledesma Committed by Commit Bot

GMC: Refresh devices without having to reopen the dialog

The global media controls UI displays the audio devices connected to
the system. This change allows the UI to refresh the devices it shows
as availability changes.

Because of an existing bug in device update processing on linux
(crbug.com/1112480) a new class for monitoring these changes was
created. The implementation for linux will poll the system whereas the
implementation for all other OSes will be notified of changes.

Bug: 1113944
Change-Id: I0d285ff0486fc1fb1307b5d78a5a998dcde131ad
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2341181
Commit-Queue: Noah Rose Ledesma <noahrose@google.com>
Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#799472}
parent 17e2a917
...@@ -978,6 +978,8 @@ static_library("ui") { ...@@ -978,6 +978,8 @@ static_library("ui") {
"global_media_controls/media_dialog_delegate.h", "global_media_controls/media_dialog_delegate.h",
"global_media_controls/media_notification_container_impl.h", "global_media_controls/media_notification_container_impl.h",
"global_media_controls/media_notification_container_observer.h", "global_media_controls/media_notification_container_observer.h",
"global_media_controls/media_notification_device_monitor.cc",
"global_media_controls/media_notification_device_monitor.h",
"global_media_controls/media_notification_device_provider.h", "global_media_controls/media_notification_device_provider.h",
"global_media_controls/media_notification_device_provider_impl.cc", "global_media_controls/media_notification_device_provider_impl.cc",
"global_media_controls/media_notification_device_provider_impl.h", "global_media_controls/media_notification_device_provider_impl.h",
......
// Copyright 2020 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 "chrome/browser/ui/global_media_controls/media_notification_device_monitor.h"
#include <algorithm>
#include <iterator>
#include "base/bind.h"
#include "base/hash/hash.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
// MediaNotificationDeviceMonitor
MediaNotificationDeviceMonitor::~MediaNotificationDeviceMonitor() = default;
std::unique_ptr<MediaNotificationDeviceMonitor>
MediaNotificationDeviceMonitor::Create(
MediaNotificationDeviceProvider* device_provider) {
// The device monitor implementation on linux does not reliably detect
// connection changes for some devices. In this case we fall back to polling the
// device provider. See crbug.com/1112480 for more information.
#if defined(OS_LINUX) && defined(USE_UDEV)
return std::make_unique<PollingDeviceMonitorImpl>(device_provider);
#else
return std::make_unique<SystemMonitorDeviceMonitorImpl>();
#endif
}
void MediaNotificationDeviceMonitor::AddDevicesChangedObserver(
DevicesChangedObserver* obs) {
observers_.AddObserver(obs);
}
void MediaNotificationDeviceMonitor::RemoveDevicesChangedObserver(
DevicesChangedObserver* obs) {
observers_.RemoveObserver(obs);
}
MediaNotificationDeviceMonitor::MediaNotificationDeviceMonitor() = default;
#if !(defined(OS_LINUX) && defined(USE_UDEV))
// SystemMonitorDeviceMonitorImpl
SystemMonitorDeviceMonitorImpl::SystemMonitorDeviceMonitorImpl() {
base::SystemMonitor::Get()->AddDevicesChangedObserver(this);
}
SystemMonitorDeviceMonitorImpl::~SystemMonitorDeviceMonitorImpl() {
base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
}
void SystemMonitorDeviceMonitorImpl::StartMonitoring() {
is_monitoring_ = true;
}
void SystemMonitorDeviceMonitorImpl::StopMonitoring() {
is_monitoring_ = false;
}
void SystemMonitorDeviceMonitorImpl::OnDevicesChanged(
base::SystemMonitor::DeviceType device_type) {
if (!is_monitoring_)
return;
// TODO(noahrose): Get an issue number for this TODO. Only notify observers in
// changes of audio output devices as opposed to audio devices in general.
if (device_type != base::SystemMonitor::DEVTYPE_AUDIO)
return;
for (auto& observer : observers_) {
observer.OnDevicesChanged();
}
}
#else
namespace {
constexpr int kPollingIntervalSeconds = 10;
} // anonymous namespace
// PollingDeviceMonitorImpl
PollingDeviceMonitorImpl::PollingDeviceMonitorImpl(
MediaNotificationDeviceProvider* device_provider)
: device_provider_(device_provider),
task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
void PollingDeviceMonitorImpl::StartMonitoring() {
if (is_monitoring_)
return;
is_monitoring_ = true;
if (!is_task_posted_) {
is_task_posted_ = true;
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PollingDeviceMonitorImpl::PollDeviceProvider,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(kPollingIntervalSeconds));
}
}
void PollingDeviceMonitorImpl::StopMonitoring() {
is_monitoring_ = false;
}
// static
int PollingDeviceMonitorImpl::get_polling_interval_for_testing() {
return kPollingIntervalSeconds;
}
PollingDeviceMonitorImpl::~PollingDeviceMonitorImpl() = default;
void PollingDeviceMonitorImpl::PollDeviceProvider() {
is_task_posted_ = false;
if (!is_monitoring_)
return;
device_provider_->GetOutputDeviceDescriptions(
base::BindOnce(&PollingDeviceMonitorImpl::OnDeviceDescriptionsRecieved,
weak_ptr_factory_.GetWeakPtr()));
}
void PollingDeviceMonitorImpl::OnDeviceDescriptionsRecieved(
media::AudioDeviceDescriptions descriptions) {
if (!is_monitoring_)
return;
if (!std::equal(descriptions.cbegin(), descriptions.cend(),
device_ids_.cbegin(), device_ids_.cend(),
[](const auto& description, const auto& id) {
return description.unique_id == id;
})) {
device_ids_.clear();
std::transform(descriptions.begin(), descriptions.end(),
std::back_inserter(device_ids_), [](auto& description) {
return std::move(description.unique_id);
});
NotifyObservers();
}
is_task_posted_ = true;
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PollingDeviceMonitorImpl::PollDeviceProvider,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(kPollingIntervalSeconds));
}
void PollingDeviceMonitorImpl::NotifyObservers() {
for (auto& observer : observers_) {
observer.OnDevicesChanged();
}
}
#endif
// Copyright 2020 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.
#ifndef CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_MONITOR_H_
#define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_MONITOR_H_
#include "base/observer_list.h"
#include "base/system/system_monitor.h"
#include "build/build_config.h"
#include "media/audio/audio_device_description.h"
class MediaNotificationDeviceProvider;
namespace base {
class SingleThreadTaskRunner;
} // namespace base
// Common interface for a class that detects changes in the audio output devices
// connected to the system and notifies observers.
class MediaNotificationDeviceMonitor {
public:
virtual ~MediaNotificationDeviceMonitor();
// Returns a pointer to the appropriate implementation for the OS.
static std::unique_ptr<MediaNotificationDeviceMonitor> Create(
MediaNotificationDeviceProvider* device_provider);
class DevicesChangedObserver : public base::CheckedObserver {
public:
// Called when the MediaNotificationDeviceMonitor detects a change in the
// connected audio output devices.
virtual void OnDevicesChanged() = 0;
};
void AddDevicesChangedObserver(DevicesChangedObserver* obs);
void RemoveDevicesChangedObserver(DevicesChangedObserver* obs);
virtual void StartMonitoring() = 0;
virtual void StopMonitoring() = 0;
protected:
bool is_monitoring_ = false;
MediaNotificationDeviceMonitor();
base::ObserverList<DevicesChangedObserver> observers_;
};
#if !(defined(OS_LINUX) && defined(USE_UDEV))
// Monitors device changes by observing the SystemMonitor
class SystemMonitorDeviceMonitorImpl
: public MediaNotificationDeviceMonitor,
public base::SystemMonitor::DevicesChangedObserver {
public:
SystemMonitorDeviceMonitorImpl();
~SystemMonitorDeviceMonitorImpl() override;
void StartMonitoring() override;
void StopMonitoring() override;
void OnDevicesChanged(base::SystemMonitor::DeviceType device_type) override;
};
#else
// Monitors device changes by polling the MediaNotificationDeviceProvider
class PollingDeviceMonitorImpl : public MediaNotificationDeviceMonitor {
public:
explicit PollingDeviceMonitorImpl(
MediaNotificationDeviceProvider* device_provider);
~PollingDeviceMonitorImpl() override;
void StartMonitoring() override;
void StopMonitoring() override;
static int get_polling_interval_for_testing();
private:
FRIEND_TEST_ALL_PREFIXES(PollingDeviceMonitorImplTest,
DeviceChangeNotifiesObserver);
void PollDeviceProvider();
void OnDeviceDescriptionsRecieved(
media::AudioDeviceDescriptions descriptions);
void NotifyObservers();
MediaNotificationDeviceProvider* const device_provider_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
std::vector<std::string> device_ids_;
bool is_task_posted_ = false;
base::WeakPtrFactory<PollingDeviceMonitorImpl> weak_ptr_factory_{this};
};
#endif // !(defined(OS_LINUX) && defined(USE_UDEV))
#endif // CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_MONITOR_H_
// Copyright 2020 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 "chrome/browser/ui/global_media_controls/media_notification_device_monitor.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
#include "media/audio/audio_device_description.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class MockDevicesChangedObserver
: public MediaNotificationDeviceMonitor::DevicesChangedObserver {
public:
MOCK_METHOD(void, OnDevicesChanged, (), (override));
};
class MockMediaNotificationDeviceProvider
: public MediaNotificationDeviceProvider {
public:
MOCK_METHOD(std::unique_ptr<MediaNotificationDeviceProvider::
GetOutputDevicesCallbackList::Subscription>,
RegisterOutputDeviceDescriptionsCallback,
(GetOutputDevicesCallback cb),
(override));
void GetOutputDeviceDescriptions(
media::AudioSystem::OnDeviceDescriptionsCallback cb) override {
std::move(cb).Run(device_descriptions);
}
media::AudioDeviceDescriptions device_descriptions;
};
class PollingDeviceMonitorImplTest : public testing::Test {
protected:
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
};
} // anonymous namespace
TEST_F(PollingDeviceMonitorImplTest, DeviceChangeNotifiesObserver) {
// When the list of audio devices changes, observers should be notified the
// next time the monitor polls.
MockDevicesChangedObserver observer;
MockMediaNotificationDeviceProvider provider;
PollingDeviceMonitorImpl monitor(&provider);
monitor.AddDevicesChangedObserver(&observer);
provider.device_descriptions.emplace_back("1", "1", "1");
EXPECT_CALL(observer, OnDevicesChanged).Times(1);
monitor.StartMonitoring();
task_environment.FastForwardBy(base::TimeDelta::FromSeconds(
PollingDeviceMonitorImpl::get_polling_interval_for_testing()));
// When the monitor polls a second time, the observer should not be notified
// as the list of devices hasn't changed.
testing::Mock::VerifyAndClearExpectations(&observer);
EXPECT_CALL(observer, OnDevicesChanged).Times(0);
task_environment.FastForwardBy(base::TimeDelta::FromSeconds(
PollingDeviceMonitorImpl::get_polling_interval_for_testing()));
testing::Mock::VerifyAndClearExpectations(&observer);
}
...@@ -13,12 +13,19 @@ class MediaNotificationDeviceProvider { ...@@ -13,12 +13,19 @@ class MediaNotificationDeviceProvider {
virtual ~MediaNotificationDeviceProvider() = default; virtual ~MediaNotificationDeviceProvider() = default;
using GetOutputDevicesCallbackList = using GetOutputDevicesCallbackList =
base::OnceCallbackList<void(const media::AudioDeviceDescriptions&)>; base::RepeatingCallbackList<void(const media::AudioDeviceDescriptions&)>;
using GetOutputDevicesCallback = GetOutputDevicesCallbackList::CallbackType; using GetOutputDevicesCallback = GetOutputDevicesCallbackList::CallbackType;
// Register a callback that will be invoked with the list of audio output
// devices currently available. After the first invocation the callback will
// be run whenever a change in connected audio devices is detected.
virtual std::unique_ptr<MediaNotificationDeviceProvider:: virtual std::unique_ptr<MediaNotificationDeviceProvider::
GetOutputDevicesCallbackList::Subscription> GetOutputDevicesCallbackList::Subscription>
GetOutputDeviceDescriptions(GetOutputDevicesCallback cb) = 0; RegisterOutputDeviceDescriptionsCallback(GetOutputDevicesCallback cb) = 0;
// Query the system for audio output devices and reply via callback.
virtual void GetOutputDeviceDescriptions(
media::AudioSystem::OnDeviceDescriptionsCallback) = 0;
}; };
#endif // CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_PROVIDER_H_ #endif // CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_PROVIDER_H_
...@@ -4,32 +4,65 @@ ...@@ -4,32 +4,65 @@
#include "chrome/browser/ui/global_media_controls/media_notification_device_provider_impl.h" #include "chrome/browser/ui/global_media_controls/media_notification_device_provider_impl.h"
#include "base/bind.h"
#include "chrome/browser/ui/global_media_controls/media_notification_device_monitor.h"
#include "content/public/browser/audio_service.h" #include "content/public/browser/audio_service.h"
MediaNotificationDeviceProviderImpl::MediaNotificationDeviceProviderImpl() = MediaNotificationDeviceProviderImpl::MediaNotificationDeviceProviderImpl()
default; : monitor_(MediaNotificationDeviceMonitor::Create(this)) {
MediaNotificationDeviceProviderImpl::~MediaNotificationDeviceProviderImpl() = monitor_->AddDevicesChangedObserver(this);
default; output_device_callback_list_.set_removal_callback(base::BindRepeating(
&MediaNotificationDeviceProviderImpl::OnSubscriberRemoved,
weak_ptr_factory_.GetWeakPtr()));
}
MediaNotificationDeviceProviderImpl::~MediaNotificationDeviceProviderImpl() {
monitor_->RemoveDevicesChangedObserver(this);
}
std::unique_ptr< std::unique_ptr<
MediaNotificationDeviceProvider::GetOutputDevicesCallbackList::Subscription> MediaNotificationDeviceProvider::GetOutputDevicesCallbackList::Subscription>
MediaNotificationDeviceProviderImpl::GetOutputDeviceDescriptions( MediaNotificationDeviceProviderImpl::RegisterOutputDeviceDescriptionsCallback(
GetOutputDevicesCallback cb) { GetOutputDevicesCallback cb) {
if (is_querying_for_output_devices_) monitor_->StartMonitoring();
return output_device_callback_list_.Add(std::move(cb)); if (has_device_list_)
cb.Run(audio_device_descriptions_);
auto subscription = output_device_callback_list_.Add(std::move(cb));
if (!has_device_list_)
GetDevices();
return subscription;
}
void MediaNotificationDeviceProviderImpl::GetOutputDeviceDescriptions(
media::AudioSystem::OnDeviceDescriptionsCallback cb) {
if (!audio_system_) if (!audio_system_)
audio_system_ = content::CreateAudioSystemForAudioService(); audio_system_ = content::CreateAudioSystemForAudioService();
audio_system_->GetDeviceDescriptions( audio_system_->GetDeviceDescriptions(
/*for_input=*/false, /*for_input=*/false, std::move(cb));
base::BindOnce( }
&MediaNotificationDeviceProviderImpl::OnReceivedDeviceDescriptions,
void MediaNotificationDeviceProviderImpl::OnDevicesChanged() {
GetDevices();
}
void MediaNotificationDeviceProviderImpl::GetDevices() {
if (is_querying_for_output_devices_)
return;
is_querying_for_output_devices_ = true;
GetOutputDeviceDescriptions(
base::BindOnce(&MediaNotificationDeviceProviderImpl::NotifySubscribers,
weak_ptr_factory_.GetWeakPtr())); weak_ptr_factory_.GetWeakPtr()));
return output_device_callback_list_.Add(std::move(cb));
} }
void MediaNotificationDeviceProviderImpl::OnReceivedDeviceDescriptions( void MediaNotificationDeviceProviderImpl::NotifySubscribers(
media::AudioDeviceDescriptions descriptions) { media::AudioDeviceDescriptions descriptions) {
is_querying_for_output_devices_ = false; is_querying_for_output_devices_ = false;
output_device_callback_list_.Notify(descriptions); audio_device_descriptions_ = std::move(descriptions);
has_device_list_ = true;
output_device_callback_list_.Notify(audio_device_descriptions_);
}
void MediaNotificationDeviceProviderImpl::OnSubscriberRemoved() {
if (output_device_callback_list_.empty())
monitor_->StopMonitoring();
} }
...@@ -5,11 +5,15 @@ ...@@ -5,11 +5,15 @@
#ifndef CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_PROVIDER_IMPL_H_ #ifndef CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_PROVIDER_IMPL_H_
#define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_PROVIDER_IMPL_H_ #define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_DEVICE_PROVIDER_IMPL_H_
#include <memory>
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "chrome/browser/ui/global_media_controls/media_notification_device_monitor.h"
#include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h" #include "chrome/browser/ui/global_media_controls/media_notification_device_provider.h"
#include "media/audio/audio_device_description.h"
class MediaNotificationDeviceProviderImpl class MediaNotificationDeviceProviderImpl
: public MediaNotificationDeviceProvider { : public MediaNotificationDeviceProvider,
public MediaNotificationDeviceMonitor::DevicesChangedObserver {
public: public:
MediaNotificationDeviceProviderImpl(); MediaNotificationDeviceProviderImpl();
MediaNotificationDeviceProviderImpl( MediaNotificationDeviceProviderImpl(
...@@ -18,19 +22,33 @@ class MediaNotificationDeviceProviderImpl ...@@ -18,19 +22,33 @@ class MediaNotificationDeviceProviderImpl
const MediaNotificationDeviceProviderImpl&) = delete; const MediaNotificationDeviceProviderImpl&) = delete;
~MediaNotificationDeviceProviderImpl() override; ~MediaNotificationDeviceProviderImpl() override;
// MediaNotificationDeviceProvider
std::unique_ptr<MediaNotificationDeviceProvider:: std::unique_ptr<MediaNotificationDeviceProvider::
GetOutputDevicesCallbackList::Subscription> GetOutputDevicesCallbackList::Subscription>
GetOutputDeviceDescriptions( RegisterOutputDeviceDescriptionsCallback(
GetOutputDevicesCallback on_descriptions_cb) override; GetOutputDevicesCallback cb) override;
void GetOutputDeviceDescriptions(
media::AudioSystem::OnDeviceDescriptionsCallback on_descriptions_cb)
override;
// MediaNotificationDeviceMonitor::DevicesChangedObserver
void OnDevicesChanged() override;
private: private:
void OnReceivedDeviceDescriptions( void GetDevices();
media::AudioDeviceDescriptions descriptions);
void NotifySubscribers(media::AudioDeviceDescriptions descriptions);
void OnSubscriberRemoved();
bool is_querying_for_output_devices_ = false; bool is_querying_for_output_devices_ = false;
bool has_device_list_ = false;
MediaNotificationDeviceProvider::GetOutputDevicesCallbackList MediaNotificationDeviceProvider::GetOutputDevicesCallbackList
output_device_callback_list_; output_device_callback_list_;
std::unique_ptr<media::AudioSystem> audio_system_; std::unique_ptr<media::AudioSystem> audio_system_;
std::unique_ptr<MediaNotificationDeviceMonitor> monitor_;
media::AudioDeviceDescriptions audio_device_descriptions_;
base::WeakPtrFactory<MediaNotificationDeviceProviderImpl> weak_ptr_factory_{ base::WeakPtrFactory<MediaNotificationDeviceProviderImpl> weak_ptr_factory_{
this}; this};
......
...@@ -274,9 +274,7 @@ void MediaNotificationService::Session::MarkActiveIfNecessary() { ...@@ -274,9 +274,7 @@ void MediaNotificationService::Session::MarkActiveIfNecessary() {
} }
MediaNotificationService::MediaNotificationService(Profile* profile) MediaNotificationService::MediaNotificationService(Profile* profile)
: overlay_media_notifications_manager_(this), : overlay_media_notifications_manager_(this) {
device_provider_(
std::make_unique<MediaNotificationDeviceProviderImpl>()) {
if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsForCast) && if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsForCast) &&
media_router::MediaRouterEnabled(profile)) { media_router::MediaRouterEnabled(profile)) {
cast_notification_provider_ = cast_notification_provider_ =
...@@ -713,9 +711,12 @@ void MediaNotificationService::OnSessionBecameInactive(const std::string& id) { ...@@ -713,9 +711,12 @@ void MediaNotificationService::OnSessionBecameInactive(const std::string& id) {
std::unique_ptr< std::unique_ptr<
MediaNotificationDeviceProvider::GetOutputDevicesCallbackList::Subscription> MediaNotificationDeviceProvider::GetOutputDevicesCallbackList::Subscription>
MediaNotificationService::GetOutputDevices( MediaNotificationService::RegisterAudioOutputDeviceDescriptionsCallback(
MediaNotificationDeviceProvider::GetOutputDevicesCallback callback) { MediaNotificationDeviceProvider::GetOutputDevicesCallback callback) {
return device_provider_->GetOutputDeviceDescriptions(std::move(callback)); if (!device_provider_)
device_provider_ = std::make_unique<MediaNotificationDeviceProviderImpl>();
return device_provider_->RegisterOutputDeviceDescriptionsCallback(
std::move(callback));
} }
void MediaNotificationService::set_device_provider_for_testing( void MediaNotificationService::set_device_provider_for_testing(
......
...@@ -112,7 +112,7 @@ class MediaNotificationService ...@@ -112,7 +112,7 @@ class MediaNotificationService
// for connected audio output devices. // for connected audio output devices.
std::unique_ptr<MediaNotificationDeviceProvider:: std::unique_ptr<MediaNotificationDeviceProvider::
GetOutputDevicesCallbackList::Subscription> GetOutputDevicesCallbackList::Subscription>
GetOutputDevices( RegisterAudioOutputDeviceDescriptionsCallback(
MediaNotificationDeviceProvider::GetOutputDevicesCallback callback); MediaNotificationDeviceProvider::GetOutputDevicesCallback callback);
void set_device_provider_for_testing( void set_device_provider_for_testing(
......
...@@ -103,13 +103,17 @@ MediaNotificationAudioDeviceSelectorView:: ...@@ -103,13 +103,17 @@ MediaNotificationAudioDeviceSelectorView::
SK_ColorBLACK); SK_ColorBLACK);
// Get a list of the connected audio output devices // Get a list of the connected audio output devices
audio_device_subscription_ = service_->GetOutputDevices(base::BindOnce( audio_device_subscription_ =
&MediaNotificationAudioDeviceSelectorView::UpdateAvailableAudioDevices, service_->RegisterAudioOutputDeviceDescriptionsCallback(
base::BindRepeating(&MediaNotificationAudioDeviceSelectorView::
UpdateAvailableAudioDevices,
weak_ptr_factory_.GetWeakPtr())); weak_ptr_factory_.GetWeakPtr()));
} }
MediaNotificationAudioDeviceSelectorView:: MediaNotificationAudioDeviceSelectorView::
~MediaNotificationAudioDeviceSelectorView() = default; ~MediaNotificationAudioDeviceSelectorView() {
audio_device_subscription_.release();
}
void MediaNotificationAudioDeviceSelectorView::UpdateAvailableAudioDevices( void MediaNotificationAudioDeviceSelectorView::UpdateAvailableAudioDevices(
const media::AudioDeviceDescriptions& device_descriptions) { const media::AudioDeviceDescriptions& device_descriptions) {
...@@ -119,17 +123,23 @@ void MediaNotificationAudioDeviceSelectorView::UpdateAvailableAudioDevices( ...@@ -119,17 +123,23 @@ void MediaNotificationAudioDeviceSelectorView::UpdateAvailableAudioDevices(
for (auto description : device_descriptions) { for (auto description : device_descriptions) {
CreateDeviceButton(description); CreateDeviceButton(description);
} }
UpdateCurrentAudioDevice(current_device_id_); UpdateCurrentAudioDevice(current_device_id_);
} }
void MediaNotificationAudioDeviceSelectorView::UpdateCurrentAudioDevice( void MediaNotificationAudioDeviceSelectorView::UpdateCurrentAudioDevice(
const std::string& current_device_id) { std::string current_device_id) {
auto it = std::find_if(sink_id_map_.begin(), sink_id_map_.end(), auto it = std::find_if(sink_id_map_.begin(), sink_id_map_.end(),
[&current_device_id](auto& item) { [&current_device_id](auto& item) {
return item.second == current_device_id; return item.second == current_device_id;
}); });
DCHECK(it != sink_id_map_.end()); // If the highlighted device is no longer available, highlight the default
// device.
if (it == sink_id_map_.end()) {
return UpdateCurrentAudioDevice(
media::AudioDeviceDescription::kDefaultDeviceId);
}
if (current_device_button_) if (current_device_button_)
current_device_button_->SetProminent(false); current_device_button_->SetProminent(false);
......
...@@ -37,7 +37,7 @@ class MediaNotificationAudioDeviceSelectorView : public views::View, ...@@ -37,7 +37,7 @@ class MediaNotificationAudioDeviceSelectorView : public views::View,
const media::AudioDeviceDescriptions& device_descriptions); const media::AudioDeviceDescriptions& device_descriptions);
// Called when an audio device switch has occurred // Called when an audio device switch has occurred
void UpdateCurrentAudioDevice(const std::string& current_device_id); void UpdateCurrentAudioDevice(std::string current_device_id);
// views::ButtonListener // views::ButtonListener
void ButtonPressed(views::Button* sender, const ui::Event& event) override; void ButtonPressed(views::Button* sender, const ui::Event& event) override;
...@@ -51,6 +51,8 @@ class MediaNotificationAudioDeviceSelectorView : public views::View, ...@@ -51,6 +51,8 @@ class MediaNotificationAudioDeviceSelectorView : public views::View,
CurrentDeviceHighlighted); CurrentDeviceHighlighted);
FRIEND_TEST_ALL_PREFIXES(MediaNotificationAudioDeviceSelectorViewTest, FRIEND_TEST_ALL_PREFIXES(MediaNotificationAudioDeviceSelectorViewTest,
DeviceHighlightedOnChange); DeviceHighlightedOnChange);
FRIEND_TEST_ALL_PREFIXES(MediaNotificationAudioDeviceSelectorViewTest,
DeviceButtonsChange);
void CreateDeviceButton( void CreateDeviceButton(
const media::AudioDeviceDescription& device_description); const media::AudioDeviceDescription& device_description);
......
...@@ -30,18 +30,33 @@ class MockMediaNotificationDeviceProvider ...@@ -30,18 +30,33 @@ class MockMediaNotificationDeviceProvider
~MockMediaNotificationDeviceProvider() override = default; ~MockMediaNotificationDeviceProvider() override = default;
void AddDevice(const std::string& device_name, const std::string& device_id) { void AddDevice(const std::string& device_name, const std::string& device_id) {
device_descriptions.emplace_back(device_name, device_id, ""); device_descriptions_.emplace_back(device_name, device_id, "");
} }
void ResetDevices() { device_descriptions_.clear(); }
void RunUICallback() { output_devices_callback_.Run(device_descriptions_); }
std::unique_ptr<MediaNotificationDeviceProvider:: std::unique_ptr<MediaNotificationDeviceProvider::
GetOutputDevicesCallbackList::Subscription> GetOutputDevicesCallbackList::Subscription>
GetOutputDeviceDescriptions(GetOutputDevicesCallback cb) override { RegisterOutputDeviceDescriptionsCallback(
std::move(cb).Run(device_descriptions); GetOutputDevicesCallback cb) override {
output_devices_callback_ = std::move(cb);
RunUICallback();
return std::unique_ptr<MockMediaNotificationDeviceProvider:: return std::unique_ptr<MockMediaNotificationDeviceProvider::
GetOutputDevicesCallbackList::Subscription>( GetOutputDevicesCallbackList::Subscription>(
nullptr); nullptr);
} }
media::AudioDeviceDescriptions device_descriptions; MOCK_METHOD(void,
GetOutputDeviceDescriptions,
(media::AudioSystem::OnDeviceDescriptionsCallback),
(override));
private:
media::AudioDeviceDescriptions device_descriptions_;
GetOutputDevicesCallback output_devices_callback_;
}; };
class MockMediaNotificationAudioDeviceSelectorViewDelegate class MockMediaNotificationAudioDeviceSelectorViewDelegate
...@@ -192,3 +207,35 @@ TEST_F(MediaNotificationAudioDeviceSelectorViewTest, ...@@ -192,3 +207,35 @@ TEST_F(MediaNotificationAudioDeviceSelectorViewTest,
buttons.begin()); buttons.begin());
EXPECT_EQ(GetButtonText(buttons.front()), "Earbuds"); EXPECT_EQ(GetButtonText(buttons.front()), "Earbuds");
} }
TEST_F(MediaNotificationAudioDeviceSelectorViewTest, DeviceButtonsChange) {
// If the device provider reports a change in connect audio devices, the UI
// should update accordingly.
provider_->AddDevice("Speaker", "1");
provider_->AddDevice("Headphones", "2");
provider_->AddDevice("Earbuds", "3");
auto* provider = provider_.get();
service_->set_device_provider_for_testing(std::move(provider_));
view_ = std::make_unique<MediaNotificationAudioDeviceSelectorView>(
nullptr, service_.get(), gfx::Size(), "1");
std::vector<std::string> button_texts;
ASSERT_TRUE(view_->device_button_container_ != nullptr);
provider->ResetDevices();
// Make "Monitor" the default device.
provider->AddDevice("Monitor",
media::AudioDeviceDescription::kDefaultDeviceId);
provider->RunUICallback();
EXPECT_EQ(view_->device_button_container_->children().size(), 1u);
ASSERT_FALSE(view_->device_button_container_->children().empty());
auto* button = static_cast<const views::MdTextButton*>(
view_->device_button_container_->children().at(0));
EXPECT_EQ(base::UTF16ToUTF8(button->GetText()), "Monitor");
// When the device highlighted in the UI is removed, the UI should fall back
// to highlighting the default device.
EXPECT_TRUE(button->GetProminent());
}
...@@ -5162,6 +5162,7 @@ test("unit_tests") { ...@@ -5162,6 +5162,7 @@ test("unit_tests") {
if (!is_chromeos && is_linux) { if (!is_chromeos && is_linux) {
sources += [ sources += [
"../browser/shell_integration_linux_unittest.cc", "../browser/shell_integration_linux_unittest.cc",
"../browser/ui/global_media_controls/media_notification_device_monitor_unittest.cc",
"../browser/upgrade_detector/get_installed_version_linux_unittest.cc", "../browser/upgrade_detector/get_installed_version_linux_unittest.cc",
] ]
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment