chromium/ui/events/ash/keyboard_capability_unittest.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/events/ash/keyboard_capability.h"

#include <linux/input-event-codes.h>

#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/test/scoped_feature_list.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "device/udev_linux/fake_udev_loader.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/ash/mojom/meta_key.mojom-shared.h"
#include "ui/events/ash/mojom/modifier_key.mojom-shared.h"
#include "ui/events/ash/mojom/modifier_key.mojom.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/ozone/evdev/event_device_info.h"
#include "ui/events/ozone/evdev/event_device_test_util.h"

namespace ui {

namespace {

constexpr char kKbdTopRowPropertyName[] = "CROS_KEYBOARD_TOP_ROW_LAYOUT";
constexpr char kKbdTopRowLayoutAttributeName[] = "function_row_physmap";

constexpr char kKbdTopRowLayoutUnspecified[] = "";
constexpr char kKbdTopRowLayout1Tag[] = "1";
constexpr char kKbdTopRowLayout2Tag[] = "2";
constexpr char kKbdTopRowLayoutWilcoTag[] = "3";
constexpr char kKbdTopRowLayoutDrallionTag[] = "4";

// A tag that should fail parsing for the top row layout.
constexpr char kKbdTopRowLayoutInvalidTag[] = "X";

// A default example of the layout string read from the function_row_physmap
// sysfs attribute. The values represent the scan codes for each position
// in the top row, which maps to F-Keys.
constexpr char kKbdDefaultCustomTopRowLayout[] =
    "01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f";

// A tag that should fail parsing for the top row custom scan code string.
constexpr char kKbdInvalidCustomTopRowLayout[] = "X X X";

// This set of scan code mappings come from x86 internal vivaldi keyboards.
enum CustomTopRowScanCode : uint32_t {
  kPreviousTrack = 0x90,
  kFullscreen = 0x91,
  kOverview = 0x92,
  kScreenshot = 0x93,
  kScreenBrightnessDown = 0x94,
  kScreenBrightnessUp = 0x95,
  kPrivacyScreenToggle = 0x96,
  kKeyboardBacklightDown = 0x97,
  kKeyboardBacklightUp = 0x98,
  kNextTrack = 0x99,
  kPlayPause = 0x9A,
  kMicrophoneMute = 0x9B,
  kKeyboardBacklightToggle = 0x9E,
  kVolumeMute = 0xA0,
  kVolumeDown = 0xAE,
  kVolumeUp = 0xB0,
  kForward = 0xE9,
  kBack = 0xEA,
  kRefresh = 0xE7,
};

constexpr int kDeviceId1 = 5;
constexpr int kDeviceId2 = 10;

InputDeviceType INTERNAL = InputDeviceType::INPUT_DEVICE_INTERNAL;
InputDeviceType EXTERNAL_USB = InputDeviceType::INPUT_DEVICE_USB;
InputDeviceType EXTERNAL_BLUETOOTH = InputDeviceType::INPUT_DEVICE_BLUETOOTH;
// For INPUT_DEVICE_UNKNOWN type, we treat it as external keyboard.
InputDeviceType EXTERNAL_UNKNOWN = InputDeviceType::INPUT_DEVICE_UNKNOWN;

struct KeyEventTestData {
  // All currently connected keyboards' connection type, e.g.
  // INPUT_DEVICE_INTERNAL.
  std::vector<InputDeviceType> keyboard_connection_types;
  // All currently connected keyboards' layout types.
  std::vector<std::string> keyboard_layout_types;
  KeyboardCode key_code;
  // Expected result of whether this key event exists on each keyboard.
  std::vector<bool> expected_has_key_event;
  // Expected result of whether this key event exists on all connected.
  bool expected_has_key_event_on_any_keyboard;
};

// NOTE: This only creates a simple KeyboardDevice based on a device
// capabilities report; it is not suitable for subclasses of KeyboardDevice.
KeyboardDevice KeyboardDeviceFromCapabilities(
    int device_id,
    const DeviceCapabilities& capabilities) {
  EventDeviceInfo device_info = {};
  CapabilitiesToDeviceInfo(capabilities, &device_info);
  return KeyboardDevice{
      InputDevice(device_id, device_info.device_type(), device_info.name(),
                  device_info.phys(), base::FilePath(capabilities.path),
                  device_info.vendor_id(), device_info.product_id(),
                  device_info.version()),
      device_info.HasKeyEvent(KEY_ASSISTANT), device_info.HasKeyEvent(KEY_FN)};
}

std::optional<uint32_t> GetEvdevKeyCodeForScanCode(const base::ScopedFD& fd,
                                                   uint32_t scancode) {
  switch (scancode) {
    case CustomTopRowScanCode::kPreviousTrack:
      return KEY_PREVIOUSSONG;
    case CustomTopRowScanCode::kFullscreen:
      return KEY_ZOOM;
    case CustomTopRowScanCode::kOverview:
      return KEY_SCALE;
    case CustomTopRowScanCode::kScreenshot:
      return KEY_SYSRQ;
    case CustomTopRowScanCode::kScreenBrightnessDown:
      return KEY_BRIGHTNESSDOWN;
    case CustomTopRowScanCode::kScreenBrightnessUp:
      return KEY_BRIGHTNESSUP;
    case CustomTopRowScanCode::kPrivacyScreenToggle:
      return KEY_PRIVACY_SCREEN_TOGGLE;
    case CustomTopRowScanCode::kKeyboardBacklightDown:
      return KEY_KBDILLUMDOWN;
    case CustomTopRowScanCode::kKeyboardBacklightUp:
      return KEY_KBDILLUMUP;
    case CustomTopRowScanCode::kNextTrack:
      return KEY_NEXTSONG;
    case CustomTopRowScanCode::kPlayPause:
      return KEY_PLAYPAUSE;
    case CustomTopRowScanCode::kMicrophoneMute:
      return KEY_MICMUTE;
    case CustomTopRowScanCode::kKeyboardBacklightToggle:
      return KEY_KBDILLUMTOGGLE;
    case CustomTopRowScanCode::kVolumeMute:
      return KEY_MUTE;
    case CustomTopRowScanCode::kVolumeDown:
      return KEY_VOLUMEDOWN;
    case CustomTopRowScanCode::kVolumeUp:
      return KEY_VOLUMEUP;
    case CustomTopRowScanCode::kForward:
      return KEY_FORWARD;
    case CustomTopRowScanCode::kBack:
      return KEY_BACK;
    case CustomTopRowScanCode::kRefresh:
      return KEY_REFRESH;
  }

  return std::nullopt;
}

class FakeDeviceManager {
 public:
  FakeDeviceManager() = default;
  FakeDeviceManager(const FakeDeviceManager&) = delete;
  FakeDeviceManager& operator=(const FakeDeviceManager&) = delete;
  ~FakeDeviceManager() { RemoveAllDevices(); }

  // Add a fake keyboard to DeviceDataManagerTestApi and provide layout info to
  // fake udev.
  void AddFakeKeyboard(const KeyboardDevice& fake_keyboard,
                       const std::string& layout,
                       bool has_custom_top_row = false) {
    fake_keyboard_devices_.push_back(fake_keyboard);

    DeviceDataManagerTestApi().SetKeyboardDevices(fake_keyboard_devices_);
    DeviceDataManagerTestApi().OnDeviceListsComplete();

    std::map<std::string, std::string> sysfs_properties;
    std::map<std::string, std::string> sysfs_attributes;
    if (has_custom_top_row) {
      sysfs_attributes[kKbdTopRowLayoutAttributeName] = layout;
    } else {
      sysfs_properties[kKbdTopRowPropertyName] = layout;
    }
    fake_udev_.AddFakeDevice(fake_keyboard.name, fake_keyboard.sys_path.value(),
                             /*subsystem=*/"input", /*devnode=*/std::nullopt,
                             /*devtype=*/std::nullopt,
                             std::move(sysfs_attributes),
                             std::move(sysfs_properties));
  }

  void RemoveAllDevices() {
    fake_udev_.Reset();
    fake_keyboard_devices_.clear();
    DeviceDataManagerTestApi().SetKeyboardDevices({});
  }

 private:
  testing::FakeUdevLoader fake_udev_;
  std::vector<KeyboardDevice> fake_keyboard_devices_;
};

}  // namespace

class KeyboardCapabilityTestBase : public testing::Test {
 public:
  KeyboardCapabilityTestBase() = default;
  ~KeyboardCapabilityTestBase() override = default;

  void SetUp() override {
    user_manager_ = std::make_unique<user_manager::FakeUserManager>();
    user_manager_->Initialize();
    keyboard_capability_ = std::make_unique<KeyboardCapability>(
        base::BindRepeating(&GetEvdevKeyCodeForScanCode));
    fake_keyboard_manager_ = std::make_unique<FakeDeviceManager>();
  }

  void TearDown() override {
    fake_keyboard_devices_.clear();
    keyboard_capability_.reset();
    user_manager_->Destroy();
    user_manager_.reset();
  }

  KeyboardDevice AddFakeKeyboardInfoToKeyboardCapability(
      int device_id,
      DeviceCapabilities capabilities,
      KeyboardCapability::DeviceType device_type,
      KeyboardCapability::KeyboardTopRowLayout top_row_layout) {
    KeyboardCapability::KeyboardInfo keyboard_info;
    keyboard_info.device_type = device_type;
    keyboard_info.top_row_layout = top_row_layout;

    KeyboardDevice fake_keyboard =
        KeyboardDeviceFromCapabilities(device_id, capabilities);

    keyboard_capability_->SetKeyboardInfoForTesting(fake_keyboard,
                                                    std::move(keyboard_info));
    fake_keyboard_devices_.push_back(fake_keyboard);
    DeviceDataManagerTestApi().SetKeyboardDevices(fake_keyboard_devices_);

    return fake_keyboard;
  }

 protected:
  std::unique_ptr<KeyboardCapability> keyboard_capability_;
  std::unique_ptr<FakeDeviceManager> fake_keyboard_manager_;
  std::unique_ptr<user_manager::FakeUserManager> user_manager_;
  std::vector<KeyboardDevice> fake_keyboard_devices_;
};

class KeyboardCapabilityTest : public KeyboardCapabilityTestBase,
                               public testing::WithParamInterface<bool> {
 public:
  void SetUp() override {
    modifier_split_feature_list_ =
        std::make_unique<base::test::ScopedFeatureList>();
    if (GetParam()) {
      modifier_split_feature_list_->InitAndEnableFeature(
          ash::features::kModifierSplit);
    } else {
      modifier_split_feature_list_->InitAndDisableFeature(
          ash::features::kModifierSplit);
    }
    KeyboardCapabilityTestBase::SetUp();
  }

 protected:
  std::unique_ptr<base::test::ScopedFeatureList> modifier_split_feature_list_;
  base::AutoReset<bool> modifier_split_reset_ =
      ash::switches::SetIgnoreModifierSplitSecretKeyForTest();
};

INSTANTIATE_TEST_SUITE_P(All, KeyboardCapabilityTest, testing::Bool());

TEST_P(KeyboardCapabilityTest, TestIsSixPackKey) {
  for (const auto& [key_code, _] : kSixPackKeyToSearchSystemKeyMap) {
    EXPECT_TRUE(keyboard_capability_->IsSixPackKey(key_code));
  }

  for (const auto& [key_code, _] : kSixPackKeyToAltSystemKeyMap) {
    EXPECT_TRUE(keyboard_capability_->IsSixPackKey(key_code));
  }

  // A key not in the kSixPackKeyToSystemKeyMap is not a six pack key.
  EXPECT_FALSE(keyboard_capability_->IsSixPackKey(KeyboardCode::VKEY_A));
}

TEST_P(KeyboardCapabilityTest, TestGetMappedFKeyIfExists) {
  KeyboardDevice fake_keyboard(
      /*id=*/1, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"fake_Keyboard");
  fake_keyboard.sys_path = base::FilePath("path1");

  // Add a fake layout1 keyboard.
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard, kKbdTopRowLayout1Tag);
  for (const auto& [key_code, f_key] : kLayout1TopRowKeyToFKeyMap) {
    EXPECT_EQ(f_key, keyboard_capability_
                         ->GetMappedFKeyIfExists(key_code, fake_keyboard)
                         .value());
  }
  // VKEY_MEDIA_PLAY_PAUSE key is not a top row key for layout1.
  EXPECT_FALSE(keyboard_capability_
                   ->GetMappedFKeyIfExists(KeyboardCode::VKEY_MEDIA_PLAY_PAUSE,
                                           fake_keyboard)
                   .has_value());

  // Replace by a fake layout2 keyboard.
  fake_keyboard_manager_->RemoveAllDevices();
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard, kKbdTopRowLayout2Tag);
  for (const auto& [key_code, f_key] : kLayout2TopRowKeyToFKeyMap) {
    EXPECT_EQ(f_key, keyboard_capability_
                         ->GetMappedFKeyIfExists(key_code, fake_keyboard)
                         .value());
  }
  // VKEY_BROWSER_FORWARD key is not a top row key for layout2.
  EXPECT_FALSE(keyboard_capability_
                   ->GetMappedFKeyIfExists(KeyboardCode::VKEY_BROWSER_FORWARD,
                                           fake_keyboard)
                   .has_value());

  // Replace by a fake wilco keyboard.
  fake_keyboard_manager_->RemoveAllDevices();
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard,
                                          kKbdTopRowLayoutWilcoTag);
  for (const auto& [key_code, f_key] : kLayoutWilcoDrallionTopRowKeyToFKeyMap) {
    EXPECT_EQ(f_key, keyboard_capability_
                         ->GetMappedFKeyIfExists(key_code, fake_keyboard)
                         .value());
  }
  // VKEY_MEDIA_PLAY_PAUSE key is not a top row key for wilco layout.
  EXPECT_FALSE(keyboard_capability_
                   ->GetMappedFKeyIfExists(KeyboardCode::VKEY_MEDIA_PLAY_PAUSE,
                                           fake_keyboard)
                   .has_value());

  // Replace by a fake drallion keyboard.
  fake_keyboard_manager_->RemoveAllDevices();
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard,
                                          kKbdTopRowLayoutDrallionTag);
  for (const auto& [key_code, f_key] : kLayoutWilcoDrallionTopRowKeyToFKeyMap) {
    EXPECT_EQ(f_key, keyboard_capability_
                         ->GetMappedFKeyIfExists(key_code, fake_keyboard)
                         .value());
  }
  // VKEY_BROWSER_FORWARD key is not a top row key for drallion layout.
  EXPECT_FALSE(keyboard_capability_
                   ->GetMappedFKeyIfExists(KeyboardCode::VKEY_BROWSER_FORWARD,
                                           fake_keyboard)
                   .has_value());
}

TEST_P(KeyboardCapabilityTest, TestHasLauncherButton) {
  // Add a non-layout2 keyboard.
  KeyboardDevice fake_keyboard1(
      /*id=*/kDeviceId1, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard1");
  fake_keyboard1.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard1, kKbdTopRowLayout1Tag);

  // Provide specific keyboard. Launcher button depends on if the keyboard is
  // layout2 type.
  EXPECT_FALSE(keyboard_capability_->HasLauncherButton(fake_keyboard1));
  // Do not provide specific keyboard. Launcher button depends on if any one
  // of the keyboards is layout2 type.
  EXPECT_FALSE(keyboard_capability_->HasLauncherButtonOnAnyKeyboard());

  // Add a layout2 keyboard.
  KeyboardDevice fake_keyboard2(
      /*id=*/kDeviceId2, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard2");
  fake_keyboard1.sys_path = base::FilePath("path2");
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard2, kKbdTopRowLayout2Tag);

  EXPECT_FALSE(keyboard_capability_->HasLauncherButton(fake_keyboard1));
  EXPECT_TRUE(keyboard_capability_->HasLauncherButton(fake_keyboard2));
  EXPECT_TRUE(keyboard_capability_->HasLauncherButtonOnAnyKeyboard());

  fake_keyboard_manager_->RemoveAllDevices();
  // Add an external layout1 keyboard.
  KeyboardDevice fake_keyboard3(
      /*id=*/kDeviceId1, /*type=*/InputDeviceType::INPUT_DEVICE_USB,
      /*name=*/"Keyboard1");
  fake_keyboard3.sys_path = base::FilePath("path3");
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard3, kKbdTopRowLayout1Tag);
  EXPECT_TRUE(keyboard_capability_->HasLauncherButtonOnAnyKeyboard());
}

TEST_P(KeyboardCapabilityTest, TestGetMetaKey) {
  // Add a non-layout2 keyboard.
  KeyboardDevice fake_keyboard1(
      /*id=*/kDeviceId1, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard1");
  fake_keyboard1.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard1, kKbdTopRowLayout1Tag);

  // Provide specific keyboard. Launcher button depends on if the keyboard is
  // layout2 type.
  EXPECT_EQ(mojom::MetaKey::kSearch,
            keyboard_capability_->GetMetaKey(fake_keyboard1));
  // Do not provide specific keyboard. Launcher button depends on if any one
  // of the keyboards is layout2 type.
  EXPECT_EQ(mojom::MetaKey::kSearch,
            keyboard_capability_->GetMetaKeyToDisplay());

  // Add a layout2 keyboard.
  KeyboardDevice fake_keyboard2(
      /*id=*/kDeviceId2, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard2");
  fake_keyboard1.sys_path = base::FilePath("path2");
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard2, kKbdTopRowLayout2Tag);

  EXPECT_EQ(mojom::MetaKey::kSearch,
            keyboard_capability_->GetMetaKey(fake_keyboard1));
  EXPECT_EQ(mojom::MetaKey::kLauncher,
            keyboard_capability_->GetMetaKey(fake_keyboard2));
  EXPECT_EQ(mojom::MetaKey::kLauncher,
            keyboard_capability_->GetMetaKeyToDisplay());
}

TEST_P(KeyboardCapabilityTest, TestGetMetaKey_ExternalChromeOS) {
  KeyboardDevice fake_keyboard1(
      /*id=*/kDeviceId1, /*type=*/InputDeviceType::INPUT_DEVICE_USB,
      /*name=*/"Keyboard1");
  fake_keyboard1.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard1, kKbdTopRowLayout1Tag);
  EXPECT_EQ(mojom::MetaKey::kLauncher,
            keyboard_capability_->GetMetaKey(fake_keyboard1));
  EXPECT_EQ(mojom::MetaKey::kLauncher,
            keyboard_capability_->GetMetaKeyToDisplay());

  fake_keyboard_manager_->RemoveAllDevices();
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard1, kKbdTopRowLayout2Tag);
  EXPECT_EQ(mojom::MetaKey::kLauncher,
            keyboard_capability_->GetMetaKey(fake_keyboard1));
  EXPECT_EQ(mojom::MetaKey::kLauncher,
            keyboard_capability_->GetMetaKeyToDisplay());
}

TEST_P(KeyboardCapabilityTest, TestGetMetaKey_ExternalNonChromeOS) {
  KeyboardDevice fake_keyboard1(
      /*id=*/kDeviceId1, /*type=*/InputDeviceType::INPUT_DEVICE_USB,
      /*name=*/"Keyboard1");
  fake_keyboard1.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard1,
                                          kKbdTopRowLayoutUnspecified);
  EXPECT_EQ(mojom::MetaKey::kExternalMeta,
            keyboard_capability_->GetMetaKey(fake_keyboard1));
  EXPECT_EQ(ash::features::IsModifierSplitEnabled()
                ? mojom::MetaKey::kLauncherRefresh
                : mojom::MetaKey::kLauncher,
            keyboard_capability_->GetMetaKeyToDisplay());

  // When an internal keyboard is added, it overrides the meta key from the
  // external keyboard.
  KeyboardDevice internal_keyboard(
      /*id=*/kDeviceId2, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard2");
  fake_keyboard1.sys_path = base::FilePath("path2");
  fake_keyboard_manager_->AddFakeKeyboard(internal_keyboard,
                                          kKbdTopRowLayout2Tag);
  EXPECT_EQ(mojom::MetaKey::kLauncher,
            keyboard_capability_->GetMetaKey(internal_keyboard));
  EXPECT_EQ(mojom::MetaKey::kLauncher,
            keyboard_capability_->GetMetaKeyToDisplay());
}

TEST_P(KeyboardCapabilityTest, TestGetMetaKey_SplitModifierKeyboard) {
  if (!ash::features::IsModifierSplitEnabled()) {
    GTEST_SKIP()
        << "This test is only applicable with split modifier feature enabled.";
  }

  const KeyboardDevice split_modifier_keyboard =
      AddFakeKeyboardInfoToKeyboardCapability(
          kDeviceId1, kSplitModifierKeyboard,
          KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
          KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom);
  EXPECT_EQ(mojom::MetaKey::kLauncherRefresh,
            keyboard_capability_->GetMetaKey(split_modifier_keyboard));
  EXPECT_EQ(mojom::MetaKey::kLauncherRefresh,
            keyboard_capability_->GetMetaKeyToDisplay());
}

TEST_P(KeyboardCapabilityTest, TestGetMetaKey_NoKeyboardsConnected) {
  ASSERT_TRUE(DeviceDataManager::GetInstance()->GetKeyboardDevices().empty());
  EXPECT_EQ(ash::features::IsModifierSplitEnabled()
                ? mojom::MetaKey::kLauncherRefresh
                : mojom::MetaKey::kLauncher,
            keyboard_capability_->GetMetaKeyToDisplay());
}

TEST_P(KeyboardCapabilityTest, TestHasSixPackKey) {
  // Add an internal keyboard.
  KeyboardDevice fake_keyboard1(
      /*id=*/1, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard1");
  fake_keyboard1.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard1, kKbdTopRowLayout1Tag);

  // Internal keyboard doesn't have six pack key.
  EXPECT_FALSE(keyboard_capability_->HasSixPackKey(fake_keyboard1));
  EXPECT_FALSE(keyboard_capability_->HasSixPackOnAnyKeyboard());

  // Add an external keyboard.
  KeyboardDevice fake_keyboard2(
      /*id=*/2, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard2");
  fake_keyboard1.sys_path = base::FilePath("path2");
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard2, kKbdTopRowLayout1Tag);

  // External keyboard has six pack key.
  EXPECT_TRUE(keyboard_capability_->HasSixPackKey(fake_keyboard2));
  EXPECT_TRUE(keyboard_capability_->HasSixPackOnAnyKeyboard());
}

TEST_P(KeyboardCapabilityTest, TestRemoveDevicesFromList) {
  const KeyboardDevice input_device1 = AddFakeKeyboardInfoToKeyboardCapability(
      kDeviceId1, kEveKeyboard,
      KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
      KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout2);
  const KeyboardDevice input_device2 = AddFakeKeyboardInfoToKeyboardCapability(
      kDeviceId2, kHpUsbKeyboard,
      KeyboardCapability::DeviceType::kDeviceExternalGenericKeyboard,
      KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout1);

  DeviceDataManagerTestApi().SetKeyboardDevices({input_device1, input_device2});
  ASSERT_EQ(2u, keyboard_capability_->keyboard_info_map().size());

  DeviceDataManagerTestApi().SetKeyboardDevices({input_device1});
  ASSERT_EQ(1u, keyboard_capability_->keyboard_info_map().size());
  EXPECT_TRUE(keyboard_capability_->keyboard_info_map().contains(kDeviceId1));

  DeviceDataManagerTestApi().SetKeyboardDevices({});
  ASSERT_EQ(0u, keyboard_capability_->keyboard_info_map().size());
}

TEST_P(KeyboardCapabilityTest, TestIdentifyRevenKeyboard) {
  base::CommandLine::ForCurrentProcess()->AppendSwitch(
      ash::switches::kRevenBranding);

  KeyboardDevice internal_keyboard(
      /*id=*/2, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"internal keyboard");
  internal_keyboard.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(internal_keyboard,
                                          kKbdTopRowLayoutUnspecified);

  EXPECT_EQ(KeyboardCapability::DeviceType::kDeviceInternalRevenKeyboard,
            keyboard_capability_->GetDeviceType(internal_keyboard));
}

TEST_P(KeyboardCapabilityTest, TestIsTopRowKey) {
  for (const auto& [key_code, _] : kLayout1TopRowKeyToFKeyMap) {
    EXPECT_TRUE(keyboard_capability_->IsTopRowKey(key_code));
  }
  for (const auto& [key_code, _] : kLayout2TopRowKeyToFKeyMap) {
    EXPECT_TRUE(keyboard_capability_->IsTopRowKey(key_code));
  }
  for (const auto& [key_code, _] : kLayoutWilcoDrallionTopRowKeyToFKeyMap) {
    EXPECT_TRUE(keyboard_capability_->IsTopRowKey(key_code));
  }

  // A key not in any of the above maps is not a top row key.
  EXPECT_FALSE(keyboard_capability_->IsTopRowKey(KeyboardCode::VKEY_A));
}

TEST_P(KeyboardCapabilityTest, TestHasGlobeKey) {
  KeyboardDevice bluetooth_keyboard(
      /*id=*/1, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard1");
  bluetooth_keyboard.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(bluetooth_keyboard,
                                          kKbdTopRowLayoutUnspecified);
  EXPECT_TRUE(keyboard_capability_->HasGlobeKey(bluetooth_keyboard));

  fake_keyboard_manager_->RemoveAllDevices();
  KeyboardDevice internal_keyboard_layout(
      /*id=*/2, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard2");
  internal_keyboard_layout.sys_path = base::FilePath("path2");
  fake_keyboard_manager_->AddFakeKeyboard(internal_keyboard_layout,
                                          kKbdTopRowLayout1Tag);
  EXPECT_FALSE(keyboard_capability_->HasGlobeKey(internal_keyboard_layout));

  KeyboardDevice bluetooth_keyboard_layout1(
      /*id=*/2, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard2");
  bluetooth_keyboard_layout1.sys_path = base::FilePath("path2");
  fake_keyboard_manager_->AddFakeKeyboard(bluetooth_keyboard_layout1,
                                          kKbdTopRowLayout1Tag);
  EXPECT_TRUE(keyboard_capability_->HasGlobeKey(bluetooth_keyboard_layout1));

  KeyboardDevice bluetooth_keyboard_layout2(
      /*id=*/3, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard3");
  bluetooth_keyboard_layout2.sys_path = base::FilePath("path3");
  fake_keyboard_manager_->AddFakeKeyboard(bluetooth_keyboard_layout2,
                                          kKbdTopRowLayout2Tag);
  EXPECT_TRUE(keyboard_capability_->HasGlobeKey(bluetooth_keyboard_layout2));

  KeyboardDevice bluetooth_keyboard_layout_custom(
      /*id=*/4, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard4");
  bluetooth_keyboard_layout_custom.sys_path = base::FilePath("path4");
  fake_keyboard_manager_->AddFakeKeyboard(bluetooth_keyboard_layout_custom,
                                          kKbdDefaultCustomTopRowLayout,
                                          /*has_custom_top_row=*/true);
  EXPECT_TRUE(
      keyboard_capability_->HasGlobeKey(bluetooth_keyboard_layout_custom));

  KeyboardDevice bluetooth_keyboard_wilco(
      /*id=*/5, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard5");
  bluetooth_keyboard_wilco.sys_path = base::FilePath("path5");
  fake_keyboard_manager_->AddFakeKeyboard(bluetooth_keyboard_wilco,
                                          kKbdTopRowLayoutWilcoTag);
  EXPECT_TRUE(keyboard_capability_->HasGlobeKey(bluetooth_keyboard_wilco));

  KeyboardDevice bluetooth_keyboard_drallion(
      /*id=*/6, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard6");
  bluetooth_keyboard_drallion.sys_path = base::FilePath("path6");
  fake_keyboard_manager_->AddFakeKeyboard(bluetooth_keyboard_drallion,
                                          kKbdTopRowLayoutDrallionTag);
  EXPECT_TRUE(keyboard_capability_->HasGlobeKey(bluetooth_keyboard_drallion));
}

TEST_P(KeyboardCapabilityTest, TestHasCalculatorKey) {
  KeyboardDevice internal_keyboard(
      /*id=*/1, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard1");
  internal_keyboard.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(internal_keyboard,
                                          kKbdTopRowLayout1Tag);
  EXPECT_FALSE(keyboard_capability_->HasCalculatorKey(internal_keyboard));

  KeyboardDevice external_keyboard(
      /*id=*/2, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard2");
  external_keyboard.sys_path = base::FilePath("path2");
  fake_keyboard_manager_->AddFakeKeyboard(external_keyboard,
                                          kKbdTopRowLayoutUnspecified);
  EXPECT_TRUE(keyboard_capability_->HasCalculatorKey(external_keyboard));
}

TEST_P(KeyboardCapabilityTest, TestHasBrowserSearchKey) {
  KeyboardDevice internal_keyboard(
      /*id=*/1, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard1");
  internal_keyboard.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(internal_keyboard,
                                          kKbdTopRowLayout1Tag);
  EXPECT_FALSE(keyboard_capability_->HasBrowserSearchKey(internal_keyboard));

  KeyboardDevice external_keyboard(
      /*id=*/2, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard2");
  external_keyboard.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(external_keyboard,
                                          kKbdTopRowLayoutUnspecified);
  EXPECT_TRUE(keyboard_capability_->HasBrowserSearchKey(external_keyboard));
}

TEST_P(KeyboardCapabilityTest, TestHasMediaKeys) {
  KeyboardDevice internal_keyboard(
      /*id=*/1, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard1");
  internal_keyboard.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(internal_keyboard,
                                          kKbdTopRowLayout1Tag);
  EXPECT_FALSE(keyboard_capability_->HasMediaKeys(internal_keyboard));

  KeyboardDevice external_keyboard(
      /*id=*/2, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard2");
  external_keyboard.sys_path = base::FilePath("path2");
  fake_keyboard_manager_->AddFakeKeyboard(external_keyboard,
                                          kKbdTopRowLayoutUnspecified);
  EXPECT_TRUE(keyboard_capability_->HasMediaKeys(external_keyboard));
}

TEST_P(KeyboardCapabilityTest, TestHasHelpKey) {
  KeyboardDevice internal_keyboard(
      /*id=*/1, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard1");
  internal_keyboard.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(internal_keyboard,
                                          kKbdTopRowLayout1Tag);
  EXPECT_FALSE(keyboard_capability_->HasHelpKey(internal_keyboard));

  KeyboardDevice external_keyboard(
      /*id=*/2, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard2");
  external_keyboard.sys_path = base::FilePath("path2");
  fake_keyboard_manager_->AddFakeKeyboard(external_keyboard,
                                          kKbdTopRowLayoutUnspecified);
  EXPECT_TRUE(keyboard_capability_->HasHelpKey(external_keyboard));
}

TEST_P(KeyboardCapabilityTest, TestHasSettingsKey) {
  KeyboardDevice internal_keyboard(
      /*id=*/1, /*type=*/InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/"Keyboard1");
  internal_keyboard.sys_path = base::FilePath("path1");
  fake_keyboard_manager_->AddFakeKeyboard(internal_keyboard,
                                          kKbdTopRowLayout1Tag);
  EXPECT_FALSE(keyboard_capability_->HasSettingsKey(internal_keyboard));

  KeyboardDevice external_keyboard(
      /*id=*/2, /*type=*/InputDeviceType::INPUT_DEVICE_BLUETOOTH,
      /*name=*/"Keyboard2");
  external_keyboard.sys_path = base::FilePath("path2");
  fake_keyboard_manager_->AddFakeKeyboard(external_keyboard,
                                          kKbdTopRowLayoutUnspecified);
  EXPECT_TRUE(keyboard_capability_->HasSettingsKey(external_keyboard));
}

class ModifierKeyTest : public KeyboardCapabilityTestBase,
                        public testing::WithParamInterface<
                            std::tuple<DeviceCapabilities,
                                       KeyboardCapability::DeviceType,
                                       KeyboardCapability::KeyboardTopRowLayout,
                                       std::vector<mojom::ModifierKey>>> {
 protected:
  base::AutoReset<bool> modifier_split_reset_ =
      ash::switches::SetIgnoreModifierSplitSecretKeyForTest();
};

// Tests that the given `DeviceCapabilities` and
// `KeyboardCapability::DeviceType` combo generates the given set of
// modifier keys.
INSTANTIATE_TEST_SUITE_P(
    All,
    ModifierKeyTest,
    testing::ValuesIn(std::vector<
                      std::tuple<DeviceCapabilities,
                                 KeyboardCapability::DeviceType,
                                 KeyboardCapability::KeyboardTopRowLayout,
                                 std::vector<mojom::ModifierKey>>>{
        {kDrobitKeyboard,
         KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
         KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom,
         {mojom::ModifierKey::kBackspace, mojom::ModifierKey::kControl,
          mojom::ModifierKey::kMeta, mojom::ModifierKey::kEscape,
          mojom::ModifierKey::kAlt}},
        {kLogitechKeyboardK120,
         KeyboardCapability::DeviceType::kDeviceExternalGenericKeyboard,
         KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout1,
         {mojom::ModifierKey::kBackspace, mojom::ModifierKey::kControl,
          mojom::ModifierKey::kMeta, mojom::ModifierKey::kEscape,
          mojom::ModifierKey::kAlt, mojom::ModifierKey::kCapsLock}},
        {kHpUsbKeyboard,
         KeyboardCapability::DeviceType::kDeviceExternalGenericKeyboard,
         KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout1,
         {mojom::ModifierKey::kBackspace, mojom::ModifierKey::kControl,
          mojom::ModifierKey::kMeta, mojom::ModifierKey::kEscape,
          mojom::ModifierKey::kAlt, mojom::ModifierKey::kCapsLock}},
        // Tests that an external chromeos keyboard correctly omits capslock.
        {kHpUsbKeyboard,
         KeyboardCapability::DeviceType::kDeviceExternalChromeOsKeyboard,
         KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom,
         {mojom::ModifierKey::kBackspace, mojom::ModifierKey::kControl,
          mojom::ModifierKey::kMeta, mojom::ModifierKey::kEscape,
          mojom::ModifierKey::kAlt}}}));

TEST_P(ModifierKeyTest, TestGetModifierKeys) {
  auto [capabilities, device_type, top_row_layout, expected_modifier_keys] =
      GetParam();

  const KeyboardDevice test_keyboard = AddFakeKeyboardInfoToKeyboardCapability(
      kDeviceId1, capabilities, device_type, top_row_layout);
  auto modifier_keys = keyboard_capability_->GetModifierKeys(test_keyboard);

  base::ranges::sort(expected_modifier_keys);
  base::ranges::sort(modifier_keys);
  EXPECT_EQ(expected_modifier_keys, modifier_keys);
}

TEST_P(KeyboardCapabilityTest, TestGetModifierKeysForSplitModifierKeyboard) {
  if (!ash::features::IsModifierSplitEnabled()) {
    GTEST_SKIP() << "Test is only valid with Modifier Split flag enabled.";
  }

  const KeyboardDevice test_keyboard = AddFakeKeyboardInfoToKeyboardCapability(
      kDeviceId1, kSplitModifierKeyboard,
      KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
      KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom);
  auto modifier_keys = keyboard_capability_->GetModifierKeys(test_keyboard);

  std::vector<mojom::ModifierKey> expected_modifier_keys = {
      mojom::ModifierKey::kBackspace, mojom::ModifierKey::kControl,
      mojom::ModifierKey::kMeta,      mojom::ModifierKey::kEscape,
      mojom::ModifierKey::kAlt,       mojom::ModifierKey::kFunction,
      mojom::ModifierKey::kRightAlt};
  base::ranges::sort(expected_modifier_keys);
  base::ranges::sort(modifier_keys);
  EXPECT_EQ(expected_modifier_keys, modifier_keys);
}

class KeyboardCapabilityDogfoodTest : public KeyboardCapabilityTestBase {
 public:
  void SetUp() override {
    modifier_split_feature_list_ =
        std::make_unique<base::test::ScopedFeatureList>();
    modifier_split_feature_list_->InitWithFeatures(
        {ash::features::kModifierSplit, ash::features::kModifierSplitDogfood},
        {});
    KeyboardCapabilityTestBase::SetUp();
  }

 protected:
  std::unique_ptr<base::test::ScopedFeatureList> modifier_split_feature_list_;
};

// With the dogfood flag enabled AND no Google account logged in, the feature
// should act as though its disabled.
TEST_F(KeyboardCapabilityDogfoodTest,
       TestGetModifierKeysForSplitModifierKeyboardDogfood) {
  AccountId non_google_account_id =
      AccountId::FromUserEmail("[email protected]");
  AccountId google_account_id =
      AccountId::FromUserEmail("[email protected]");
  user_manager_->AddUser(non_google_account_id);
  user_manager_->AddUser(google_account_id);

  // When a non-google account is signed in, keyboard capability should not
  // consider it a split modifier keyboard.
  user_manager_->UserLoggedIn(
      non_google_account_id,
      user_manager::FakeUserManager::GetFakeUsernameHash(non_google_account_id),
      /*browser_restart=*/false, /*is_child=*/false);
  const KeyboardDevice test_keyboard = AddFakeKeyboardInfoToKeyboardCapability(
      kDeviceId1, kSplitModifierKeyboard,
      KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
      KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom);
  {
    auto modifier_keys = keyboard_capability_->GetModifierKeys(test_keyboard);

    std::vector<mojom::ModifierKey> expected_modifier_keys = {
        mojom::ModifierKey::kBackspace, mojom::ModifierKey::kControl,
        mojom::ModifierKey::kMeta,      mojom::ModifierKey::kEscape,
        mojom::ModifierKey::kAlt,       mojom::ModifierKey::kAssistant};
    base::ranges::sort(expected_modifier_keys);
    base::ranges::sort(modifier_keys);
    EXPECT_EQ(expected_modifier_keys, modifier_keys);
  }
  user_manager_->LogoutAllUsers();

  // Once a google account signs in, it should now be considered a split
  // modifier keyboard.
  user_manager_->UserLoggedIn(
      google_account_id,
      user_manager::FakeUserManager::GetFakeUsernameHash(google_account_id),
      /*browser_restart=*/false, /*is_child=*/false);
  {
    auto modifier_keys = keyboard_capability_->GetModifierKeys(test_keyboard);
    std::vector<mojom::ModifierKey> expected_modifier_keys = {
        mojom::ModifierKey::kBackspace, mojom::ModifierKey::kControl,
        mojom::ModifierKey::kMeta,      mojom::ModifierKey::kEscape,
        mojom::ModifierKey::kAlt,       mojom::ModifierKey::kFunction,
        mojom::ModifierKey::kRightAlt};
    base::ranges::sort(expected_modifier_keys);
    base::ranges::sort(modifier_keys);
    EXPECT_EQ(expected_modifier_keys, modifier_keys);
  }
}

TEST_P(KeyboardCapabilityTest, TestGetModifierKeysForEveKeyboard) {
  keyboard_capability_->SetBoardNameForTesting("eve");

  const KeyboardDevice test_keyboard = AddFakeKeyboardInfoToKeyboardCapability(
      kDeviceId1, kEveKeyboard,
      KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
      KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout2);
  auto modifier_keys = keyboard_capability_->GetModifierKeys(test_keyboard);

  std::vector<mojom::ModifierKey> expected_modifier_keys = {
      mojom::ModifierKey::kBackspace, mojom::ModifierKey::kControl,
      mojom::ModifierKey::kMeta,      mojom::ModifierKey::kEscape,
      mojom::ModifierKey::kAlt,       mojom::ModifierKey::kAssistant};
  base::ranges::sort(expected_modifier_keys);
  base::ranges::sort(modifier_keys);
  EXPECT_EQ(expected_modifier_keys, modifier_keys);
}

class KeyEventTest
    : public KeyboardCapabilityTestBase,
      public testing::WithParamInterface<std::tuple<bool, KeyEventTestData>> {
 public:
  void SetUp() override {
    modifier_split_feature_list_ =
        std::make_unique<base::test::ScopedFeatureList>();
    if (std::get<0>(GetParam())) {
      modifier_split_feature_list_->InitAndEnableFeature(
          ash::features::kModifierSplit);
    } else {
      modifier_split_feature_list_->InitAndDisableFeature(
          ash::features::kModifierSplit);
    }
    KeyboardCapabilityTestBase::SetUp();
  }

 protected:
  std::unique_ptr<base::test::ScopedFeatureList> modifier_split_feature_list_;
  base::AutoReset<bool> modifier_split_reset_ =
      ash::switches::SetIgnoreModifierSplitSecretKeyForTest();
};

// Tests that given the keyboard connection type and layout type, check if this
// keyboard has a specific key event.
INSTANTIATE_TEST_SUITE_P(
    All,
    KeyEventTest,
    testing::Combine(
        testing::Bool(),
        testing::ValuesIn(std::vector<KeyEventTestData>{
            // Testing top row keys.
            {{INTERNAL},
             {kKbdTopRowLayout1Tag},
             VKEY_BROWSER_FORWARD,
             {true},
             true},
            {{EXTERNAL_BLUETOOTH},
             {kKbdTopRowLayout1Tag},
             VKEY_ZOOM,
             {true},
             true},
            {{EXTERNAL_USB},
             {kKbdTopRowLayout1Tag},
             VKEY_MEDIA_PLAY_PAUSE,
             {false},
             false},
            {{INTERNAL},
             {kKbdTopRowLayout2Tag},
             VKEY_BROWSER_FORWARD,
             {false},
             false},
            {{EXTERNAL_UNKNOWN},
             {kKbdTopRowLayout2Tag},
             VKEY_MEDIA_PLAY_PAUSE,
             {true},
             true},
            {{INTERNAL}, {kKbdTopRowLayoutWilcoTag}, VKEY_ZOOM, {true}, true},
            {{EXTERNAL_BLUETOOTH},
             {kKbdTopRowLayoutDrallionTag},
             VKEY_BRIGHTNESS_UP,

             {true},
             true},
            {{INTERNAL, EXTERNAL_BLUETOOTH},
             {kKbdTopRowLayout1Tag, kKbdTopRowLayout2Tag},
             VKEY_BROWSER_FORWARD,
             {true, false},
             true},
            {{INTERNAL, EXTERNAL_BLUETOOTH},
             {kKbdTopRowLayout2Tag, kKbdTopRowLayout2Tag},
             VKEY_BROWSER_FORWARD,
             {false, false},
             false},
            {{INTERNAL, EXTERNAL_USB, EXTERNAL_BLUETOOTH},
             {kKbdTopRowLayout1Tag, kKbdTopRowLayout2Tag,
              kKbdTopRowLayoutWilcoTag},
             VKEY_VOLUME_UP,
             {true, true, true},
             true},

            // Testing six pack keys.
            {{INTERNAL}, {kKbdTopRowLayout1Tag}, VKEY_INSERT, {false}, false},
            {{EXTERNAL_USB}, {kKbdTopRowLayout1Tag}, VKEY_INSERT, {true}, true},
            {{INTERNAL, EXTERNAL_BLUETOOTH},
             {kKbdTopRowLayout1Tag, kKbdTopRowLayoutWilcoTag},
             VKEY_HOME,
             {false, true},
             true},

            // Testing other keys.
            {{INTERNAL}, {kKbdTopRowLayout1Tag}, VKEY_LEFT, {true}, true},
            {{EXTERNAL_BLUETOOTH},
             {kKbdTopRowLayout2Tag},
             VKEY_ESCAPE,
             {true},
             true},
            {{EXTERNAL_UNKNOWN},
             {kKbdTopRowLayoutWilcoTag},
             VKEY_A,
             {true},
             true},
            {{INTERNAL}, {kKbdTopRowLayoutDrallionTag}, VKEY_2, {true}, true},
        })));

TEST_P(KeyEventTest, TestHasKeyEvent) {
  auto [keyboard_connection_types, keyboard_layout_types, key_code,
        expected_has_key_event, expected_has_key_event_on_any_keyboard] =
      std::get<1>(GetParam());

  fake_keyboard_manager_->RemoveAllDevices();
  for (size_t i = 0; i < keyboard_layout_types.size(); i++) {
    std::string layout = keyboard_layout_types[i];
    KeyboardDevice fake_keyboard(
        /*id=*/i, /*type=*/keyboard_connection_types[i],
        /*name=*/layout);
    fake_keyboard.sys_path = base::FilePath("path" + layout);
    fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard, layout);

    if (expected_has_key_event[i]) {
      EXPECT_TRUE(keyboard_capability_->HasKeyEvent(key_code, fake_keyboard));
    } else {
      EXPECT_FALSE(keyboard_capability_->HasKeyEvent(key_code, fake_keyboard));
    }
  }

  if (expected_has_key_event_on_any_keyboard) {
    EXPECT_TRUE(keyboard_capability_->HasKeyEventOnAnyKeyboard(key_code));
  } else {
    EXPECT_FALSE(keyboard_capability_->HasKeyEventOnAnyKeyboard(key_code));
  }
}

TEST_P(KeyboardCapabilityTest, TestHasAssistantKey) {
  // Add a fake kEveKeyboard keyboard, which has the assistant key.
  const KeyboardDevice test_keyboard_1 =
      AddFakeKeyboardInfoToKeyboardCapability(
          kDeviceId1, kEveKeyboard,
          KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
          KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout2);

  keyboard_capability_->SetBoardNameForTesting("eve");
  EXPECT_TRUE(keyboard_capability_->HasAssistantKey(test_keyboard_1));

  keyboard_capability_->SetBoardNameForTesting("nocturne");
  EXPECT_TRUE(keyboard_capability_->HasAssistantKey(test_keyboard_1));

  keyboard_capability_->SetBoardNameForTesting("atlas");
  EXPECT_TRUE(keyboard_capability_->HasAssistantKey(test_keyboard_1));

  keyboard_capability_->SetBoardNameForTesting("anything_else");
  EXPECT_EQ(!GetParam(),
            keyboard_capability_->HasAssistantKey(test_keyboard_1));

  // Reset board back to eve to test that device identification works as
  // expected in the false case.
  keyboard_capability_->SetBoardNameForTesting("eve");

  // Add a fake kDrallionKeyboard keyboard, which does not have the
  // assistant key.
  const KeyboardDevice test_keyboard_2 =
      AddFakeKeyboardInfoToKeyboardCapability(
          kDeviceId1, kDrallionKeyboard,
          KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
          KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutDrallion);

  EXPECT_FALSE(keyboard_capability_->HasAssistantKey(test_keyboard_2));
}

TEST_P(KeyboardCapabilityTest, IdentifyKeyboardUnspecified) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_INTERNAL,
                              "Internal Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(input_device,
                                          kKbdTopRowLayoutUnspecified);

  EXPECT_EQ(KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
            keyboard_capability_->GetDeviceType(input_device));
  EXPECT_EQ(KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutDefault,
            keyboard_capability_->GetTopRowLayout(input_device));
  EXPECT_EQ(0u, keyboard_capability_->GetTopRowScanCodes(input_device)->size());
}

TEST_P(KeyboardCapabilityTest, IdentifyKeyboardInvalidLayoutTag) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_INTERNAL,
                              "Internal Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(input_device,
                                          kKbdTopRowLayoutInvalidTag);

  EXPECT_EQ(KeyboardCapability::DeviceType::kDeviceUnknown,
            keyboard_capability_->GetDeviceType(input_device));
  EXPECT_EQ(KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutDefault,
            keyboard_capability_->GetTopRowLayout(input_device));
  EXPECT_TRUE(!keyboard_capability_->GetTopRowScanCodes(input_device) ||
              keyboard_capability_->GetTopRowScanCodes(input_device)->empty());
}

TEST_P(KeyboardCapabilityTest, IdentifyKeyboardInvalidCustomLayout) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_INTERNAL,
                              "Internal Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(
      input_device, kKbdInvalidCustomTopRowLayout, /*has_custom_top_row=*/true);

  EXPECT_EQ(KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
            keyboard_capability_->GetDeviceType(input_device));
  EXPECT_EQ(KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutDefault,
            keyboard_capability_->GetTopRowLayout(input_device));
  EXPECT_EQ(0u, keyboard_capability_->GetTopRowScanCodes(input_device)->size());
}

TEST_P(KeyboardCapabilityTest, IdentifyKeyboardLayout1External) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_UNKNOWN,
                              "External Chrome Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(input_device, kKbdTopRowLayout1Tag,
                                          /*has_custom_top_row=*/false);

  EXPECT_EQ(KeyboardCapability::DeviceType::kDeviceExternalChromeOsKeyboard,
            keyboard_capability_->GetDeviceType(input_device));
  EXPECT_EQ(KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout1,
            keyboard_capability_->GetTopRowLayout(input_device));
  EXPECT_EQ(0u, keyboard_capability_->GetTopRowScanCodes(input_device)->size());
}

TEST_P(KeyboardCapabilityTest, IdentifyKeyboardLayout2External) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_UNKNOWN,
                              "External Chrome Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(input_device, kKbdTopRowLayout2Tag,
                                          /*has_custom_top_row=*/false);

  EXPECT_EQ(KeyboardCapability::DeviceType::kDeviceExternalChromeOsKeyboard,
            keyboard_capability_->GetDeviceType(input_device));
  EXPECT_EQ(KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout2,
            keyboard_capability_->GetTopRowLayout(input_device));
  EXPECT_EQ(0u, keyboard_capability_->GetTopRowScanCodes(input_device)->size());
}

TEST_P(KeyboardCapabilityTest, IdentifyKeyboardCustomLayout) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_INTERNAL,
                              "Internal Custom Layout Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(input_device,
                                          kKbdDefaultCustomTopRowLayout,
                                          /*has_custom_top_row=*/true);

  EXPECT_EQ(KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
            keyboard_capability_->GetDeviceType(input_device));
  EXPECT_EQ(KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom,
            keyboard_capability_->GetTopRowLayout(input_device));

  const auto* top_row_scan_codes_ptr =
      keyboard_capability_->GetTopRowScanCodes(input_device);
  ASSERT_TRUE(top_row_scan_codes_ptr);
  const auto& top_row_scan_codes = *top_row_scan_codes_ptr;

  // Basic inspection to match kKbdDefaultCustomTopRowLayout
  EXPECT_EQ(15u, top_row_scan_codes.size());

  for (size_t i = 0; i < top_row_scan_codes.size(); i++) {
    EXPECT_EQ(i + 1, top_row_scan_codes[i]);
  }
}

TEST_P(KeyboardCapabilityTest, IdentifyKeyboardWilcoTopRowLayout) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_INTERNAL,
                              "Internal Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(input_device,
                                          kKbdTopRowLayoutWilcoTag,
                                          /*has_custom_top_row=*/false);

  EXPECT_EQ(KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
            keyboard_capability_->GetDeviceType(input_device));
  EXPECT_EQ(KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutWilco,
            keyboard_capability_->GetTopRowLayout(input_device));
  EXPECT_EQ(0u, keyboard_capability_->GetTopRowScanCodes(input_device)->size());
}

TEST_P(KeyboardCapabilityTest, IdentifyKeyboardDrallionTopRowLayout) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_INTERNAL,
                              "Internal Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(input_device,
                                          kKbdTopRowLayoutDrallionTag,
                                          /*has_custom_top_row=*/false);

  EXPECT_EQ(KeyboardCapability::DeviceType::kDeviceInternalKeyboard,
            keyboard_capability_->GetDeviceType(input_device));
  EXPECT_EQ(KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutDrallion,
            keyboard_capability_->GetTopRowLayout(input_device));
  EXPECT_EQ(0u, keyboard_capability_->GetTopRowScanCodes(input_device)->size());
}

TEST_P(KeyboardCapabilityTest, TopRowLayout1) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_INTERNAL,
                              "Internal Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(input_device, kKbdTopRowLayout1Tag,
                                          /*has_custom_top_row=*/false);

  for (TopRowActionKey action_key = TopRowActionKey::kNone;
       action_key <= TopRowActionKey::kMaxValue;
       action_key =
           static_cast<TopRowActionKey>(static_cast<int>(action_key) + 1)) {
    EXPECT_EQ(
        base::Contains(kLayout1TopRowActionKeys, action_key),
        keyboard_capability_->HasTopRowActionKey(input_device, action_key))
        << "Action Key: " << static_cast<int>(action_key);
  }

  KeyboardCode expected_fkey = VKEY_F1;
  for (const auto action_key : kLayout1TopRowActionKeys) {
    EXPECT_EQ(expected_fkey, keyboard_capability_->GetCorrespondingFunctionKey(
                                 input_device, action_key));
    EXPECT_EQ(action_key,
              keyboard_capability_->GetCorrespondingActionKeyForFKey(
                  input_device, expected_fkey));
    expected_fkey =
        static_cast<KeyboardCode>(static_cast<int>(expected_fkey) + 1);
  }
}

TEST_P(KeyboardCapabilityTest, TopRowLayout2) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_INTERNAL,
                              "Internal Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(input_device, kKbdTopRowLayout2Tag,
                                          /*has_custom_top_row=*/false);

  for (TopRowActionKey action_key = TopRowActionKey::kNone;
       action_key <= TopRowActionKey::kMaxValue;
       action_key =
           static_cast<TopRowActionKey>(static_cast<int>(action_key) + 1)) {
    EXPECT_EQ(
        base::Contains(kLayout2TopRowActionKeys, action_key),
        keyboard_capability_->HasTopRowActionKey(input_device, action_key))
        << "Action Key: " << static_cast<int>(action_key);
  }

  KeyboardCode expected_fkey = VKEY_F1;
  for (const auto action_key : kLayout2TopRowActionKeys) {
    EXPECT_EQ(expected_fkey, keyboard_capability_->GetCorrespondingFunctionKey(
                                 input_device, action_key));
    EXPECT_EQ(action_key,
              keyboard_capability_->GetCorrespondingActionKeyForFKey(
                  input_device, expected_fkey));
    expected_fkey =
        static_cast<KeyboardCode>(static_cast<int>(expected_fkey) + 1);
  }
}

TEST_P(KeyboardCapabilityTest, TopRowLayoutWilco) {
  KeyboardDevice wilco_device(kDeviceId1, INPUT_DEVICE_INTERNAL,
                              "Internal Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(wilco_device,
                                          kKbdTopRowLayoutWilcoTag,
                                          /*has_custom_top_row=*/false);
  KeyboardDevice drallion_device(kDeviceId2, INPUT_DEVICE_INTERNAL,
                                 "Internal Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(drallion_device,
                                          kKbdTopRowLayoutDrallionTag,
                                          /*has_custom_top_row=*/false);

  for (TopRowActionKey action_key = TopRowActionKey::kNone;
       action_key <= TopRowActionKey::kMaxValue;
       action_key =
           static_cast<TopRowActionKey>(static_cast<int>(action_key) + 1)) {
    EXPECT_EQ(
        base::Contains(kLayoutWilcoDrallionTopRowActionKeys, action_key),
        keyboard_capability_->HasTopRowActionKey(wilco_device, action_key))
        << "Action Key: " << static_cast<int>(action_key);
    EXPECT_EQ(
        base::Contains(kLayoutWilcoDrallionTopRowActionKeys, action_key),
        keyboard_capability_->HasTopRowActionKey(drallion_device, action_key))
        << "Action Key: " << static_cast<int>(action_key);
  }

  KeyboardCode expected_fkey = VKEY_F1;
  for (const auto action_key : kLayoutWilcoDrallionTopRowActionKeys) {
    EXPECT_EQ(expected_fkey, keyboard_capability_->GetCorrespondingFunctionKey(
                                 wilco_device, action_key));
    EXPECT_EQ(expected_fkey, keyboard_capability_->GetCorrespondingFunctionKey(
                                 drallion_device, action_key));
    EXPECT_EQ(action_key,
              keyboard_capability_->GetCorrespondingActionKeyForFKey(
                  wilco_device, expected_fkey));
    EXPECT_EQ(action_key,
              keyboard_capability_->GetCorrespondingActionKeyForFKey(
                  drallion_device, expected_fkey));
    expected_fkey =
        static_cast<KeyboardCode>(static_cast<int>(expected_fkey) + 1);
  }
}

TEST_P(KeyboardCapabilityTest, NullTopRowDescriptor) {
  KeyboardDevice input_device(kDeviceId1, INPUT_DEVICE_BLUETOOTH,
                              "External Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(input_device,
                                          "C0000 C0000 C0000 C0000",
                                          /*has_custom_top_row=*/true);
  EXPECT_EQ(
      KeyboardCapability::DeviceType::kDeviceExternalNullTopRowChromeOsKeyboard,
      keyboard_capability_->GetDeviceType(input_device));
  EXPECT_TRUE(keyboard_capability_->HasCapsLockKey(input_device));
}

class TopRowLayoutCustomTest
    : public KeyboardCapabilityTestBase,
      public testing::WithParamInterface<std::vector<TopRowActionKey>> {
 public:
  void SetUp() override {
    KeyboardCapabilityTestBase::SetUp();
    top_row_action_keys_ = GetParam();
    custom_layout_string_.clear();

    std::vector<std::string> custom_scan_codes;
    custom_scan_codes.reserve(top_row_action_keys_.size());
    for (const auto& action_key : top_row_action_keys_) {
      const uint32_t scan_code = ConvertTopRowActionKeyToScanCode(action_key);
      custom_scan_codes.push_back(
          base::ToLowerASCII(base::HexEncode(&scan_code, 1)));
    }

    custom_layout_string_ = base::JoinString(custom_scan_codes, " ");
  }

  uint32_t ConvertTopRowActionKeyToScanCode(TopRowActionKey action_key) {
    switch (action_key) {
      case TopRowActionKey::kBack:
        return CustomTopRowScanCode::kBack;
      case TopRowActionKey::kForward:
        return CustomTopRowScanCode::kForward;
      case TopRowActionKey::kRefresh:
        return CustomTopRowScanCode::kRefresh;
      case TopRowActionKey::kFullscreen:
        return CustomTopRowScanCode::kFullscreen;
      case TopRowActionKey::kOverview:
        return CustomTopRowScanCode::kOverview;
      case TopRowActionKey::kScreenshot:
        return CustomTopRowScanCode::kScreenshot;
      case TopRowActionKey::kScreenBrightnessDown:
        return CustomTopRowScanCode::kScreenBrightnessDown;
      case TopRowActionKey::kScreenBrightnessUp:
        return CustomTopRowScanCode::kScreenBrightnessUp;
      case TopRowActionKey::kMicrophoneMute:
        return CustomTopRowScanCode::kMicrophoneMute;
      case TopRowActionKey::kVolumeMute:
        return CustomTopRowScanCode::kVolumeMute;
      case TopRowActionKey::kVolumeDown:
        return CustomTopRowScanCode::kVolumeDown;
      case TopRowActionKey::kVolumeUp:
        return CustomTopRowScanCode::kVolumeUp;
      case TopRowActionKey::kKeyboardBacklightToggle:
        return CustomTopRowScanCode::kKeyboardBacklightToggle;
      case TopRowActionKey::kKeyboardBacklightDown:
        return CustomTopRowScanCode::kKeyboardBacklightDown;
      case TopRowActionKey::kKeyboardBacklightUp:
        return CustomTopRowScanCode::kKeyboardBacklightUp;
      case TopRowActionKey::kNextTrack:
        return CustomTopRowScanCode::kNextTrack;
      case TopRowActionKey::kPreviousTrack:
        return CustomTopRowScanCode::kPreviousTrack;
      case TopRowActionKey::kPlayPause:
        return CustomTopRowScanCode::kPlayPause;
      case TopRowActionKey::kPrivacyScreenToggle:
        return CustomTopRowScanCode::kPrivacyScreenToggle;
      case TopRowActionKey::kAccessibility:
      case TopRowActionKey::kAllApplications:
      case TopRowActionKey::kEmojiPicker:
      case TopRowActionKey::kDictation:
      case TopRowActionKey::kUnknown:
      case TopRowActionKey::kNone:
        return 0;
    }
  }

 protected:
  std::vector<TopRowActionKey> top_row_action_keys_;
  std::string custom_layout_string_;
  base::AutoReset<bool> modifier_split_reset_ =
      ash::switches::SetIgnoreModifierSplitSecretKeyForTest();
};

INSTANTIATE_TEST_SUITE_P(
    All,
    TopRowLayoutCustomTest,
    testing::ValuesIn(std::vector<std::vector<TopRowActionKey>>{
        // Test with full 15 key set.
        {
            TopRowActionKey::kBack,
            TopRowActionKey::kForward,
            TopRowActionKey::kRefresh,
            TopRowActionKey::kFullscreen,
            TopRowActionKey::kOverview,
            TopRowActionKey::kScreenshot,
            TopRowActionKey::kScreenBrightnessDown,
            TopRowActionKey::kScreenBrightnessUp,
            TopRowActionKey::kMicrophoneMute,
            TopRowActionKey::kVolumeMute,
            TopRowActionKey::kVolumeDown,
            TopRowActionKey::kVolumeUp,
            TopRowActionKey::kKeyboardBacklightToggle,
            TopRowActionKey::kKeyboardBacklightDown,
            TopRowActionKey::kKeyboardBacklightUp,
        },
        // Test the remaining untested set of keys.
        {
            TopRowActionKey::kOverview,
            TopRowActionKey::kScreenshot,
            TopRowActionKey::kScreenBrightnessDown,
            TopRowActionKey::kScreenBrightnessUp,
            TopRowActionKey::kMicrophoneMute,
            TopRowActionKey::kVolumeMute,
            TopRowActionKey::kVolumeDown,
            TopRowActionKey::kVolumeUp,
            TopRowActionKey::kKeyboardBacklightToggle,
            TopRowActionKey::kKeyboardBacklightDown,
            TopRowActionKey::kKeyboardBacklightUp,
            TopRowActionKey::kNextTrack,
            TopRowActionKey::kPreviousTrack,
            TopRowActionKey::kPlayPause,
        },
        // Tests with a small subset of the possible keys.
        {
            TopRowActionKey::kBack,
            TopRowActionKey::kForward,
            TopRowActionKey::kRefresh,
            TopRowActionKey::kPrivacyScreenToggle,
        },
        {
            TopRowActionKey::kMicrophoneMute,
            TopRowActionKey::kVolumeMute,
            TopRowActionKey::kVolumeDown,
            TopRowActionKey::kVolumeUp,
            TopRowActionKey::kKeyboardBacklightToggle,
        }}));

TEST_P(TopRowLayoutCustomTest, TopRowLayout) {
  KeyboardDevice keyboard(kDeviceId1, INPUT_DEVICE_INTERNAL,
                          "Internal Keyboard");
  fake_keyboard_manager_->AddFakeKeyboard(keyboard, custom_layout_string_,
                                          /*has_custom_top_row=*/true);
  for (TopRowActionKey action_key = TopRowActionKey::kNone;
       action_key <= TopRowActionKey::kMaxValue;
       action_key =
           static_cast<TopRowActionKey>(static_cast<int>(action_key) + 1)) {
    EXPECT_EQ(base::Contains(top_row_action_keys_, action_key),
              keyboard_capability_->HasTopRowActionKey(keyboard, action_key))
        << "Action Key: " << static_cast<int>(action_key);
  }

  KeyboardCode expected_fkey = VKEY_F1;
  for (const auto action_key : top_row_action_keys_) {
    EXPECT_EQ(expected_fkey, keyboard_capability_->GetCorrespondingFunctionKey(
                                 keyboard, action_key));
    EXPECT_EQ(action_key,
              keyboard_capability_->GetCorrespondingActionKeyForFKey(
                  keyboard, expected_fkey));
    expected_fkey =
        static_cast<KeyboardCode>(static_cast<int>(expected_fkey) + 1);
  }
}

}  // namespace ui