blob: e7541cded482e3327450214653ab1b6a2371a4d3 [file] [log] [blame]
// 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 "ash/system/unified/notification_icons_controller.h"
#include <optional>
#include <string>
#include "ash/constants/ash_constants.h"
#include "ash/public/cpp/vm_camera_mic_constants.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/system/notification_center/ash_message_center_lock_screen_controller.h"
#include "ash/system/notification_center/message_center_utils.h"
#include "ash/system/notification_center/notification_center_tray.h"
#include "ash/system/status_area_animation_controller.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_container.h"
#include "ash/system/tray/tray_utils.h"
#include "ash/system/unified/notification_counter_view.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/vector_icons.h"
#include "ui/views/controls/image_view.h"
namespace ash {
namespace {
constexpr int kIconsViewDisplaySizeThreshold = 768;
// Maximum number of notification icons shown in the system tray button.
constexpr int kMaxNotificationIconsShown = 2;
const char kCapsLockNotifierId[] = "ash.caps-lock";
const char kBatteryNotificationNotifierId[] = "ash.battery";
const char kUsbNotificationNotifierId[] = "ash.power";
bool ShouldShowNotification(message_center::Notification* notification) {
SessionControllerImpl* session_controller =
Shell::Get()->session_controller();
if (!session_controller->ShouldShowNotificationTray() ||
(session_controller->IsScreenLocked() &&
!AshMessageCenterLockScreenController::IsEnabled())) {
return false;
}
std::string notifier_id = notification->notifier_id().id;
if (message_center::MessageCenter::Get()->IsQuietMode() &&
notifier_id != kCapsLockNotifierId) {
return false;
}
// We don't want to show these notifications since the information is
// already indicated by another item in tray.
if (notifier_id == kVmCameraMicNotifierId ||
notifier_id == kBatteryNotificationNotifierId ||
notifier_id == kUsbNotificationNotifierId ||
notifier_id == kPrivacyIndicatorsNotifierId) {
return false;
}
// We only show notification icon in the tray if it is either:
// * Pinned (generally used for background process such as sharing your
// screen, capslock, etc.).
// * Critical warning (display failure, disk space critically low, etc.).
return notification->pinned() ||
notification->system_notification_warning_level() ==
message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
}
} // namespace
NotificationIconTrayItemView::NotificationIconTrayItemView(
Shelf* shelf,
NotificationIconsController* controller)
: TrayItemView(shelf), controller_(controller) {
CreateImageView();
}
NotificationIconTrayItemView::~NotificationIconTrayItemView() = default;
void NotificationIconTrayItemView::SetNotification(
message_center::Notification* notification) {
notification_id_ = notification->id();
notification_ = notification->DeepCopy(
*notification, GetColorProvider(), /*include_body_image=*/true,
/*include_small_image=*/true, /*include_icon_images=*/true);
UpdateImageViewColor();
image_view()->SetTooltipText(notification->title());
}
void NotificationIconTrayItemView::MaybeReset() {
if ((!target_visible() && IsAnimating()) ||
shelf()
->status_area_widget()
->animation_controller()
->is_hide_animation_scheduled()) {
return;
}
Reset();
}
void NotificationIconTrayItemView::Reset() {
notification_id_ = std::string();
notification_.reset();
image_view()->SetImage(ui::ImageModel());
image_view()->SetTooltipText(std::u16string());
}
void NotificationIconTrayItemView::ImmediatelyUpdateVisibility() {
TrayItemView::ImmediatelyUpdateVisibility();
if (!target_visible()) {
Reset();
}
}
void NotificationIconTrayItemView::AnimationEnded(
const gfx::Animation* animation) {
TrayItemView::AnimationEnded(animation);
if (!target_visible()) {
Reset();
}
}
const std::u16string& NotificationIconTrayItemView::GetAccessibleNameString()
const {
if (notification_id_.empty())
return base::EmptyString16();
return image_view()->GetTooltipText();
}
const std::string& NotificationIconTrayItemView::GetNotificationId() const {
return notification_id_;
}
void NotificationIconTrayItemView::HandleLocaleChange() {}
void NotificationIconTrayItemView::OnThemeChanged() {
TrayItemView::OnThemeChanged();
controller_->UpdateNotificationIcons();
}
void NotificationIconTrayItemView::UpdateLabelOrImageViewColor(bool active) {
TrayItemView::UpdateLabelOrImageViewColor(active);
UpdateImageViewColor();
}
void NotificationIconTrayItemView::UpdateImageViewColor() {
if (!GetWidget() || !notification_) {
return;
}
const auto* color_provider = GetColorProvider();
ui::ColorId color_id = kColorAshIconColorPrimary;
color_id = is_active() ? cros_tokens::kCrosSysSystemOnPrimaryContainer
: cros_tokens::kCrosSysOnSurface;
gfx::Image masked_small_icon = notification_->GenerateMaskedSmallIcon(
kUnifiedTrayIconSize, color_provider->GetColor(color_id),
color_provider->GetColor(ui::kColorNotificationIconBackground),
color_provider->GetColor(ui::kColorNotificationIconForeground));
if (!masked_small_icon.IsEmpty()) {
image_view()->SetImage(ui::ImageModel::FromImage(masked_small_icon));
} else {
image_view()->SetImage(ui::ImageModel::FromVectorIcon(
message_center::kProductIcon, color_id, kUnifiedTrayIconSize));
}
}
BEGIN_METADATA(NotificationIconTrayItemView)
END_METADATA
NotificationIconsController::NotificationIconsController(
Shelf* shelf,
NotificationCenterTray* notification_center_tray)
: shelf_(shelf),
notification_center_tray_(notification_center_tray) {
// `notification_center_tray` should not be null.
DCHECK(notification_center_tray);
// Initialize `icons_view_visible_` according to display size.
UpdateIconsViewVisibleForDisplaySize();
message_center::MessageCenter::Get()->AddObserver(this);
Shell::Get()->session_controller()->AddObserver(this);
}
NotificationIconsController::~NotificationIconsController() {
message_center::MessageCenter::Get()->RemoveObserver(this);
Shell::Get()->session_controller()->RemoveObserver(this);
}
void NotificationIconsController::AddNotificationTrayItems(
TrayContainer* tray_container) {
for (int i = 0; i < kMaxNotificationIconsShown; ++i) {
NotificationIconTrayItemView* tray_item = tray_container->AddChildView(
std::make_unique<NotificationIconTrayItemView>(shelf_,
/*controller=*/this));
tray_items_.push_back(tray_item);
notification_center_tray_->AddTooltipChangedCallbackToNotificationIcon(tray_item);
}
notification_counter_view_ = tray_container->AddChildView(
std::make_unique<NotificationCounterView>(shelf_, /*controller=*/this));
quiet_mode_view_ =
tray_container->AddChildView(std::make_unique<QuietModeView>(shelf_));
}
bool NotificationIconsController::TrayItemHasNotification() const {
return first_unused_item_index_ != 0;
}
size_t NotificationIconsController::TrayNotificationIconsCount() const {
// `first_unused_item_index_` is also the total number of notification icons
// shown in the tray.
return first_unused_item_index_;
}
std::optional<std::u16string>
NotificationIconsController::GetAccessibleNameString() const {
if (quiet_mode_view_ && quiet_mode_view_->GetVisible()) {
return quiet_mode_view_->GetAccessibleNameString();
}
if (!notification_counter_view_) {
return std::nullopt;
}
if (!TrayItemHasNotification()) {
return notification_counter_view_->GetAccessibleNameString();
}
std::vector<std::u16string> status;
status.push_back(l10n_util::GetPluralStringFUTF16(
IDS_ASH_STATUS_TRAY_NOTIFICATIONS_IMPORTANT_COUNT_ACCESSIBLE_NAME,
TrayNotificationIconsCount()));
for (NotificationIconTrayItemView* tray_item : tray_items_) {
status.push_back(tray_item->GetAccessibleNameString());
}
status.push_back(
notification_counter_view_->GetAccessibleNameString().value_or(
std::u16string()));
return l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_TRAY_NOTIFICATIONS_ICONS_ACCESSIBLE_NAME, status, nullptr);
}
void NotificationIconsController::UpdateNotificationIcons() {
// Iterates `tray_items_` and notifications in reverse order so new pinned
// notifications get shown on the left side.
auto notifications =
message_center_utils::GetSortedNotificationsWithOwnView();
auto tray_it = tray_items_.rbegin();
for (auto notification_it = notifications.rbegin();
notification_it != notifications.rend(); ++notification_it) {
if (tray_it == tray_items_.rend()) {
break;
}
if (ShouldShowNotification(*notification_it)) {
(*tray_it)->SetNotification(*notification_it);
(*tray_it)->SetVisible(icons_view_visible_);
++tray_it;
}
}
first_unused_item_index_ = std::distance(tray_items_.rbegin(), tray_it);
notification_center_tray_->UpdateAccessibleName();
for (; tray_it != tray_items_.rend(); ++tray_it) {
// Note: It is important to set the visibility before resetting so that the
// icon image does not disappear while the tray item is still visible.
(*tray_it)->SetVisible(false);
(*tray_it)->MaybeReset();
}
}
void NotificationIconsController::UpdateNotificationIndicators() {
notification_counter_view_->Update();
quiet_mode_view_->Update();
}
void NotificationIconsController::UpdateIconsViewVisibleForDisplaySize() {
aura::Window* window = shelf_->status_area_widget()->GetNativeWindow();
auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(window);
const int display_size =
std::max(display.size().width(), display.size().height());
icons_view_visible_ = display_size >= kIconsViewDisplaySizeThreshold;
}
void NotificationIconsController::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
aura::Window* window = shelf_->status_area_widget()->GetNativeWindow();
if (display::Screen::GetScreen()->GetDisplayNearestWindow(window).id() !=
display.id()) {
return;
}
auto old_icons_view_visible = icons_view_visible_;
UpdateIconsViewVisibleForDisplaySize();
if (old_icons_view_visible == icons_view_visible_)
return;
UpdateNotificationIcons();
UpdateNotificationIndicators();
}
void NotificationIconsController::OnNotificationAdded(const std::string& id) {
message_center::Notification* notification =
message_center::MessageCenter::Get()->FindVisibleNotificationById(id);
// `notification` is null if it is not visible.
if (notification && ShouldShowNotification(notification)) {
// Reset the notification icons if a notification is added since we don't
// know the position where its icon should be added.
UpdateNotificationIcons();
}
UpdateNotificationIndicators();
// There are certain sequences of notification updates that can require
// explicitly updating the notification center tray even after its tray items
// have been updated - for instance, adding an initial notification on the
// lock screen. See http://b/297579552.
notification_center_tray_->UpdateVisibility();
}
void NotificationIconsController::OnNotificationRemoved(const std::string& id,
bool by_user) {
// If the notification removed is displayed in an icon, call update to show
// another notification if needed.
if (GetNotificationIconShownInTray(id))
UpdateNotificationIcons();
UpdateNotificationIndicators();
// There are certain sequences of notification updates that can require
// explicitly updating the notification center tray even after its tray items
// have been updated - for instance, removing a notification group's parent
// notification when the only remaining notifications belong to that group.
// See http://b/296918234.
notification_center_tray_->UpdateVisibility();
}
void NotificationIconsController::OnNotificationUpdated(const std::string& id) {
// A notification update may impact certain notification icon(s) visibility
// in the tray, so update all notification icons.
UpdateNotificationIcons();
UpdateNotificationIndicators();
}
void NotificationIconsController::OnNotificationDisplayed(
const std::string& notification_id,
const message_center::DisplaySource source) {
UpdateNotificationIcons();
UpdateNotificationIndicators();
}
void NotificationIconsController::OnQuietModeChanged(bool in_quiet_mode) {
UpdateNotificationIcons();
UpdateNotificationIndicators();
}
void NotificationIconsController::OnSessionStateChanged(
session_manager::SessionState state) {
UpdateNotificationIcons();
UpdateNotificationIndicators();
}
NotificationIconTrayItemView*
NotificationIconsController::GetNotificationIconShownInTray(
const std::string& id) {
for (NotificationIconTrayItemView* tray_item : tray_items_) {
if (tray_item->GetNotificationId() == id)
return tray_item;
}
return nullptr;
}
} // namespace ash