Commit c83cc719 authored by James Cook's avatar James Cook Committed by Commit Bot

lacros: Rich notification support

Add support for image, list, and progress notifications. This
involves serializing the RichNotificationData structure.

Move some existing crosapi::Bitmap serialization code into a
shared utility file. Add deserialization to SkBitmap. For now
the image format is hard-coded. crbug.com/1116652 tracks more
flexible image serialization.

Add unit tests for both ash side and lacros side.

Bug: 1113889
Test: unit_tests, manual with Notifications Galore chrome app

Change-Id: I27b1c74f828751f35d845e625052386530de9722
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2357749
Commit-Queue: James Cook <jamescook@chromium.org>
Reviewed-by: default avatarJun Mukai <mukai@chromium.org>
Reviewed-by: default avatarGreg Kerr <kerrnel@chromium.org>
Reviewed-by: default avatarDarin Fisher <darin@chromium.org>
Reviewed-by: default avatarErik Chen <erikchen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#799386}
parent 612358a6
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/check.h" #include "base/check.h"
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h" #include "base/numerics/ranges.h"
#include "base/optional.h" #include "base/optional.h"
#include "chromeos/crosapi/cpp/bitmap.h"
#include "chromeos/crosapi/cpp/bitmap_util.h"
#include "chromeos/crosapi/mojom/message_center.mojom.h" #include "chromeos/crosapi/mojom/message_center.mojom.h"
#include "chromeos/crosapi/mojom/notification.mojom.h" #include "chromeos/crosapi/mojom/notification.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/pending_remote.h"
...@@ -37,6 +39,62 @@ mc::NotificationType FromMojo(mojom::NotificationType type) { ...@@ -37,6 +39,62 @@ mc::NotificationType FromMojo(mojom::NotificationType type) {
} }
} }
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;
}
}
gfx::Image ImageFromBitmap(const crosapi::Bitmap& bitmap) {
SkBitmap sk_bitmap = crosapi::SkBitmapFromBitmap(bitmap);
// TODO(https://crbug.com/1113889): High DPI support.
return gfx::Image::CreateFrom1xBitmap(sk_bitmap);
}
std::unique_ptr<mc::Notification> FromMojo(
mojom::NotificationPtr notification) {
mc::RichNotificationData rich_data;
rich_data.priority = base::ClampToRange(notification->priority, -2, 2);
rich_data.never_timeout = notification->require_interaction;
rich_data.timestamp = notification->timestamp;
if (notification->image)
rich_data.image = ImageFromBitmap(notification->image.value());
if (notification->badge)
rich_data.small_image = ImageFromBitmap(notification->badge.value());
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 = base::ClampToRange(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;
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);
gfx::Image icon;
if (notification->icon)
icon = ImageFromBitmap(notification->icon.value());
GURL origin_url = notification->origin_url.value_or(GURL());
// TODO(crbug.com/1113889): NotifierId support.
return std::make_unique<mc::Notification>(
FromMojo(notification->type), notification->id, notification->title,
notification->message, icon, notification->display_source, origin_url,
mc::NotifierId(), rich_data, /*delegate=*/nullptr);
}
// Forwards NotificationDelegate methods to a remote delegate over mojo. If the // Forwards NotificationDelegate methods to a remote delegate over mojo. If the
// remote delegate disconnects (e.g. lacros-chrome crashes) the corresponding // remote delegate disconnects (e.g. lacros-chrome crashes) the corresponding
// notification will be removed. // notification will be removed.
...@@ -116,14 +174,10 @@ void MessageCenterAsh::DisplayNotification( ...@@ -116,14 +174,10 @@ void MessageCenterAsh::DisplayNotification(
notification->id, std::move(delegate)); notification->id, std::move(delegate));
forwarding_delegate->Init(); forwarding_delegate->Init();
// TODO(crbug.com/1113889): Icon support. std::unique_ptr<mc::Notification> mc_notification =
// TODO(crbug.com/1113889): NotifierId support. FromMojo(std::move(notification));
// TODO(crbug.com/1113889): RichNotificationData support. mc_notification->set_delegate(forwarding_delegate);
mc::MessageCenter::Get()->AddNotification(std::make_unique<mc::Notification>( mc::MessageCenter::Get()->AddNotification(std::move(mc_notification));
FromMojo(notification->type), notification->id, notification->title,
notification->message, gfx::Image(), notification->display_source,
notification->origin_url.value_or(GURL()), mc::NotifierId(),
mc::RichNotificationData(), std::move(forwarding_delegate)));
} }
void MessageCenterAsh::CloseNotification(const std::string& id) { void MessageCenterAsh::CloseNotification(const std::string& id) {
......
...@@ -10,10 +10,9 @@ ...@@ -10,10 +10,9 @@
#include "ash/public/cpp/shell_window_ids.h" #include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "chromeos/crosapi/cpp/bitmap.h" #include "chromeos/crosapi/cpp/bitmap.h"
#include "chromeos/crosapi/cpp/bitmap_util.h"
#include "ui/snapshot/snapshot.h" #include "ui/snapshot/snapshot.h"
namespace crosapi { namespace crosapi {
...@@ -119,22 +118,8 @@ void ScreenManagerAsh::OnWindowDestroying(aura::Window* window) { ...@@ -119,22 +118,8 @@ void ScreenManagerAsh::OnWindowDestroying(aura::Window* window) {
void ScreenManagerAsh::DidTakeSnapshot(SnapshotCallback callback, void ScreenManagerAsh::DidTakeSnapshot(SnapshotCallback callback,
gfx::Image image) { gfx::Image image) {
SkBitmap bitmap = image.AsBitmap(); SkBitmap sk_bitmap = image.AsBitmap();
Bitmap snapshot = BitmapFromSkBitmap(sk_bitmap);
// This code currently relies on the assumption that the bitmap is unpadded,
// and uses 4 bytes per pixel.
int size;
size = base::CheckMul(bitmap.width(), bitmap.height()).ValueOrDie();
size = base::CheckMul(size, 4).ValueOrDie();
CHECK_EQ(bitmap.computeByteSize(), base::checked_cast<size_t>(size));
uint8_t* base = static_cast<uint8_t*>(bitmap.getPixels());
std::vector<uint8_t> bytes(base, base + bitmap.computeByteSize());
Bitmap snapshot;
snapshot.width = bitmap.width();
snapshot.height = bitmap.height();
snapshot.pixels.swap(bytes);
std::move(callback).Run(std::move(snapshot)); std::move(callback).Run(std::move(snapshot));
} }
......
...@@ -44,7 +44,7 @@ class ScreenManagerAsh : public mojom::ScreenManager, aura::WindowObserver { ...@@ -44,7 +44,7 @@ class ScreenManagerAsh : public mojom::ScreenManager, aura::WindowObserver {
void OnWindowDestroying(aura::Window* window) final; void OnWindowDestroying(aura::Window* window) final;
private: private:
using SnapshotCallback = base::OnceCallback<void(const Bitmap&)>; using SnapshotCallback = base::OnceCallback<void(Bitmap)>;
void DidTakeSnapshot(SnapshotCallback callback, gfx::Image image); void DidTakeSnapshot(SnapshotCallback callback, gfx::Image image);
// This class generates unique, non-reused IDs for windows on demand. The IDs // This class generates unique, non-reused IDs for windows on demand. The IDs
......
...@@ -8,9 +8,12 @@ ...@@ -8,9 +8,12 @@
#include "base/check.h" #include "base/check.h"
#include "base/notreached.h" #include "base/notreached.h"
#include "base/numerics/ranges.h"
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/optional.h" #include "base/optional.h"
#include "chrome/browser/notifications/notification_platform_bridge_delegate.h" #include "chrome/browser/notifications/notification_platform_bridge_delegate.h"
#include "chromeos/crosapi/cpp/bitmap.h"
#include "chromeos/crosapi/cpp/bitmap_util.h"
#include "chromeos/crosapi/mojom/message_center.mojom.h" #include "chromeos/crosapi/mojom/message_center.mojom.h"
#include "chromeos/crosapi/mojom/notification.mojom.h" #include "chromeos/crosapi/mojom/notification.mojom.h"
#include "ui/message_center/public/cpp/notification.h" #include "ui/message_center/public/cpp/notification.h"
...@@ -37,6 +40,62 @@ crosapi::mojom::NotificationType ToMojo(message_center::NotificationType type) { ...@@ -37,6 +40,62 @@ crosapi::mojom::NotificationType ToMojo(message_center::NotificationType type) {
} }
} }
crosapi::mojom::FullscreenVisibility ToMojo(
message_center::FullscreenVisibility visibility) {
switch (visibility) {
case message_center::FullscreenVisibility::NONE:
return crosapi::mojom::FullscreenVisibility::kNone;
case message_center::FullscreenVisibility::OVER_USER:
return crosapi::mojom::FullscreenVisibility::kOverUser;
}
}
crosapi::mojom::NotificationPtr ToMojo(
const message_center::Notification& notification) {
auto mojo_note = crosapi::mojom::Notification::New();
mojo_note->type = ToMojo(notification.type());
mojo_note->id = notification.id();
mojo_note->title = notification.title();
mojo_note->message = notification.message();
mojo_note->display_source = notification.display_source();
mojo_note->origin_url = notification.origin_url();
if (!notification.icon().IsEmpty()) {
SkBitmap icon = notification.icon().AsBitmap();
mojo_note->icon = crosapi::BitmapFromSkBitmap(icon);
}
mojo_note->priority = base::ClampToRange(notification.priority(), -2, 2);
mojo_note->require_interaction = notification.never_timeout();
mojo_note->timestamp = notification.timestamp();
if (!notification.image().IsEmpty()) {
SkBitmap image = notification.image().AsBitmap();
mojo_note->image = crosapi::BitmapFromSkBitmap(image);
}
if (!notification.small_image().IsEmpty()) {
SkBitmap badge = notification.small_image().AsBitmap();
mojo_note->badge = crosapi::BitmapFromSkBitmap(badge);
}
for (const auto& item : notification.items()) {
auto mojo_item = crosapi::mojom::NotificationItem::New();
mojo_item->title = item.title;
mojo_item->message = item.message;
mojo_note->items.push_back(std::move(mojo_item));
}
mojo_note->progress = base::ClampToRange(notification.progress(), -1, 100);
mojo_note->progress_status = notification.progress_status();
for (const auto& button : notification.buttons()) {
auto mojo_button = crosapi::mojom::ButtonInfo::New();
mojo_button->title = button.title;
mojo_note->buttons.push_back(std::move(mojo_button));
}
mojo_note->pinned = notification.pinned();
mojo_note->renotify = notification.renotify();
mojo_note->silent = notification.silent();
mojo_note->accessible_name = notification.accessible_name();
mojo_note->fullscreen_visibility =
ToMojo(notification.fullscreen_visibility());
return mojo_note;
}
} // namespace } // namespace
// Keeps track of notifications being displayed in the remote message center. // Keeps track of notifications being displayed in the remote message center.
...@@ -118,15 +177,6 @@ void NotificationPlatformBridgeLacros::Display( ...@@ -118,15 +177,6 @@ void NotificationPlatformBridgeLacros::Display(
// NotificationPlatformBridgeChromeOs, which includes a profile ID as part of // NotificationPlatformBridgeChromeOs, which includes a profile ID as part of
// the notification ID. Lacros does not support Chrome OS multi-signin, so we // the notification ID. Lacros does not support Chrome OS multi-signin, so we
// don't need to handle inactive user notification blockers in ash. // don't need to handle inactive user notification blockers in ash.
auto note = crosapi::mojom::Notification::New();
note->type = ToMojo(notification.type());
note->id = notification.id();
note->title = notification.title();
note->message = notification.message();
note->display_source = notification.display_source();
note->origin_url = notification.origin_url();
// TODO(https://crbug.com/1113889): Icon.
// TODO(https://crbug.com/1113889): RichNotificationData fields.
// Clean up any old notification with the same ID before creating the new one. // Clean up any old notification with the same ID before creating the new one.
remote_notifications_.erase(notification.id()); remote_notifications_.erase(notification.id());
...@@ -134,7 +184,7 @@ void NotificationPlatformBridgeLacros::Display( ...@@ -134,7 +184,7 @@ void NotificationPlatformBridgeLacros::Display(
auto pending_notification = std::make_unique<RemoteNotificationDelegate>( auto pending_notification = std::make_unique<RemoteNotificationDelegate>(
notification.id(), bridge_delegate_, weak_factory_.GetWeakPtr()); notification.id(), bridge_delegate_, weak_factory_.GetWeakPtr());
(*message_center_remote_) (*message_center_remote_)
->DisplayNotification(std::move(note), ->DisplayNotification(ToMojo(notification),
pending_notification->BindNotificationDelegate()); pending_notification->BindNotificationDelegate());
remote_notifications_[notification.id()] = std::move(pending_notification); remote_notifications_[notification.id()] = std::move(pending_notification);
} }
......
...@@ -12,7 +12,12 @@ component("cpp") { ...@@ -12,7 +12,12 @@ component("cpp") {
sources = [ sources = [
"bitmap.cc", "bitmap.cc",
"bitmap.h", "bitmap.h",
"bitmap_util.cc",
"bitmap_util.h",
] ]
configs += [ ":crosapi_implementation" ] configs += [ ":crosapi_implementation" ]
deps = [ "//base" ] deps = [
"//base",
"//skia",
]
} }
include_rules = [
"+third_party/skia/include/core",
]
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
namespace crosapi { namespace crosapi {
Bitmap::Bitmap() = default; Bitmap::Bitmap() = default;
Bitmap::Bitmap(Bitmap&& other) = default;
Bitmap& Bitmap::operator=(Bitmap&& other) = default;
Bitmap::~Bitmap() = default; Bitmap::~Bitmap() = default;
} // namespace crosapi } // namespace crosapi
...@@ -14,9 +14,13 @@ ...@@ -14,9 +14,13 @@
namespace crosapi { namespace crosapi {
// A 4-byte RGBA bitmap representation. Its size must be exactly equal to // A 4-byte RGBA bitmap representation. Its size must be exactly equal to
// width * height * 4. // width * height * 4. Move-only because copying |pixels| is expensive.
struct COMPONENT_EXPORT(CROSAPI) Bitmap { struct COMPONENT_EXPORT(CROSAPI) Bitmap {
Bitmap(); Bitmap();
Bitmap(const Bitmap&) = delete;
Bitmap& operator=(const Bitmap&) = delete;
Bitmap(Bitmap&& other);
Bitmap& operator=(Bitmap&& other);
~Bitmap(); ~Bitmap();
uint32_t width = 0; uint32_t width = 0;
uint32_t height = 0; uint32_t height = 0;
......
// 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 "chromeos/crosapi/cpp/bitmap_util.h"
#include <algorithm>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "chromeos/crosapi/cpp/bitmap.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkImageInfo.h"
namespace crosapi {
Bitmap BitmapFromSkBitmap(const SkBitmap& sk_bitmap) {
int size;
size = base::CheckMul(sk_bitmap.width(), sk_bitmap.height()).ValueOrDie();
size = base::CheckMul(size, 4).ValueOrDie();
CHECK_EQ(sk_bitmap.computeByteSize(), base::checked_cast<size_t>(size));
uint8_t* base = static_cast<uint8_t*>(sk_bitmap.getPixels());
std::vector<uint8_t> bytes(base, base + sk_bitmap.computeByteSize());
crosapi::Bitmap snapshot;
snapshot.width = sk_bitmap.width();
snapshot.height = sk_bitmap.height();
snapshot.pixels.swap(bytes);
return snapshot;
}
SkBitmap SkBitmapFromBitmap(const Bitmap& snapshot) {
SkImageInfo info =
SkImageInfo::Make(snapshot.width, snapshot.height, kBGRA_8888_SkColorType,
kPremul_SkAlphaType);
CHECK_EQ(info.computeByteSize(info.minRowBytes()), snapshot.pixels.size());
SkBitmap sk_bitmap;
CHECK(sk_bitmap.tryAllocPixels(info));
std::copy(snapshot.pixels.begin(), snapshot.pixels.end(),
static_cast<uint8_t*>(sk_bitmap.getPixels()));
sk_bitmap.notifyPixelsChanged();
return sk_bitmap;
}
} // namespace crosapi
// 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 CHROMEOS_CROSAPI_CPP_BITMAP_UTIL_H_
#define CHROMEOS_CROSAPI_CPP_BITMAP_UTIL_H_
#include "base/component_export.h"
class SkBitmap;
namespace crosapi {
struct Bitmap;
// Converts an SkBitmap to a crosapi::Bitmap. Assumes that the bitmap is
// unpadded, and uses 4 bytes per pixel. CHECK fails if the bitmap has an
// invalid size (e.g. its size is not equal to width * height * 4).
COMPONENT_EXPORT(CROSAPI) Bitmap BitmapFromSkBitmap(const SkBitmap& sk_bitmap);
// Converts a crosapi::Bitmap to an SkBitmap. Assumes the bitmap is 8-bit
// ARGB with premultiplied alpha.
// TODO(https://crbug.com/1116652): Support more flexible image options.
COMPONENT_EXPORT(CROSAPI) SkBitmap SkBitmapFromBitmap(const Bitmap& bitmap);
} // namespace crosapi
#endif // CHROMEOS_CROSAPI_CPP_BITMAP_UTIL_H_
...@@ -21,6 +21,7 @@ mojom("mojom") { ...@@ -21,6 +21,7 @@ mojom("mojom") {
{ {
mojom = "crosapi.mojom.Bitmap" mojom = "crosapi.mojom.Bitmap"
cpp = "crosapi::Bitmap" cpp = "crosapi::Bitmap"
move_only = true
}, },
] ]
traits_headers = [ "//chromeos/crosapi/mojom/bitmap_mojom_traits.h" ] traits_headers = [ "//chromeos/crosapi/mojom/bitmap_mojom_traits.h" ]
......
...@@ -4,8 +4,9 @@ ...@@ -4,8 +4,9 @@
module crosapi.mojom; module crosapi.mojom;
import "mojo/public/mojom/base/time.mojom"; import "chromeos/crosapi/mojom/bitmap.mojom";
import "mojo/public/mojom/base/string16.mojom"; import "mojo/public/mojom/base/string16.mojom";
import "mojo/public/mojom/base/time.mojom";
import "url/mojom/url.mojom"; import "url/mojom/url.mojom";
// Type of notification to show. See the chrome.notifications extension API: // Type of notification to show. See the chrome.notifications extension API:
...@@ -25,6 +26,30 @@ enum NotificationType { ...@@ -25,6 +26,30 @@ enum NotificationType {
kProgress = 3, kProgress = 3,
}; };
// How to display notifications when a fullscreen window is open. For example,
// a child account time limit notification should appear over a fullscreen
// video.
[Extensible]
enum FullscreenVisibility {
// Do not show over fullscreen windows.
kNone = 0,
// Show on top of user-created fullscreen windows.
kOverUser = 1,
};
// List items for NotificationType::kList.
struct NotificationItem {
mojo_base.mojom.String16 title;
mojo_base.mojom.String16 message;
};
// Data for each button. Implemented as a struct for extensibility (e.g. so we
// could add icon buttons).
struct ButtonInfo {
mojo_base.mojom.String16 title;
};
// A notification to show in the system message center. Fields exist to support // A notification to show in the system message center. Fields exist to support
// both the web platform notification API and the chrome.notifications extension // both the web platform notification API and the chrome.notifications extension
// API. See documentation at: // API. See documentation at:
...@@ -51,6 +76,56 @@ struct Notification { ...@@ -51,6 +76,56 @@ struct Notification {
// notification. Otherwise empty. // notification. Otherwise empty.
url.mojom.Url? origin_url; url.mojom.Url? origin_url;
// TODO(https://crbug.com/1113889): Support additional fields, such as images // Icon for the notification.
// (icon, large image), priority, timestamp, buttons, item lists, etc. Bitmap? icon;
// Priority from -2 to 2. Zero is the default. Other values are clamped.
int32 priority;
// User must explicitly dismiss.
bool require_interaction;
// Time at which the notification is applicable (past, present or future).
// See web platform API "timestamp" and chrome.notifications "eventTime".
mojo_base.mojom.Time timestamp;
// For type kImage, the large image.
Bitmap? image;
// Badge to show the source of the notification (e.g. a 16x16 browser icon).
Bitmap? badge;
// Item list for type kList. Not displayed for other types.
array<NotificationItem> items;
// Progress from 0 to 100 for type kProgress. Negative values result in an
// animating "indefinite progress" indicator. Values above 100 are clamped.
int32 progress;
// Status message for type kProgress.
mojo_base.mojom.String16 progress_status;
// Up to 4 buttons. It is not guaranteed that all buttons will be visible,
// especially if the labels are long.
array<ButtonInfo> buttons;
// Pinned notifications cannot be closed by the user, only by a call to
// MessageCenter::CloseNotification.
bool pinned;
// Whether an announcement should occur (e.g. a popup or vibration) when an
// existing notification is updated. See web platform API
// Notification.renotify.
bool renotify;
// Whether all announcement mechanisms should be suppressed. See web platform
// API Notification.silent.
bool silent;
// Accessible description of the notification's contents. If empty, the
// message field will be used instead.
mojo_base.mojom.String16 accessible_name;
// Whether to show on top of fullscreen windows. See enum definition.
FullscreenVisibility fullscreen_visibility;
}; };
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