chromium/ash/system/input_device_settings/input_device_settings_notification_controller.cc

// Copyright 2023 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/input_device_settings/input_device_settings_notification_controller.h"

#include <array>
#include <optional>
#include <string>

#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/constants/ash_features.h"
#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/resources/grit/ash_public_unscaled_resources.h"
#include "ash/public/cpp/system/anchored_nudge_data.h"
#include "ash/public/cpp/system/anchored_nudge_manager.h"
#include "ash/public/cpp/system_tray_client.h"
#include "ash/public/mojom/input_device_settings.mojom-forward.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/input_device_settings/input_device_settings_metadata.h"
#include "ash/system/input_device_settings/input_device_settings_metrics_manager.h"
#include "ash/system/input_device_settings/input_device_settings_pref_names.h"
#include "ash/system/model/system_tray_model.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_map.h"
#include "base/metrics/histogram_functions.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/events/ash/mojom/simulate_right_click_modifier.mojom-shared.h"
#include "ui/events/ash/mojom/six_pack_shortcut_modifier.mojom-shared.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_skia_rep_default.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"

namespace ash {

namespace {

// Needs to stay in sync with `kLargeImageMaxHeight` declared in
// ui/message_center/views/notification_view_md.cc.
const int kMaxNotificationHeight = 218;

int CalculateScaledWidth(int width, int height) {
  return (kMaxNotificationHeight * width) / height;
}

gfx::Image ResizeImage(gfx::ImageSkia image) {
  const SkBitmap bitmap = *image.bitmap();
  SkBitmap bitmap5x =
      skia::ImageOperations::Resize(bitmap, skia::ImageOperations::RESIZE_BEST,
                                    5 * bitmap.width(), 5 * bitmap.height());
  gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFromBitmap(bitmap5x, 5.0);
  if (image_skia.height() > kMaxNotificationHeight) {
    image_skia = gfx::ImageSkiaOperations::CreateResizedImage(
        image_skia, skia::ImageOperations::RESIZE_BEST,
        gfx::Size(CalculateScaledWidth(image_skia.width(), image_skia.height()),
                  kMaxNotificationHeight));
  }
  return gfx::Image(image_skia);
}
// A nudge/tutorial will not be shown if it already been shown 3 times, or if 24
// hours have not yet passed since it was last shown.
constexpr int kNudgeMaxShownCount = 3;
constexpr base::TimeDelta kNudgeTimeBetweenShown = base::Hours(24);

const char kKeyboardSettingsLearnMoreLink[] =
    "https://support.google.com/chromebook?p=keyboard_settings";
constexpr char kTopRowKeyNoMatchNudgeId[] = "top-row-key-no-match-nudge-id";
constexpr char kSixPackKeyNoMatchNudgeId[] = "six-patch-key-no-match-nudge-id";
constexpr char kCapsLockNoMatchNudgeId[] = "caps-lock-no-match-nudge-id";

using SimulateRightClickModifier = ui::mojom::SimulateRightClickModifier;
using SixPackShortcutModifier = ui::mojom::SixPackShortcutModifier;

// Maps a six pack key to the search/alt shortcut strings.
static constexpr auto kSixPackNotificationsMap =
    base::MakeFixedFlatMap<ui::KeyboardCode, std::array<int, 2>>({
        {ui::KeyboardCode::VKEY_DELETE,
         {IDS_ASH_SETTINGS_SIX_PACK_KEY_DELETE_ALT,
          IDS_ASH_SETTINGS_SIX_PACK_KEY_DELETE_SEARCH}},
        {ui::KeyboardCode::VKEY_HOME,
         {IDS_ASH_SETTINGS_SIX_PACK_KEY_HOME_ALT,
          IDS_ASH_SETTINGS_SIX_PACK_KEY_HOME_SEARCH}},
        {ui::KeyboardCode::VKEY_END,
         {IDS_ASH_SETTINGS_SIX_PACK_KEY_END_ALT,
          IDS_ASH_SETTINGS_SIX_PACK_KEY_END_SEARCH}},
        {ui::KeyboardCode::VKEY_NEXT,
         {IDS_ASH_SETTINGS_SIX_PACK_KEY_PAGE_DOWN_ALT,
          IDS_ASH_SETTINGS_SIX_PACK_KEY_PAGE_DOWN_SEARCH}},
        {ui::KeyboardCode::VKEY_PRIOR,
         {IDS_ASH_SETTINGS_SIX_PACK_KEY_PAGE_UP_ALT,
          IDS_ASH_SETTINGS_SIX_PACK_KEY_PAGE_UP_SEARCH}},
    });

constexpr auto kSixPackKeyToPrefName =
    base::MakeFixedFlatMap<ui::KeyboardCode, const char*>({
        {ui::KeyboardCode::VKEY_DELETE,
         {prefs::kSixPackKeyDeleteNotificationsRemaining}},
        {ui::KeyboardCode::VKEY_HOME,
         {prefs::kSixPackKeyHomeNotificationsRemaining}},
        {ui::KeyboardCode::VKEY_PRIOR,
         {prefs::kSixPackKeyPageUpNotificationsRemaining}},
        {ui::KeyboardCode::VKEY_END,
         {prefs::kSixPackKeyEndNotificationsRemaining}},
        {ui::KeyboardCode::VKEY_NEXT,
         {prefs::kSixPackKeyPageDownNotificationsRemaining}},
        {ui::KeyboardCode::VKEY_INSERT,
         {prefs::kSixPackKeyInsertNotificationsRemaining}},
    });

constexpr auto kKeyCodeToSixPackKeyPrefName =
    base::MakeFixedFlatMap<ui::KeyboardCode, const char*>({
        {ui::KeyboardCode::VKEY_DELETE, {prefs::kSixPackKeyDelete}},
        {ui::KeyboardCode::VKEY_HOME, {prefs::kSixPackKeyHome}},
        {ui::KeyboardCode::VKEY_PRIOR, {prefs::kSixPackKeyPageUp}},
        {ui::KeyboardCode::VKEY_END, {prefs::kSixPackKeyEnd}},
        {ui::KeyboardCode::VKEY_NEXT, {prefs::kSixPackKeyPageDown}},
        {ui::KeyboardCode::VKEY_INSERT, {prefs::kSixPackKeyInsert}},
    });

constexpr auto kKeyCodeToSixPackKeyRemappingNudgeShownCountPref =
    base::MakeFixedFlatMap<ui::KeyboardCode, const char*>({
        {ui::KeyboardCode::VKEY_DELETE,
         {prefs::kDeleteRemappingNudgeShownCount}},
        {ui::KeyboardCode::VKEY_HOME, {prefs::kHomeRemappingNudgeShownCount}},
        {ui::KeyboardCode::VKEY_PRIOR,
         {prefs::kPageUpRemappingNudgeShownCount}},
        {ui::KeyboardCode::VKEY_END, {prefs::kEndRemappingNudgeShownCount}},
        {ui::KeyboardCode::VKEY_NEXT,
         {prefs::kPageDownRemappingNudgeShownCount}},
        {ui::KeyboardCode::VKEY_INSERT,
         {prefs::kInsertRemappingNudgeShownCount}},
    });

constexpr auto kKeyCodeToSixPackKeyRemappingNudgeLastShownPref =
    base::MakeFixedFlatMap<ui::KeyboardCode, const char*>({
        {ui::KeyboardCode::VKEY_DELETE,
         {prefs::kDeleteRemappingNudgeLastShown}},
        {ui::KeyboardCode::VKEY_HOME, {prefs::kHomeRemappingNudgeLastShown}},
        {ui::KeyboardCode::VKEY_PRIOR, {prefs::kPageUpRemappingNudgeLastShown}},
        {ui::KeyboardCode::VKEY_END, {prefs::kEndRemappingNudgeLastShown}},
        {ui::KeyboardCode::VKEY_NEXT,
         {prefs::kPageDownRemappingNudgeLastShown}},
        {ui::KeyboardCode::VKEY_INSERT,
         {prefs::kInsertRemappingNudgeLastShown}},
    });

// Device key of the virtual mouse often used by integration tests, avoid
// showing notification in this case.
const char kVirtualMouseDeviceKey[] = "0000:0000";

const char kNotifierId[] = "input_device_settings_controller";
const char kAltRightClickRewriteNotificationId[] =
    "alt_right_click_rewrite_blocked_by_setting";
const char kSearchRightClickRewriteNotificationId[] =
    "search_right_click_rewrite_blocked_by_setting";
const char kRightClickRewriteDisabledNotificationId[] =
    "right_click_rewrite_disabled_by_setting";
const char kSixPackKeyDeleteRewriteNotificationId[] =
    "delete_six_pack_rewrite_blocked_by_setting";
const char kSixPackKeyInsertRewriteNotificationId[] =
    "insert_six_pack_rewrite_blocked_by_setting";
const char kSixPackKeyHomeRewriteNotificationId[] =
    "home_six_pack_rewrite_blocked_by_setting";
const char kSixPackKeyEndRewriteNotificationId[] =
    "end_six_pack_rewrite_blocked_by_setting";
const char kSixPackKeyPageUpRewriteNotificationId[] =
    "page_up_six_pack_rewrite_blocked_by_setting";
const char kSixPackKeyPageDownRewriteNotificationId[] =
    "page_down_six_pack_rewrite_blocked_by_setting";
const char kInputDeviceSettingsMousePrefix[] =
    "peripheral_customization_mouse_";
const char kInputDeviceSettingsGraphicsTabletPrefix[] =
    "peripheral_customization_graphics_tablet_";
const char kWelcomeExperienceNotificationPrefix[] = "welcome_experience";
const char kDelimiter[] = "_";

bool IsRightClickRewriteDisabled(SimulateRightClickModifier active_modifier) {
  return active_modifier == SimulateRightClickModifier::kNone;
}

bool IsSixPackShortcutDisabled(SixPackShortcutModifier active_modifier) {
  return active_modifier == SixPackShortcutModifier::kNone;
}

std::u16string GetRightClickRewriteNotificationMessage(
    SimulateRightClickModifier blocked_modifier,
    SimulateRightClickModifier active_modifier) {
  if (IsRightClickRewriteDisabled(active_modifier)) {
    return l10n_util::GetStringUTF16(
        IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_RIGHT_CLICK_DISABLED);
  }

  const int launcher_key_name_id =
      Shell::Get()->keyboard_capability()->HasLauncherButtonOnAnyKeyboard()
          ? IDS_ASH_SETTINGS_SHORTCUT_MODIFIER_LAUNCHER
          : IDS_ASH_SETTINGS_SHORTCUT_MODIFIER_SEARCH;
  const std::u16string launcher_key_name =
      l10n_util::GetStringUTF16(launcher_key_name_id);

  switch (blocked_modifier) {
    case SimulateRightClickModifier::kAlt:
      return l10n_util::GetStringFUTF16(
          IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_ALT_RIGHT_CLICK,
          launcher_key_name);
    case SimulateRightClickModifier::kSearch:
      return l10n_util::GetStringFUTF16(
          IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_LAUNCHER_RIGHT_CLICK,
          launcher_key_name);
    case SimulateRightClickModifier::kNone:
      NOTREACHED();
  }
}

std::u16string GetRightClickRewriteNotificationTitle(
    SimulateRightClickModifier active_modifier) {
  if (IsRightClickRewriteDisabled(active_modifier)) {
    return l10n_util::GetStringUTF16(
        IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_RIGHT_CLICK_DISABLED_TITLE);
  }
  return l10n_util::GetStringUTF16(
      IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_RIGHT_CLICK_TITLE);
}

std::string GetRightClickNotificationId(
    SimulateRightClickModifier blocked_modifier,
    SimulateRightClickModifier active_modifier) {
  if (IsRightClickRewriteDisabled(active_modifier)) {
    return kRightClickRewriteDisabledNotificationId;
  }
  switch (blocked_modifier) {
    case SimulateRightClickModifier::kAlt:
      return kAltRightClickRewriteNotificationId;
    case SimulateRightClickModifier::kSearch:
      return kSearchRightClickRewriteNotificationId;
    case SimulateRightClickModifier::kNone:
      NOTREACHED();
  }
}

std::string GetWelcomeExperienceNotificationId(uint32_t id) {
  return std::string(kWelcomeExperienceNotificationPrefix) + kDelimiter +
         base::NumberToString(id);
}

std::string GetMouseNotificationID(uint32_t id) {
  if (features::IsWelcomeExperienceEnabled()) {
    return GetWelcomeExperienceNotificationId(id);
  }
  return kInputDeviceSettingsMousePrefix + base::NumberToString(id);
}

std::string GetGraphicsTabletNotificationID(uint32_t id) {
  if (features::IsWelcomeExperienceEnabled()) {
    return GetWelcomeExperienceNotificationId(id);
  }
  return kInputDeviceSettingsGraphicsTabletPrefix + base::NumberToString(id);
}

// 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 IsActiveUserSession() {
  const auto* session_controller = Shell::Get()->session_controller();
  return session_controller->GetSessionState() ==
             session_manager::SessionState::ACTIVE &&
         !session_controller->IsUserSessionBlocked();
}

bool IsGuestSession() {
  const auto* session_controller = Shell::Get()->session_controller();
  return session_controller->IsUserGuest();
}

// If the user has reached the settings page through the notification, do
// not show any more new notifications.
void PreventNotificationFromShowingAgain(const char* pref_name) {
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      pref_name, 0);
}

bool ShouldShowSixPackKeyNotification() {
  const auto* accelerator_controller = Shell::Get()->accelerator_controller();
  CHECK(accelerator_controller);
  // Six pack key notification should not show if accelerators are being blocked
  // as the user does not expect these keys to be interpreted as a six pack key.
  return !accelerator_controller->ShouldPreventProcessingAccelerators() &&
         IsActiveUserSession();
}

std::u16string GetSixPackKeyName(ui::KeyboardCode key_code) {
  switch (key_code) {
    case ui::VKEY_DELETE:
      return l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_SIX_PACK_KEY_DELETE);
    case ui::VKEY_INSERT:
      return l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_SIX_PACK_KEY_INSERT);
    case ui::VKEY_HOME:
      return l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_SIX_PACK_KEY_HOME);
    case ui::VKEY_END:
      return l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_SIX_PACK_KEY_END);
    case ui::VKEY_PRIOR:
      return l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_SIX_PACK_KEY_PAGE_UP);
    case ui::VKEY_NEXT:
      return l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_SIX_PACK_KEY_PAGE_DOWN);
    default:
      NOTREACHED();
  }
}

std::u16string GetSixPackShortcutUpdatedString(ui::KeyboardCode key_code) {
  switch (key_code) {
    case ui::VKEY_PRIOR:
      return l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_PAGE_UP_NUDGE_DESCRIPTION);
    case ui::VKEY_NEXT:
      return l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_PAGE_DOWN_NUDGE_DESCRIPTION);
    case ui::VKEY_HOME:
      return l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_HOME_NUDGE_DESCRIPTION);
    case ui::VKEY_END:
      return l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_END_NUDGE_DESCRIPTION);
    case ui::VKEY_DELETE:
      return l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_DELETE_NUDGE_DESCRIPTION);
    default:
      NOTREACHED();
  }
}

void InsertSixPackShortcutKeyboardCodes(
    ui::KeyboardCode key_code,
    std::vector<ui::KeyboardCode>& keyboard_codes) {
  switch (key_code) {
    case ui::VKEY_PRIOR:
      keyboard_codes.push_back(ui::VKEY_UP);
      break;
    case ui::VKEY_NEXT:
      keyboard_codes.push_back(ui::VKEY_DOWN);
      break;
    case ui::VKEY_HOME:
      keyboard_codes.push_back(ui::VKEY_LEFT);
      break;
    case ui::VKEY_END:
      keyboard_codes.push_back(ui::VKEY_RIGHT);
      break;
    case ui::VKEY_DELETE:
      keyboard_codes.push_back(ui::VKEY_BACK);
      break;
    case ui::VKEY_INSERT:
      keyboard_codes.push_back(ui::VKEY_SHIFT);
      keyboard_codes.push_back(ui::VKEY_BACK);
      break;
    default:
      NOTREACHED();
  }
}

std::u16string GetSixPackShortcut(ui::KeyboardCode key_code,
                                  SixPackShortcutModifier modifier) {
  CHECK(modifier != SixPackShortcutModifier::kNone);
  int message_id =
      kSixPackNotificationsMap.at(key_code)[static_cast<int>(modifier) - 1];

  if (modifier == SixPackShortcutModifier::kSearch) {
    const int launcher_key_name_id =
        Shell::Get()->keyboard_capability()->HasLauncherButtonOnAnyKeyboard()
            ? IDS_ASH_SETTINGS_SHORTCUT_MODIFIER_LAUNCHER
            : IDS_ASH_SETTINGS_SHORTCUT_MODIFIER_SEARCH;
    const std::u16string launcher_key_name =
        l10n_util::GetStringUTF16(launcher_key_name_id);
    return l10n_util::GetStringFUTF16(message_id, launcher_key_name);
  }
  return l10n_util::GetStringUTF16(message_id);
}

std::u16string GetSixPackNotificationMessage(
    ui::KeyboardCode key_code,
    SixPackShortcutModifier blocked_modifier,
    SixPackShortcutModifier active_modifier) {
  const auto six_pack_key_name = GetSixPackKeyName(key_code);
  if (IsSixPackShortcutDisabled(active_modifier)) {
    return l10n_util::GetStringFUTF16(
        IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_SIX_PACK_SHORTCUT_DISABLED,
        six_pack_key_name);
  }

  // There's only one active shortcut for the "Insert" six pack key.
  CHECK(key_code != ui::KeyboardCode::VKEY_INSERT);
  const auto new_shortcut = GetSixPackShortcut(key_code, active_modifier);
  const auto old_shortcut = GetSixPackShortcut(key_code, blocked_modifier);
  return l10n_util::GetStringFUTF16(
      IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_SIX_PACK_KEY, six_pack_key_name,
      new_shortcut, old_shortcut);
}

std::string GetSixPackNotificationId(ui::KeyboardCode key_code, int device_id) {
  std::string notification_id;
  switch (key_code) {
    case ui::VKEY_DELETE:
      notification_id = kSixPackKeyDeleteRewriteNotificationId;
      break;
    case ui::VKEY_INSERT:
      notification_id = kSixPackKeyInsertRewriteNotificationId;
      break;
    case ui::VKEY_HOME:
      notification_id = kSixPackKeyHomeRewriteNotificationId;
      break;
    case ui::VKEY_END:
      notification_id = kSixPackKeyEndRewriteNotificationId;
      break;
    case ui::VKEY_PRIOR:
      notification_id = kSixPackKeyPageUpRewriteNotificationId;
      break;
    case ui::VKEY_NEXT:
      notification_id = kSixPackKeyPageDownRewriteNotificationId;
      break;
    default:
      NOTREACHED();
  }
  return notification_id + kDelimiter + base::NumberToString(device_id);
}

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

void ShowRemapKeysSubpage(int device_id) {
  Shell::Get()->system_tray_model()->client()->ShowRemapKeysSubpage(device_id);
}

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

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

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

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

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

void OnLearnMoreClicked() {
  NewWindowDelegate::GetPrimary()->OpenUrl(
      GURL(kKeyboardSettingsLearnMoreLink),
      NewWindowDelegate::OpenUrlFrom::kUserInteraction,
      NewWindowDelegate::Disposition::kNewForegroundTab);
}

// Compares button remapping lists to see if they are equal for notification
// purposes. This ignores name mismatches, but compares everything else
// (including ordering).
bool ButtonRemappingListsAreEqual(
    const std::vector<mojom::ButtonRemappingPtr>& button_remappings1,
    const std::vector<mojom::ButtonRemappingPtr>& button_remappings2) {
  if (button_remappings1.size() != button_remappings2.size()) {
    return false;
  }

  for (size_t i = 0; i < button_remappings1.size(); i++) {
    const auto& button_remapping1 = button_remappings1[i];
    const auto& button_remapping2 = button_remappings2[i];
    if (button_remapping1->button != button_remapping2->button ||
        button_remapping1->remapping_action !=
            button_remapping2->remapping_action) {
      return false;
    }
  }

  return true;
}

const std::u16string GetBatteryLevelMessage(
    const mojom::BatteryInfo& battery_info) {
  return l10n_util::GetStringFUTF16(
      IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_WELCOME_EXPERIENCE_BATTERY_DESCRIPTION,
      base::NumberToString16(battery_info.battery_percentage));
}

}  // namespace

InputDeviceSettingsNotificationController::
    InputDeviceSettingsNotificationController(
        message_center::MessageCenter* message_center)
    : message_center_(message_center) {
  CHECK(message_center_);
}

void InputDeviceSettingsNotificationController::RegisterProfilePrefs(
    PrefRegistrySimple* pref_registry) {
  // We'll show the remap to right click and Six Pack keys notifications a
  // total of three times each.
  pref_registry->RegisterIntegerPref(
      prefs::kRemapToRightClickNotificationsRemaining, 3,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  pref_registry->RegisterIntegerPref(
      prefs::kSixPackKeyDeleteNotificationsRemaining, 3,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  pref_registry->RegisterIntegerPref(
      prefs::kSixPackKeyHomeNotificationsRemaining, 3,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  pref_registry->RegisterIntegerPref(
      prefs::kSixPackKeyEndNotificationsRemaining, 3,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  pref_registry->RegisterIntegerPref(
      prefs::kSixPackKeyPageUpNotificationsRemaining, 3,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  pref_registry->RegisterIntegerPref(
      prefs::kSixPackKeyPageDownNotificationsRemaining, 3,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  pref_registry->RegisterIntegerPref(
      prefs::kSixPackKeyInsertNotificationsRemaining, 3,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  pref_registry->RegisterIntegerPref(prefs::kCapsLockRemappingNudgeShownCount,
                                     0);
  pref_registry->RegisterIntegerPref(prefs::kTopRowRemappingNudgeShownCount, 0);
  pref_registry->RegisterTimePref(prefs::kCapsLockRemappingNudgeLastShown,
                                  base::Time());
  pref_registry->RegisterTimePref(prefs::kTopRowRemappingNudgeLastShown,
                                  base::Time());
  pref_registry->RegisterListPref(prefs::kPeripheralNotificationMiceSeen);
  pref_registry->RegisterListPref(
      prefs::kPeripheralNotificationGraphicsTabletsSeen);
  pref_registry->RegisterListPref(prefs::kWelcomeExperienceNotificationSeen);
  pref_registry->RegisterDictionaryPref(
      prefs::kKeyboardSettingSixPackKeyRemappings);
}

InputDeviceSettingsNotificationController::
    ~InputDeviceSettingsNotificationController() = default;

void InputDeviceSettingsNotificationController::
    NotifyRightClickRewriteBlockedBySetting(
        SimulateRightClickModifier blocked_modifier,
        SimulateRightClickModifier active_modifier) {
  CHECK_NE(blocked_modifier, SimulateRightClickModifier::kNone);
  if (!IsActiveUserSession()) {
    return;
  }

  PrefService* prefs =
      Shell::Get()->session_controller()->GetActivePrefService();
  CHECK(prefs);
  int num_notifications_remaining =
      prefs->GetInteger(prefs::kRemapToRightClickNotificationsRemaining);
  if (num_notifications_remaining == 0) {
    return;
  }

  prefs->SetInteger(prefs::kRemapToRightClickNotificationsRemaining,
                    num_notifications_remaining - 1);
  const auto notification_id =
      GetRightClickNotificationId(blocked_modifier, active_modifier);
  message_center::RichNotificationData rich_notification_data;
  rich_notification_data.buttons.emplace_back(
      l10n_util::GetStringUTF16(IDS_ASH_DEVICE_SETTINGS_EDIT_SHORTCUT_BUTTON));
  rich_notification_data.buttons.emplace_back(
      l10n_util::GetStringUTF16(IDS_ASH_DEVICE_SETTINGS_LEARN_MORE_BUTTON));
  auto notification = CreateSystemNotificationPtr(
      message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
      GetRightClickRewriteNotificationTitle(active_modifier),
      GetRightClickRewriteNotificationMessage(blocked_modifier,
                                              active_modifier),
      std::u16string(), GURL(),
      message_center::NotifierId(
          message_center::NotifierType::SYSTEM_COMPONENT, kNotifierId,
          NotificationCatalogName::kEventRewriterDeprecation),
      rich_notification_data,
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(&InputDeviceSettingsNotificationController::
                                  HandleRightClickNotificationClicked,
                              weak_ptr_factory_.GetWeakPtr(), notification_id)),
      kNotificationKeyboardIcon,
      message_center::SystemNotificationWarningLevel::NORMAL);
  message_center_->AddNotification(std::move(notification));
}

void InputDeviceSettingsNotificationController::NotifyMouseFirstTimeConnected(
    const mojom::Mouse& mouse,
    const gfx::ImageSkia& device_image) {
  if (!IsActiveUserSession() || !mouse.is_external || IsGuestSession()) {
    return;
  }

  // Avoid showing notification for the virtual mouse device.
  if (mouse.device_key == kVirtualMouseDeviceKey) {
    return;
  }

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

  const char* pref_name = features::IsWelcomeExperienceEnabled()
                              ? prefs::kWelcomeExperienceNotificationSeen
                              : prefs::kPeripheralNotificationMiceSeen;
  if (base::Contains(prefs->GetList(pref_name), mouse.device_key)) {
    return;
  }

  auto seen_device_list = prefs->GetList(pref_name).Clone();

  seen_device_list.Append(mouse.device_key);
  prefs->SetList(pref_name, std::move(seen_device_list));

  CHECK(mouse.settings);
  // Do not show notification if the device remapping list has already been
  // changed which means they have already used the customization feature.
  if (!ButtonRemappingListsAreEqual(
          GetButtonRemappingListForConfig(mouse.mouse_button_config),
          mouse.settings->button_remappings)) {
    return;
  }
  NotifyMouseIsCustomizable(mouse, device_image);
}

void InputDeviceSettingsNotificationController::
    NotifyGraphicsTabletFirstTimeConnected(
        const mojom::GraphicsTablet& graphics_tablet,
        const gfx::ImageSkia& device_image) {
  if (!IsActiveUserSession() || IsGuestSession()) {
    return;
  }

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

  const char* pref_name =
      features::IsWelcomeExperienceEnabled()
          ? prefs::kWelcomeExperienceNotificationSeen
          : prefs::kPeripheralNotificationGraphicsTabletsSeen;

  auto seen_device_list = prefs->GetList(pref_name).Clone();

  for (const auto& value : seen_device_list) {
    if (value.is_string() && value.GetString() == graphics_tablet.device_key) {
      return;
    }
  }
  seen_device_list.Append(graphics_tablet.device_key);
  prefs->SetList(pref_name, std::move(seen_device_list));

  CHECK(graphics_tablet.settings);
  // Do not show notification if the device remapping list has already been
  // changed which means they have already used the customization feature.
  if (!ButtonRemappingListsAreEqual(
          GetPenButtonRemappingListForConfig(
              graphics_tablet.graphics_tablet_button_config),
          graphics_tablet.settings->pen_button_remappings) ||
      !ButtonRemappingListsAreEqual(
          GetTabletButtonRemappingListForConfig(
              graphics_tablet.graphics_tablet_button_config),
          graphics_tablet.settings->tablet_button_remappings)) {
    return;
  }
  NotifyGraphicsTabletIsCustomizable(graphics_tablet, device_image);
}

void InputDeviceSettingsNotificationController::
    HandleSixPackNotificationClicked(int device_id,
                                     const char* pref_name,
                                     const std::string& notification_id,
                                     std::optional<int> button_index) {
  // Clicked on body.
  if (!button_index) {
    ShowRemapKeysSubpage(device_id);
    RemoveNotification(notification_id);
    PreventNotificationFromShowingAgain(pref_name);
    return;
  }

  switch (*button_index) {
    case NotificationButtonIndex::BUTTON_EDIT_SHORTCUT:
      ShowRemapKeysSubpage(device_id);
      break;
    case NotificationButtonIndex::BUTTON_LEARN_MORE:
      OnLearnMoreClicked();
      break;
  }
  PreventNotificationFromShowingAgain(pref_name);
  RemoveNotification(notification_id);
}

void InputDeviceSettingsNotificationController::
    HandleRightClickNotificationClicked(const std::string& notification_id,
                                        std::optional<int> button_index) {
  // Clicked on body.
  if (!button_index) {
    ShowTouchpadSettings();
    PreventNotificationFromShowingAgain(
        prefs::kRemapToRightClickNotificationsRemaining);
    RemoveNotification(notification_id);
    return;
  }

  switch (*button_index) {
    case NotificationButtonIndex::BUTTON_EDIT_SHORTCUT:
      ShowTouchpadSettings();
      break;
    case NotificationButtonIndex::BUTTON_LEARN_MORE:
      OnLearnMoreClicked();
      break;
  }
  PreventNotificationFromShowingAgain(
      prefs::kRemapToRightClickNotificationsRemaining);
  RemoveNotification(notification_id);
}

void HandleMouseCustomizationNotificationClicked(
    const std::string& notification_id,
    std::optional<int> button_index) {
  ShowMouseSettings();
  base::UmaHistogramEnumeration(
      "ChromeOS.WelcomeExperienceNotificationEvent",
      InputDeviceSettingsMetricsManager::
          WelcomeExperienceNotificationEventType::kClicked);
  RemoveNotification(notification_id);
  return;
}

void HandleKeyboardCustomizationNotificationClicked(
    const std::string& notification_id,
    std::optional<int> button_index) {
  ShowKeyboardSettings();
  base::UmaHistogramEnumeration(
      "ChromeOS.WelcomeExperienceNotificationEvent",
      InputDeviceSettingsMetricsManager::
          WelcomeExperienceNotificationEventType::kClicked);
  RemoveNotification(notification_id);
  return;
}

void HandleTouchpadCustomizationNotificationClicked(
    const std::string& notification_id,
    std::optional<int> button_index) {
  ShowTouchpadSettings();
  base::UmaHistogramEnumeration(
      "ChromeOS.WelcomeExperienceNotificationEvent",
      InputDeviceSettingsMetricsManager::
          WelcomeExperienceNotificationEventType::kClicked);
  RemoveNotification(notification_id);
  return;
}

void HandlePointingStickCustomizationNotificationClicked(
    const std::string& notification_id,
    std::optional<int> button_index) {
  ShowPointingStickSettings();
  RemoveNotification(notification_id);
  return;
}

void HandleGraphicsTabletCustomizationNotificationClicked(
    const std::string& notification_id,
    std::optional<int> button_index) {
  ShowGraphicsTabletSettings();
  base::UmaHistogramEnumeration(
      "ChromeOS.WelcomeExperienceNotificationEvent",
      InputDeviceSettingsMetricsManager::
          WelcomeExperienceNotificationEventType::kClicked);
  RemoveNotification(notification_id);
  return;
}

// TODO(b/279503977): Use `blocked_modifier` and `active_modifier` to display
// the notification message once strings are finalized.
void InputDeviceSettingsNotificationController::
    NotifySixPackRewriteBlockedBySetting(
        ui::KeyboardCode key_code,
        SixPackShortcutModifier blocked_modifier,
        SixPackShortcutModifier active_modifier,
        int device_id) {
  if (!ShouldShowSixPackKeyNotification()) {
    return;
  }
  CHECK_NE(blocked_modifier, SixPackShortcutModifier::kNone);
  CHECK(ui::KeyboardCapability::IsSixPackKey(key_code));

  auto it = kSixPackKeyToPrefName.find(key_code);
  CHECK(it != kSixPackKeyToPrefName.end());
  const char* pref = it->second;
  PrefService* prefs =
      Shell::Get()->session_controller()->GetActivePrefService();
  CHECK(prefs);
  int num_notifications_remaining = prefs->GetInteger(pref);
  if (num_notifications_remaining == 0) {
    return;
  }
  prefs->SetInteger(pref, num_notifications_remaining - 1);

  const auto notification_id = GetSixPackNotificationId(key_code, device_id);
  message_center::RichNotificationData rich_notification_data;
  rich_notification_data.buttons.emplace_back(
      l10n_util::GetStringUTF16(IDS_ASH_DEVICE_SETTINGS_EDIT_SHORTCUT_BUTTON));
  rich_notification_data.buttons.emplace_back(
      l10n_util::GetStringUTF16(IDS_ASH_DEVICE_SETTINGS_LEARN_MORE_BUTTON));
  auto notification = CreateSystemNotificationPtr(
      message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
      l10n_util::GetStringUTF16(IDS_ASH_SETTINGS_SHORTCUT_NOTIFICATION_TITLE),
      GetSixPackNotificationMessage(key_code, blocked_modifier,
                                    active_modifier),
      std::u16string(), GURL(),
      message_center::NotifierId(
          message_center::NotifierType::SYSTEM_COMPONENT, kNotifierId,
          NotificationCatalogName::kEventRewriterDeprecation),
      rich_notification_data,
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(&InputDeviceSettingsNotificationController::
                                  HandleSixPackNotificationClicked,
                              weak_ptr_factory_.GetWeakPtr(), device_id, pref,
                              notification_id)),
      kNotificationKeyboardIcon,
      message_center::SystemNotificationWarningLevel::NORMAL);
  message_center_->AddNotification(std::move(notification));
}

void InputDeviceSettingsNotificationController::
    NotifyKeyboardFirstTimeConnected(const mojom::Keyboard& keyboard,
                                     const gfx::ImageSkia& device_image) {
  if (!IsActiveUserSession() || !keyboard.is_external || IsGuestSession()) {
    return;
  }

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

  if (base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     keyboard.device_key)) {
    return;
  }

  auto seen_device_list =
      prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).Clone();

  seen_device_list.Append(keyboard.device_key);
  prefs->SetList(prefs::kWelcomeExperienceNotificationSeen,
                 std::move(seen_device_list));

  CHECK(keyboard.settings);
  ShowKeyboardSettingsNotification(keyboard, device_image);
}

void InputDeviceSettingsNotificationController::
    NotifyTouchpadFirstTimeConnected(const mojom::Touchpad& touchpad,
                                     const gfx::ImageSkia& device_image) {
  if (!IsActiveUserSession() || !touchpad.is_external || IsGuestSession()) {
    return;
  }

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

  if (base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     touchpad.device_key)) {
    return;
  }

  auto seen_device_list =
      prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).Clone();

  seen_device_list.Append(touchpad.device_key);
  prefs->SetList(prefs::kWelcomeExperienceNotificationSeen,
                 std::move(seen_device_list));

  CHECK(touchpad.settings);
  ShowTouchpadSettingsNotification(touchpad, device_image);
}

void InputDeviceSettingsNotificationController::
    ShowPointingStickSettingsNotification(
        const mojom::PointingStick& pointing_stick) {
  const auto peripheral_name = base::UTF8ToUTF16(pointing_stick.name);
  const auto notification_id =
      GetWelcomeExperienceNotificationId(pointing_stick.id);
  message_center::RichNotificationData rich_notification_data;
  rich_notification_data.buttons.emplace_back(l10n_util::GetStringUTF16(
      IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_OPEN_SETTINGS_BUTTON));
  auto notification = CreateSystemNotificationPtr(
      message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
      l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_WELCOME_EXPERIENCE_POINTING_STICK_TITLE),
      l10n_util::GetStringFUTF16(
          IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_WELCOME_EXPERIENCE_POINTING_STICK,
          peripheral_name),
      std::u16string(), GURL(),
      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
                                 kNotifierId,
                                 NotificationCatalogName::kInputDeviceSettings),
      rich_notification_data,
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(
              &HandlePointingStickCustomizationNotificationClicked,
              notification_id)),
      kSettingsIcon, message_center::SystemNotificationWarningLevel::NORMAL);
  message_center_->AddNotification(std::move(notification));
}

void InputDeviceSettingsNotificationController::
    NotifyPointingStickFirstTimeConnected(
        const mojom::PointingStick& pointing_stick) {
  if (!IsActiveUserSession() || !pointing_stick.is_external ||
      IsGuestSession()) {
    return;
  }

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

  if (base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     pointing_stick.device_key)) {
    return;
  }

  auto seen_device_list =
      prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).Clone();

  seen_device_list.Append(pointing_stick.device_key);
  prefs->SetList(prefs::kWelcomeExperienceNotificationSeen,
                 std::move(seen_device_list));

  CHECK(pointing_stick.settings);
  ShowPointingStickSettingsNotification(pointing_stick);
}

void InputDeviceSettingsNotificationController::NotifyMouseIsCustomizable(
    const mojom::Mouse& mouse,
    const gfx::ImageSkia& device_image) {
  const auto peripheral_name = base::UTF8ToUTF16(mouse.name);
  const auto notification_id = GetMouseNotificationID(mouse.id);
  const auto message =
      mouse.battery_info.is_null()
          ? l10n_util::GetStringFUTF16(
                IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_MOUSE_CUSTOMIZATION,
                peripheral_name)
          : GetBatteryLevelMessage(*mouse.battery_info);
  message_center::RichNotificationData rich_notification_data;
  if (!device_image.isNull()) {
    rich_notification_data.image = ResizeImage(device_image);
  }
  rich_notification_data.buttons.emplace_back(l10n_util::GetStringUTF16(
      IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_OPEN_SETTINGS_BUTTON));
  auto notification = CreateSystemNotificationPtr(
      message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
      l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_PERIPHERAL_CUSTOMIZATION_TITLE),
      message, std::u16string(), GURL(),
      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
                                 kNotifierId,
                                 NotificationCatalogName::kInputDeviceSettings),
      rich_notification_data,
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(&HandleMouseCustomizationNotificationClicked,
                              notification_id)),
      kSettingsIcon, message_center::SystemNotificationWarningLevel::NORMAL);
  notification_id_to_device_key_map_[notification_id] = mouse.device_key;
  base::UmaHistogramEnumeration(
      "ChromeOS.WelcomeExperienceNotificationEvent",
      InputDeviceSettingsMetricsManager::
          WelcomeExperienceNotificationEventType::kShown);
  message_center_->AddNotification(std::move(notification));
}

void InputDeviceSettingsNotificationController::
    ShowKeyboardSettingsNotification(const mojom::Keyboard& keyboard,
                                     const gfx::ImageSkia& device_image) {
  const auto peripheral_name = base::UTF8ToUTF16(keyboard.name);
  const auto notification_id = GetWelcomeExperienceNotificationId(keyboard.id);
  const auto message =
      keyboard.battery_info.is_null()
          ? l10n_util::GetStringFUTF16(
                IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_WELCOME_EXPERIENCE_KEYBOARD,
                peripheral_name)
          : GetBatteryLevelMessage(*keyboard.battery_info);
  message_center::RichNotificationData rich_notification_data;
  if (!device_image.isNull()) {
    rich_notification_data.image = ResizeImage(device_image);
  }
  rich_notification_data.buttons.emplace_back(l10n_util::GetStringUTF16(
      IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_OPEN_SETTINGS_BUTTON));
  auto notification = CreateSystemNotificationPtr(
      message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
      l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_WELCOME_EXPERIENCE_KEYBOARD_TITLE),
      message, std::u16string(), GURL(),
      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
                                 kNotifierId,
                                 NotificationCatalogName::kInputDeviceSettings),
      rich_notification_data,
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(&HandleKeyboardCustomizationNotificationClicked,
                              notification_id)),
      kSettingsIcon, message_center::SystemNotificationWarningLevel::NORMAL);
  notification_id_to_device_key_map_[notification_id] = keyboard.device_key;
  base::UmaHistogramEnumeration(
      "ChromeOS.WelcomeExperienceNotificationEvent",
      InputDeviceSettingsMetricsManager::
          WelcomeExperienceNotificationEventType::kShown);
  message_center_->AddNotification(std::move(notification));
}

void InputDeviceSettingsNotificationController::
    ShowTouchpadSettingsNotification(const mojom::Touchpad& touchpad,
                                     const gfx::ImageSkia& device_image) {
  const auto peripheral_name = base::UTF8ToUTF16(touchpad.name);
  const auto message =
      touchpad.battery_info.is_null()
          ? l10n_util::GetStringFUTF16(
                IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_WELCOME_EXPERIENCE_TOUCHPAD,
                peripheral_name)
          : GetBatteryLevelMessage(*touchpad.battery_info);
  const auto notification_id = GetWelcomeExperienceNotificationId(touchpad.id);
  message_center::RichNotificationData rich_notification_data;
  if (!device_image.isNull()) {
    rich_notification_data.image = ResizeImage(device_image);
  }
  rich_notification_data.buttons.emplace_back(l10n_util::GetStringUTF16(
      IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_OPEN_SETTINGS_BUTTON));
  auto notification = CreateSystemNotificationPtr(
      message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
      l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_WELCOME_EXPERIENCE_TOUCHPAD_TITLE),
      message, std::u16string(), GURL(),
      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
                                 kNotifierId,
                                 NotificationCatalogName::kInputDeviceSettings),
      rich_notification_data,
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(&HandleTouchpadCustomizationNotificationClicked,
                              notification_id)),
      kSettingsIcon, message_center::SystemNotificationWarningLevel::NORMAL);
  notification_id_to_device_key_map_[notification_id] = touchpad.device_key;
  base::UmaHistogramEnumeration(
      "ChromeOS.WelcomeExperienceNotificationEvent",
      InputDeviceSettingsMetricsManager::
          WelcomeExperienceNotificationEventType::kShown);
  message_center_->AddNotification(std::move(notification));
}

void InputDeviceSettingsNotificationController::
    NotifyGraphicsTabletIsCustomizable(
        const mojom::GraphicsTablet& graphics_tablet,
        const gfx::ImageSkia& device_image) {
  const auto peripheral_name = base::UTF8ToUTF16(graphics_tablet.name);
  const auto message =
      graphics_tablet.battery_info.is_null()
          ? l10n_util::GetStringFUTF16(
                IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_GRAPHICS_TABLET_CUSTOMIZATION,
                peripheral_name)
          : GetBatteryLevelMessage(*graphics_tablet.battery_info);
  const auto notification_id =
      GetGraphicsTabletNotificationID(graphics_tablet.id);
  message_center::RichNotificationData rich_notification_data;
  if (!device_image.isNull()) {
    rich_notification_data.image = ResizeImage(device_image);
  }
  rich_notification_data.buttons.emplace_back(l10n_util::GetStringUTF16(
      IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_OPEN_SETTINGS_BUTTON));
  auto notification = CreateSystemNotificationPtr(
      message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
      l10n_util::GetStringUTF16(
          IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_PERIPHERAL_CUSTOMIZATION_GRAPHICS_TABLET_TITLE),
      message, std::u16string(), GURL(),
      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
                                 kNotifierId,
                                 NotificationCatalogName::kInputDeviceSettings),
      rich_notification_data,
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          base::BindRepeating(
              &HandleGraphicsTabletCustomizationNotificationClicked,
              notification_id)),
      kSettingsIcon, message_center::SystemNotificationWarningLevel::NORMAL);
  notification_id_to_device_key_map_[notification_id] =
      graphics_tablet.device_key;
  base::UmaHistogramEnumeration(
      "ChromeOS.WelcomeExperienceNotificationEvent",
      InputDeviceSettingsMetricsManager::
          WelcomeExperienceNotificationEventType::kShown);
  message_center_->AddNotification(std::move(notification));
}

void InputDeviceSettingsNotificationController::ShowTopRowRewritingNudge() {
  if (!IsActiveUserSession()) {
    return;
  }

  CHECK(ash::Shell::HasInstance() && Shell::Get()->session_controller());
  PrefService* prefs =
      Shell::Get()->session_controller()->GetActivePrefService();

  const int shown_count =
      prefs->GetInteger(prefs::kTopRowRemappingNudgeShownCount);
  const base::Time last_shown_time =
      prefs->GetTime(prefs::kTopRowRemappingNudgeLastShown);
  // Do not show the nudge more than three times, or if it has already been
  // shown in the past 24 hours.
  const base::Time now = base::Time::Now();
  if ((shown_count >= kNudgeMaxShownCount) ||
      ((now - last_shown_time) < kNudgeTimeBetweenShown)) {
    return;
  }

  prefs->SetInteger(prefs::kTopRowRemappingNudgeShownCount, shown_count + 1);
  prefs->SetTime(prefs::kTopRowRemappingNudgeLastShown, now);

  AnchoredNudgeData nudge_data(
      kTopRowKeyNoMatchNudgeId, NudgeCatalogName::kSearchTopRowKeyPressed,
      l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_TOP_ROW_NUDGE_DESCRIPTION));
  nudge_data.image_model =
      ui::ResourceBundle::GetSharedInstance().GetThemedLottieImageNamed(
          IDR_KEYBOARD_FN_KEY_NUDGE_IMAGE);

  AnchoredNudgeManager::Get()->Show(nudge_data);
}

void InputDeviceSettingsNotificationController::ShowSixPackKeyRewritingNudge(
    ui::KeyboardCode key_code,
    SixPackShortcutModifier old_matched_modifier) {
  if (!IsActiveUserSession() ||
      !ui::KeyboardCapability::IsSixPackKey(key_code)) {
    return;
  }

  // Insert does not have a notification to show even though it is a six-pack
  // key.
  if (key_code == ui::VKEY_INSERT) {
    return;
  }

  CHECK(ash::Shell::HasInstance() && Shell::Get()->session_controller());
  PrefService* prefs =
      Shell::Get()->session_controller()->GetActivePrefService();
  CHECK(prefs);

  const auto* six_pack_key_remappings =
      prefs->GetDict(prefs::kKeyboardDefaultChromeOSSettings)
          .FindDict(prefs::kKeyboardSettingSixPackKeyRemappings);

  std::optional<int> six_pack_key_modifier =
      static_cast<int>(SixPackShortcutModifier::kSearch);

  // Only show the notification if the modifier key matches the pref in user's
  // last device for the behavior.
  if (six_pack_key_remappings) {
    auto it = kKeyCodeToSixPackKeyPrefName.find(key_code);
    CHECK(it != kKeyCodeToSixPackKeyPrefName.end());
    const char* pref = it->second;

    six_pack_key_modifier = six_pack_key_remappings->FindInt(pref);
    if (six_pack_key_modifier != std::nullopt &&
        six_pack_key_modifier != static_cast<int>(old_matched_modifier)) {
      return;
    }
  }

  const auto shown_count_pref_iter =
      kKeyCodeToSixPackKeyRemappingNudgeShownCountPref.find(key_code);
  CHECK(shown_count_pref_iter !=
        kKeyCodeToSixPackKeyRemappingNudgeShownCountPref.end());
  const char* shown_count_pref_name = shown_count_pref_iter->second;
  const int shown_count = prefs->GetInteger(shown_count_pref_name);

  const auto last_shown_time_iter =
      kKeyCodeToSixPackKeyRemappingNudgeLastShownPref.find(key_code);
  CHECK(last_shown_time_iter !=
        kKeyCodeToSixPackKeyRemappingNudgeLastShownPref.end());
  const char* last_shown_time_pref_name = last_shown_time_iter->second;
  const base::Time last_shown_time = prefs->GetTime(last_shown_time_pref_name);

  // Do not show the nudge more than three times, or if it has already been
  // shown in the past 24 hours.
  const base::Time now = base::Time::Now();
  if ((shown_count >= kNudgeMaxShownCount) ||
      ((now - last_shown_time) < kNudgeTimeBetweenShown)) {
    return;
  }

  prefs->SetInteger(shown_count_pref_name, shown_count + 1);
  prefs->SetTime(last_shown_time_pref_name, now);

  AnchoredNudgeData nudge_data(kSixPackKeyNoMatchNudgeId,
                               NudgeCatalogName::kSixPackRemappingPressed,
                               GetSixPackShortcutUpdatedString(key_code));
  std::vector<ui::KeyboardCode> keyboard_codes = {ui::VKEY_FUNCTION};
  InsertSixPackShortcutKeyboardCodes(key_code, keyboard_codes);
  nudge_data.keyboard_codes = std::move(keyboard_codes);
  nudge_data.image_model =
      ui::ResourceBundle::GetSharedInstance().GetThemedLottieImageNamed(
          IDR_KEYBOARD_FN_KEY_NUDGE_IMAGE);
  AnchoredNudgeManager::Get()->Show(nudge_data);
}

void InputDeviceSettingsNotificationController::ShowCapsLockRewritingNudge() {
  if (!IsActiveUserSession()) {
    return;
  }

  CHECK(ash::Shell::HasInstance() && Shell::Get()->session_controller());
  PrefService* prefs =
      Shell::Get()->session_controller()->GetActivePrefService();

  const int shown_count =
      prefs->GetInteger(prefs::kCapsLockRemappingNudgeShownCount);
  const base::Time last_shown_time =
      prefs->GetTime(prefs::kCapsLockRemappingNudgeLastShown);
  // Do not show the nudge more than three times, or if it has already been
  // shown in the past 24 hours.
  const base::Time now = base::Time::Now();
  if ((shown_count >= kNudgeMaxShownCount) ||
      ((now - last_shown_time) < kNudgeTimeBetweenShown)) {
    return;
  }

  prefs->SetInteger(prefs::kCapsLockRemappingNudgeShownCount, shown_count + 1);
  prefs->SetTime(prefs::kCapsLockRemappingNudgeLastShown, now);

  AnchoredNudgeData nudge_data(
      kCapsLockNoMatchNudgeId, NudgeCatalogName::kCapsLockShortcutPressed,
      l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_CAPS_LOCK_NUDGE_DESCRIPTION));
  nudge_data.keyboard_codes = {ui::VKEY_FUNCTION, ui::VKEY_RIGHT_ALT};
  nudge_data.image_model =
      ui::ResourceBundle::GetSharedInstance().GetThemedLottieImageNamed(
          IDR_KEYBOARD_CAPSLOCK_KEY_NUDGE_IMAGE);

  AnchoredNudgeManager::Get()->Show(nudge_data);
}

std::optional<std::string>
InputDeviceSettingsNotificationController::GetDeviceKeyForNotificationId(
    const std::string& notification_id) {
  auto it = notification_id_to_device_key_map_.find(notification_id);
  if (it == notification_id_to_device_key_map_.end()) {
    return std::nullopt;
  }
  return it->second;
}

}  // namespace ash