// Copyright 2022 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_tray.h"
#include <memory>
#include <string>
#include "ash/constants/ash_features.h"
#include "ash/constants/tray_background_view_catalog.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/shelf/shelf.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/notification_center/notification_center_bubble.h"
#include "ash/system/notification_center/views/notification_center_view.h"
#include "ash/system/notification_center/notification_metrics_recorder.h"
#include "ash/system/privacy/privacy_indicators_tray_item_view.h"
#include "ash/system/tray/tray_background_view.h"
#include "ash/system/tray/tray_bubble_view.h"
#include "ash/system/tray/tray_container.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/display/screen.h"
namespace ash {
NotificationCenterTray::NotificationCenterTray(Shelf* shelf)
: TrayBackgroundView(shelf,
TrayBackgroundViewCatalogName::kNotificationCenter,
RoundedCornerBehavior::kStartRounded),
notification_grouping_controller_(
std::make_unique<NotificationGroupingController>(this)),
popup_collection_(std::make_unique<AshMessagePopupCollection>(
display::Screen::GetScreen(),
shelf)),
notification_metrics_recorder_(
std::make_unique<NotificationMetricsRecorder>(this)),
notification_icons_controller_(
std::make_unique<NotificationIconsController>(
shelf,
/*notification_center_tray=*/this)) {
SetCallback(base::BindRepeating(&NotificationCenterTray::OnTrayButtonPressed,
base::Unretained(this)));
SetID(VIEW_ID_SA_NOTIFICATION_TRAY);
set_use_bounce_in_animation(false);
tray_container()->SetMargin(
/*main_axis_margin=*/kUnifiedTrayContentPadding -
ShelfConfig::Get()->status_area_hit_region_padding(),
0);
}
NotificationCenterTray::~NotificationCenterTray() {
for (views::View* tray_item : tray_container()->children()) {
static_cast<TrayItemView*>(tray_item)->RemoveObserver(this);
}
}
void NotificationCenterTray::AddNotificationCenterTrayObserver(
Observer* observer) {
observers_.AddObserver(observer);
}
void NotificationCenterTray::RemoveNotificationCenterTrayObserver(
Observer* observer) {
observers_.RemoveObserver(observer);
}
void NotificationCenterTray::OnTrayItemVisibilityAboutToChange(
bool target_visibility) {
// A change in one of this tray's tray items could have implications for this
// tray's overall visibility (e.g. if the only visible tray item wants to
// become hidden, which could happen when dismissing all notifications). We
// need to update this tray's visibility here, before the tray item gets a
// chance to start its own visibility change animation, so that this tray does
// not briefly become empty, for instance.
//
// If the tray item's visibility change does not imply a change in visibility
// for this tray, then `SetVisiblePreferred()` (which is called by
// `UpdateVisibility()`) will do nothing.
UpdateVisibility();
}
void NotificationCenterTray::OnSystemTrayVisibilityChanged(
bool system_tray_visible) {
system_tray_visible_ = system_tray_visible;
UpdateVisibility();
}
void NotificationCenterTray::OnTrayButtonPressed() {
if (GetBubbleWidget()) {
CloseBubble();
return;
}
ShowBubble();
}
NotificationListView* NotificationCenterTray::GetNotificationListView() {
if (!bubble_) {
return nullptr;
}
auto* notification_center_view = bubble_->GetNotificationCenterView();
return notification_center_view
? notification_center_view->notification_list_view()
: nullptr;
}
bool NotificationCenterTray::IsBubbleShown() const {
return !!bubble_;
}
void NotificationCenterTray::Initialize() {
TrayBackgroundView::Initialize();
// Add all child `TrayItemView`s.
// TODO(b/255986529): Rewrite the `NotificationIconsController` class so that
// we do not have to add icon views that are owned by the
// `NotificationCenterTray` from the controller. We should make sure views are
// only added by host views.
notification_icons_controller_->AddNotificationTrayItems(tray_container());
// Privacy indicator is only enabled when Video Conference is disabled.
if (!features::IsVideoConferenceEnabled()) {
privacy_indicators_view_ = tray_container()->AddChildView(
std::make_unique<PrivacyIndicatorsTrayItemView>(shelf()));
}
for (views::View* tray_item : tray_container()->children()) {
static_cast<TrayItemView*>(tray_item)->AddObserver(this);
}
for (auto& observer : observers_) {
observer.OnAllTrayItemsAdded();
}
// Update this tray's visibility as well as the visibility of all of its tray
// items according to the current state of notifications.
UpdateVisibility();
notification_icons_controller_->UpdateNotificationIcons();
notification_icons_controller_->UpdateNotificationIndicators();
}
std::u16string NotificationCenterTray::GetAccessibleNameForBubble() {
return l10n_util::GetStringUTF16(IDS_ASH_MESSAGE_CENTER_ACCESSIBLE_NAME);
}
std::u16string NotificationCenterTray::GetAccessibleNameForTray() {
return notification_icons_controller_->GetAccessibleNameString().value_or(
l10n_util::GetStringUTF16(IDS_ASH_MESSAGE_CENTER_ACCESSIBLE_NAME));
}
void NotificationCenterTray::HandleLocaleChange() {}
void NotificationCenterTray::HideBubbleWithView(
const TrayBubbleView* bubble_view) {
if (bubble_->GetBubbleView() == bubble_view) {
CloseBubble();
}
}
void NotificationCenterTray::HideBubble(const TrayBubbleView* bubble_view) {
CloseBubble();
}
void NotificationCenterTray::ClickedOutsideBubble(
const ui::LocatedEvent& event) {
CloseBubble();
}
void NotificationCenterTray::UpdateTrayItemColor(bool is_active) {
DCHECK(chromeos::features::IsJellyEnabled());
for (views::View* tray_item : tray_container()->children()) {
static_cast<TrayItemView*>(tray_item)->UpdateLabelOrImageViewColor(
is_active);
}
}
void NotificationCenterTray::CloseBubbleInternal() {
if (!bubble_) {
return;
}
bubble_.reset();
SetIsActive(false);
// Inform the message center that the bubble has closed so that popups are
// created for new notifications.
message_center::MessageCenter::Get()->SetVisibility(
message_center::VISIBILITY_TRANSIENT);
}
void NotificationCenterTray::ShowBubble() {
if (bubble_) {
return;
}
// Inform the message center that the bubble is showing so that we do not
// create popups for incoming notifications and dismiss existing popups. This
// needs to happen before the bubble is created so that the
// `NotificationListView` is the active `NotificationViewController` when the
// `NotificationGroupingController` access it. This happens when notifications
// are added to the `NotificationListView`.
message_center::MessageCenter::Get()->SetVisibility(
message_center::VISIBILITY_MESSAGE_CENTER);
bubble_ = std::make_unique<NotificationCenterBubble>(this);
bubble_->ShowBubble();
SetIsActive(true);
}
void NotificationCenterTray::UpdateAfterLoginStatusChange() {
UpdateVisibility();
}
TrayBubbleView* NotificationCenterTray::GetBubbleView() {
return bubble_ ? bubble_->GetBubbleView() : nullptr;
}
views::Widget* NotificationCenterTray::GetBubbleWidget() const {
return bubble_ ? bubble_->GetBubbleWidget() : nullptr;
}
void NotificationCenterTray::UpdateLayout() {
TrayBackgroundView::UpdateLayout();
if (privacy_indicators_view_) {
privacy_indicators_view_->UpdateAlignmentForShelf(shelf());
}
}
void NotificationCenterTray::UpdateVisibility() {
// `NotificationIconsController` handles updating this tray's tray items, so
// no need to do that here.
const bool new_visibility =
message_center::MessageCenter::Get()->NotificationCount() > 0 &&
system_tray_visible_;
SetVisiblePreferred(new_visibility);
if (chromeos::features::IsJellyEnabled()) {
UpdateTrayItemColor(is_active());
}
// We should close the bubble if there are no more notifications to show.
if (!new_visibility && bubble_) {
CloseBubble();
}
}
BEGIN_METADATA(NotificationCenterTray)
END_METADATA
} // namespace ash