chromium/chrome/browser/notifications/chrome_ash_message_center_client.cc

// Copyright 2017 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/chrome_ash_message_center_client.h"

#include "ash/public/cpp/notifier_metadata.h"
#include "ash/public/cpp/notifier_settings_observer.h"
#include "base/feature_list.h"
#include "base/i18n/string_compare.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/notifications/arc_application_notifier_controller.h"
#include "chrome/browser/notifications/extension_notifier_controller.h"
#include "chrome/browser/notifications/pwa_notifier_controller.h"
#include "chrome/browser/notifications/web_page_notifier_controller.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chrome/common/webui_url_constants.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notifier_id.h"
#include "url/origin.h"

using message_center::MessageCenter;
using message_center::NotifierId;

namespace {

// The singleton instance, which is tracked to allow access from tests.
ChromeAshMessageCenterClient* g_chrome_ash_message_center_client = nullptr;

// All notifier actions are performed on the notifiers for the currently active
// profile, so this just returns the active profile.
Profile* GetProfileForNotifiers() {
  return ash::ProfileHelper::Get()->GetProfileByUser(
      user_manager::UserManager::Get()->GetActiveUser());
}

class NotifierComparator {
 public:
  explicit NotifierComparator(icu::Collator* collator) : collator_(collator) {}

  bool operator()(const ash::NotifierMetadata& n1,
                  const ash::NotifierMetadata& n2) {
    if (n1.notifier_id.type != n2.notifier_id.type)
      return n1.notifier_id.type < n2.notifier_id.type;

    if (collator_) {
      return base::i18n::CompareString16WithCollator(*collator_, n1.name,
                                                     n2.name) == UCOL_LESS;
    }
    return n1.name < n2.name;
  }

 private:
  raw_ptr<icu::Collator> collator_;
};

// This delegate forwards NotificationDelegate methods to their equivalent in
// NotificationPlatformBridgeDelegate.
class ForwardingNotificationDelegate
    : public message_center::NotificationDelegate {
 public:
  ForwardingNotificationDelegate(const std::string& notification_id,
                                 NotificationPlatformBridgeDelegate* delegate)
      : notification_id_(notification_id), delegate_(delegate) {}
  ForwardingNotificationDelegate(const ForwardingNotificationDelegate&) =
      delete;
  ForwardingNotificationDelegate& operator=(
      const ForwardingNotificationDelegate&) = delete;

  // message_center::NotificationDelegate:
  void Close(bool by_user) override {
    delegate_->HandleNotificationClosed(notification_id_, by_user);
  }

  void Click(const std::optional<int>& button_index,
             const std::optional<std::u16string>& reply) override {
    if (button_index) {
      delegate_->HandleNotificationButtonClicked(notification_id_,
                                                 *button_index, reply);
    } else {
      delegate_->HandleNotificationClicked(notification_id_);
    }
  }

  void SettingsClick() override {
    delegate_->HandleNotificationSettingsButtonClicked(notification_id_);
  }

  void DisableNotification() override {
    delegate_->DisableNotification(notification_id_);
  }

 private:
  ~ForwardingNotificationDelegate() override = default;

  // The ID of the notification.
  const std::string notification_id_;

  raw_ptr<NotificationPlatformBridgeDelegate> delegate_;
};

}  // namespace

ChromeAshMessageCenterClient::ChromeAshMessageCenterClient(
    NotificationPlatformBridgeDelegate* delegate)
    : delegate_(delegate) {
  DCHECK(!g_chrome_ash_message_center_client);
  g_chrome_ash_message_center_client = this;

  if (base::FeatureList::IsEnabled(features::kQuickSettingsPWANotifications)) {
    sources_.insert(
        std::make_pair(message_center::NotifierType::APPLICATION,
                       std::make_unique<PwaNotifierController>(this)));
  } else {
    sources_.insert(
        std::make_pair(message_center::NotifierType::APPLICATION,
                       std::make_unique<ExtensionNotifierController>(this)));

    sources_.insert(
        std::make_pair(message_center::NotifierType::WEB_PAGE,
                       std::make_unique<WebPageNotifierController>(this)));
  }

  sources_.insert(std::make_pair(
      message_center::NotifierType::ARC_APPLICATION,
      std::make_unique<arc::ArcApplicationNotifierController>(this)));
}

ChromeAshMessageCenterClient::~ChromeAshMessageCenterClient() {
  DCHECK_EQ(this, g_chrome_ash_message_center_client);
  g_chrome_ash_message_center_client = nullptr;
}

void ChromeAshMessageCenterClient::Display(
    NotificationHandler::Type notification_type,
    Profile* profile,
    const message_center::Notification& notification,
    std::unique_ptr<NotificationCommon::Metadata> metadata) {
  auto message_center_notification =
      std::make_unique<message_center::Notification>(
          base::WrapRefCounted(
              new ForwardingNotificationDelegate(notification.id(), delegate_)),
          notification);

  // During shutdown, Ash is destroyed before |this|, taking the MessageCenter
  // with it.
  if (MessageCenter::Get()) {
    MessageCenter::Get()->AddNotification(
        std::move(message_center_notification));
  }
}

void ChromeAshMessageCenterClient::Close(Profile* profile,
                                         const std::string& notification_id) {
  // During shutdown, Ash is destroyed before |this|, taking the MessageCenter
  // with it.
  if (MessageCenter::Get()) {
    MessageCenter::Get()->RemoveNotification(notification_id,
                                             false /* by_user */);
  }
}

void ChromeAshMessageCenterClient::GetDisplayed(
    Profile* profile,
    GetDisplayedNotificationsCallback callback) const {
  message_center::NotificationList::Notifications notifications =
      MessageCenter::Get()->GetNotifications();

  std::set<std::string> notification_ids;
  for (message_center::Notification* notification : notifications) {
    notification_ids.insert(notification->id());
  }

  std::move(callback).Run(std::move(notification_ids), /*supports_sync=*/true);
}

void ChromeAshMessageCenterClient::GetDisplayedForOrigin(
    Profile* profile,
    const GURL& origin,
    GetDisplayedNotificationsCallback callback) const {
  message_center::NotificationList::Notifications notifications =
      MessageCenter::Get()->GetNotifications();

  std::set<std::string> notification_ids;
  for (message_center::Notification* notification : notifications) {
    if (url::IsSameOriginWith(notification->origin_url(), origin)) {
      notification_ids.insert(notification->id());
    }
  }

  std::move(callback).Run(std::move(notification_ids), /*supports_sync=*/true);
}

void ChromeAshMessageCenterClient::SetReadyCallback(
    NotificationBridgeReadyCallback callback) {
  // Ash is always available in-process, so report the client is ready.
  std::move(callback).Run(true);
}

void ChromeAshMessageCenterClient::GetNotifiers() {
  if (notifier_observers_.empty())
    return;

  Profile* profile = GetProfileForNotifiers();
  if (!profile) {
    user_manager::UserManager::Get()
        ->GetActiveUser()
        ->AddProfileCreatedObserver(
            base::BindOnce(&ChromeAshMessageCenterClient::GetNotifiers,
                           weak_ptr_.GetWeakPtr()));
    LOG(ERROR) << "GetNotifiers called before profile fully loaded, see "
                  "https://crbug.com/968825";
    return;
  }

  std::vector<ash::NotifierMetadata> notifiers;
  for (auto& source : sources_) {
    auto source_notifiers = source.second->GetNotifierList(profile);
    for (auto& notifier : source_notifiers)
      notifiers.push_back(std::move(notifier));
  }

  UErrorCode error = U_ZERO_ERROR;
  std::unique_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
  NotifierComparator comparator(U_SUCCESS(error) ? collator.get() : nullptr);
  std::sort(notifiers.begin(), notifiers.end(), comparator);

  for (auto& observer : notifier_observers_)
    observer.OnNotifiersUpdated(notifiers);
}

void ChromeAshMessageCenterClient::SetNotifierEnabled(
    const NotifierId& notifier_id,
    bool enabled) {
  sources_[notifier_id.type]->SetNotifierEnabled(GetProfileForNotifiers(),
                                                 notifier_id, enabled);
}

void ChromeAshMessageCenterClient::AddNotifierSettingsObserver(
    ash::NotifierSettingsObserver* observer) {
  notifier_observers_.AddObserver(observer);
}

void ChromeAshMessageCenterClient::RemoveNotifierSettingsObserver(
    ash::NotifierSettingsObserver* observer) {
  notifier_observers_.RemoveObserver(observer);
}

void ChromeAshMessageCenterClient::OnIconImageUpdated(
    const NotifierId& notifier_id,
    const gfx::ImageSkia& image) {
  for (auto& observer : notifier_observers_)
    observer.OnNotifierIconUpdated(notifier_id, image);
}

void ChromeAshMessageCenterClient::OnNotifierEnabledChanged(
    const NotifierId& notifier_id,
    bool enabled) {
  if (!enabled)
    MessageCenter::Get()->RemoveNotificationsForNotifierId(notifier_id);
}