chromium/ash/system/notification_center/notification_center_controller.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/system/notification_center/notification_center_controller.h"

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/system/notification_center/message_center_constants.h"
#include "ash/system/notification_center/message_center_utils.h"
#include "ash/system/notification_center/message_view_factory.h"
#include "ash/system/notification_center/views/message_view_container.h"
#include "ash/system/notification_center/views/notification_center_view.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/views/message_view.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/view.h"

namespace ash {

namespace {

constexpr gfx::Insets NotificationListDefaultMargins =
    gfx::Insets::VH(kMessageListNotificationSpacing, 0);

}  // namespace

NotificationCenterController::NotificationCenterController() {
  CHECK(features::IsNotificationCenterControllerEnabled());

  auto* message_center = message_center::MessageCenter::Get();
  CHECK(message_center);
  message_center->AddObserver(this);
}

NotificationCenterController::~NotificationCenterController() {
  auto* message_center = message_center::MessageCenter::Get();
  if (message_center) {
    message_center->RemoveObserver(this);
  }
}

std::unique_ptr<views::View>
NotificationCenterController::CreateNotificationCenterView() {
  auto notification_center_view = std::make_unique<NotificationCenterView>();
  notification_center_view_ = notification_center_view.get();
  notification_center_view_tracker_.SetView(notification_center_view_);
  notification_center_view_tracker_.SetIsDeletingCallback(base::BindOnce(
      [](raw_ptr<NotificationCenterView>& notification_center_view) {
        notification_center_view = nullptr;
      },
      std::ref(notification_center_view_)));
  return std::move(notification_center_view);
}

void NotificationCenterController::InitNotificationCenterView() {
  CHECK(notification_center_view_);

  auto notifications =
      message_center_utils::GetSortedNotificationsWithOwnView();

  if (!features::AreOngoingProcessesEnabled()) {
    notification_center_view_->Init(notifications);
    return;
  }

  // Ongoing processes include pinned system notifications exclusively.
  // Any other type of notifications, including non-pinned or non-system
  // (sourced from Web, ARC, etc.) will exist in the regular notification list.
  std::vector<message_center::Notification*> regular_notifications,
      ongoing_processes;

  for (auto* notification : notifications) {
    if (notification->notifier_id().type !=
        message_center::NotifierType::SYSTEM_COMPONENT) {
      regular_notifications.push_back(notification);
      continue;
    }

    if (notification->pinned()) {
      ongoing_processes.push_back(notification);
    } else {
      regular_notifications.push_back(notification);
    }
  }

  auto ongoing_process_list_view =
      views::Builder<views::FlexLayoutView>()
          .SetID(VIEW_ID_NOTIFICATION_BUBBLE_ONGOING_PROCESS_LIST)
          .SetOrientation(views::LayoutOrientation::kVertical)
          .CustomConfigure(base::BindOnce([](views::FlexLayoutView* layout) {
            layout->SetDefault(views::kMarginsKey,
                               NotificationListDefaultMargins);
          }))
          .Build();
  ongoing_process_list_view_ = ongoing_process_list_view.get();

  ongoing_process_list_view_tracker_.SetView(ongoing_process_list_view_);
  ongoing_process_list_view_tracker_.SetIsDeletingCallback(base::BindOnce(
      [](raw_ptr<views::View>& ongoing_process_list_view) {
        ongoing_process_list_view = nullptr;
      },
      std::ref(ongoing_process_list_view_)));

  // TODO(b/322835713): Also create the regular notification list view from the
  // controller instead of from `NotificationCenterView`.
  for (auto* notification : ongoing_processes) {
    AddNotificationChildView(notification);
  }
  UpdateListViewBorders(/*is_ongoing_process=*/true, /*force_update=*/true);

  notification_center_view_->Init(regular_notifications,
                                  std::move(ongoing_process_list_view));
}

MessageViewContainer*
NotificationCenterController::GetOngoingProcessMessageViewContainerById(
    const std::string& id) {
  // TODO(b/322835713): This function should search both regular notification
  // and ongoing process lists when they're all created by this controller.
  auto* list_view = ongoing_process_list_view_.get();

  const auto i = base::ranges::find(
      list_view->children(), id,
      [](const views::View* v) { return AsMVC(v)->GetNotificationId(); });
  return (i == list_view->children().cend()) ? nullptr : AsMVC(*i);
}

void NotificationCenterController::OnNotificationAdded(const std::string& id) {
  if (!features::AreOngoingProcessesEnabled() || !notification_center_view_) {
    return;
  }

  auto* notification =
      message_center::MessageCenter::Get()->FindVisibleNotificationById(id);
  if (!notification) {
    return;
  }

  bool is_ongoing_process = notification->pinned() &&
                            notification->notifier_id().type ==
                                message_center::NotifierType::SYSTEM_COMPONENT;
  if (!is_ongoing_process) {
    // TODO(b/322835713): Also create and manage other notification views from
    // this controller instead of from `NotificationListView`.
    notification_center_view_->OnNotificationAdded(id);
    return;
  }

  if (!ongoing_process_list_view_) {
    return;
  }

  if (GetOngoingProcessMessageViewContainerById(id)) {
    OnNotificationUpdated(id);
    return;
  }

  AddNotificationChildView(notification);
  UpdateListViewBorders(is_ongoing_process);
  notification_center_view_->ListPreferredSizeChanged();
}

void NotificationCenterController::OnNotificationRemoved(const std::string& id,
                                                         bool by_user) {
  if (!notification_center_view_) {
    return;
  }

  notification_center_view_->OnNotificationRemoved(id, by_user);

  if (!features::AreOngoingProcessesEnabled()) {
    return;
  }

  // TODO(b/322835713): Also create and manage other notification views from
  // this controller instead of from `NotificationListView`.
  if (!ongoing_process_list_view_) {
    return;
  }

  auto* message_view_container = GetOngoingProcessMessageViewContainerById(id);
  if (!message_view_container) {
    return;
  }

  ongoing_process_list_view_->RemoveChildView(message_view_container);
  UpdateListViewBorders(/*is_ongoing_process=*/true);
  notification_center_view_->ListPreferredSizeChanged();
}

void NotificationCenterController::OnNotificationUpdated(
    const std::string& id) {
  if (!features::AreOngoingProcessesEnabled() || !notification_center_view_) {
    return;
  }

  auto* notification =
      message_center::MessageCenter::Get()->FindVisibleNotificationById(id);
  if (!notification) {
    return;
  }

  bool is_ongoing_process = notification->pinned() &&
                            notification->notifier_id().type ==
                                message_center::NotifierType::SYSTEM_COMPONENT;
  if (!is_ongoing_process) {
    // TODO(b/322835713): Also manage other notification views from this
    // controller instead of from `NotificationListView`.
    notification_center_view_->OnNotificationUpdated(id);
    return;
  }

  if (!ongoing_process_list_view_) {
    return;
  }

  auto* message_view_container = GetOngoingProcessMessageViewContainerById(id);
  if (!message_view_container) {
    return;
  }

  auto previous_height = message_view_container->CalculateHeight();
  message_view_container->UpdateWithNotification(*notification);

  if (previous_height != message_view_container->CalculateHeight()) {
    notification_center_view_->ListPreferredSizeChanged();
  }
}

void NotificationCenterController::UpdateListViewBorders(
    const bool is_ongoing_process,
    const bool force_update) {
  auto list_view = is_ongoing_process ? ongoing_process_list_view_ : nullptr;
  if (!list_view) {
    return;
  }

  auto children = list_view->children();
  for (views::View* child : children) {
    AsMVC(child)->UpdateBorder(/*is_top=*/child == children.front(),
                               /*is_bottom=*/child == children.back(),
                               force_update);
  }
}

std::unique_ptr<message_center::MessageView>
NotificationCenterController::CreateMessageView(
    const message_center::Notification& notification) {
  CHECK(features::AreOngoingProcessesEnabled());

  auto message_view =
      MessageViewFactory::Create(notification, /*shown_in_popup=*/false);
  if (!notification.group_child()) {
    message_view->SetIsNested();
  }
  notification_center_view_->ConfigureMessageView(message_view.get());
  return message_view;
}

void NotificationCenterController::AddNotificationChildView(
    message_center::Notification* notification) {
  // TODO(b/322835713): Currently only handles ongoing processes. Also create
  // other notifications from this controller.
  auto list_view = ongoing_process_list_view_;

  auto message_view_container = std::make_unique<MessageViewContainer>(
      /*message_view=*/CreateMessageView(*notification));

  // The insertion order for notifications is reversed.
  list_view->AddChildViewAt(std::move(message_view_container),
                            /*index=*/list_view->children().size());

  message_center::MessageCenter::Get()->DisplayedNotification(
      notification->id(), message_center::DISPLAY_SOURCE_MESSAGE_CENTER);
}

const MessageViewContainer* NotificationCenterController::AsMVC(
    const views::View* v) {
  return static_cast<const MessageViewContainer*>(v);
}

MessageViewContainer* NotificationCenterController::AsMVC(views::View* v) {
  return static_cast<MessageViewContainer*>(v);
}

}  // namespace ash