chromium/chrome/browser/ash/crosapi/message_center_ash.cc

// 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 <optional>
#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 "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;
  }
}

mc::SettingsButtonHandler FromMojo(
    mojom::SettingsButtonHandler settings_button_handler) {
  switch (settings_button_handler) {
    case mojom::SettingsButtonHandler::kNone:
      return mc::SettingsButtonHandler::NONE;
    case mojom::SettingsButtonHandler::kInline:
      return mc::SettingsButtonHandler::INLINE;
    case mojom::SettingsButtonHandler::kDelegate:
      return mc::SettingsButtonHandler::DELEGATE;
  }
}

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(mojo_item->title, 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;
  rich_data.settings_button_handler =
      FromMojo(notification->settings_button_handler);
  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;
    if (notification->notifier_id->group_key.has_value()) {
      notifier_id.group_key = notification->notifier_id->group_key.value();
    }
  }

  if (notification->image_path) {
    rich_data.image_path = notification->image_path;
  }

  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 std::optional<int>& button_index,
             const std::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