| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/crosapi/message_center_ash.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "chromeos/crosapi/mojom/message_center.mojom.h" |
| #include "chromeos/crosapi/mojom/notification.mojom-shared.h" |
| #include "chromeos/crosapi/mojom/notification.mojom.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/message_center/message_center.h" |
| #include "ui/message_center/public/cpp/notification.h" |
| #include "ui/message_center/public/cpp/notifier_id.h" |
| #include "url/gurl.h" |
| |
| namespace mc = message_center; |
| |
| namespace crosapi { |
| namespace { |
| |
| mc::NotificationType FromMojo(mojom::NotificationType type) { |
| switch (type) { |
| case mojom::NotificationType::kSimple: |
| return mc::NOTIFICATION_TYPE_SIMPLE; |
| case mojom::NotificationType::kImage: |
| return mc::NOTIFICATION_TYPE_IMAGE; |
| case mojom::NotificationType::kList: |
| return mc::NOTIFICATION_TYPE_MULTIPLE; |
| case mojom::NotificationType::kProgress: |
| return mc::NOTIFICATION_TYPE_PROGRESS; |
| } |
| } |
| |
| mc::NotifierType FromMojo(mojom::NotifierType type) { |
| switch (type) { |
| case mojom::NotifierType::kApplication: |
| return mc::NotifierType::APPLICATION; |
| case mojom::NotifierType::kArcApplication: |
| return mc::NotifierType::ARC_APPLICATION; |
| case mojom::NotifierType::kWebPage: |
| return mc::NotifierType::WEB_PAGE; |
| case mojom::NotifierType::kSystemComponent: |
| return mc::NotifierType::SYSTEM_COMPONENT; |
| case mojom::NotifierType::kCrostiniApplication: |
| return mc::NotifierType::CROSTINI_APPLICATION; |
| case mojom::NotifierType::kPhoneHub: |
| return mc::NotifierType::PHONE_HUB; |
| } |
| } |
| |
| mc::FullscreenVisibility FromMojo(mojom::FullscreenVisibility visibility) { |
| switch (visibility) { |
| case mojom::FullscreenVisibility::kNone: |
| return mc::FullscreenVisibility::NONE; |
| case mojom::FullscreenVisibility::kOverUser: |
| return mc::FullscreenVisibility::OVER_USER; |
| } |
| } |
| |
| std::unique_ptr<mc::Notification> FromMojo( |
| mojom::NotificationPtr notification) { |
| mc::RichNotificationData rich_data; |
| rich_data.priority = std::clamp(notification->priority, -2, 2); |
| rich_data.never_timeout = notification->require_interaction; |
| rich_data.timestamp = notification->timestamp; |
| if (!notification->image.isNull()) |
| rich_data.image = gfx::Image(notification->image); |
| if (!notification->badge.isNull()) { |
| rich_data.small_image = gfx::Image(notification->badge); |
| if (notification->badge_needs_additional_masking_has_value) { |
| rich_data.small_image_needs_additional_masking = |
| notification->badge_needs_additional_masking; |
| } |
| } |
| for (const auto& mojo_item : notification->items) { |
| mc::NotificationItem item; |
| item.title = mojo_item->title; |
| item.message = mojo_item->message; |
| rich_data.items.push_back(item); |
| } |
| rich_data.progress = std::clamp(notification->progress, -1, 100); |
| rich_data.progress_status = notification->progress_status; |
| for (const auto& mojo_button : notification->buttons) { |
| mc::ButtonInfo button; |
| button.title = mojo_button->title; |
| button.placeholder = mojo_button->placeholder; |
| rich_data.buttons.push_back(button); |
| } |
| rich_data.pinned = notification->pinned; |
| rich_data.renotify = notification->renotify; |
| rich_data.silent = notification->silent; |
| rich_data.accessible_name = notification->accessible_name; |
| rich_data.fullscreen_visibility = |
| FromMojo(notification->fullscreen_visibility); |
| rich_data.accent_color = notification->accent_color; |
| |
| gfx::Image icon; |
| if (!notification->icon.isNull()) |
| icon = gfx::Image(notification->icon); |
| GURL origin_url = notification->origin_url.value_or(GURL()); |
| |
| mc::NotifierId notifier_id = mc::NotifierId(); |
| if (notification->notifier_id) { |
| notifier_id.type = FromMojo(notification->notifier_id->type); |
| notifier_id.id = notification->notifier_id->id; |
| if (notification->notifier_id->url.has_value()) |
| notifier_id.url = notification->notifier_id->url.value(); |
| if (notification->notifier_id->title.has_value()) |
| notifier_id.title = notification->notifier_id->title; |
| notifier_id.profile_id = notification->notifier_id->profile_id; |
| } |
| |
| return std::make_unique<mc::Notification>( |
| FromMojo(notification->type), notification->id, notification->title, |
| notification->message, ui::ImageModel::FromImage(icon), |
| notification->display_source, origin_url, notifier_id, rich_data, |
| /*delegate=*/nullptr); |
| } |
| |
| // Forwards NotificationDelegate methods to a remote delegate over mojo. If the |
| // remote delegate disconnects (e.g. lacros-chrome crashes) the corresponding |
| // notification will be removed. |
| class ForwardingDelegate : public message_center::NotificationDelegate { |
| public: |
| ForwardingDelegate(const std::string& notification_id, |
| mojo::PendingRemote<mojom::NotificationDelegate> delegate) |
| : notification_id_(notification_id), |
| remote_delegate_(std::move(delegate)) { |
| DCHECK(!notification_id_.empty()); |
| DCHECK(remote_delegate_); |
| } |
| ForwardingDelegate(const ForwardingDelegate&) = delete; |
| ForwardingDelegate& operator=(const ForwardingDelegate&) = delete; |
| |
| void Init() { |
| // Cannot be done in constructor because base::BindOnce() requires a |
| // non-zero reference count. |
| remote_delegate_.set_disconnect_handler( |
| base::BindOnce(&ForwardingDelegate::OnDisconnect, this)); |
| } |
| |
| private: |
| // Private due to ref-counting. |
| ~ForwardingDelegate() override = default; |
| |
| void OnDisconnect() { |
| mc::Notification* notification = |
| mc::MessageCenter::Get()->FindNotificationById(notification_id_); |
| if (!notification) |
| return; |
| // If the disconnect occurred because an existing notification was updated |
| // with new content, don't close it. https://crbug.com/1270544 |
| if (notification->delegate() != this) |
| return; |
| // NOTE: Triggers a call to Close() if the notification is still showing. |
| mc::MessageCenter::Get()->RemoveNotification(notification_id_, |
| /*by_user=*/false); |
| } |
| |
| // message_center::NotificationDelegate: |
| void Close(bool by_user) override { |
| // Can be called after |remote_delegate_| is disconnected. |
| if (remote_delegate_) |
| remote_delegate_->OnNotificationClosed(by_user); |
| } |
| |
| void Click(const absl::optional<int>& button_index, |
| const absl::optional<std::u16string>& reply) override { |
| // The button index comes out of |
| // trusted ash-side message center UI code and is guaranteed not to be |
| // negative. |
| if (button_index) { |
| remote_delegate_->OnNotificationButtonClicked( |
| base::checked_cast<uint32_t>(*button_index), reply); |
| } else { |
| remote_delegate_->OnNotificationClicked(); |
| } |
| } |
| |
| void SettingsClick() override { |
| remote_delegate_->OnNotificationSettingsButtonClicked(); |
| } |
| |
| void DisableNotification() override { |
| remote_delegate_->OnNotificationDisabled(); |
| } |
| |
| const std::string notification_id_; |
| mojo::Remote<mojom::NotificationDelegate> remote_delegate_; |
| }; |
| |
| } // namespace |
| |
| MessageCenterAsh::MessageCenterAsh() = default; |
| |
| MessageCenterAsh::~MessageCenterAsh() = default; |
| |
| void MessageCenterAsh::BindReceiver( |
| mojo::PendingReceiver<mojom::MessageCenter> receiver) { |
| receivers_.Add(this, std::move(receiver)); |
| } |
| |
| void MessageCenterAsh::DisplayNotification( |
| mojom::NotificationPtr notification, |
| mojo::PendingRemote<mojom::NotificationDelegate> delegate) { |
| auto forwarding_delegate = base::MakeRefCounted<ForwardingDelegate>( |
| notification->id, std::move(delegate)); |
| forwarding_delegate->Init(); |
| |
| std::unique_ptr<mc::Notification> mc_notification = |
| FromMojo(std::move(notification)); |
| mc_notification->set_delegate(forwarding_delegate); |
| mc::MessageCenter::Get()->AddNotification(std::move(mc_notification)); |
| } |
| |
| void MessageCenterAsh::CloseNotification(const std::string& id) { |
| mc::MessageCenter::Get()->RemoveNotification(id, /*by_user=*/false); |
| } |
| |
| void MessageCenterAsh::GetDisplayedNotifications( |
| GetDisplayedNotificationsCallback callback) { |
| mc::NotificationList::Notifications notifications = |
| mc::MessageCenter::Get()->GetNotifications(); |
| std::vector<std::string> ids; |
| for (mc::Notification* notification : notifications) |
| ids.push_back(notification->id()); |
| std::move(callback).Run(ids); |
| } |
| |
| } // namespace crosapi |