// 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/notifications/notification_platform_bridge_lacros.h"
#include <string>
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/notifications/notification_platform_bridge_delegate.h"
#include "chromeos/crosapi/mojom/message_center.mojom.h"
#include "chromeos/crosapi/mojom/notification.mojom.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_manager.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "ui/native_theme/native_theme.h"
#include "url/gurl.h"
using base::ASCIIToUTF16;
using gfx::test::AreBitmapsEqual;
using gfx::test::AreImagesEqual;
namespace {
// Tracks user actions that would be passed into chrome's cross-platform
// notification subsystem.
class TestPlatformBridgeDelegate : public NotificationPlatformBridgeDelegate {
public:
TestPlatformBridgeDelegate() = default;
TestPlatformBridgeDelegate(const TestPlatformBridgeDelegate&) = delete;
TestPlatformBridgeDelegate& operator=(const TestPlatformBridgeDelegate&) =
delete;
~TestPlatformBridgeDelegate() override = default;
// NotificationPlatformBridgeDelegate:
void HandleNotificationClosed(const std::string& id, bool by_user) override {
++closed_count_;
}
void HandleNotificationClicked(const std::string& id) override {
++clicked_count_;
}
void HandleNotificationButtonClicked(
const std::string& id,
int button_index,
const std::optional<std::u16string>& reply) override {
++button_clicked_count_;
last_button_clicked_arguments_.emplace(
ButtonClickedArguments{button_index, reply});
}
void HandleNotificationSettingsButtonClicked(const std::string& id) override {
++settings_button_clicked_count_;
}
void DisableNotification(const std::string& id) override {
++disabled_count_;
}
struct ButtonClickedArguments {
int button_index;
std::optional<std::u16string> reply;
};
// Public because this is test code.
int closed_count_ = 0;
int clicked_count_ = 0;
int button_clicked_count_ = 0;
int settings_button_clicked_count_ = 0;
int disabled_count_ = 0;
std::optional<ButtonClickedArguments> last_button_clicked_arguments_;
};
// Simulates MessageCenterAsh in ash-chrome.
class TestMessageCenter : public crosapi::mojom::MessageCenter {
public:
explicit TestMessageCenter(
mojo::PendingReceiver<crosapi::mojom::MessageCenter> receiver)
: receiver_(this, std::move(receiver)) {}
TestMessageCenter(const TestMessageCenter&) = delete;
TestMessageCenter& operator=(const TestMessageCenter&) = delete;
~TestMessageCenter() override = default;
void DisplayNotification(
crosapi::mojom::NotificationPtr notification,
mojo::PendingRemote<crosapi::mojom::NotificationDelegate> delegate)
override {
++display_count_;
last_notification_ = std::move(notification);
// Use unique_ptr to avoid having to deal with unbinding the remote.
last_notification_delegate_remote_ =
std::make_unique<mojo::Remote<crosapi::mojom::NotificationDelegate>>(
std::move(delegate));
}
void CloseNotification(const std::string& id) override {
++close_count_;
last_close_id_ = id;
}
void GetDisplayedNotifications(
GetDisplayedNotificationsCallback callback) override {}
int display_count_ = 0;
std::string last_display_id_;
crosapi::mojom::NotificationPtr last_notification_;
std::unique_ptr<mojo::Remote<crosapi::mojom::NotificationDelegate>>
last_notification_delegate_remote_;
int close_count_ = 0;
std::string last_close_id_;
mojo::Receiver<crosapi::mojom::MessageCenter> receiver_;
};
class NotificationPlatformBridgeLacrosTest : public testing::Test {
public:
NotificationPlatformBridgeLacrosTest() = default;
~NotificationPlatformBridgeLacrosTest() override = default;
// Public because this is test code.
content::BrowserTaskEnvironment task_environment_;
mojo::Remote<crosapi::mojom::MessageCenter> message_center_remote_;
TestMessageCenter test_message_center_{
message_center_remote_.BindNewPipeAndPassReceiver()};
TestPlatformBridgeDelegate bridge_delegate_;
NotificationPlatformBridgeLacros bridge_{&bridge_delegate_,
&message_center_remote_};
};
TEST_F(NotificationPlatformBridgeLacrosTest, SerializationSimple) {
// Create a message_center notification.
message_center::RichNotificationData rich_data;
rich_data.priority = 2;
rich_data.never_timeout = true;
base::Time now = base::Time::Now();
rich_data.timestamp = now;
rich_data.renotify = true;
rich_data.accessible_name = u"accessible_name";
rich_data.fullscreen_visibility =
message_center::FullscreenVisibility::OVER_USER;
rich_data.image_path = base::FilePath("dummy/path");
rich_data.settings_button_handler =
message_center::SettingsButtonHandler::INLINE;
// Create badge and icon with both low DPI and high DPI versions.
gfx::Image badge = gfx::test::CreateImage(1, 2);
badge.AsImageSkia().AddRepresentation(
gfx::ImageSkiaRep(gfx::test::CreateBitmap(2, 4), /*scale=*/2.0f));
rich_data.small_image = badge;
gfx::Image icon = gfx::test::CreateImage(3, 4);
icon.AsImageSkia().AddRepresentation(
gfx::ImageSkiaRep(gfx::test::CreateBitmap(6, 8), /*scale=*/2.0f));
message_center::ButtonInfo button1;
button1.title = u"button1";
message_center::ButtonInfo button2;
button2.title = u"button2";
rich_data.buttons = {button1, button2};
message_center::Notification ui_notification(
message_center::NOTIFICATION_TYPE_SIMPLE, "test_id", u"title", u"message",
ui::ImageModel::FromImage(icon), u"display_source",
GURL("http://example.com/"), message_center::NotifierId(), rich_data,
nullptr);
// Show the notification.
bridge_.Display(NotificationHandler::Type::TRANSIENT, /*profile=*/nullptr,
ui_notification, /*metadata=*/nullptr);
message_center_remote_.FlushForTesting();
EXPECT_EQ(1, test_message_center_.display_count_);
// Fields were serialized properly.
crosapi::mojom::Notification* last_notification =
test_message_center_.last_notification_.get();
EXPECT_EQ("test_id", last_notification->id);
EXPECT_EQ(u"title", last_notification->title);
EXPECT_EQ(u"message", last_notification->message);
EXPECT_EQ(u"display_source", last_notification->display_source);
EXPECT_EQ("http://example.com/", last_notification->origin_url->spec());
EXPECT_EQ(2, last_notification->priority);
EXPECT_TRUE(last_notification->require_interaction);
EXPECT_EQ(now, last_notification->timestamp);
EXPECT_EQ(u"accessible_name", last_notification->accessible_name);
EXPECT_EQ(crosapi::mojom::FullscreenVisibility::kOverUser,
last_notification->fullscreen_visibility);
EXPECT_EQ(last_notification->image_path,
ui_notification.rich_notification_data().image_path);
EXPECT_EQ(crosapi::mojom::SettingsButtonHandler::kInline,
last_notification->settings_button_handler);
ASSERT_FALSE(last_notification->badge.isNull());
EXPECT_TRUE(last_notification->badge_needs_additional_masking_has_value);
EXPECT_TRUE(last_notification->badge.HasRepresentation(1.0f));
EXPECT_TRUE(last_notification->badge.HasRepresentation(2.0f));
EXPECT_TRUE(AreImagesEqual(badge, gfx::Image(last_notification->badge)));
ASSERT_FALSE(last_notification->icon.isNull());
EXPECT_TRUE(last_notification->icon.HasRepresentation(1.0f));
EXPECT_TRUE(last_notification->icon.HasRepresentation(2.0f));
EXPECT_TRUE(AreImagesEqual(icon, gfx::Image(last_notification->icon)));
ASSERT_EQ(2u, last_notification->buttons.size());
EXPECT_EQ(u"button1", last_notification->buttons[0]->title);
EXPECT_EQ(u"button2", last_notification->buttons[1]->title);
}
TEST_F(NotificationPlatformBridgeLacrosTest, SerializationImage) {
// Create a message_center notification.
gfx::Image image = gfx::test::CreateImage(5, 6);
message_center::RichNotificationData rich_data;
rich_data.image = image;
message_center::Notification ui_notification(
message_center::NOTIFICATION_TYPE_IMAGE, "test_id", std::u16string(),
std::u16string(), ui::ImageModel(), std::u16string(), GURL(),
message_center::NotifierId(), rich_data, nullptr);
// Show the notification.
bridge_.Display(NotificationHandler::Type::TRANSIENT, /*profile=*/nullptr,
ui_notification, /*metadata=*/nullptr);
message_center_remote_.FlushForTesting();
// Notification was shown with correct fields.
crosapi::mojom::Notification* last_notification =
test_message_center_.last_notification_.get();
ASSERT_TRUE(last_notification);
ASSERT_FALSE(last_notification->image.isNull());
EXPECT_TRUE(AreImagesEqual(image, gfx::Image(last_notification->image)));
EXPECT_FALSE(last_notification->image_path);
}
TEST_F(NotificationPlatformBridgeLacrosTest, SerializationList) {
// Create a message_center notification.
message_center::NotificationItem item1(u"title1", u"message1");
message_center::NotificationItem item2(u"title2", u"message2");
message_center::RichNotificationData rich_data;
rich_data.items = {item1, item2};
message_center::Notification ui_notification(
message_center::NOTIFICATION_TYPE_MULTIPLE, "test_id", std::u16string(),
std::u16string(), ui::ImageModel(), std::u16string(), GURL(),
message_center::NotifierId(), rich_data, nullptr);
// Show the notification.
bridge_.Display(NotificationHandler::Type::TRANSIENT, /*profile=*/nullptr,
ui_notification, /*metadata=*/nullptr);
message_center_remote_.FlushForTesting();
// Notification was shown with correct fields.
crosapi::mojom::Notification* last_notification =
test_message_center_.last_notification_.get();
ASSERT_TRUE(last_notification);
ASSERT_EQ(2u, last_notification->items.size());
EXPECT_EQ(u"title1", last_notification->items[0]->title);
EXPECT_EQ(u"message1", last_notification->items[0]->message);
EXPECT_EQ(u"title2", last_notification->items[1]->title);
EXPECT_EQ(u"message2", last_notification->items[1]->message);
}
TEST_F(NotificationPlatformBridgeLacrosTest, SerializationProgress) {
// Create a message_center notification.
message_center::RichNotificationData rich_data;
rich_data.progress = 55;
rich_data.progress_status = u"status";
message_center::Notification ui_notification(
message_center::NOTIFICATION_TYPE_PROGRESS, "test_id", std::u16string(),
std::u16string(), ui::ImageModel(), std::u16string(), GURL(),
message_center::NotifierId(), rich_data, nullptr);
// Show the notification.
bridge_.Display(NotificationHandler::Type::TRANSIENT, /*profile=*/nullptr,
ui_notification, /*metadata=*/nullptr);
message_center_remote_.FlushForTesting();
// Notification was shown with correct fields.
crosapi::mojom::Notification* last_notification =
test_message_center_.last_notification_.get();
ASSERT_TRUE(last_notification);
EXPECT_EQ(55, last_notification->progress);
EXPECT_EQ(u"status", last_notification->progress_status);
// Update progress by creating a new notification with the same ID.
message_center::RichNotificationData rich_data2;
rich_data2.progress = 66;
rich_data2.progress_status = u"status2";
message_center::Notification ui_notification2(
message_center::NOTIFICATION_TYPE_PROGRESS, "test_id", std::u16string(),
std::u16string(), ui::ImageModel(), std::u16string(), GURL(),
message_center::NotifierId(), rich_data2, nullptr);
// Update the notification.
bridge_.Display(NotificationHandler::Type::TRANSIENT, /*profile=*/nullptr,
ui_notification2, /*metadata=*/nullptr);
message_center_remote_.FlushForTesting();
// Updated notification was sent.
last_notification = test_message_center_.last_notification_.get();
ASSERT_TRUE(last_notification);
EXPECT_EQ(66, last_notification->progress);
EXPECT_EQ(u"status2", last_notification->progress_status);
}
TEST_F(NotificationPlatformBridgeLacrosTest, SerializationAccentColor) {
const SkColor kTestColor = SkColorSetRGB(0x01, 0x41, 0x33);
// Create a message_center notification.
message_center::RichNotificationData rich_data;
// Set the desired color.
rich_data.accent_color = kTestColor;
message_center::Notification ui_notification(
message_center::NOTIFICATION_TYPE_PROGRESS, "test_id", std::u16string(),
std::u16string(), ui::ImageModel(), std::u16string(), GURL(),
message_center::NotifierId(), rich_data, nullptr);
// Show the notification.
bridge_.Display(NotificationHandler::Type::TRANSIENT, /*profile=*/nullptr,
ui_notification, /*metadata=*/nullptr);
message_center_remote_.FlushForTesting();
// Notification was shown with correct fields.
crosapi::mojom::Notification* last_notification =
test_message_center_.last_notification_.get();
ASSERT_TRUE(last_notification);
EXPECT_THAT(last_notification->accent_color, testing::Optional(kTestColor));
}
TEST_F(NotificationPlatformBridgeLacrosTest, SerializationAccentColorId) {
const SkColor kTestColor = SkColorSetRGB(0xDE, 0xAD, 0xBF);
// Setup color provider
ui::ColorProvider* color_provider =
ui::ColorProviderManager::GetForTesting().GetColorProviderFor(
ui::NativeTheme::GetInstanceForNativeUi()->GetColorProviderKey(
nullptr));
color_provider->SetColorForTesting(ui::kColorSysPrimary, kTestColor);
// Create a message_center notification.
message_center::RichNotificationData rich_data;
rich_data.accent_color_id = ui::kColorSysPrimary;
message_center::Notification ui_notification(
message_center::NOTIFICATION_TYPE_PROGRESS, "test_id", std::u16string(),
std::u16string(), ui::ImageModel(), std::u16string(), GURL(),
message_center::NotifierId(), rich_data, nullptr);
// Show the notification.
bridge_.Display(NotificationHandler::Type::TRANSIENT, /*profile=*/nullptr,
ui_notification, /*metadata=*/nullptr);
message_center_remote_.FlushForTesting();
// Notification was shown with correct fields.
crosapi::mojom::Notification* last_notification =
test_message_center_.last_notification_.get();
ASSERT_TRUE(last_notification);
EXPECT_THAT(last_notification->accent_color, testing::Optional(kTestColor));
}
TEST_F(NotificationPlatformBridgeLacrosTest, UserActions) {
// Create a test notification.
message_center::Notification ui_notification(
message_center::NOTIFICATION_TYPE_SIMPLE, "test_id", std::u16string(),
std::u16string(), ui::ImageModel(), std::u16string(), GURL(),
message_center::NotifierId(), {}, nullptr);
// Show the notification.
bridge_.Display(NotificationHandler::Type::TRANSIENT, /*profile=*/nullptr,
ui_notification, /*metadata=*/nullptr);
message_center_remote_.FlushForTesting();
EXPECT_EQ(1, test_message_center_.display_count_);
// Grab the last notification.
crosapi::mojom::Notification* last_notification =
test_message_center_.last_notification_.get();
EXPECT_EQ("test_id", last_notification->id);
// Grab the mojo::Remote<> for the last notification's delegate.
ASSERT_TRUE(test_message_center_.last_notification_delegate_remote_);
mojo::Remote<crosapi::mojom::NotificationDelegate>&
notification_delegate_remote =
*test_message_center_.last_notification_delegate_remote_;
// Verify remote user actions are forwarded through to |bridge_delegate_|.
notification_delegate_remote->OnNotificationClicked();
notification_delegate_remote.FlushForTesting();
EXPECT_EQ(1, bridge_delegate_.clicked_count_);
notification_delegate_remote->OnNotificationButtonClicked(
/*button_index=*/0, /*reply=*/std::nullopt);
notification_delegate_remote.FlushForTesting();
EXPECT_EQ(1, bridge_delegate_.button_clicked_count_);
ASSERT_TRUE(bridge_delegate_.last_button_clicked_arguments_.has_value());
EXPECT_EQ(0, bridge_delegate_.last_button_clicked_arguments_->button_index);
EXPECT_FALSE(
bridge_delegate_.last_button_clicked_arguments_->reply.has_value());
// Test Inline reply.
notification_delegate_remote->OnNotificationButtonClicked(
/*button_index=*/1, /*reply=*/std::make_optional(u"test"));
notification_delegate_remote.FlushForTesting();
EXPECT_EQ(2, bridge_delegate_.button_clicked_count_);
ASSERT_TRUE(bridge_delegate_.last_button_clicked_arguments_.has_value());
EXPECT_EQ(1, bridge_delegate_.last_button_clicked_arguments_->button_index);
ASSERT_TRUE(
bridge_delegate_.last_button_clicked_arguments_->reply.has_value());
EXPECT_EQ(u"test", bridge_delegate_.last_button_clicked_arguments_->reply);
notification_delegate_remote->OnNotificationSettingsButtonClicked();
notification_delegate_remote.FlushForTesting();
EXPECT_EQ(1, bridge_delegate_.settings_button_clicked_count_);
notification_delegate_remote->OnNotificationDisabled();
notification_delegate_remote.FlushForTesting();
EXPECT_EQ(1, bridge_delegate_.disabled_count_);
// Close the notification.
bridge_.Close(/*profile=*/nullptr, "test_id");
message_center_remote_.FlushForTesting();
EXPECT_EQ(1, test_message_center_.close_count_);
EXPECT_EQ("test_id", test_message_center_.last_close_id_);
}
} // namespace