Commit b6a67a3a authored by Tommy Steimel's avatar Tommy Steimel Committed by Commit Bot

GMC: Allow swiping to dismiss media sessions

This CL adds gesture handling to the GMC dialog to allow users on touch
devices to dismiss sessions by swiping.

Bug: 1008905
Change-Id: I115a21ca37af7d128478139d25a1bc0dae4c0c57
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1829671
Commit-Queue: Tommy Steimel <steimel@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#705612}
parent 9f4c5454
......@@ -939,6 +939,8 @@ jumbo_split_static_library("ui") {
"global_error/global_error_service_factory.h",
"global_media_controls/media_dialog_delegate.cc",
"global_media_controls/media_dialog_delegate.h",
"global_media_controls/media_notification_container_impl.h",
"global_media_controls/media_notification_container_observer.h",
"global_media_controls/media_toolbar_button_controller.cc",
"global_media_controls/media_toolbar_button_controller.h",
"global_media_controls/media_toolbar_button_controller_delegate.cc",
......@@ -2825,8 +2827,8 @@ jumbo_split_static_library("ui") {
"views/global_media_controls/media_dialog_view.cc",
"views/global_media_controls/media_dialog_view.h",
"views/global_media_controls/media_dialog_view_observer.h",
"views/global_media_controls/media_notification_container_impl.cc",
"views/global_media_controls/media_notification_container_impl.h",
"views/global_media_controls/media_notification_container_impl_view.cc",
"views/global_media_controls/media_notification_container_impl_view.h",
"views/global_media_controls/media_notification_list_view.cc",
"views/global_media_controls/media_notification_list_view.h",
"views/global_media_controls/media_toolbar_button_view.cc",
......
......@@ -13,13 +13,20 @@ namespace media_message_center {
class MediaNotificationItem;
} // namespace media_message_center
class MediaNotificationContainerImpl;
// Delegate for MediaToolbarButtonController that is told when to display or
// hide a media session.
class MediaDialogDelegate {
public:
virtual void ShowMediaSession(
// Displays a media session and returns a pointer to the
// MediaNotificationContainerImpl that was added to the dialog. The returned
// MediaNotificationContainerImpl is owned by the MediaDialogDelegate.
virtual MediaNotificationContainerImpl* ShowMediaSession(
const std::string& id,
base::WeakPtr<media_message_center::MediaNotificationItem> item) = 0;
// Hides a media session.
virtual void HideMediaSession(const std::string& id) = 0;
protected:
......
// 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.
#ifndef CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_H_
#define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_H_
class MediaNotificationContainerObserver;
class MediaNotificationContainerImpl {
public:
virtual void AddObserver(MediaNotificationContainerObserver* observer) = 0;
virtual void RemoveObserver(MediaNotificationContainerObserver* observer) = 0;
protected:
virtual ~MediaNotificationContainerImpl() = default;
};
#endif // CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_H_
// 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.
#ifndef CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_OBSERVER_H_
#define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_OBSERVER_H_
#include "base/observer_list_types.h"
class MediaNotificationContainerObserver : public base::CheckedObserver {
public:
// Called when the container's expanded state changes.
virtual void OnContainerExpanded(bool expanded) = 0;
// Called when the metadata displayed in the container changes.
virtual void OnContainerMetadataChanged() = 0;
// Called when the container is dismissed from the dialog.
virtual void OnContainerDismissed(const std::string& id) = 0;
// Called when the container is about to be deleted.
virtual void OnContainerDestroyed(const std::string& id) = 0;
protected:
~MediaNotificationContainerObserver() override = default;
};
#endif // CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_OBSERVER_H_
......@@ -9,6 +9,7 @@
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/global_media_controls/media_dialog_delegate.h"
#include "chrome/browser/ui/global_media_controls/media_notification_container_impl.h"
#include "chrome/browser/ui/global_media_controls/media_toolbar_button_controller_delegate.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "components/media_message_center/media_notification_item.h"
......@@ -92,7 +93,10 @@ MediaToolbarButtonController::MediaToolbarButtonController(
weak_ptr_factory_.GetWeakPtr()));
}
MediaToolbarButtonController::~MediaToolbarButtonController() = default;
MediaToolbarButtonController::~MediaToolbarButtonController() {
for (auto container_pair : observed_containers_)
container_pair.second->RemoveObserver(this);
}
void MediaToolbarButtonController::OnFocusGained(
media_session::mojom::AudioFocusRequestStatePtr session) {
......@@ -161,7 +165,14 @@ void MediaToolbarButtonController::ShowNotification(const std::string& id) {
if (it != sessions_.end())
item = it->second.item()->GetWeakPtr();
MediaNotificationContainerImpl* container =
dialog_delegate_->ShowMediaSession(id, item);
// Observe the container for dismissal.
if (container) {
container->AddObserver(this);
observed_containers_[id] = container;
}
}
void MediaToolbarButtonController::HideNotification(const std::string& id) {
......@@ -202,6 +213,20 @@ void MediaToolbarButtonController::LogMediaSessionActionButtonPressed(
IsWebContentsFocused(web_contents));
}
void MediaToolbarButtonController::OnContainerDismissed(const std::string& id) {
auto it = sessions_.find(id);
if (it != sessions_.end())
it->second.item()->Dismiss();
}
void MediaToolbarButtonController::OnContainerDestroyed(const std::string& id) {
auto iter = observed_containers_.find(id);
DCHECK(iter != observed_containers_.end());
iter->second->RemoveObserver(this);
observed_containers_.erase(iter);
}
void MediaToolbarButtonController::SetDialogDelegate(
MediaDialogDelegate* delegate) {
DCHECK(!delegate || !dialog_delegate_);
......@@ -219,20 +244,20 @@ void MediaToolbarButtonController::SetDialogDelegate(
if (it != sessions_.end())
item = it->second.item()->GetWeakPtr();
MediaNotificationContainerImpl* container =
dialog_delegate_->ShowMediaSession(id, item);
// Observe the container for dismissal.
if (container) {
container->AddObserver(this);
observed_containers_[id] = container;
}
}
media_message_center::RecordConcurrentNotificationCount(
active_controllable_session_ids_.size());
}
void MediaToolbarButtonController::OnDismissButtonClicked(
const std::string& id) {
auto it = sessions_.find(id);
if (it != sessions_.end())
it->second.item()->Dismiss();
}
void MediaToolbarButtonController::OnReceivedAudioFocusRequests(
std::vector<media_session::mojom::AudioFocusRequestStatePtr> sessions) {
for (auto& session : sessions)
......
......@@ -11,6 +11,7 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ui/global_media_controls/media_notification_container_observer.h"
#include "components/media_message_center/media_notification_controller.h"
#include "content/public/browser/web_contents_observer.h"
#include "mojo/public/cpp/bindings/receiver.h"
......@@ -31,6 +32,7 @@ class Connector;
} // namespace service_manager
class MediaDialogDelegate;
class MediaNotificationContainerImpl;
class MediaToolbarButtonControllerDelegate;
// Controller for the MediaToolbarButtonView that decides when to show or hide
......@@ -38,7 +40,8 @@ class MediaToolbarButtonControllerDelegate;
// MediaDialogView to display.
class MediaToolbarButtonController
: public media_session::mojom::AudioFocusObserver,
public media_message_center::MediaNotificationController {
public media_message_center::MediaNotificationController,
public MediaNotificationContainerObserver {
public:
MediaToolbarButtonController(const base::UnguessableToken& source_id,
service_manager::Connector* connector,
......@@ -58,10 +61,13 @@ class MediaToolbarButtonController
scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override;
void LogMediaSessionActionButtonPressed(const std::string& id) override;
void SetDialogDelegate(MediaDialogDelegate* delegate);
// MediaNotificationContainerObserver implementation.
void OnContainerExpanded(bool expanded) override {}
void OnContainerMetadataChanged() override {}
void OnContainerDismissed(const std::string& id) override;
void OnContainerDestroyed(const std::string& id) override;
// Called when the dismiss button was clicked on a session.
void OnDismissButtonClicked(const std::string& id);
void SetDialogDelegate(MediaDialogDelegate* delegate);
private:
friend class MediaToolbarButtonControllerTest;
......@@ -117,7 +123,10 @@ class MediaToolbarButtonController
// Stores a Session for each media session keyed by its |request_id| in string
// format.
std::map<const std::string, Session> sessions_;
std::map<std::string, Session> sessions_;
// A map of all containers we're currently observing.
std::map<std::string, MediaNotificationContainerImpl*> observed_containers_;
// Connections with the media session service to listen for audio focus
// updates and control media sessions.
......
......@@ -62,7 +62,8 @@ class MockMediaDialogDelegate : public MediaDialogDelegate {
// MediaDialogDelegate implementation.
MOCK_METHOD2(
ShowMediaSession,
void(const std::string& id,
MediaNotificationContainerImpl*(
const std::string& id,
base::WeakPtr<media_message_center::MediaNotificationItem> item));
MOCK_METHOD1(HideMediaSession, void(const std::string& id));
......@@ -165,7 +166,7 @@ class MediaToolbarButtonControllerTest : public testing::Test {
}
void SimulateDismissButtonClicked(const base::UnguessableToken& id) {
controller_->OnDismissButtonClicked(id.ToString());
controller_->OnContainerDismissed(id.ToString());
}
void ExpectHistogramCountRecorded(int count, int size) {
......
......@@ -9,7 +9,7 @@
#include "chrome/browser/ui/global_media_controls/media_toolbar_button_controller.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/global_media_controls/media_dialog_view_observer.h"
#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl.h"
#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
#include "chrome/browser/ui/views/global_media_controls/media_notification_list_view.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "ui/views/background.h"
......@@ -60,16 +60,22 @@ bool MediaDialogView::IsShowing() {
return instance_ != nullptr;
}
void MediaDialogView::ShowMediaSession(
MediaNotificationContainerImpl* MediaDialogView::ShowMediaSession(
const std::string& id,
base::WeakPtr<media_message_center::MediaNotificationItem> item) {
active_sessions_view_->ShowNotification(
id, std::make_unique<MediaNotificationContainerImpl>(this, controller_,
id, item));
auto container =
std::make_unique<MediaNotificationContainerImplView>(id, item);
MediaNotificationContainerImplView* container_ptr = container.get();
container_ptr->AddObserver(this);
observed_containers_[id] = container_ptr;
active_sessions_view_->ShowNotification(id, std::move(container));
OnAnchorBoundsChanged();
for (auto& observer : observers_)
observer.OnMediaSessionShown();
return container_ptr;
}
void MediaDialogView::HideMediaSession(const std::string& id) {
......@@ -118,6 +124,23 @@ gfx::Size MediaDialogView::CalculatePreferredSize() const {
return gfx::Size(width, 1);
}
void MediaDialogView::OnContainerExpanded(bool expanded) {
OnAnchorBoundsChanged();
}
void MediaDialogView::OnContainerMetadataChanged() {
for (auto& observer : observers_)
observer.OnMediaSessionMetadataUpdated();
}
void MediaDialogView::OnContainerDestroyed(const std::string& id) {
auto iter = observed_containers_.find(id);
DCHECK(iter != observed_containers_.end());
iter->second->RemoveObserver(this);
observed_containers_.erase(iter);
}
void MediaDialogView::AddObserver(MediaDialogViewObserver* observer) {
observers_.AddObserver(observer);
}
......@@ -126,12 +149,7 @@ void MediaDialogView::RemoveObserver(MediaDialogViewObserver* observer) {
observers_.RemoveObserver(observer);
}
void MediaDialogView::OnMediaSessionMetadataChanged() {
for (auto& observer : observers_)
observer.OnMediaSessionMetadataUpdated();
}
const std::map<const std::string, MediaNotificationContainerImpl*>&
const std::map<const std::string, MediaNotificationContainerImplView*>&
MediaDialogView::GetNotificationsForTesting() const {
return active_sessions_view_->notifications_for_testing();
}
......@@ -146,7 +164,10 @@ MediaDialogView::MediaDialogView(views::View* anchor_view,
DCHECK(controller_);
}
MediaDialogView::~MediaDialogView() = default;
MediaDialogView::~MediaDialogView() {
for (auto container_pair : observed_containers_)
container_pair.second->RemoveObserver(this);
}
void MediaDialogView::Init() {
// Remove margins.
......
......@@ -8,6 +8,7 @@
#include "base/observer_list.h"
#include "base/optional.h"
#include "chrome/browser/ui/global_media_controls/media_dialog_delegate.h"
#include "chrome/browser/ui/global_media_controls/media_notification_container_observer.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
namespace service_manager {
......@@ -15,13 +16,14 @@ class Connector;
} // namespace service_manager
class MediaDialogViewObserver;
class MediaNotificationContainerImpl;
class MediaNotificationContainerImplView;
class MediaNotificationListView;
class MediaToolbarButtonController;
// Dialog that shows media controls that control the active media session.
class MediaDialogView : public views::BubbleDialogDelegateView,
public MediaDialogDelegate {
public MediaDialogDelegate,
public MediaNotificationContainerObserver {
public:
static void ShowDialog(views::View* anchor_view,
MediaToolbarButtonController* controller,
......@@ -32,7 +34,7 @@ class MediaDialogView : public views::BubbleDialogDelegateView,
static MediaDialogView* GetDialogViewForTesting() { return instance_; }
// MediaDialogDelegate implementation.
void ShowMediaSession(
MediaNotificationContainerImpl* ShowMediaSession(
const std::string& id,
base::WeakPtr<media_message_center::MediaNotificationItem> item) override;
void HideMediaSession(const std::string& id) override;
......@@ -45,12 +47,16 @@ class MediaDialogView : public views::BubbleDialogDelegateView,
void AddedToWidget() override;
gfx::Size CalculatePreferredSize() const override;
// MediaNotificationContainerObserver implementation.
void OnContainerExpanded(bool expanded) override;
void OnContainerMetadataChanged() override;
void OnContainerDismissed(const std::string& id) override {}
void OnContainerDestroyed(const std::string& id) override;
void AddObserver(MediaDialogViewObserver* observer);
void RemoveObserver(MediaDialogViewObserver* observer);
void OnMediaSessionMetadataChanged();
const std::map<const std::string, MediaNotificationContainerImpl*>&
const std::map<const std::string, MediaNotificationContainerImplView*>&
GetNotificationsForTesting() const;
private:
......@@ -74,6 +80,10 @@ class MediaDialogView : public views::BubbleDialogDelegateView,
base::ObserverList<MediaDialogViewObserver> observers_;
// A map of all containers we're currently observing.
std::map<const std::string, MediaNotificationContainerImplView*>
observed_containers_;
DISALLOW_COPY_AND_ASSIGN(MediaDialogView);
};
......
......@@ -10,7 +10,7 @@
#include "chrome/browser/ui/global_media_controls/media_toolbar_button_observer.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/global_media_controls/media_dialog_view_observer.h"
#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl.h"
#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
#include "chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/test/base/in_process_browser_test.h"
......
......@@ -2,14 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl.h"
#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
#include "chrome/browser/ui/global_media_controls/media_notification_container_observer.h"
#include "chrome/browser/ui/global_media_controls/media_toolbar_button_controller.h"
#include "chrome/browser/ui/views/global_media_controls/media_dialog_view.h"
#include "chrome/grit/generated_resources.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/animation/ink_drop_mask.h"
#include "ui/views/animation/slide_out_controller.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
......@@ -33,7 +35,7 @@ constexpr int kMinVisibleActionsForExpanding = 4;
} // anonymous namespace
class MediaNotificationContainerImpl::DismissButton
class MediaNotificationContainerImplView::DismissButton
: public views::ImageButton {
public:
explicit DismissButton(views::ButtonListener* listener)
......@@ -52,23 +54,23 @@ class MediaNotificationContainerImpl::DismissButton
DISALLOW_COPY_AND_ASSIGN(DismissButton);
};
MediaNotificationContainerImpl::MediaNotificationContainerImpl(
MediaDialogView* parent,
MediaToolbarButtonController* controller,
MediaNotificationContainerImplView::MediaNotificationContainerImplView(
const std::string& id,
base::WeakPtr<media_message_center::MediaNotificationItem> item)
: parent_(parent),
controller_(controller),
id_(id),
: id_(id),
foreground_color_(kDefaultForegroundColor),
background_color_(kDefaultBackgroundColor) {
DCHECK(parent_);
DCHECK(controller_);
SetLayoutManager(std::make_unique<views::FillLayout>());
SetPreferredSize(kNormalSize);
swipeable_container_ = std::make_unique<views::View>();
swipeable_container_->set_owned_by_client();
swipeable_container_->SetLayoutManager(std::make_unique<views::FillLayout>());
swipeable_container_->SetPaintToLayer();
swipeable_container_->layer()->SetFillsBoundsOpaquely(false);
AddChildView(swipeable_container_.get());
dismiss_button_container_ = std::make_unique<views::View>();
dismiss_button_container_->set_owned_by_client();
dismiss_button_container_->SetPreferredSize(kDismissButtonSize);
......@@ -90,35 +92,44 @@ MediaNotificationContainerImpl::MediaNotificationContainerImpl(
view_->set_owned_by_client();
ForceExpandedState();
AddChildView(view_.get());
swipeable_container_->AddChildView(view_.get());
slide_out_controller_ =
std::make_unique<views::SlideOutController>(this, this);
}
MediaNotificationContainerImpl::~MediaNotificationContainerImpl() = default;
MediaNotificationContainerImplView::~MediaNotificationContainerImplView() {
for (auto& observer : observers_)
observer.OnContainerDestroyed(id_);
}
void MediaNotificationContainerImpl::OnExpanded(bool expanded) {
void MediaNotificationContainerImplView::OnExpanded(bool expanded) {
SetPreferredSize(expanded ? kExpandedSize : kNormalSize);
PreferredSizeChanged();
parent_->OnAnchorBoundsChanged();
for (auto& observer : observers_)
observer.OnContainerExpanded(expanded);
}
void MediaNotificationContainerImpl::OnMediaSessionInfoChanged(
void MediaNotificationContainerImplView::OnMediaSessionInfoChanged(
const media_session::mojom::MediaSessionInfoPtr& session_info) {
dismiss_button_container_->SetVisible(
!session_info || session_info->playback_state !=
media_session::mojom::MediaPlaybackState::kPlaying);
}
void MediaNotificationContainerImpl::OnMediaSessionMetadataChanged() {
parent_->OnMediaSessionMetadataChanged();
void MediaNotificationContainerImplView::OnMediaSessionMetadataChanged() {
for (auto& observer : observers_)
observer.OnContainerMetadataChanged();
}
void MediaNotificationContainerImpl::OnVisibleActionsChanged(
void MediaNotificationContainerImplView::OnVisibleActionsChanged(
const std::set<media_session::mojom::MediaSessionAction>& actions) {
has_many_actions_ = actions.size() >= kMinVisibleActionsForExpanding;
ForceExpandedState();
}
void MediaNotificationContainerImpl::OnMediaArtworkChanged(
void MediaNotificationContainerImplView::OnMediaArtworkChanged(
const gfx::ImageSkia& image) {
has_artwork_ = !image.isNull();
......@@ -126,7 +137,7 @@ void MediaNotificationContainerImpl::OnMediaArtworkChanged(
ForceExpandedState();
}
void MediaNotificationContainerImpl::OnColorsChanged(SkColor foreground,
void MediaNotificationContainerImplView::OnColorsChanged(SkColor foreground,
SkColor background) {
if (foreground_color_ != foreground) {
foreground_color_ = foreground;
......@@ -138,19 +149,42 @@ void MediaNotificationContainerImpl::OnColorsChanged(SkColor foreground,
}
}
void MediaNotificationContainerImpl::ButtonPressed(views::Button* sender,
ui::Layer* MediaNotificationContainerImplView::GetSlideOutLayer() {
return swipeable_container_->layer();
}
void MediaNotificationContainerImplView::OnSlideOut() {
DismissNotification();
}
void MediaNotificationContainerImplView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
DCHECK_EQ(dismiss_button_, sender);
controller_->OnDismissButtonClicked(id_);
DismissNotification();
}
void MediaNotificationContainerImpl::UpdateDismissButtonIcon() {
void MediaNotificationContainerImplView::AddObserver(
MediaNotificationContainerObserver* observer) {
observers_.AddObserver(observer);
}
void MediaNotificationContainerImplView::RemoveObserver(
MediaNotificationContainerObserver* observer) {
observers_.RemoveObserver(observer);
}
views::ImageButton*
MediaNotificationContainerImplView::GetDismissButtonForTesting() {
return dismiss_button_;
}
void MediaNotificationContainerImplView::UpdateDismissButtonIcon() {
views::SetImageFromVectorIconWithColor(
dismiss_button_, vector_icons::kCloseRoundedIcon, kDismissButtonIconSize,
foreground_color_);
}
void MediaNotificationContainerImpl::UpdateDismissButtonBackground() {
void MediaNotificationContainerImplView::UpdateDismissButtonBackground() {
if (!has_artwork_) {
dismiss_button_container_->SetBackground(nullptr);
return;
......@@ -160,7 +194,12 @@ void MediaNotificationContainerImpl::UpdateDismissButtonBackground() {
background_color_, kDismissButtonBackgroundRadius));
}
void MediaNotificationContainerImpl::ForceExpandedState() {
void MediaNotificationContainerImplView::DismissNotification() {
for (auto& observer : observers_)
observer.OnContainerDismissed(id_);
}
void MediaNotificationContainerImplView::ForceExpandedState() {
if (view_) {
bool should_expand = has_many_actions_ || has_artwork_;
view_->SetForcedExpandedState(&should_expand);
......
......@@ -2,14 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_H_
#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_H_
#ifndef CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_VIEW_H_
#include <string>
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "chrome/browser/ui/global_media_controls/media_notification_container_impl.h"
#include "components/media_message_center/media_notification_container.h"
#include "components/media_message_center/media_notification_view.h"
#include "ui/views/animation/slide_out_controller_delegate.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/view.h"
......@@ -17,23 +20,26 @@ namespace media_message_center {
class MediaNotificationItem;
} // namespace media_message_center
class MediaDialogView;
class MediaToolbarButtonController;
namespace views {
class SlideOutController;
} // namespace views
// MediaNotificationContainerImpl holds a media notification for display within
// the MediaDialogView. The media notification shows metadata for a media
class MediaNotificationContainerObserver;
// MediaNotificationContainerImplView holds a media notification for display
// within the MediaDialogView. The media notification shows metadata for a media
// session and can control playback.
class MediaNotificationContainerImpl
class MediaNotificationContainerImplView
: public views::View,
public media_message_center::MediaNotificationContainer,
public MediaNotificationContainerImpl,
public views::SlideOutControllerDelegate,
public views::ButtonListener {
public:
MediaNotificationContainerImpl(
MediaDialogView* parent,
MediaToolbarButtonController* controller,
MediaNotificationContainerImplView(
const std::string& id,
base::WeakPtr<media_message_center::MediaNotificationItem> item);
~MediaNotificationContainerImpl() override;
~MediaNotificationContainerImplView() override;
// media_message_center::MediaNotificationContainer:
void OnExpanded(bool expanded) override;
......@@ -46,9 +52,21 @@ class MediaNotificationContainerImpl
void OnMediaArtworkChanged(const gfx::ImageSkia& image) override;
void OnColorsChanged(SkColor foreground, SkColor background) override;
// views::SlideOutControllerDelegate:
ui::Layer* GetSlideOutLayer() override;
void OnSlideStarted() override {}
void OnSlideChanged(bool in_progress) override {}
void OnSlideOut() override;
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// MediaNotificationContainerImpl:
void AddObserver(MediaNotificationContainerObserver* observer) override;
void RemoveObserver(MediaNotificationContainerObserver* observer) override;
views::ImageButton* GetDismissButtonForTesting();
media_message_center::MediaNotificationView* view_for_testing() {
return view_.get();
}
......@@ -60,20 +78,13 @@ class MediaNotificationContainerImpl
void UpdateDismissButtonBackground();
void DismissNotification();
// Updates the forced expanded state of |view_|.
void ForceExpandedState();
// The MediaNotificationContainerImpl is owned by the
// MediaNotificationListView which is owned by the MediaDialogView, so the raw
// pointer is safe here.
MediaDialogView* const parent_;
// The MediaToolbarButtonController is owned by the MediaToolbarButton which
// outlives the MediaDialogView (and therefore the
// MediaNotificationContainerImpl).
MediaToolbarButtonController* const controller_;
const std::string id_;
std::unique_ptr<views::View> swipeable_container_;
std::unique_ptr<views::View> dismiss_button_container_;
DismissButton* dismiss_button_;
std::unique_ptr<media_message_center::MediaNotificationView> view_;
......@@ -84,7 +95,12 @@ class MediaNotificationContainerImpl
bool has_artwork_ = false;
bool has_many_actions_ = false;
DISALLOW_COPY_AND_ASSIGN(MediaNotificationContainerImpl);
base::ObserverList<MediaNotificationContainerObserver> observers_;
// Handles gesture events for swiping to dismiss notifications.
std::unique_ptr<views::SlideOutController> slide_out_controller_;
DISALLOW_COPY_AND_ASSIGN(MediaNotificationContainerImplView);
};
#endif // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_H_
#endif // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_NOTIFICATION_CONTAINER_IMPL_VIEW_H_
// 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 "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/global_media_controls/media_notification_container_observer.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/test/button_test_api.h"
#include "ui/views/test/views_test_base.h"
using media_session::mojom::MediaPlaybackState;
using media_session::mojom::MediaSessionAction;
using ::testing::_;
namespace {
const char kTestNotificationId[] = "testid";
const char kOtherTestNotificationId[] = "othertestid";
class MockMediaNotificationContainerObserver
: public MediaNotificationContainerObserver {
public:
MockMediaNotificationContainerObserver() = default;
~MockMediaNotificationContainerObserver() = default;
// MediaNotificationContainerObserver implementation.
MOCK_METHOD1(OnContainerExpanded, void(bool expanded));
MOCK_METHOD0(OnContainerMetadataChanged, void());
MOCK_METHOD1(OnContainerDismissed, void(const std::string& id));
MOCK_METHOD1(OnContainerDestroyed, void(const std::string& id));
private:
DISALLOW_COPY_AND_ASSIGN(MockMediaNotificationContainerObserver);
};
} // anonymous namespace
class MediaNotificationContainerImplViewTest : public views::ViewsTestBase {
public:
MediaNotificationContainerImplViewTest() = default;
~MediaNotificationContainerImplViewTest() override = default;
// ViewsTestBase:
void SetUp() override {
ViewsTestBase::SetUp();
views::Widget::InitParams params =
CreateParams(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = gfx::Rect(400, 300);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget_.Init(std::move(params));
views::View* container_view = new views::View();
widget_.SetContentsView(container_view);
auto notification_container =
std::make_unique<MediaNotificationContainerImplView>(
kTestNotificationId, nullptr);
notification_container_ =
container_view->AddChildView(std::move(notification_container));
observer_ = std::make_unique<MockMediaNotificationContainerObserver>();
notification_container_->AddObserver(observer_.get());
SimulateMediaSessionData();
widget_.Show();
}
void TearDown() override {
notification_container_->RemoveObserver(observer_.get());
widget_.Close();
ViewsTestBase::TearDown();
}
void SimulateNotificationSwipedToDismiss() {
// When the notification is swiped, the SlideOutController sends this to the
// MediaNotificationContainerImplView.
notification_container()->OnSlideOut();
}
bool IsDismissButtonVisible() { return GetDismissButton()->IsDrawn(); }
void SimulateDismissButtonClicked() {
ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), 0, 0);
views::test::ButtonTestApi(GetDismissButton()).NotifyClick(event);
}
void SimulateSessionPlaying() { SimulateSessionInfo(true); }
void SimulateSessionPaused() { SimulateSessionInfo(false); }
void SimulateMetadataChanged() {
media_session::MediaMetadata metadata;
metadata.source_title = base::ASCIIToUTF16("source_title2");
metadata.title = base::ASCIIToUTF16("title2");
metadata.artist = base::ASCIIToUTF16("artist2");
GetView()->UpdateWithMediaMetadata(metadata);
}
void SimulateAllActionsEnabled() {
actions_.insert(MediaSessionAction::kPlay);
actions_.insert(MediaSessionAction::kPause);
actions_.insert(MediaSessionAction::kPreviousTrack);
actions_.insert(MediaSessionAction::kNextTrack);
actions_.insert(MediaSessionAction::kSeekBackward);
actions_.insert(MediaSessionAction::kSeekForward);
actions_.insert(MediaSessionAction::kStop);
NotifyUpdatedActions();
}
void SimulateOnlyPlayPauseEnabled() {
actions_.clear();
actions_.insert(MediaSessionAction::kPlay);
actions_.insert(MediaSessionAction::kPause);
NotifyUpdatedActions();
}
void SimulateHasArtwork() {
SkBitmap image;
image.allocN32Pixels(10, 10);
image.eraseColor(SK_ColorMAGENTA);
GetView()->UpdateWithMediaArtwork(
gfx::ImageSkia::CreateFrom1xBitmap(image));
}
void SimulateHasNoArtwork() {
GetView()->UpdateWithMediaArtwork(gfx::ImageSkia());
}
MockMediaNotificationContainerObserver& observer() { return *observer_; }
MediaNotificationContainerImplView* notification_container() {
return notification_container_;
}
private:
void SimulateSessionInfo(bool playing) {
media_session::mojom::MediaSessionInfoPtr session_info(
media_session::mojom::MediaSessionInfo::New());
session_info->playback_state =
playing ? MediaPlaybackState::kPlaying : MediaPlaybackState::kPaused;
session_info->is_controllable = true;
GetView()->UpdateWithMediaSessionInfo(std::move(session_info));
}
void SimulateMediaSessionData() {
SimulateSessionInfo(true);
media_session::MediaMetadata metadata;
metadata.source_title = base::ASCIIToUTF16("source_title");
metadata.title = base::ASCIIToUTF16("title");
metadata.artist = base::ASCIIToUTF16("artist");
GetView()->UpdateWithMediaMetadata(metadata);
SimulateOnlyPlayPauseEnabled();
}
void NotifyUpdatedActions() { GetView()->UpdateWithMediaActions(actions_); }
media_message_center::MediaNotificationView* GetView() {
return notification_container()->view_for_testing();
}
views::ImageButton* GetDismissButton() {
return notification_container()->GetDismissButtonForTesting();
}
views::Widget widget_;
MediaNotificationContainerImplView* notification_container_ = nullptr;
std::unique_ptr<MockMediaNotificationContainerObserver> observer_;
// Set of actions currently enabled.
std::set<MediaSessionAction> actions_;
DISALLOW_COPY_AND_ASSIGN(MediaNotificationContainerImplViewTest);
};
TEST_F(MediaNotificationContainerImplViewTest, SwipeToDismiss) {
EXPECT_CALL(observer(), OnContainerDismissed(kTestNotificationId));
SimulateNotificationSwipedToDismiss();
}
TEST_F(MediaNotificationContainerImplViewTest, ClickToDismiss) {
// We don't show the dismiss button when playing.
SimulateSessionPlaying();
EXPECT_FALSE(IsDismissButtonVisible());
// We only show it when paused.
SimulateSessionPaused();
EXPECT_TRUE(IsDismissButtonVisible());
// Clicking it should inform observers that we've been dismissed.
EXPECT_CALL(observer(), OnContainerDismissed(kTestNotificationId));
SimulateDismissButtonClicked();
}
TEST_F(MediaNotificationContainerImplViewTest, ForceExpandedState) {
bool notification_expanded = false;
EXPECT_CALL(observer(), OnContainerExpanded(_))
.WillRepeatedly([&notification_expanded](bool expanded) {
notification_expanded = expanded;
});
// When we have many actions enabled, we should be forced into the expanded
// state.
SimulateAllActionsEnabled();
EXPECT_TRUE(notification_expanded);
// When we don't have many actions enabled, we should be forced out of the
// expanded state.
SimulateOnlyPlayPauseEnabled();
EXPECT_FALSE(notification_expanded);
// We will also be forced into the expanded state when artwork is present.
SimulateHasArtwork();
EXPECT_TRUE(notification_expanded);
// Once the artwork is gone, we should be forced back out of the expanded
// state.
SimulateHasNoArtwork();
EXPECT_FALSE(notification_expanded);
}
TEST_F(MediaNotificationContainerImplViewTest, SendsMetadataUpdates) {
EXPECT_CALL(observer(), OnContainerMetadataChanged());
SimulateMetadataChanged();
}
TEST_F(MediaNotificationContainerImplViewTest, SendsDestroyedUpdates) {
auto container = std::make_unique<MediaNotificationContainerImplView>(
kOtherTestNotificationId, nullptr);
MockMediaNotificationContainerObserver observer;
container->AddObserver(&observer);
// When the container is destroyed, it should notify the observer.
EXPECT_CALL(observer, OnContainerDestroyed(kOtherTestNotificationId));
container.reset();
testing::Mock::VerifyAndClearExpectations(&observer);
}
......@@ -4,7 +4,7 @@
#include "chrome/browser/ui/views/global_media_controls/media_notification_list_view.h"
#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl.h"
#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl_view.h"
#include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
#include "ui/views/layout/box_layout.h"
......@@ -31,7 +31,7 @@ MediaNotificationListView::~MediaNotificationListView() = default;
void MediaNotificationListView::ShowNotification(
const std::string& id,
std::unique_ptr<MediaNotificationContainerImpl> notification) {
std::unique_ptr<MediaNotificationContainerImplView> notification) {
DCHECK(!base::Contains(notifications_, id));
DCHECK_NE(nullptr, notification.get());
......
......@@ -10,7 +10,7 @@
#include "ui/views/controls/scroll_view.h"
class MediaNotificationContainerImpl;
class MediaNotificationContainerImplView;
// MediaNotificationListView is a container that holds a list of active media
// sessions.
......@@ -21,17 +21,18 @@ class MediaNotificationListView : public views::ScrollView {
void ShowNotification(
const std::string& id,
std::unique_ptr<MediaNotificationContainerImpl> notification);
std::unique_ptr<MediaNotificationContainerImplView> notification);
void HideNotification(const std::string& id);
bool empty() { return notifications_.empty(); }
const std::map<const std::string, MediaNotificationContainerImpl*>&
const std::map<const std::string, MediaNotificationContainerImplView*>&
notifications_for_testing() const {
return notifications_;
}
private:
std::map<const std::string, MediaNotificationContainerImpl*> notifications_;
std::map<const std::string, MediaNotificationContainerImplView*>
notifications_;
DISALLOW_COPY_AND_ASSIGN(MediaNotificationListView);
};
......
......@@ -5002,6 +5002,7 @@ test("unit_tests") {
"../browser/ui/views/frame/web_contents_close_handler_unittest.cc",
"../browser/ui/views/fullscreen_control/fullscreen_control_popup_unittest.cc",
"../browser/ui/views/global_error_bubble_view_unittest.cc",
"../browser/ui/views/global_media_controls/media_notification_container_impl_view_unittest.cc",
"../browser/ui/views/hover_button_unittest.cc",
"../browser/ui/views/infobars/infobar_view_unittest.cc",
"../browser/ui/views/intent_picker_bubble_view_unittest.cc",
......
......@@ -385,7 +385,11 @@ void MediaNotificationView::UpdateActionButtonsVisibility() {
action_button->InvalidateLayout();
}
container_->OnVisibleActionsChanged(visible_actions);
// We want to give the container a list of all possibly visible actions, and
// not just currently visible actions so it can make a decision on whether or
// not to force an expanded state.
container_->OnVisibleActionsChanged(GetTopVisibleActions(
enabled_actions_, ignored_actions, GetMaxNumActions(true)));
}
void MediaNotificationView::UpdateViewForExpandedState() {
......@@ -427,10 +431,9 @@ void MediaNotificationView::UpdateViewForExpandedState() {
}
header_row_->SetExpanded(expanded);
container_->OnExpanded(expanded);
UpdateActionButtonsVisibility();
container_->OnExpanded(expanded);
}
void MediaNotificationView::CreateMediaButton(
......
......@@ -650,10 +650,12 @@ TEST_F(MAYBE_MediaNotificationViewTest, UpdateMetadata_AppName) {
}
TEST_F(MAYBE_MediaNotificationViewTest, Buttons_WhenCollapsed) {
EXPECT_CALL(container(), OnVisibleActionsChanged(std::set<MediaSessionAction>(
{MediaSessionAction::kPlay,
MediaSessionAction::kPreviousTrack,
MediaSessionAction::kNextTrack})));
EXPECT_CALL(
container(),
OnVisibleActionsChanged(std::set<MediaSessionAction>(
{MediaSessionAction::kPlay, MediaSessionAction::kPreviousTrack,
MediaSessionAction::kNextTrack, MediaSessionAction::kSeekBackward,
MediaSessionAction::kSeekForward})));
EnableAllActions();
view()->SetExpanded(false);
testing::Mock::VerifyAndClearExpectations(&container());
......@@ -666,18 +668,21 @@ TEST_F(MAYBE_MediaNotificationViewTest, Buttons_WhenCollapsed) {
EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kSeekBackward));
EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kSeekForward));
EXPECT_CALL(container(),
EXPECT_CALL(
container(),
OnVisibleActionsChanged(std::set<MediaSessionAction>(
{MediaSessionAction::kPlay, MediaSessionAction::kSeekBackward,
MediaSessionAction::kNextTrack})));
MediaSessionAction::kNextTrack, MediaSessionAction::kSeekForward})));
DisableAction(MediaSessionAction::kPreviousTrack);
testing::Mock::VerifyAndClearExpectations(&container());
EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kPreviousTrack));
EXPECT_CALL(container(), OnVisibleActionsChanged(std::set<MediaSessionAction>(
{MediaSessionAction::kPlay,
MediaSessionAction::kPreviousTrack,
MediaSessionAction::kNextTrack})));
EXPECT_CALL(
container(),
OnVisibleActionsChanged(std::set<MediaSessionAction>(
{MediaSessionAction::kPlay, MediaSessionAction::kPreviousTrack,
MediaSessionAction::kNextTrack, MediaSessionAction::kSeekBackward,
MediaSessionAction::kSeekForward})));
EnableAction(MediaSessionAction::kPreviousTrack);
testing::Mock::VerifyAndClearExpectations(&container());
EXPECT_TRUE(IsActionButtonVisible(MediaSessionAction::kPreviousTrack));
......@@ -685,25 +690,30 @@ TEST_F(MAYBE_MediaNotificationViewTest, Buttons_WhenCollapsed) {
EXPECT_CALL(container(), OnVisibleActionsChanged(std::set<MediaSessionAction>(
{MediaSessionAction::kPlay,
MediaSessionAction::kPreviousTrack,
MediaSessionAction::kNextTrack})));
MediaSessionAction::kNextTrack,
MediaSessionAction::kSeekBackward})));
DisableAction(MediaSessionAction::kSeekForward);
testing::Mock::VerifyAndClearExpectations(&container());
EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kSeekForward));
EXPECT_CALL(container(), OnVisibleActionsChanged(std::set<MediaSessionAction>(
{MediaSessionAction::kPlay,
MediaSessionAction::kPreviousTrack,
MediaSessionAction::kNextTrack})));
EXPECT_CALL(
container(),
OnVisibleActionsChanged(std::set<MediaSessionAction>(
{MediaSessionAction::kPlay, MediaSessionAction::kPreviousTrack,
MediaSessionAction::kNextTrack, MediaSessionAction::kSeekBackward,
MediaSessionAction::kSeekForward})));
EnableAction(MediaSessionAction::kSeekForward);
testing::Mock::VerifyAndClearExpectations(&container());
EXPECT_FALSE(IsActionButtonVisible(MediaSessionAction::kSeekForward));
}
TEST_F(MAYBE_MediaNotificationViewTest, Buttons_WhenExpanded) {
EXPECT_CALL(container(), OnVisibleActionsChanged(std::set<MediaSessionAction>(
{MediaSessionAction::kPlay,
MediaSessionAction::kPreviousTrack,
MediaSessionAction::kNextTrack})));
EXPECT_CALL(
container(),
OnVisibleActionsChanged(std::set<MediaSessionAction>(
{MediaSessionAction::kPlay, MediaSessionAction::kPreviousTrack,
MediaSessionAction::kNextTrack, MediaSessionAction::kSeekBackward,
MediaSessionAction::kSeekForward})));
EnableAllActions();
testing::Mock::VerifyAndClearExpectations(&container());
......
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