chromium/ash/system/input_device_settings/input_device_settings_notification_controller_unittest.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 "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/test/test_new_window_delegate.h"
#include "ash/public/cpp/test/test_system_tray_client.h"
#include "ash/public/mojom/input_device_settings.mojom-forward.h"
#include "ash/public/mojom/input_device_settings.mojom-shared.h"
#include "ash/public/mojom/input_device_settings.mojom.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_metrics_manager.h"
#include "ash/system/input_device_settings/input_device_settings_pref_names.h"
#include "ash/system/toast/anchored_nudge.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "ash/test/ash_test_base.h"
#include "base/containers/contains.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.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_unittest_util.h"
#include "ui/message_center/fake_message_center.h"

namespace ash {

namespace {

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";

const mojom::Mouse kMouse1 = mojom::Mouse(
    /*name=*/"Razer Basilisk V3",
    /*is_external=*/true,
    /*id=*/1,
    /*device_key=*/"fake-device-key1",
    /*customization_restriction=*/
    mojom::CustomizationRestriction::kAllowCustomizations,
    /*mouse_button_config=*/mojom::MouseButtonConfig::kNoConfig,
    mojom::MouseSettings::New(),
    mojom::BatteryInfo::New(),
    mojom::CompanionAppInfo::New());

const mojom::GraphicsTablet kGraphicsTablet2 = mojom::GraphicsTablet(
    /*name=*/"Wacom Intuos S",
    /*id=*/2,
    /*device_key=*/"fake-device-key2",
    /*customization_restriction=*/
    ::ash::mojom::CustomizationRestriction::kAllowCustomizations,
    /*graphics_tablet_button_config=*/
    mojom::GraphicsTabletButtonConfig::kNoConfig,
    mojom::GraphicsTabletSettings::New(),
    mojom::BatteryInfo::New(),
    mojom::CompanionAppInfo::New());

int GetPrefNotificationCount(const char* pref_name) {
  PrefService* prefs =
      Shell::Get()->session_controller()->GetActivePrefService();
  return prefs->GetInteger(pref_name);
}

class TestMessageCenter : public message_center::FakeMessageCenter {
 public:
  TestMessageCenter() = default;

  TestMessageCenter(const TestMessageCenter&) = delete;
  TestMessageCenter& operator=(const TestMessageCenter&) = delete;

  ~TestMessageCenter() override = default;

  void ClickOnNotification(const std::string& id) override {
    message_center::Notification* notification =
        FindVisibleNotificationById(id);
    CHECK(notification);
    notification->delegate()->Click(std::nullopt, std::nullopt);
  }

  void ClickOnNotificationButton(const std::string& id,
                                 int button_index) override {
    message_center::Notification* notification =
        FindVisibleNotificationById(id);
    CHECK(notification);
    notification->delegate()->Click(button_index, std::nullopt);
  }
};

class MockNewWindowDelegate : public testing::NiceMock<TestNewWindowDelegate> {
 public:
  // TestNewWindowDelegate:
  MOCK_METHOD(void,
              OpenUrl,
              (const GURL& url, OpenUrlFrom from, Disposition disposition),
              (override));
};

void CancelNudge(const std::string& id) {
  Shell::Get()->anchored_nudge_manager()->Cancel(id);
  base::RunLoop().RunUntilIdle();
}

}  // namespace

class InputDeviceSettingsNotificationControllerTest : public AshTestBase {
 public:
  InputDeviceSettingsNotificationControllerTest() = default;

  InputDeviceSettingsNotificationControllerTest(
      const InputDeviceSettingsNotificationControllerTest&) = delete;
  InputDeviceSettingsNotificationControllerTest& operator=(
      const InputDeviceSettingsNotificationControllerTest&) = delete;

  ~InputDeviceSettingsNotificationControllerTest() override = default;

  message_center::FakeMessageCenter* message_center() {
    return message_center_.get();
  }
  InputDeviceSettingsNotificationController* controller() {
    return controller_.get();
  }

  void NotifyMouseIsCustomizable(const mojom::Mouse& mouse,
                                 gfx::ImageSkia image = gfx::ImageSkia()) {
    controller()->NotifyMouseIsCustomizable(mouse, image);
  }

  void NotifyMouseFirstTimeConnected(const mojom::Mouse& mouse,
                                     gfx::ImageSkia image = gfx::ImageSkia()) {
    controller()->NotifyMouseFirstTimeConnected(mouse, image);
  }

  void NotifyTouchpadFirstTimeConnected(
      const mojom::Touchpad& touchpad,
      gfx::ImageSkia image = gfx::ImageSkia()) {
    controller()->NotifyTouchpadFirstTimeConnected(touchpad, image);
  }

  void NotifyGraphicsTabletIsCustomizable(
      const mojom::GraphicsTablet& graphics_tablet,
      gfx::ImageSkia image = gfx::ImageSkia()) {
    controller()->NotifyGraphicsTabletIsCustomizable(graphics_tablet, image);
  }

  void NotifyGraphicsTabletFirstTimeConnected(
      const mojom::GraphicsTablet& graphics_tablet,
      gfx::ImageSkia image = gfx::ImageSkia()) {
    controller()->NotifyGraphicsTabletFirstTimeConnected(graphics_tablet,
                                                         image);
  }

  void NotifyKeyboardFirstTimeConnected(
      const mojom::Keyboard& keyboard,
      gfx::ImageSkia image = gfx::ImageSkia()) {
    controller()->NotifyKeyboardFirstTimeConnected(keyboard, image);
  }

  // AshTestBase:
  void SetUp() override {
    auto delegate = std::make_unique<MockNewWindowDelegate>();
    new_window_delegate_ = delegate.get();
    delegate_provider_ =
        std::make_unique<TestNewWindowDelegateProvider>(std::move(delegate));
    AshTestBase::SetUp();
    message_center_ = std::make_unique<TestMessageCenter>();
    controller_ = std::make_unique<InputDeviceSettingsNotificationController>(
        message_center_.get());
  }

  void TearDown() override {
    controller_.reset();
    message_center_.reset();
    AshTestBase::TearDown();
  }

 protected:
  MockNewWindowDelegate& new_window_delegate() { return *new_window_delegate_; }
  raw_ptr<MockNewWindowDelegate, DanglingUntriaged> new_window_delegate_;
  std::unique_ptr<TestMessageCenter> message_center_;
  std::unique_ptr<InputDeviceSettingsNotificationController> controller_;
  std::unique_ptr<TestNewWindowDelegateProvider> delegate_provider_;
};

TEST_F(InputDeviceSettingsNotificationControllerTest,
       NotifyRightClickRewriteBlockedBySetting) {
  size_t expected_notification_count = 1;
  controller()->NotifyRightClickRewriteBlockedBySetting(
      ui::mojom::SimulateRightClickModifier::kAlt,
      ui::mojom::SimulateRightClickModifier::kSearch);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(message_center()->FindVisibleNotificationById(
      "alt_right_click_rewrite_blocked_by_setting"));

  controller()->NotifyRightClickRewriteBlockedBySetting(
      ui::mojom::SimulateRightClickModifier::kSearch,
      ui::mojom::SimulateRightClickModifier::kAlt);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(message_center()->FindVisibleNotificationById(
      "search_right_click_rewrite_blocked_by_setting"));

  controller()->NotifyRightClickRewriteBlockedBySetting(
      ui::mojom::SimulateRightClickModifier::kAlt,
      ui::mojom::SimulateRightClickModifier::kNone);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(message_center()->FindVisibleNotificationById(
      "right_click_rewrite_disabled_by_setting"));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       RemapToRightClickNotificationOnlyShownForActiveUserSessions) {
  GetSessionControllerClient()->LockScreen();

  controller()->NotifyRightClickRewriteBlockedBySetting(
      ui::mojom::SimulateRightClickModifier::kAlt,
      ui::mojom::SimulateRightClickModifier::kSearch);
  EXPECT_EQ(message_center()->NotificationCount(), 0u);
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       SixPackKeyNotificationShownAtMostThreeTimes) {
  EXPECT_EQ(3, GetPrefNotificationCount(
                   prefs::kSixPackKeyDeleteNotificationsRemaining));

  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_DELETE, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_EQ(1u, message_center()->NotificationCount());
  EXPECT_EQ(2, GetPrefNotificationCount(
                   prefs::kSixPackKeyDeleteNotificationsRemaining));

  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_DELETE, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_EQ(1, GetPrefNotificationCount(
                   prefs::kSixPackKeyDeleteNotificationsRemaining));

  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_DELETE, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_EQ(0, GetPrefNotificationCount(
                   prefs::kSixPackKeyDeleteNotificationsRemaining));

  message_center()->RemoveAllNotifications(
      false, message_center::MessageCenter::RemoveType::ALL);
  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_DELETE, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_EQ(0u, message_center()->NotificationCount());

  // Only the delete notification pref should have changed.
  EXPECT_EQ(
      3, GetPrefNotificationCount(prefs::kSixPackKeyEndNotificationsRemaining));
  EXPECT_EQ(3, GetPrefNotificationCount(
                   prefs::kSixPackKeyHomeNotificationsRemaining));
  EXPECT_EQ(3, GetPrefNotificationCount(
                   prefs::kSixPackKeyInsertNotificationsRemaining));
  EXPECT_EQ(3, GetPrefNotificationCount(
                   prefs::kSixPackKeyPageUpNotificationsRemaining));
  EXPECT_EQ(3, GetPrefNotificationCount(
                   prefs::kSixPackKeyPageDownNotificationsRemaining));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       RightClickNotificationShownAtMostThreeTimes) {
  EXPECT_EQ(3, GetPrefNotificationCount(
                   prefs::kRemapToRightClickNotificationsRemaining));

  controller()->NotifyRightClickRewriteBlockedBySetting(
      ui::mojom::SimulateRightClickModifier::kAlt,
      ui::mojom::SimulateRightClickModifier::kSearch);
  EXPECT_EQ(1u, message_center()->NotificationCount());
  EXPECT_EQ(2, GetPrefNotificationCount(
                   prefs::kRemapToRightClickNotificationsRemaining));

  controller()->NotifyRightClickRewriteBlockedBySetting(
      ui::mojom::SimulateRightClickModifier::kSearch,
      ui::mojom::SimulateRightClickModifier::kAlt);
  EXPECT_EQ(2u, message_center()->NotificationCount());
  EXPECT_EQ(1, GetPrefNotificationCount(
                   prefs::kRemapToRightClickNotificationsRemaining));

  controller()->NotifyRightClickRewriteBlockedBySetting(
      ui::mojom::SimulateRightClickModifier::kAlt,
      ui::mojom::SimulateRightClickModifier::kNone);
  EXPECT_EQ(3u, message_center()->NotificationCount());
  EXPECT_EQ(0, GetPrefNotificationCount(
                   prefs::kRemapToRightClickNotificationsRemaining));

  controller()->NotifyRightClickRewriteBlockedBySetting(
      ui::mojom::SimulateRightClickModifier::kAlt,
      ui::mojom::SimulateRightClickModifier::kSearch);
  EXPECT_EQ(3u, message_center()->NotificationCount());
  EXPECT_EQ(0, GetPrefNotificationCount(
                   prefs::kRemapToRightClickNotificationsRemaining));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       StopShowingNotificationIfUserClicksOnIt) {
  EXPECT_EQ(3, GetPrefNotificationCount(
                   prefs::kRemapToRightClickNotificationsRemaining));

  controller()->NotifyRightClickRewriteBlockedBySetting(
      ui::mojom::SimulateRightClickModifier::kAlt,
      ui::mojom::SimulateRightClickModifier::kSearch);
  message_center()->ClickOnNotification(
      "alt_right_click_rewrite_blocked_by_setting");
  EXPECT_EQ(0, GetPrefNotificationCount(
                   prefs::kRemapToRightClickNotificationsRemaining));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       ShowPeripheralSettingsOnCustomizationNotificationClick) {
  NotifyMouseIsCustomizable(kMouse1);
  message_center()->ClickOnNotification("welcome_experience_1");
  EXPECT_EQ(GetSystemTrayClient()->show_mouse_settings_count(), 1);

  NotifyGraphicsTabletIsCustomizable(kGraphicsTablet2);
  message_center()->ClickOnNotification("welcome_experience_2");
  EXPECT_EQ(GetSystemTrayClient()->show_graphics_tablet_settings_count(), 1);
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       ShowPeripheralSettingsOnCustomizationNotificationButtonClick) {
  NotifyMouseIsCustomizable(kMouse1);
  message_center()->ClickOnNotificationButton("welcome_experience_1",
                                              /*button_index=*/0);
  EXPECT_EQ(GetSystemTrayClient()->show_mouse_settings_count(), 1);

  NotifyGraphicsTabletIsCustomizable(kGraphicsTablet2);
  message_center()->ClickOnNotificationButton("welcome_experience_2",
                                              /*button_index=*/0);
  EXPECT_EQ(GetSystemTrayClient()->show_graphics_tablet_settings_count(), 1);
}

// TODO(b/279503977): Add test that verifies behavior of clicking on the
// "Learn more" button.
TEST_F(InputDeviceSettingsNotificationControllerTest,
       ShowTouchpadSettingsOnRightClickNotificationClick) {
  controller()->NotifyRightClickRewriteBlockedBySetting(
      ui::mojom::SimulateRightClickModifier::kAlt,
      ui::mojom::SimulateRightClickModifier::kSearch);
  message_center()->ClickOnNotificationButton(
      "alt_right_click_rewrite_blocked_by_setting",
      NotificationButtonIndex::BUTTON_EDIT_SHORTCUT);
  EXPECT_EQ(GetSystemTrayClient()->show_touchpad_settings_count(), 1);
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       ShowRemapKeysSettingsOnSixPackNotificationClick) {
  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_DELETE, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  message_center()->ClickOnNotificationButton(
      "delete_six_pack_rewrite_blocked_by_setting_1",
      NotificationButtonIndex::BUTTON_EDIT_SHORTCUT);
  EXPECT_EQ(GetSystemTrayClient()->show_remap_keys_subpage_count(), 1);
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       NotifySixPackRewriteBlockedBySetting) {
  size_t expected_notification_count = 1;
  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_DELETE, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(message_center()->FindVisibleNotificationById(
      "delete_six_pack_rewrite_blocked_by_setting_1"));

  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_INSERT, ui::mojom::SixPackShortcutModifier::kSearch,
      ui::mojom::SixPackShortcutModifier::kNone,
      /*device_id=*/1);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(message_center()->FindVisibleNotificationById(
      "insert_six_pack_rewrite_blocked_by_setting_1"));

  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_HOME, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(message_center()->FindVisibleNotificationById(
      "home_six_pack_rewrite_blocked_by_setting_1"));

  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_END, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(message_center()->FindVisibleNotificationById(
      "end_six_pack_rewrite_blocked_by_setting_1"));

  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_PRIOR, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(message_center()->FindVisibleNotificationById(
      "page_up_six_pack_rewrite_blocked_by_setting_1"));

  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_NEXT, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(message_center()->FindVisibleNotificationById(
      "page_down_six_pack_rewrite_blocked_by_setting_1"));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       SixPackRewriteNotificationOnlyShownForActiveUserSessions) {
  GetSessionControllerClient()->LockScreen();
  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_PRIOR, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_EQ(message_center()->NotificationCount(), 0u);
}

TEST_F(InputDeviceSettingsNotificationControllerTest, LearnMoreButtonClicked) {
  controller()->NotifySixPackRewriteBlockedBySetting(
      ui::VKEY_DELETE, ui::mojom::SixPackShortcutModifier::kAlt,
      ui::mojom::SixPackShortcutModifier::kSearch,
      /*device_id=*/1);
  EXPECT_CALL(
      new_window_delegate(),
      OpenUrl(GURL("https://support.google.com/chromebook?p=keyboard_settings"),
              NewWindowDelegate::OpenUrlFrom::kUserInteraction,
              NewWindowDelegate::Disposition::kNewForegroundTab));
  message_center()->ClickOnNotificationButton(
      "delete_six_pack_rewrite_blocked_by_setting_1",
      NotificationButtonIndex::BUTTON_LEARN_MORE);
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       NotifyMouseFirstTimeConnected) {
  size_t expected_notification_count = 1;
  mojom::MousePtr mojom_mouse = mojom::Mouse::New();
  mojom_mouse->device_key = "0001:0001";
  mojom_mouse->id = 1;
  mojom_mouse->is_external = true;
  mojom_mouse->settings = mojom::MouseSettings::New();

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

  EXPECT_TRUE(
      prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).empty());
  NotifyMouseFirstTimeConnected(*mojom_mouse);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            1u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0001:0001")));
  NotifyMouseFirstTimeConnected(*mojom_mouse);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            1u);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(
      message_center()->FindVisibleNotificationById("welcome_experience_1"));

  mojom_mouse->id = 2;
  mojom_mouse->device_key = "0001:0002";

  NotifyMouseFirstTimeConnected(*mojom_mouse);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            2u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0001:0002")));
  EXPECT_EQ(expected_notification_count, message_center()->NotificationCount());
  EXPECT_TRUE(
      message_center()->FindVisibleNotificationById("welcome_experience_2"));

  mojom_mouse->id = 3;
  mojom_mouse->device_key = "0001:0003";
  mojom_mouse->settings->button_remappings.push_back(
      mojom::ButtonRemapping::New(
          /*name=*/"Button 1",
          mojom::Button::NewCustomizableButton(
              mojom::CustomizableButton::kBack),
          /*remapping_action=*/nullptr));
  NotifyMouseFirstTimeConnected(*mojom_mouse);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            3u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0001:0003")));
  EXPECT_EQ(expected_notification_count, message_center()->NotificationCount());
  EXPECT_FALSE(
      message_center()->FindVisibleNotificationById("welcome_experience_3"));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       NotifyGraphicsTabletFirstTimeConnected) {
  size_t expected_notification_count = 1;
  mojom::GraphicsTabletPtr mojom_graphics_tablet = mojom::GraphicsTablet::New();
  mojom_graphics_tablet->id = 1;
  mojom_graphics_tablet->device_key = "0002:0001";
  mojom_graphics_tablet->settings = mojom::GraphicsTabletSettings::New();
  PrefService* prefs =
      Shell::Get()->session_controller()->GetActivePrefService();

  EXPECT_TRUE(
      prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).empty());
  NotifyGraphicsTabletFirstTimeConnected(*mojom_graphics_tablet);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            1u);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(
      message_center()->FindVisibleNotificationById("welcome_experience_1"));

  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0002:0001")));
  NotifyGraphicsTabletFirstTimeConnected(*mojom_graphics_tablet);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            1u);

  mojom_graphics_tablet->id = 2;
  mojom_graphics_tablet->device_key = "0002:0002";

  NotifyGraphicsTabletFirstTimeConnected(*mojom_graphics_tablet);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            2u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0002:0002")));
  EXPECT_EQ(expected_notification_count, message_center()->NotificationCount());
  EXPECT_TRUE(
      message_center()->FindVisibleNotificationById("welcome_experience_2"));

  mojom_graphics_tablet->id = 3;
  mojom_graphics_tablet->device_key = "0002:0003";
  mojom_graphics_tablet->settings->pen_button_remappings.push_back(
      mojom::ButtonRemapping::New(
          /*name=*/"Button 1",
          mojom::Button::NewCustomizableButton(
              mojom::CustomizableButton::kBack),
          /*remapping_action=*/nullptr));

  NotifyGraphicsTabletFirstTimeConnected(*mojom_graphics_tablet);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            3u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0002:0003")));
  EXPECT_EQ(expected_notification_count, message_center()->NotificationCount());
  EXPECT_FALSE(
      message_center()->FindVisibleNotificationById("welcome_experience_3"));

  mojom_graphics_tablet->id = 4;
  mojom_graphics_tablet->device_key = "0002:0004";
  mojom_graphics_tablet->settings->pen_button_remappings.clear();
  mojom_graphics_tablet->settings->tablet_button_remappings.push_back(
      mojom::ButtonRemapping::New(
          /*name=*/"Button 1",
          mojom::Button::NewCustomizableButton(
              mojom::CustomizableButton::kBack),
          /*remapping_action=*/nullptr));

  NotifyGraphicsTabletFirstTimeConnected(*mojom_graphics_tablet);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            4u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0002:0004")));
  EXPECT_EQ(expected_notification_count, message_center()->NotificationCount());
  EXPECT_FALSE(
      message_center()->FindVisibleNotificationById("welcome_experience_4"));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       ShowTopRowRewritingNudge) {
  AnchoredNudgeManagerImpl* nudge_manager =
      Shell::Get()->anchored_nudge_manager();
  ASSERT_TRUE(nudge_manager);
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kTopRowKeyNoMatchNudgeId));

  controller()->ShowTopRowRewritingNudge();
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kTopRowKeyNoMatchNudgeId));
  CancelNudge(kTopRowKeyNoMatchNudgeId);

  // Call top row remapping nudge again before 24 hours, the nudge should not
  // show.
  controller()->ShowTopRowRewritingNudge();
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kTopRowKeyNoMatchNudgeId));

  // Pretend top row remapping was called before 24 hours, should show nudge
  // again.
  Shell::Get()->session_controller()->GetActivePrefService()->SetTime(
      prefs::kTopRowRemappingNudgeLastShown,
      base::Time::Now() - base::Hours(24));
  controller()->ShowTopRowRewritingNudge();
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kTopRowKeyNoMatchNudgeId));
  CancelNudge(kTopRowKeyNoMatchNudgeId);

  // Pretend top row remapping nudge was called 3 times, should not show
  // nudge again.
  Shell::Get()->session_controller()->GetActivePrefService()->SetTime(
      prefs::kTopRowRemappingNudgeLastShown,
      base::Time::Now() - base::Hours(24));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kTopRowRemappingNudgeShownCount, 3u);
  controller()->ShowTopRowRewritingNudge();
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kTopRowKeyNoMatchNudgeId));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       ShowSixPackKeyRewritingNudge) {
  const AnchoredNudge* nudge =
      Shell::Get()->anchored_nudge_manager()->GetNudgeIfShown(
          kSixPackKeyNoMatchNudgeId);
  ASSERT_FALSE(nudge);
  base::Value::Dict overrides;
  overrides.Set(prefs::kSixPackKeyInsert, /*kSearch*/ 2);
  overrides.Set(prefs::kSixPackKeyDelete, /*kAlt*/ 1);
  overrides.Set(prefs::kSixPackKeyHome, /*kSearch*/ 2);
  overrides.Set(prefs::kSixPackKeyPageUp, /*kAlt*/ 1);
  overrides.Set(prefs::kSixPackKeyEnd, /*kSearch*/ 2);
  base::Value::Dict remappings;
  remappings.Set(prefs::kKeyboardSettingSixPackKeyRemappings,
                 std::move(overrides));
  Shell::Get()->session_controller()->GetActivePrefService()->SetDict(
      prefs::kKeyboardDefaultChromeOSSettings, std::move(remappings));
  AnchoredNudgeManagerImpl* nudge_manager =
      Shell::Get()->anchored_nudge_manager();
  ASSERT_TRUE(nudge_manager);

  // Display nudge for VKEY_DELETE.
  controller()->ShowSixPackKeyRewritingNudge(
      ui::VKEY_DELETE, ui::mojom::SixPackShortcutModifier::kSearch);
  // Modifier not match, nudge should not show.
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
  controller()->ShowSixPackKeyRewritingNudge(
      ui::VKEY_DELETE, ui::mojom::SixPackShortcutModifier::kAlt);
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
  EXPECT_EQ(
      nudge_manager->GetNudgeBodyTextForTest(kSixPackKeyNoMatchNudgeId),
      l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_DELETE_NUDGE_DESCRIPTION));
  CancelNudge(kSixPackKeyNoMatchNudgeId);
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));

  // Display nudge for VKEY_HOME.
  controller()->ShowSixPackKeyRewritingNudge(
      ui::VKEY_HOME, ui::mojom::SixPackShortcutModifier::kSearch);
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
  EXPECT_EQ(
      nudge_manager->GetNudgeBodyTextForTest(kSixPackKeyNoMatchNudgeId),
      l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_HOME_NUDGE_DESCRIPTION));
  CancelNudge(kSixPackKeyNoMatchNudgeId);
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));

  // Display nudge for VKEY_END.
  controller()->ShowSixPackKeyRewritingNudge(
      ui::VKEY_END, ui::mojom::SixPackShortcutModifier::kSearch);
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
  EXPECT_EQ(
      nudge_manager->GetNudgeBodyTextForTest(kSixPackKeyNoMatchNudgeId),
      l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_END_NUDGE_DESCRIPTION));
  CancelNudge(kSixPackKeyNoMatchNudgeId);
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));

  // Display nudge for VKEY_PRIOR.
  controller()->ShowSixPackKeyRewritingNudge(
      ui::VKEY_PRIOR, ui::mojom::SixPackShortcutModifier::kAlt);
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
  EXPECT_EQ(
      nudge_manager->GetNudgeBodyTextForTest(kSixPackKeyNoMatchNudgeId),
      l10n_util::GetStringUTF16(
          IDS_ASH_SETTINGS_KEYBOARD_USE_FN_KEY_FOR_PAGE_UP_NUDGE_DESCRIPTION));
  CancelNudge(kSixPackKeyNoMatchNudgeId);
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));

  // Six pack key VKEY_NEXT is not in the prefDict, should default show kSearch.
  controller()->ShowSixPackKeyRewritingNudge(
      ui::VKEY_NEXT, ui::mojom::SixPackShortcutModifier::kSearch);
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
  CancelNudge(kSixPackKeyNoMatchNudgeId);

  // Call the method with a non six pack key, should not show anything.
  // Six pack key VKEY_NEXT is not in the prefDict, should not show anything.
  controller()->ShowSixPackKeyRewritingNudge(
      ui::VKEY_BRIGHTNESS_UP, ui::mojom::SixPackShortcutModifier::kSearch);
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));

  // Call VKEY_PRIOR again before 24 hours, the nudge should not show.
  controller()->ShowSixPackKeyRewritingNudge(
      ui::VKEY_PRIOR, ui::mojom::SixPackShortcutModifier::kAlt);
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));

  // Pretend VKEY_PRIOR was called before 24 hours, should show nudge again.
  Shell::Get()->session_controller()->GetActivePrefService()->SetTime(
      prefs::kPageUpRemappingNudgeLastShown,
      base::Time::Now() - base::Hours(24));
  controller()->ShowSixPackKeyRewritingNudge(
      ui::VKEY_PRIOR, ui::mojom::SixPackShortcutModifier::kAlt);
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
  CancelNudge(kSixPackKeyNoMatchNudgeId);

  // Pretend VKEY_PRIOR has called 3 times, should not show nudge again.
  Shell::Get()->session_controller()->GetActivePrefService()->SetTime(
      prefs::kPageUpRemappingNudgeLastShown,
      base::Time::Now() - base::Hours(24));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kPageUpRemappingNudgeShownCount, 3u);
  controller()->ShowSixPackKeyRewritingNudge(
      ui::VKEY_PRIOR, ui::mojom::SixPackShortcutModifier::kAlt);
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       ShowCapsLockRewritingNudge) {
  AnchoredNudgeManagerImpl* nudge_manager =
      Shell::Get()->anchored_nudge_manager();
  ASSERT_TRUE(nudge_manager);
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kCapsLockNoMatchNudgeId));

  controller()->ShowCapsLockRewritingNudge();
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kCapsLockNoMatchNudgeId));
  CancelNudge(kCapsLockNoMatchNudgeId);

  // Call caps lock nudge again before 24 hours, the nudge should not show.
  controller()->ShowCapsLockRewritingNudge();
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kCapsLockNoMatchNudgeId));

  // Pretend caps lock was called before 24 hours, should show nudge again.
  Shell::Get()->session_controller()->GetActivePrefService()->SetTime(
      prefs::kCapsLockRemappingNudgeLastShown,
      base::Time::Now() - base::Hours(24));
  controller()->ShowCapsLockRewritingNudge();
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kCapsLockNoMatchNudgeId));
  CancelNudge(kCapsLockNoMatchNudgeId);

  // Pretend caps lock nudge was called 3 times, should not show nudge again.
  Shell::Get()->session_controller()->GetActivePrefService()->SetTime(
      prefs::kCapsLockRemappingNudgeLastShown,
      base::Time::Now() - base::Hours(24));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kCapsLockRemappingNudgeShownCount, 3u);
  controller()->ShowCapsLockRewritingNudge();
  EXPECT_FALSE(nudge_manager->GetNudgeIfShown(kCapsLockNoMatchNudgeId));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       NotifyKeyboardFirstTimeConnected) {
  base::HistogramTester histogram_tester;
  size_t expected_notification_count = 1;
  mojom::KeyboardPtr mojom_keyboard = mojom::Keyboard::New();
  mojom_keyboard->device_key = "0001:0001";
  mojom_keyboard->id = 1;
  mojom_keyboard->is_external = true;
  mojom_keyboard->settings = mojom::KeyboardSettings::New();

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

  EXPECT_TRUE(
      prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).empty());
  NotifyKeyboardFirstTimeConnected(*mojom_keyboard);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            1u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0001:0001")));
  NotifyKeyboardFirstTimeConnected(*mojom_keyboard);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            1u);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(
      message_center()->FindVisibleNotificationById("welcome_experience_1"));
  histogram_tester.ExpectBucketCount(
      "ChromeOS.WelcomeExperienceNotificationEvent",
      InputDeviceSettingsMetricsManager::
          WelcomeExperienceNotificationEventType::kShown,
      /*expected_count=*/1u);

  mojom_keyboard->id = 2;
  mojom_keyboard->device_key = "0001:0002";

  NotifyKeyboardFirstTimeConnected(*mojom_keyboard);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            2u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0001:0002")));
  EXPECT_EQ(expected_notification_count, message_center()->NotificationCount());
  EXPECT_TRUE(
      message_center()->FindVisibleNotificationById("welcome_experience_2"));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       NotifyTouchpadFirstTimeConnected) {
  base::HistogramTester histogram_tester;
  size_t expected_notification_count = 1;
  mojom::TouchpadPtr mojom_touchpad = mojom::Touchpad::New();
  mojom_touchpad->device_key = "0001:0001";
  mojom_touchpad->id = 1;
  mojom_touchpad->is_external = true;
  mojom_touchpad->settings = mojom::TouchpadSettings::New();

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

  EXPECT_TRUE(
      prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).empty());
  NotifyTouchpadFirstTimeConnected(*mojom_touchpad);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            1u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0001:0001")));
  NotifyTouchpadFirstTimeConnected(*mojom_touchpad);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            1u);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(
      message_center()->FindVisibleNotificationById("welcome_experience_1"));
  histogram_tester.ExpectBucketCount(
      "ChromeOS.WelcomeExperienceNotificationEvent",
      InputDeviceSettingsMetricsManager::
          WelcomeExperienceNotificationEventType::kShown,
      /*expected_count=*/1u);
  mojom_touchpad->id = 2;
  mojom_touchpad->device_key = "0001:0002";

  NotifyTouchpadFirstTimeConnected(*mojom_touchpad);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            2u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0001:0002")));
  EXPECT_EQ(expected_notification_count, message_center()->NotificationCount());
  EXPECT_TRUE(
      message_center()->FindVisibleNotificationById("welcome_experience_2"));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       NotifyPointingStickFirstTimeConnected) {
  size_t expected_notification_count = 1;
  mojom::PointingStickPtr mojom_pointing_stick = mojom::PointingStick::New();
  mojom_pointing_stick->device_key = "0001:0001";
  mojom_pointing_stick->id = 1;
  mojom_pointing_stick->is_external = true;
  mojom_pointing_stick->settings = mojom::PointingStickSettings::New();

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

  EXPECT_TRUE(
      prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).empty());
  controller()->NotifyPointingStickFirstTimeConnected(*mojom_pointing_stick);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            1u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0001:0001")));
  controller()->NotifyPointingStickFirstTimeConnected(*mojom_pointing_stick);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            1u);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  EXPECT_TRUE(
      message_center()->FindVisibleNotificationById("welcome_experience_1"));

  mojom_pointing_stick->id = 2;
  mojom_pointing_stick->device_key = "0001:0002";

  controller()->NotifyPointingStickFirstTimeConnected(*mojom_pointing_stick);
  EXPECT_EQ(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen).size(),
            2u);
  EXPECT_TRUE(
      base::Contains(prefs->GetList(prefs::kWelcomeExperienceNotificationSeen),
                     base::Value("0001:0002")));
  EXPECT_EQ(expected_notification_count, message_center()->NotificationCount());
  EXPECT_TRUE(
      message_center()->FindVisibleNotificationById("welcome_experience_2"));
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       NotificationsForBluetoothDevicesDisplaysBatteryLevel) {
  size_t expected_notification_count = 1;
  mojom::MousePtr mojom_mouse = mojom::Mouse::New();
  mojom_mouse->device_key = "0001:0001";
  mojom_mouse->id = 1;
  mojom_mouse->is_external = true;
  mojom_mouse->settings = mojom::MouseSettings::New();
  mojom_mouse->battery_info =
      mojom::BatteryInfo::New(78, mojom::ChargeState::kDischarging);

  NotifyMouseFirstTimeConnected(*mojom_mouse);
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  const auto* notification =
      message_center()->FindVisibleNotificationById("welcome_experience_1");
  ASSERT_TRUE(notification);
  EXPECT_EQ(
      l10n_util::GetStringFUTF16(
          IDS_ASH_DEVICE_SETTINGS_NOTIFICATIONS_WELCOME_EXPERIENCE_BATTERY_DESCRIPTION,
          base::NumberToString16(
              mojom_mouse->battery_info->battery_percentage)),
      notification->message());
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       NotificationWithDeviceImage) {
  size_t expected_notification_count = 1;
  mojom::MousePtr mojom_mouse = mojom::Mouse::New();
  mojom_mouse->device_key = "0001:0001";
  mojom_mouse->id = 1;
  mojom_mouse->is_external = true;
  mojom_mouse->settings = mojom::MouseSettings::New();

  NotifyMouseFirstTimeConnected(
      *mojom_mouse,
      gfx::test::CreateImageSkia(/*width=*/300, /*height=*/300, SK_ColorRED));
  EXPECT_EQ(expected_notification_count++,
            message_center()->NotificationCount());
  const auto* notification =
      message_center()->FindVisibleNotificationById("welcome_experience_1");
  EXPECT_FALSE(notification->image().IsEmpty());
}

TEST_F(InputDeviceSettingsNotificationControllerTest,
       NotificationOnlyShownForExternalDevices) {
  mojom::MousePtr mojom_mouse = mojom::Mouse::New();
  mojom_mouse->device_key = "0001:0001";
  mojom_mouse->id = 1;
  mojom_mouse->is_external = false;
  mojom_mouse->settings = mojom::MouseSettings::New();

  NotifyMouseFirstTimeConnected(*mojom_mouse);
  EXPECT_EQ(0u, message_center()->NotificationCount());
  mojom_mouse->device_key = "0001:0002";
  mojom_mouse->id = 2;
  mojom_mouse->is_external = true;
  NotifyMouseFirstTimeConnected(*mojom_mouse);
  EXPECT_EQ(1u, message_center()->NotificationCount());
}

}  // namespace ash