chromium/ash/system/pcie_peripheral/pcie_peripheral_notification_controller.cc

// Copyright 2021 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/pcie_peripheral/pcie_peripheral_notification_controller.h"

#include <optional>
#include <string>

#include "ash/constants/ash_pref_names.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/public/cpp/system_tray_client.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/model/system_tray_model.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "url/gurl.h"

namespace ash {

namespace {
const char kNotifierPciePeripheral[] = "ash.pcie_peripheral";
const char kLearnMoreHelpUrl[] =
    "https://www.support.google.com/chromebook?p=connect_thblt_usb4_accy";
const int kNotificationsClicksThreshold = 3;

const char kPciePeripheralLimitedPerformanceNotificationId[] =
    "cros_pcie_peripheral_limited_performance_notification_id";
const char kPciePeripheralLimitedPerformanceGuestModeNotificationId[] =
    "cros_pcie_peripheral_limited_performance_guest_mode_notification_id";
const char kPciePeripheralGuestModeNotSupportedNotificationId[] =
    "cros_pcie_peripheral_guest_mode_not_supported_notifcation_id";
const char kPciePeripheralDeviceBlockedNotificationId[] =
    "cros_pcie_peripheral_device_blocked_notifcation_id";
const char kPciePeripheralBillboardDeviceNotificationId[] =
    "cros_pcie_peripheral_billboard_device_notifcation_id";

// Represents the buttons in the notification.
enum ButtonIndex { kSettings, kLearnMore };

int GetNotificationClickPrefCount() {
  PrefService* prefs =
      Shell::Get()->session_controller()->GetActivePrefService();

  return prefs->GetInteger(prefs::kPciePeripheralDisplayNotificationRemaining);
}

void UpdateNotificationPrefCount(bool clicked_settings) {
  int current_pref_val = GetNotificationClickPrefCount();

  // We're already not showing any new notifications, don't update.
  if (current_pref_val == 0)
    return;

  PrefService* prefs =
      Shell::Get()->session_controller()->GetActivePrefService();

  // If the user has reached the settings page through the notification, do
  // not show any more new notifications.
  if (clicked_settings) {
    prefs->SetInteger(prefs::kPciePeripheralDisplayNotificationRemaining, 0);
    return;
  }

  // Otherwise, decrement the pref count.
  prefs->SetInteger(prefs::kPciePeripheralDisplayNotificationRemaining,
                    current_pref_val - 1);
}

void ShowPrivacyAndSecuritySettings() {
  Shell::Get()->system_tray_model()->client()->ShowPrivacyAndSecuritySettings();
}

void RemoveNotification(const std::string& notification_id) {
  message_center::MessageCenter::Get()->RemoveNotification(notification_id,
                                                           /*from_user=*/true);
}

void OnPeripheralLimitedNotificationClicked(std::optional<int> button_index) {
  // Clicked on body.
  if (!button_index) {
    ShowPrivacyAndSecuritySettings();
    UpdateNotificationPrefCount(/*clicked_settings=*/true);
    RemoveNotification(kPciePeripheralLimitedPerformanceNotificationId);
    return;
  }

  switch (*button_index) {
    case ButtonIndex::kSettings:
      ShowPrivacyAndSecuritySettings();
      UpdateNotificationPrefCount(/*clicked_settings=*/true);
      break;
    case ButtonIndex::kLearnMore:
      NewWindowDelegate::GetPrimary()->OpenUrl(
          GURL(kLearnMoreHelpUrl),
          NewWindowDelegate::OpenUrlFrom::kUserInteraction,
          NewWindowDelegate::Disposition::kNewForegroundTab);
      break;
  }
  RemoveNotification(kPciePeripheralLimitedPerformanceNotificationId);
}

void OnGuestNotificationClicked(bool is_thunderbolt_only) {
  NewWindowDelegate::GetPrimary()->OpenUrl(
      GURL(kLearnMoreHelpUrl), NewWindowDelegate::OpenUrlFrom::kUserInteraction,
      NewWindowDelegate::Disposition::kNewForegroundTab);

  if (is_thunderbolt_only) {
    RemoveNotification(kPciePeripheralGuestModeNotSupportedNotificationId);
    return;
  }

  RemoveNotification(kPciePeripheralLimitedPerformanceGuestModeNotificationId);
}

void OnPeripheralBlockedNotificationClicked() {
  NewWindowDelegate::GetPrimary()->OpenUrl(
      GURL(kLearnMoreHelpUrl), NewWindowDelegate::OpenUrlFrom::kUserInteraction,
      NewWindowDelegate::Disposition::kNewForegroundTab);
  RemoveNotification(kPciePeripheralDeviceBlockedNotificationId);
}

void OnBillboardNotificationClicked() {
  NewWindowDelegate::GetPrimary()->OpenUrl(
      GURL(kLearnMoreHelpUrl), NewWindowDelegate::OpenUrlFrom::kUserInteraction,
      NewWindowDelegate::Disposition::kNewForegroundTab);
  RemoveNotification(kPciePeripheralBillboardDeviceNotificationId);
}

// We only display notifications for active user sessions (signed-in/guest with
// desktop ready). Also do not show notifications in signin or lock screen.
bool ShouldDisplayNotification() {
  return Shell::Get()->session_controller()->GetSessionState() ==
             session_manager::SessionState::ACTIVE &&
         !Shell::Get()->session_controller()->IsUserSessionBlocked();
}

}  // namespace

PciePeripheralNotificationController::PciePeripheralNotificationController(
    message_center::MessageCenter* message_center)
    : message_center_(message_center) {
  DCHECK(message_center_);
}

PciePeripheralNotificationController::~PciePeripheralNotificationController() {
  if (ash::PeripheralNotificationManager::IsInitialized())
    ash::PeripheralNotificationManager::Get()->RemoveObserver(this);
}

void PciePeripheralNotificationController::
    OnPeripheralNotificationManagerInitialized() {
  DCHECK(ash::PeripheralNotificationManager::IsInitialized());

  ash::PeripheralNotificationManager::Get()->AddObserver(this);
}

void PciePeripheralNotificationController::NotifyBillboardDevice() {
  std::unique_ptr<message_center::Notification> notification =
      CreateSystemNotificationPtr(
          message_center::NOTIFICATION_TYPE_SIMPLE,
          kPciePeripheralBillboardDeviceNotificationId,
          /*title=*/std::u16string(),
          l10n_util::GetStringUTF16(
              IDS_ASH_PCIE_PERIPHERAL_NOTIFICATION_BILLBOARD_DEVICE),
          /*display_source=*/std::u16string(), GURL(),
          message_center::NotifierId(
              message_center::NotifierType::SYSTEM_COMPONENT,
              kNotifierPciePeripheral,
              NotificationCatalogName::kPcieBillboardDevice),
          message_center::RichNotificationData(),
          base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
              base::BindRepeating(&OnBillboardNotificationClicked)),
          kSettingsIcon,
          message_center::SystemNotificationWarningLevel::CRITICAL_WARNING);

  message_center_->AddNotification(std::move(notification));
}

void PciePeripheralNotificationController::NotifyLimitedPerformance() {
  // Don't show the notification if the user has already clicked on the
  // notification three times.
  if (!ShouldDisplayNotification() || GetNotificationClickPrefCount() == 0)
    return;

  message_center::RichNotificationData optional;
  optional.buttons.push_back(
      message_center::ButtonInfo(l10n_util::GetStringUTF16(
          IDS_ASH_PCIE_PERIPHERAL_NOTIFICATION_SETTINGS_BUTTON_TEXT)));
  optional.buttons.push_back(
      message_center::ButtonInfo(l10n_util::GetStringUTF16(
          IDS_ASH_PCIE_PERIPHERAL_NOTIFICATION_LEARN_MORE_BUTTON_TEXT)));

  std::unique_ptr<message_center::Notification> notification =
      CreateSystemNotificationPtr(
          message_center::NOTIFICATION_TYPE_SIMPLE,
          kPciePeripheralLimitedPerformanceNotificationId,
          l10n_util::GetStringUTF16(
              IDS_ASH_PCIE_PERIPHERAL_NOTIFICATION_PERFORMANCE_LIMITED_TITLE),
          l10n_util::GetStringUTF16(
              IDS_ASH_PCIE_PERIPHERAL_NOTIFICATION_PERFORMANCE_LIMITED_BODY),
          /*display_source=*/std::u16string(), GURL(),
          message_center::NotifierId(
              message_center::NotifierType::SYSTEM_COMPONENT,
              kNotifierPciePeripheral,
              NotificationCatalogName::kPcieLimitedPerformance),
          optional,
          base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
              base::BindRepeating(&OnPeripheralLimitedNotificationClicked)),
          kSettingsIcon,
          message_center::SystemNotificationWarningLevel::WARNING);

  message_center_->AddNotification(std::move(notification));
  UpdateNotificationPrefCount(/*clicked_settings=*/false);
}

void PciePeripheralNotificationController::NotifyGuestModeNotification(
    bool is_thunderbolt_only) {
  if (!ShouldDisplayNotification())
    return;

  std::unique_ptr<message_center::Notification> notification =
      CreateSystemNotificationPtr(
          message_center::NOTIFICATION_TYPE_SIMPLE,
          is_thunderbolt_only
              ? kPciePeripheralGuestModeNotSupportedNotificationId
              : kPciePeripheralLimitedPerformanceGuestModeNotificationId,
          /*title=*/std::u16string(),
          is_thunderbolt_only
              ? l10n_util::GetStringUTF16(
                    IDS_ASH_PCIE_PERIPHERAL_NOTIFICATION_GUEST_MODE_NOT_SUPPORTED)
              : l10n_util::GetStringUTF16(
                    IDS_ASH_PCIE_PERIPHERAL_NOTIFICATION_PERFORMANCE_LIMITED_GUEST_MODE),
          /*display_source=*/std::u16string(), GURL(),
          message_center::NotifierId(
              message_center::NotifierType::SYSTEM_COMPONENT,
              kNotifierPciePeripheral, NotificationCatalogName::kPcieGuestMode),
          message_center::RichNotificationData(),
          base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
              base::BindRepeating(&OnGuestNotificationClicked,
                                  is_thunderbolt_only)),
          kSettingsIcon,
          is_thunderbolt_only
              ? message_center::SystemNotificationWarningLevel::CRITICAL_WARNING
              : message_center::SystemNotificationWarningLevel::WARNING);

  message_center_->AddNotification(std::move(notification));
}

void PciePeripheralNotificationController::
    NotifyPeripheralBlockedNotification() {
  std::unique_ptr<message_center::Notification> notification =
      CreateSystemNotificationPtr(
          message_center::NOTIFICATION_TYPE_SIMPLE,
          kPciePeripheralDeviceBlockedNotificationId,
          l10n_util::GetStringUTF16(
              IDS_ASH_PCIE_PERIPHERAL_NOTIFICATION_DEVICE_BLOCKED_TITLE),
          l10n_util::GetStringUTF16(
              IDS_ASH_PCIE_PERIPHERAL_NOTIFICATION_DEVICE_BLOCKED_BODY),
          /*display_source=*/std::u16string(), GURL(),
          message_center::NotifierId(
              message_center::NotifierType::SYSTEM_COMPONENT,
              kNotifierPciePeripheral,
              NotificationCatalogName::kPciePeripheralBlocked),
          message_center::RichNotificationData(),
          base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
              base::BindRepeating(&OnPeripheralBlockedNotificationClicked)),
          kSettingsIcon,
          message_center::SystemNotificationWarningLevel::CRITICAL_WARNING);

  message_center_->AddNotification(std::move(notification));
}

void PciePeripheralNotificationController::
    OnLimitedPerformancePeripheralReceived() {
  NotifyLimitedPerformance();
}

void PciePeripheralNotificationController::OnGuestModeNotificationReceived(
    bool is_thunderbolt_only) {
  NotifyGuestModeNotification(is_thunderbolt_only);
}

void PciePeripheralNotificationController::OnPeripheralBlockedReceived() {
  NotifyPeripheralBlockedNotification();
}

void PciePeripheralNotificationController::OnBillboardDeviceConnected() {
  NotifyBillboardDevice();
}

// static
void PciePeripheralNotificationController::RegisterProfilePrefs(
    PrefRegistrySimple* registry) {
  // By default, we let the user click on the notifications three times before
  // hiding future notifications.
  registry->RegisterIntegerPref(
      prefs::kPciePeripheralDisplayNotificationRemaining,
      kNotificationsClicksThreshold);
}
}  // namespace ash