blob: c7ca64c7fe1476e9bd76ee8307b5976c9108b331 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/media/media_notification_controller_impl.h"
#include "ash/media/media_notification_constants.h"
#include "ash/media/media_notification_container_impl.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/session/session_controller_impl.h"
#include "ash/session/session_observer.h"
#include "ash/shell.h"
#include "base/bind.h"
#include "base/stl_util.h"
#include "components/media_message_center/media_notification_item.h"
#include "components/media_message_center/media_notification_util.h"
#include "services/media_session/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/notification_blocker.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notifier_id.h"
#include "ui/message_center/views/message_view_factory.h"
namespace ash {
namespace {
std::unique_ptr<message_center::MessageView> CreateCustomMediaNotificationView(
const message_center::Notification& notification) {
DCHECK_EQ(kMediaSessionNotificationCustomViewType,
notification.custom_view_type());
auto* controller = Shell::Get()->media_notification_controller();
if (controller)
return controller->CreateMediaNotification(notification);
return nullptr;
}
// MediaNotificationBlocker will block media notifications if the screen is
// locked.
class MediaNotificationBlocker : public message_center::NotificationBlocker,
public SessionObserver {
public:
MediaNotificationBlocker(message_center::MessageCenter* message_center,
SessionControllerImpl* session_controller)
: message_center::NotificationBlocker(message_center),
locked_(session_controller->IsScreenLocked()),
session_controller_(session_controller) {
message_center->AddNotificationBlocker(this);
session_controller->AddObserver(this);
NotifyBlockingStateChanged();
}
~MediaNotificationBlocker() override {
message_center()->RemoveNotificationBlocker(this);
session_controller_->RemoveObserver(this);
}
void CheckState() override {
OnLockStateChanged(session_controller_->IsScreenLocked());
}
bool ShouldShowNotification(
const message_center::Notification& notification) const override {
if (notification.notifier_id() ==
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT,
kMediaSessionNotifierId)) {
return !locked_;
}
return true;
}
bool ShouldShowNotificationAsPopup(
const message_center::Notification& notification) const override {
return ShouldShowNotification(notification);
}
void OnLockStateChanged(bool locked) override {
if (locked == locked_)
return;
locked_ = locked;
NotifyBlockingStateChanged();
}
private:
bool locked_;
SessionControllerImpl* const session_controller_;
DISALLOW_COPY_AND_ASSIGN(MediaNotificationBlocker);
};
} // namespace
MediaNotificationControllerImpl::MediaNotificationControllerImpl(
service_manager::Connector* connector)
: blocker_(std::make_unique<MediaNotificationBlocker>(
message_center::MessageCenter::Get(),
Shell::Get()->session_controller())) {
if (!message_center::MessageViewFactory::HasCustomNotificationViewFactory(
kMediaSessionNotificationCustomViewType)) {
message_center::MessageViewFactory::SetCustomNotificationViewFactory(
kMediaSessionNotificationCustomViewType,
base::BindRepeating(&CreateCustomMediaNotificationView));
}
// |connector| can be null in tests.
if (!connector)
return;
mojo::Remote<media_session::mojom::AudioFocusManager> audio_focus_remote;
connector->Connect(media_session::mojom::kServiceName,
audio_focus_remote.BindNewPipeAndPassReceiver());
connector->Connect(media_session::mojom::kServiceName,
controller_manager_remote.BindNewPipeAndPassReceiver());
audio_focus_remote->AddObserver(
audio_focus_observer_receiver_.BindNewPipeAndPassRemote());
}
MediaNotificationControllerImpl::~MediaNotificationControllerImpl() = default;
void MediaNotificationControllerImpl::OnFocusGained(
media_session::mojom::AudioFocusRequestStatePtr session) {
const std::string id = session->request_id->ToString();
// If we have an existing unfrozen item then this is a duplicate call and
// we should ignore it.
auto it = notifications_.find(id);
if (it != notifications_.end() && !it->second.frozen())
return;
mojo::Remote<media_session::mojom::MediaController> controller;
// |controller_manager_remote| may be null in tests where connector is
// unavailable.
if (controller_manager_remote) {
controller_manager_remote->CreateMediaControllerForSession(
controller.BindNewPipeAndPassReceiver(), *session->request_id);
}
if (it != notifications_.end()) {
// If the notification was previously frozen then we should reset the
// controller because the mojo pipe would have been reset.
it->second.SetController(std::move(controller),
std::move(session->session_info));
} else {
notifications_.emplace(
std::piecewise_construct, std::forward_as_tuple(id),
std::forward_as_tuple(
this, id, session->source_name.value_or(std::string()),
std::move(controller), std::move(session->session_info)));
}
}
void MediaNotificationControllerImpl::OnFocusLost(
media_session::mojom::AudioFocusRequestStatePtr session) {
auto it = notifications_.find(session->request_id->ToString());
if (it == notifications_.end())
return;
// If we lost focus then we should freeze the notification as it may regain
// focus after a second or so.
it->second.Freeze();
}
void MediaNotificationControllerImpl::ShowNotification(const std::string& id) {
// If a notification already exists, do nothing.
if (message_center::MessageCenter::Get()->FindVisibleNotificationById(id))
return;
std::unique_ptr<message_center::Notification> notification =
ash::CreateSystemNotification(
message_center::NotificationType::NOTIFICATION_TYPE_CUSTOM, id,
base::string16(), base::string16(), base::string16(), GURL(),
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT,
kMediaSessionNotifierId),
message_center::RichNotificationData(), nullptr, gfx::VectorIcon(),
message_center::SystemNotificationWarningLevel::NORMAL);
// Set the priority to low to prevent the notification showing as a popup and
// keep it at the bottom of the list.
notification->set_priority(message_center::LOW_PRIORITY);
notification->set_custom_view_type(kMediaSessionNotificationCustomViewType);
message_center::MessageCenter::Get()->AddNotification(
std::move(notification));
media_message_center::RecordConcurrentNotificationCount(
message_center::MessageCenter::Get()
->FindNotificationsByAppId(kMediaSessionNotifierId)
.size());
}
void MediaNotificationControllerImpl::HideNotification(const std::string& id) {
message_center::MessageCenter::Get()->RemoveNotification(id, false);
}
void MediaNotificationControllerImpl::RemoveItem(const std::string& id) {
notifications_.erase(id);
}
scoped_refptr<base::SequencedTaskRunner>
MediaNotificationControllerImpl::GetTaskRunner() const {
return task_runner_for_testing_;
}
std::unique_ptr<MediaNotificationContainerImpl>
MediaNotificationControllerImpl::CreateMediaNotification(
const message_center::Notification& notification) {
base::WeakPtr<media_message_center::MediaNotificationItem> item;
auto it = notifications_.find(notification.id());
if (it != notifications_.end())
item = it->second.GetWeakPtr();
return std::make_unique<MediaNotificationContainerImpl>(notification,
std::move(item));
}
bool MediaNotificationControllerImpl::HasItemForTesting(
const std::string& id) const {
return base::Contains(notifications_, id);
}
} // namespace ash