chromium/chrome/browser/ash/events/event_rewriter_unittest.cc

// Copyright 2014 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/event_rewriter.h"

#include <memory>
#include <optional>
#include <vector>

#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/accessibility/sticky_keys/sticky_keys_controller.h"
#include "ash/accessibility/sticky_keys/sticky_keys_overlay.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/input_device_settings_controller.h"
#include "ash/public/cpp/test/mock_input_device_settings_controller.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/shell.h"
#include "ash/system/input_device_settings/input_device_settings_notification_controller.h"
#include "ash/system/input_device_settings/input_device_settings_pref_names.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "base/command_line.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/events/event_rewriter_delegate_impl.h"
#include "chrome/browser/ash/input_method/input_method_configuration.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/notifications/deprecation_notification_controller.h"
#include "chrome/browser/ash/preferences/preferences.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_ash_test_base.h"
#include "components/prefs/pref_member.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_manager/scoped_user_manager.h"
#include "device/udev_linux/fake_udev_loader.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/ime/ash/fake_ime_keyboard.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/ash/mock_input_method_manager.h"
#include "ui/base/ime/ash/mock_input_method_manager_impl.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/ash/caps_lock_event_rewriter.h"
#include "ui/events/ash/discard_key_event_rewriter.h"
#include "ui/events/ash/event_rewriter_ash.h"
#include "ui/events/ash/event_rewriter_metrics.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/events/ash/keyboard_device_id_event_rewriter.h"
#include "ui/events/ash/keyboard_modifier_event_rewriter.h"
#include "ui/events/ash/mojom/extended_fkeys_modifier.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/ash/mojom/simulate_right_click_modifier.mojom-shared.h"
#include "ui/events/ash/mojom/six_pack_shortcut_modifier.mojom-shared.h"
#include "ui/events/ash/pref_names.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/devices/keyboard_device.h"
#include "ui/events/devices/touchpad_device.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/ozone/layout/stub/stub_keyboard_layout_engine.h"
#include "ui/events/test/events_test_utils.h"
#include "ui/events/test/test_event_processor.h"
#include "ui/events/test/test_event_rewriter_continuation.h"
#include "ui/events/test/test_event_source.h"
#include "ui/events/types/event_type.h"
#include "ui/lottie/resource.h"
#include "ui/message_center/fake_message_center.h"
#include "ui/wm/core/window_util.h"

namespace {

constexpr int kKeyboardDeviceId = 123;
constexpr uint32_t kNoScanCode = 0;
constexpr char kKbdSysPath[] = "/devices/platform/i8042/serio2/input/input1";
constexpr char kKbdTopRowPropertyName[] = "CROS_KEYBOARD_TOP_ROW_LAYOUT";
constexpr char kKbdTopRowLayoutAttributeName[] = "function_row_physmap";
constexpr char kSixPackKeyNoMatchNudgeId[] = "six-patch-key-no-match-nudge-id";
constexpr char kTopRowKeyNoMatchNudgeId[] = "top-row-key-no-match-nudge-id";

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

constexpr int kTouchpadId1 = 10;
constexpr int kTouchpadId2 = 11;

constexpr int kMouseDeviceId = 456;

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

// Tag used to mark events as being for right alt.
constexpr std::pair<std::string, std::vector<uint8_t>> kPropertyRightAlt = {
    "right_alt_event",
    {}};

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
constexpr char kCros1pInputMethodIdPrefix[] =
    "_comp_ime_jkghodnilhceideoidjikpgommlajknk";
#endif

class TestEventSink : public ui::EventSink {
 public:
  TestEventSink() = default;
  TestEventSink(const TestEventSink&) = delete;
  TestEventSink& operator=(const TestEventSink&) = delete;
  ~TestEventSink() override = default;

  // Returns the recorded events.
  std::vector<std::unique_ptr<ui::Event>> TakeEvents() {
    return std::move(events_);
  }

  // ui::EventSink:
  ui::EventDispatchDetails OnEventFromSource(ui::Event* event) override {
    auto cloned_event = event->Clone();
    ui::EventTestApi(cloned_event.get())
        .set_native_event(event->native_event());
    events_.emplace_back(std::move(cloned_event));
    return ui::EventDispatchDetails();
  }

 private:
  std::vector<std::unique_ptr<ui::Event>> events_;
};

class TestKeyboardModifierEventRewriterDelegate
    : public ui::KeyboardModifierEventRewriter::Delegate {
 public:
  explicit TestKeyboardModifierEventRewriterDelegate(
      ui::EventRewriterAsh::Delegate* ash_delegate)
      : ash_delegate_(ash_delegate) {}

  std::optional<ui::mojom::ModifierKey> GetKeyboardRemappedModifierValue(
      int device_id,
      ui::mojom::ModifierKey modifier_key,
      const std::string& pref_name) const override {
    return ash_delegate_->GetKeyboardRemappedModifierValue(
        device_id, modifier_key, pref_name);
  }

  bool RewriteModifierKeys() override {
    return ash_delegate_->RewriteModifierKeys();
  }

 private:
  raw_ptr<ui::EventRewriterAsh::Delegate> ash_delegate_;
};

class TestEventRewriterContinuation
    : public ui::test::TestEventRewriterContinuation {
 public:
  TestEventRewriterContinuation() = default;
  ~TestEventRewriterContinuation() override = default;
  TestEventRewriterContinuation(const TestEventRewriterContinuation&) = delete;
  TestEventRewriterContinuation& operator=(
      const TestEventRewriterContinuation&) = delete;

  ui::EventDispatchDetails SendEvent(const ui::Event* event) override {
    passthrough_events.push_back(event->Clone());
    return ui::EventDispatchDetails();
  }

  ui::EventDispatchDetails SendEventFinally(const ui::Event* event) override {
    rewritten_events.push_back(event->Clone());
    return ui::EventDispatchDetails();
  }

  ui::EventDispatchDetails DiscardEvent() override {
    return ui::EventDispatchDetails();
  }

  std::vector<std::unique_ptr<ui::Event>> rewritten_events;
  std::vector<std::unique_ptr<ui::Event>> passthrough_events;

  base::WeakPtrFactory<TestEventRewriterContinuation> weak_ptr_factory_{this};
};

// Key representation in test cases.
struct TestKeyEvent {
  ui::EventType type;
  ui::DomCode code;
  ui::DomKey key;
  ui::KeyboardCode keycode;
  ui::EventFlags flags = ui::EF_NONE;
  uint32_t scan_code = kNoScanCode;
  std::vector<std::pair<std::string, std::vector<uint8_t>>> properties;

  std::string ToString() const;
};

std::string TestKeyEvent::ToString() const {
  std::string type_name(ui::EventTypeName(type));
  std::string flags_name = base::JoinString(ui::EventFlagsNames(flags), "|");

  std::string property_dump;
  for (const auto& property : properties) {
    property_dump += (property_dump.empty() ? "" : "|") + property.first;
    property_dump += "=" + base::HexEncode(property.second);
  }

  return base::StringPrintf(
      "type=%s(%d) "
      "code=%s(0x%06X) "
      "key=%s(0x%08X) "
      "keycode=0x%02X "
      "flags=%s(0x%X) "
      "scan_code=0x%08X "
      "properties=%s",
      type_name.c_str(), type,
      ui::KeycodeConverter::DomCodeToCodeString(code).c_str(),
      static_cast<uint32_t>(code),
      ui::KeycodeConverter::DomKeyToKeyString(key).c_str(),
      static_cast<uint32_t>(key), keycode, flags_name.c_str(), flags, scan_code,
      property_dump.data());
}

inline std::ostream& operator<<(std::ostream& os, const TestKeyEvent& event) {
  return os << event.ToString();
}

inline bool operator==(const TestKeyEvent& e1, const TestKeyEvent& e2) {
  return e1.type == e2.type && e1.code == e2.code && e1.key == e2.key &&
         e1.keycode == e2.keycode && e1.flags == e2.flags &&
         e1.scan_code == e2.scan_code && e1.properties == e2.properties;
}

// Factory template of TestKeyEvents just to reduce a lot of code/data
// duplication.
template <ui::DomCode code,
          ui::DomKey::Base key,
          ui::KeyboardCode keycode,
          ui::EventFlags modifier_flag = ui::EF_NONE,
          ui::DomKey::Base shifted_key = key>
struct TestKey {
  // Returns press key event.
  static constexpr TestKeyEvent Pressed(
      ui::EventFlags flags = ui::EF_NONE,
      std::vector<std::pair<std::string, std::vector<uint8_t>>> properties =
          {}) {
    return {ui::EventType::kKeyPressed,
            code,
            (flags & ui::EF_SHIFT_DOWN) ? shifted_key : key,
            keycode,
            flags | modifier_flag,
            kNoScanCode,
            std::move(properties)};
  }

  // Returns release key event.
  static constexpr TestKeyEvent Released(
      ui::EventFlags flags = ui::EF_NONE,
      std::vector<std::pair<std::string, std::vector<uint8_t>>> properties =
          {}) {
    // Note: modifier flag should not be present on release events.
    return {ui::EventType::kKeyReleased,
            code,
            (flags & ui::EF_SHIFT_DOWN) ? shifted_key : key,
            keycode,
            flags,
            kNoScanCode,
            std::move(properties)};
  }

  // Returns press then release key events.
  static std::vector<TestKeyEvent> Typed(
      ui::EventFlags flags = ui::EF_NONE,
      std::vector<std::pair<std::string, std::vector<uint8_t>>> properties =
          {}) {
    return {Pressed(flags, properties), Released(flags, std::move(properties))};
  }
};

// Short cut of TestKey construction for Character keys.
template <ui::DomCode code,
          char key,
          ui::KeyboardCode keycode,
          char shifted_key = key>
using TestCharKey = TestKey<code,
                            ui::DomKey::FromCharacter(key),
                            keycode,
                            ui::EF_NONE,
                            ui::DomKey::FromCharacter(shifted_key)>;

using KeyUnknown =
    TestKey<ui::DomCode::NONE, ui::DomKey::UNIDENTIFIED, ui::VKEY_UNKNOWN>;

// Character keys. Shift chars are based on US layout.
using KeyA = TestCharKey<ui::DomCode::US_A, 'a', ui::VKEY_A, 'A'>;
using KeyB = TestCharKey<ui::DomCode::US_B, 'b', ui::VKEY_B, 'B'>;
using KeyC = TestCharKey<ui::DomCode::US_C, 'c', ui::VKEY_C, 'C'>;
using KeyD = TestCharKey<ui::DomCode::US_D, 'd', ui::VKEY_D, 'D'>;
using KeyN = TestCharKey<ui::DomCode::US_N, 'n', ui::VKEY_N, 'N'>;
using KeyT = TestCharKey<ui::DomCode::US_T, 't', ui::VKEY_T, 'T'>;
using KeyComma = TestCharKey<ui::DomCode::COMMA, ',', ui::VKEY_OEM_COMMA, '<'>;
using KeyPeriod =
    TestCharKey<ui::DomCode::PERIOD, '.', ui::VKEY_OEM_PERIOD, '>'>;
using KeyDigit1 = TestCharKey<ui::DomCode::DIGIT1, '1', ui::VKEY_1, '!'>;
using KeyDigit2 = TestCharKey<ui::DomCode::DIGIT2, '2', ui::VKEY_2, '@'>;
using KeyDigit3 = TestCharKey<ui::DomCode::DIGIT3, '3', ui::VKEY_3, '#'>;
using KeyDigit4 = TestCharKey<ui::DomCode::DIGIT4, '4', ui::VKEY_4, '$'>;
using KeyDigit5 = TestCharKey<ui::DomCode::DIGIT5, '5', ui::VKEY_5, '%'>;
using KeyDigit6 = TestCharKey<ui::DomCode::DIGIT6, '6', ui::VKEY_6, '^'>;
using KeyDigit7 = TestCharKey<ui::DomCode::DIGIT7, '7', ui::VKEY_7, '&'>;
using KeyDigit8 = TestCharKey<ui::DomCode::DIGIT8, '8', ui::VKEY_8, '*'>;
using KeyDigit9 = TestCharKey<ui::DomCode::DIGIT9, '9', ui::VKEY_9, '('>;
using KeyDigit0 = TestCharKey<ui::DomCode::DIGIT0, '0', ui::VKEY_0, ')'>;
using KeyMinus = TestCharKey<ui::DomCode::MINUS, '-', ui::VKEY_OEM_MINUS, '_'>;
using KeyEqual = TestCharKey<ui::DomCode::EQUAL, '=', ui::VKEY_OEM_PLUS, '+'>;

// Modifier keys.
using KeyLShift = TestKey<ui::DomCode::SHIFT_LEFT,
                          ui::DomKey::SHIFT,
                          ui::VKEY_SHIFT,
                          ui::EF_SHIFT_DOWN>;
using KeyRShift = TestKey<ui::DomCode::SHIFT_RIGHT,
                          ui::DomKey::SHIFT,
                          ui::VKEY_SHIFT,
                          ui::EF_SHIFT_DOWN>;
using KeyLMeta = TestKey<ui::DomCode::META_LEFT,
                         ui::DomKey::META,
                         ui::VKEY_LWIN,
                         ui::EF_COMMAND_DOWN>;
using KeyRMeta = TestKey<ui::DomCode::META_RIGHT,
                         ui::DomKey::META,
                         ui::VKEY_RWIN,
                         ui::EF_COMMAND_DOWN>;
using KeyLControl = TestKey<ui::DomCode::CONTROL_LEFT,
                            ui::DomKey::CONTROL,
                            ui::VKEY_CONTROL,
                            ui::EF_CONTROL_DOWN>;
using KeyRControl = TestKey<ui::DomCode::CONTROL_RIGHT,
                            ui::DomKey::CONTROL,
                            ui::VKEY_CONTROL,
                            ui::EF_CONTROL_DOWN>;
using KeyLAlt = TestKey<ui::DomCode::ALT_LEFT,
                        ui::DomKey::ALT,
                        ui::VKEY_MENU,
                        ui::EF_ALT_DOWN>;
using KeyRAlt = TestKey<ui::DomCode::ALT_RIGHT,
                        ui::DomKey::ALT,
                        ui::VKEY_MENU,
                        ui::EF_ALT_DOWN>;
using KeyCapsLock = TestKey<ui::DomCode::CAPS_LOCK,
                            ui::DomKey::CAPS_LOCK,
                            ui::VKEY_CAPITAL,
                            ui::EF_MOD3_DOWN>;
using KeyFunction = TestKey<ui::DomCode::FN,
                            ui::DomKey::FN,
                            ui::VKEY_FUNCTION,
                            ui::EF_FUNCTION_DOWN>;

// Function keys.
using KeyEscape =
    TestKey<ui::DomCode::ESCAPE, ui::DomKey::ESCAPE, ui::VKEY_ESCAPE>;
using KeyF1 = TestKey<ui::DomCode::F1, ui::DomKey::F1, ui::VKEY_F1>;
using KeyF2 = TestKey<ui::DomCode::F2, ui::DomKey::F2, ui::VKEY_F2>;
using KeyF3 = TestKey<ui::DomCode::F3, ui::DomKey::F3, ui::VKEY_F3>;
using KeyF4 = TestKey<ui::DomCode::F4, ui::DomKey::F4, ui::VKEY_F4>;
using KeyF5 = TestKey<ui::DomCode::F5, ui::DomKey::F5, ui::VKEY_F5>;
using KeyF6 = TestKey<ui::DomCode::F6, ui::DomKey::F6, ui::VKEY_F6>;
using KeyF7 = TestKey<ui::DomCode::F7, ui::DomKey::F7, ui::VKEY_F7>;
using KeyF8 = TestKey<ui::DomCode::F8, ui::DomKey::F8, ui::VKEY_F8>;
using KeyF9 = TestKey<ui::DomCode::F9, ui::DomKey::F9, ui::VKEY_F9>;
using KeyF10 = TestKey<ui::DomCode::F10, ui::DomKey::F10, ui::VKEY_F10>;
using KeyF11 = TestKey<ui::DomCode::F11, ui::DomKey::F11, ui::VKEY_F11>;
using KeyF12 = TestKey<ui::DomCode::F12, ui::DomKey::F12, ui::VKEY_F12>;
using KeyF13 = TestKey<ui::DomCode::F13, ui::DomKey::F13, ui::VKEY_F13>;
using KeyF14 = TestKey<ui::DomCode::F14, ui::DomKey::F14, ui::VKEY_F14>;
using KeyF15 = TestKey<ui::DomCode::F15, ui::DomKey::F15, ui::VKEY_F15>;
using KeyBackspace =
    TestKey<ui::DomCode::BACKSPACE, ui::DomKey::BACKSPACE, ui::VKEY_BACK>;

// Chrome OS Special keys.
using KeyBrowserBack = TestKey<ui::DomCode::BROWSER_BACK,
                               ui::DomKey::BROWSER_BACK,
                               ui::VKEY_BROWSER_BACK>;
using KeyBrowserForward = TestKey<ui::DomCode::BROWSER_FORWARD,
                                  ui::DomKey::BROWSER_FORWARD,
                                  ui::VKEY_BROWSER_FORWARD>;
using KeyBrowserRefresh = TestKey<ui::DomCode::BROWSER_REFRESH,
                                  ui::DomKey::BROWSER_REFRESH,
                                  ui::VKEY_BROWSER_REFRESH>;
using KeyZoomToggle =
    TestKey<ui::DomCode::ZOOM_TOGGLE, ui::DomKey::ZOOM_TOGGLE, ui::VKEY_ZOOM>;
using KeySelectTask = TestKey<ui::DomCode::SELECT_TASK,
                              ui::DomKey::LAUNCH_MY_COMPUTER,
                              ui::VKEY_MEDIA_LAUNCH_APP1>;
using KeyBrightnessDown = TestKey<ui::DomCode::BRIGHTNESS_DOWN,
                                  ui::DomKey::BRIGHTNESS_DOWN,
                                  ui::VKEY_BRIGHTNESS_DOWN>;
using KeyBrightnessUp = TestKey<ui::DomCode::BRIGHTNESS_UP,
                                ui::DomKey::BRIGHTNESS_UP,
                                ui::VKEY_BRIGHTNESS_UP>;
using KeyMediaPlayPause = TestKey<ui::DomCode::MEDIA_PLAY_PAUSE,
                                  ui::DomKey::MEDIA_PLAY_PAUSE,
                                  ui::VKEY_MEDIA_PLAY_PAUSE>;
using KeyVolumeMute = TestKey<ui::DomCode::VOLUME_MUTE,
                              ui::DomKey::AUDIO_VOLUME_MUTE,
                              ui::VKEY_VOLUME_MUTE>;
using KeyVolumeDown = TestKey<ui::DomCode::VOLUME_DOWN,
                              ui::DomKey::AUDIO_VOLUME_DOWN,
                              ui::VKEY_VOLUME_DOWN>;
using KeyVolumeUp = TestKey<ui::DomCode::VOLUME_UP,
                            ui::DomKey::AUDIO_VOLUME_UP,
                            ui::VKEY_VOLUME_UP>;
using KeyPrivacyScreenToggle =
    TestKey<ui::DomCode::PRIVACY_SCREEN_TOGGLE,
            ui::DomKey::F12,  // no DomKey for PRIVACY_SCREEN_TOGGLE>
            ui::VKEY_PRIVACY_SCREEN_TOGGLE>;
using KeyLaunchAssistant = TestKey<ui::DomCode::LAUNCH_ASSISTANT,
                                   ui::DomKey::LAUNCH_ASSISTANT,
                                   ui::VKEY_ASSISTANT>;
using KeyRightAlt = TestKey<ui::DomCode::LAUNCH_ASSISTANT,
                            ui::DomKey::LAUNCH_ASSISTANT,
                            ui::VKEY_ASSISTANT,
                            ui::EF_NONE,
                            ui::DomKey::LAUNCH_ASSISTANT>;

using KeyHangulMode =
    TestKey<ui::DomCode::ALT_RIGHT, ui::DomKey::HANGUL_MODE, ui::VKEY_HANGUL>;

// 6-pack keys.
using KeyInsert =
    TestKey<ui::DomCode::INSERT, ui::DomKey::INSERT, ui::VKEY_INSERT>;
using KeyDelete = TestKey<ui::DomCode::DEL, ui::DomKey::DEL, ui::VKEY_DELETE>;
using KeyHome = TestKey<ui::DomCode::HOME, ui::DomKey::HOME, ui::VKEY_HOME>;
using KeyEnd = TestKey<ui::DomCode::END, ui::DomKey::END, ui::VKEY_END>;
using KeyPageUp =
    TestKey<ui::DomCode::PAGE_UP, ui::DomKey::PAGE_UP, ui::VKEY_PRIOR>;
using KeyPageDown =
    TestKey<ui::DomCode::PAGE_DOWN, ui::DomKey::PAGE_DOWN, ui::VKEY_NEXT>;

// Arrow keys.
using KeyArrowUp =
    TestKey<ui::DomCode::ARROW_UP, ui::DomKey::ARROW_UP, ui::VKEY_UP>;
using KeyArrowDown =
    TestKey<ui::DomCode::ARROW_DOWN, ui::DomKey::ARROW_DOWN, ui::VKEY_DOWN>;
using KeyArrowLeft =
    TestKey<ui::DomCode::ARROW_LEFT, ui::DomKey::ARROW_LEFT, ui::VKEY_LEFT>;
using KeyArrowRight =
    TestKey<ui::DomCode::ARROW_RIGHT, ui::DomKey::ARROW_RIGHT, ui::VKEY_RIGHT>;

// Numpad keys.
using KeyNumpad0 = TestCharKey<ui::DomCode::NUMPAD0, '0', ui::VKEY_NUMPAD0>;
using KeyNumpadDecimal =
    TestCharKey<ui::DomCode::NUMPAD_DECIMAL, '.', ui::VKEY_DECIMAL>;
using KeyNumpad1 = TestCharKey<ui::DomCode::NUMPAD1, '1', ui::VKEY_NUMPAD1>;
using KeyNumpad2 = TestCharKey<ui::DomCode::NUMPAD2, '2', ui::VKEY_NUMPAD2>;
using KeyNumpad3 = TestCharKey<ui::DomCode::NUMPAD3, '3', ui::VKEY_NUMPAD3>;
using KeyNumpad4 = TestCharKey<ui::DomCode::NUMPAD4, '4', ui::VKEY_NUMPAD4>;
using KeyNumpad5 = TestCharKey<ui::DomCode::NUMPAD5, '5', ui::VKEY_NUMPAD5>;
using KeyNumpad6 = TestCharKey<ui::DomCode::NUMPAD6, '6', ui::VKEY_NUMPAD6>;
using KeyNumpad7 = TestCharKey<ui::DomCode::NUMPAD7, '7', ui::VKEY_NUMPAD7>;
using KeyNumpad8 = TestCharKey<ui::DomCode::NUMPAD8, '8', ui::VKEY_NUMPAD8>;
using KeyNumpad9 = TestCharKey<ui::DomCode::NUMPAD9, '9', ui::VKEY_NUMPAD9>;

// Numpad keys without NumLock key.
using KeyNumpadInsert =
    TestKey<ui::DomCode::NUMPAD0, ui::DomKey::INSERT, ui::VKEY_INSERT>;
using KeyNumpadDelete =
    TestKey<ui::DomCode::NUMPAD_DECIMAL, ui::DomKey::DEL, ui::VKEY_DELETE>;
using KeyNumpadEnd =
    TestKey<ui::DomCode::NUMPAD1, ui::DomKey::END, ui::VKEY_END>;
using KeyNumpadArrowDown =
    TestKey<ui::DomCode::NUMPAD2, ui::DomKey::ARROW_DOWN, ui::VKEY_DOWN>;
using KeyNumpadPageDown =
    TestKey<ui::DomCode::NUMPAD3, ui::DomKey::PAGE_DOWN, ui::VKEY_NEXT>;
using KeyNumpadArrowLeft =
    TestKey<ui::DomCode::NUMPAD4, ui::DomKey::ARROW_LEFT, ui::VKEY_LEFT>;
using KeyNumpadClear =
    TestKey<ui::DomCode::NUMPAD5, ui::DomKey::CLEAR, ui::VKEY_CLEAR>;
using KeyNumpadArrowRight =
    TestKey<ui::DomCode::NUMPAD6, ui::DomKey::ARROW_RIGHT, ui::VKEY_RIGHT>;
using KeyNumpadHome =
    TestKey<ui::DomCode::NUMPAD7, ui::DomKey::HOME, ui::VKEY_HOME>;
using KeyNumpadArrowUp =
    TestKey<ui::DomCode::NUMPAD8, ui::DomKey::ARROW_UP, ui::VKEY_UP>;
using KeyNumpadPageUp =
    TestKey<ui::DomCode::NUMPAD9, ui::DomKey::PAGE_UP, ui::VKEY_PRIOR>;

// Keyboard representation in tests.
struct TestKeyboard {
  const char* name;
  const char* layout;
  ui::InputDeviceType type;
  bool has_custom_top_row;
  bool has_assistant_key = false;
  bool has_function_key = false;
};
constexpr TestKeyboard kInternalChromeKeyboard = {
    "Internal Keyboard",
    kKbdTopRowLayoutUnspecified,
    ui::INPUT_DEVICE_INTERNAL,
    /*has_custom_top_row=*/false,
};
constexpr TestKeyboard kInternalChromeCustomLayoutKeyboard = {
    "Internal Custom Layout Keyboard",
    kKbdDefaultCustomTopRowLayout,
    ui::INPUT_DEVICE_INTERNAL,
    /*has_custom_top_row=*/true,
};
constexpr TestKeyboard kInternalChromeSplitModifierLayoutKeyboard = {
    "Internal Custom Layout Keyboard", kKbdDefaultCustomTopRowLayout,
    ui::INPUT_DEVICE_INTERNAL,
    /*has_custom_top_row=*/true,
    /*has_assistant_key=*/true,
    /*has_function_key=*/true,
};
constexpr TestKeyboard kExternalChromeKeyboard = {
    "External Chrome Keyboard",
    kKbdTopRowLayout1Tag,
    ui::INPUT_DEVICE_UNKNOWN,
    /*has_custom_top_row=*/false,
};
constexpr TestKeyboard kExternalChromeCustomLayoutKeyboard = {
    "External Chrome Custom Layout Keyboard",
    kKbdDefaultCustomTopRowLayout,
    ui::INPUT_DEVICE_UNKNOWN,
    /*has_custom_top_row=*/true,
};
constexpr TestKeyboard kExternalGenericKeyboard = {
    "PC Keyboard",
    kKbdTopRowLayoutUnspecified,
    ui::INPUT_DEVICE_UNKNOWN,
    /*has_custom_top_row=*/false,
};
constexpr TestKeyboard kExternalAppleKeyboard = {
    "Apple Keyboard",
    kKbdTopRowLayoutUnspecified,
    ui::INPUT_DEVICE_UNKNOWN,
    /*has_custom_top_row=*/false,
};

constexpr TestKeyboard kChromeKeyboardVariants[] = {
    kInternalChromeKeyboard,
    kExternalChromeKeyboard,
};
constexpr TestKeyboard kChromeCustomKeyboardVariants[] = {
    kInternalChromeCustomLayoutKeyboard,
    kExternalChromeCustomLayoutKeyboard,
};
constexpr TestKeyboard kNonAppleKeyboardVariants[] = {
    kInternalChromeKeyboard,  kInternalChromeCustomLayoutKeyboard,
    kExternalChromeKeyboard,  kExternalChromeCustomLayoutKeyboard,
    kExternalGenericKeyboard,
};
constexpr TestKeyboard kNonAppleNonCustomLayoutKeyboardVariants[] = {
    kInternalChromeKeyboard,
    kExternalChromeKeyboard,
    kExternalGenericKeyboard,
};
constexpr TestKeyboard kAllKeyboardVariants[] = {
    kInternalChromeKeyboard,
    kInternalChromeCustomLayoutKeyboard,
    kInternalChromeSplitModifierLayoutKeyboard,
    kExternalChromeKeyboard,
    kExternalChromeCustomLayoutKeyboard,
    kExternalGenericKeyboard,
    kExternalAppleKeyboard,
};

// Wilco keyboard configs

constexpr TestKeyboard kWilco1_0Keyboard{
    "Wilco Keyboard",
    kKbdTopRowLayoutWilcoTag,
    ui::INPUT_DEVICE_INTERNAL,
    /*has_custom_top_row=*/false,
};

constexpr TestKeyboard kWilco1_5Keyboard{
    "Drallion Keyboard",
    kKbdTopRowLayoutDrallionTag,
    ui::INPUT_DEVICE_INTERNAL,
    /*has_custom_top_row=*/false,
};

constexpr TestKeyboard kWilcoKeyboardVariants[] = {
    kWilco1_0Keyboard,
    kWilco1_5Keyboard,
};

}  // namespace

namespace ash {

class EventRewriterTestBase : public ChromeAshTestBase {
 public:
  EventRewriterTestBase()
      : fake_user_manager_(new FakeChromeUserManager),
        user_manager_enabler_(base::WrapUnique(fake_user_manager_.get())) {}
  ~EventRewriterTestBase() override {}

  void SetUp() override {
    ui::ResourceBundle::SetLottieParsingFunctions(
        &lottie::ParseLottieAsStillImage,
        &lottie::ParseLottieAsThemedStillImage);
    keyboard_layout_engine_ = std::make_unique<ui::StubKeyboardLayoutEngine>();
    // Inject custom table to make this closer to en-US behavior.
    keyboard_layout_engine_->SetCustomLookupTableForTesting({
        // Keep MetaRight as MetaRight.
        {ui::DomCode::META_RIGHT, ui::DomKey::META, ui::DomKey::META,
         ui::VKEY_RWIN},

        // Inject select_task key.
        {ui::DomCode::SELECT_TASK, ui::DomKey::LAUNCH_MY_COMPUTER,
         ui::DomKey::LAUNCH_MY_COMPUTER, ui::VKEY_MEDIA_LAUNCH_APP1},

        // Map numpad keys.
        {ui::DomCode::NUMPAD0, ui::DomKey::FromCharacter('0'),
         ui::DomKey::INSERT, ui::VKEY_NUMPAD0},
        {ui::DomCode::NUMPAD1, ui::DomKey::FromCharacter('1'), ui::DomKey::END,
         ui::VKEY_NUMPAD1},
        {ui::DomCode::NUMPAD2, ui::DomKey::FromCharacter('2'),
         ui::DomKey::ARROW_DOWN, ui::VKEY_NUMPAD2},
        {ui::DomCode::NUMPAD3, ui::DomKey::FromCharacter('3'),
         ui::DomKey::PAGE_DOWN, ui::VKEY_NUMPAD3},
        {ui::DomCode::NUMPAD4, ui::DomKey::FromCharacter('4'),
         ui::DomKey::ARROW_RIGHT, ui::VKEY_NUMPAD4},
        {ui::DomCode::NUMPAD5, ui::DomKey::FromCharacter('5'),
         ui::DomKey::CLEAR, ui::VKEY_NUMPAD5},
        {ui::DomCode::NUMPAD6, ui::DomKey::FromCharacter('6'),
         ui::DomKey::ARROW_LEFT, ui::VKEY_NUMPAD6},
        {ui::DomCode::NUMPAD7, ui::DomKey::FromCharacter('7'), ui::DomKey::HOME,
         ui::VKEY_NUMPAD7},
        {ui::DomCode::NUMPAD8, ui::DomKey::FromCharacter('8'),
         ui::DomKey::ARROW_UP, ui::VKEY_NUMPAD8},
        {ui::DomCode::NUMPAD9, ui::DomKey::FromCharacter('9'),
         ui::DomKey::PAGE_UP, ui::VKEY_NUMPAD9},
    });
    keyboard_capability_ =
        ui::KeyboardCapability::CreateStubKeyboardCapability();
    input_method_manager_mock_ = new input_method::MockInputMethodManagerImpl;
    input_method::InitializeForTesting(
        input_method_manager_mock_);  // pass ownership
    auto deprecation_controller =
        std::make_unique<DeprecationNotificationController>(&message_center_);
    deprecation_controller_ = deprecation_controller.get();
    auto input_device_settings_notification_controller =
        std::make_unique<InputDeviceSettingsNotificationController>(
            &message_center_);
    input_device_settings_notification_controller_ =
        input_device_settings_notification_controller.get();
    ChromeAshTestBase::SetUp();

    input_device_settings_controller_resetter_ = std::make_unique<
        InputDeviceSettingsController::ScopedResetterForTest>();
    input_device_settings_controller_mock_ =
        std::make_unique<MockInputDeviceSettingsController>();
    keyboard_settings = mojom::KeyboardSettings::New();
    // Disable F11/F12 settings by default.
    keyboard_settings->f11 = ui::mojom::ExtendedFkeysModifier::kDisabled;
    keyboard_settings->f12 = ui::mojom::ExtendedFkeysModifier::kDisabled;
    EXPECT_CALL(*input_device_settings_controller_mock_,
                GetKeyboardSettings(testing::_))
        .WillRepeatedly(testing::Return(keyboard_settings.get()));

    delegate_ = std::make_unique<EventRewriterDelegateImpl>(
        nullptr, std::move(deprecation_controller),
        std::move(input_device_settings_notification_controller),
        input_device_settings_controller_mock_.get());
    delegate_->set_pref_service_for_testing(prefs());
    device_data_manager_test_api_.SetKeyboardDevices({});
    keyboard_device_id_event_rewriter_ =
        std::make_unique<ui::KeyboardDeviceIdEventRewriter>(
            keyboard_capability_.get());
    keyboard_modifier_event_rewriter_ =
        std::make_unique<ui::KeyboardModifierEventRewriter>(
            std::make_unique<TestKeyboardModifierEventRewriterDelegate>(
                delegate_.get()),
            keyboard_layout_engine_.get(), keyboard_capability_.get(),
            &fake_ime_keyboard_);
    caps_lock_event_rewriter_ = std::make_unique<ui::CapsLockEventRewriter>(
        keyboard_layout_engine_.get(), keyboard_capability_.get(),
        &fake_ime_keyboard_);
    event_rewriter_ash_ = std::make_unique<ui::EventRewriterAsh>(
        delegate_.get(), keyboard_capability_.get(),
        Shell::Get()->sticky_keys_controller(), false, &fake_ime_keyboard_);
    discard_key_event_rewriter_ =
        std::make_unique<ui::DiscardKeyEventRewriter>();

    source_.AddEventRewriter(keyboard_device_id_event_rewriter_.get());
    if (ash::features::IsKeyboardRewriterFixEnabled()) {
      source_.AddEventRewriter(keyboard_modifier_event_rewriter_.get());
    }
    if (ash::features::IsKeyboardRewriterFixEnabled() &&
        features::IsModifierSplitEnabled()) {
      source_.AddEventRewriter(caps_lock_event_rewriter_.get());
    }
    source_.AddEventRewriter(event_rewriter_ash_.get());
    if (!ash::features::IsKeyboardRewriterFixEnabled() &&
        features::IsModifierSplitEnabled()) {
      source_.AddEventRewriter(caps_lock_event_rewriter_.get());
    }
    if (features::IsModifierSplitEnabled()) {
      source_.AddEventRewriter(discard_key_event_rewriter_.get());
    }
  }

  void TearDown() override {
    if (features::IsModifierSplitEnabled()) {
      source_.RemoveEventRewriter(discard_key_event_rewriter_.get());
    }
    if (!ash::features::IsKeyboardRewriterFixEnabled() &&
        features::IsModifierSplitEnabled()) {
      source_.RemoveEventRewriter(caps_lock_event_rewriter_.get());
    }
    source_.RemoveEventRewriter(event_rewriter_ash_.get());
    if (ash::features::IsKeyboardRewriterFixEnabled() &&
        features::IsModifierSplitEnabled()) {
      source_.RemoveEventRewriter(caps_lock_event_rewriter_.get());
    }
    if (ash::features::IsKeyboardRewriterFixEnabled()) {
      source_.RemoveEventRewriter(keyboard_modifier_event_rewriter_.get());
    }
    source_.RemoveEventRewriter(keyboard_device_id_event_rewriter_.get());

    event_rewriter_ash_.reset();
    caps_lock_event_rewriter_.reset();
    keyboard_modifier_event_rewriter_.reset();
    keyboard_device_id_event_rewriter_.reset();

    input_device_settings_controller_mock_.reset();
    input_device_settings_controller_resetter_.reset();
    ChromeAshTestBase::TearDown();
    // Shutdown() deletes the IME mock object.
    input_method::Shutdown();
    keyboard_capability_.reset();
    keyboard_layout_engine_.reset();
  }

  ui::test::TestEventSource& source() { return source_; }

 protected:
  std::vector<TestKeyEvent> RunRewriter(
      const std::vector<TestKeyEvent>& events,
      ui::EventFlags extra_flags = ui::EF_NONE,
      int device_id = kKeyboardDeviceId) {
    struct ModifierInfo {
      ui::EventFlags flag;
      ui::DomCode code;
      ui::DomKey key;
      ui::KeyboardCode keycode;
    };
    // We'll use modifier keys at left side heuristically.
    static constexpr ModifierInfo kModifierList[] = {
        {ui::EF_SHIFT_DOWN, ui::DomCode::SHIFT_LEFT, ui::DomKey::SHIFT,
         ui::VKEY_SHIFT},
        {ui::EF_CONTROL_DOWN, ui::DomCode::CONTROL_LEFT, ui::DomKey::CONTROL,
         ui::VKEY_CONTROL},
        {ui::EF_ALT_DOWN, ui::DomCode::ALT_LEFT, ui::DomKey::ALT,
         ui::VKEY_MENU},
        {ui::EF_COMMAND_DOWN, ui::DomCode::META_LEFT, ui::DomKey::META,
         ui::VKEY_LWIN},
        {ui::EF_MOD3_DOWN, ui::DomCode::CAPS_LOCK, ui::DomKey::CAPS_LOCK,
         ui::VKEY_CAPITAL},
        {ui::EF_FUNCTION_DOWN, ui::DomCode::FN, ui::DomKey::FN,
         ui::VKEY_FUNCTION},
    };

    // Send modifier key press events to update rewriter's modifier flag state.
    ui::EventFlags current_flags = 0;
    for (const auto& modifier : kModifierList) {
      if (!(extra_flags & modifier.flag)) {
        continue;
      }
      current_flags |= modifier.flag;
      SendKeyEvent(TestKeyEvent{ui::EventType::kKeyPressed, modifier.code,
                                modifier.key, modifier.keycode, current_flags},
                   device_id);
    }
    CHECK_EQ(current_flags, extra_flags);

    // Add extra_flags to each TestkeyEvent.
    std::vector<TestKeyEvent> key_events;
    for (const auto& event : events) {
      key_events.push_back(TestKeyEvent{
          event.type, event.code, event.key, event.keycode,
          event.flags | current_flags, event.scan_code, event.properties});
    }
    auto result = SendKeyEvents(key_events, device_id);

    // Send modifier key release events to unset rewriter'.s modifier flag
    // state.
    for (const auto& modifier : base::Reversed(kModifierList)) {
      if (!(extra_flags & modifier.flag)) {
        continue;
      }
      current_flags &= ~modifier.flag;
      SendKeyEvent(TestKeyEvent{ui::EventType::kKeyReleased, modifier.code,
                                modifier.key, modifier.keycode, current_flags},
                   device_id);
    }
    CHECK_EQ(current_flags, 0);

    return result;
  }

  // Sends a KeyEvent to the rewriter, returns the rewritten events.
  // Note: one event may be rewritten into multiple events.
  std::vector<TestKeyEvent> SendKeyEvent(const TestKeyEvent& event,
                                         int device_id = kKeyboardDeviceId) {
    return SendKeyEvents({event}, device_id);
  }

  std::vector<TestKeyEvent> SendKeyEvents(
      const std::vector<TestKeyEvent>& events,
      int device_id = kKeyboardDeviceId) {
    // Just in case some events may be there.
    if (!TakeEvents().empty()) {
      ADD_FAILURE() << "Rewritten events were left";
    }

    // Convert TestKeyEvent into ui::KeyEvent, then dispatch it to the
    // rewriter.
    for (const TestKeyEvent& event : events) {
      ui::KeyEvent key_event(event.type, event.keycode, event.code, event.flags,
                             event.key, ui::EventTimeForNow());
      key_event.set_scan_code(event.scan_code);
      key_event.set_source_device_id(device_id);
      ui::EventDispatchDetails details = source_.Send(&key_event);
      CHECK(!details.dispatcher_destroyed);
    }

    // Convert the rewritten ui::Events back to TestKeyEvent.
    auto rewritten_events = TakeEvents();
    std::vector<TestKeyEvent> result;
    for (const auto& rewritten_event : rewritten_events) {
      auto* rewritten_key_event = rewritten_event->AsKeyEvent();
      if (!rewritten_key_event) {
        ADD_FAILURE() << "Unexpected rewritten key event: "
                      << rewritten_event->ToString();
        continue;
      }
      std::vector<std::pair<std::string, std::vector<uint8_t>>> properties;
      if (rewritten_key_event->properties()) {
        for (const auto& property : *rewritten_key_event->properties()) {
          properties.push_back(property);
        }
      }
      result.push_back(
          {rewritten_key_event->type(), rewritten_key_event->code(),
           rewritten_key_event->GetDomKey(), rewritten_key_event->key_code(),
           rewritten_key_event->flags(), rewritten_key_event->scan_code(),
           std::move(properties)});
    }
    return result;
  }

  // Parameterized version of test depending on feature flag values. The feature
  // kUseSearchClickForRightClick determines if this should test for alt-click
  // or search-click.
  void DontRewriteIfNotRewritten(int right_click_flags);

  ui::MouseEvent RewriteMouseButtonEvent(const ui::MouseEvent& event) {
    TestEventRewriterContinuation continuation;
    event_rewriter_ash_->RewriteMouseButtonEventForTesting(
        event, continuation.weak_ptr_factory_.GetWeakPtr());
    if (!continuation.rewritten_events.empty()) {
      return ui::MouseEvent(*continuation.rewritten_events[0]->AsMouseEvent());
    }
    return ui::MouseEvent(event);
  }

  sync_preferences::TestingPrefServiceSyncable* prefs() { return &prefs_; }

  void InitModifierKeyPref(IntegerPrefMember* int_pref,
                           const std::string& pref_name,
                           ui::mojom::ModifierKey remap_from,
                           ui::mojom::ModifierKey remap_to) {
    if (!features::IsInputDeviceSettingsSplitEnabled()) {
      if (int_pref->GetPrefName() !=
          pref_name) {  // skip if already initialized.
        int_pref->Init(pref_name, prefs());
      }
      int_pref->SetValue(static_cast<int>(remap_to));
      return;
    }
    if (remap_from == remap_to) {
      keyboard_settings->modifier_remappings.erase(remap_from);
      return;
    }

    keyboard_settings->modifier_remappings[remap_from] = remap_to;
  }

  void SetUpKeyboard(const TestKeyboard& test_keyboard) {
    // Add a fake device to udev.
    const ui::KeyboardDevice keyboard(
        kKeyboardDeviceId, test_keyboard.type, test_keyboard.name,
        /*phys=*/"", base::FilePath(kKbdSysPath),
        /*vendor=*/-1,
        /*product=*/-1, /*version=*/-1,
        /*has_assistant_key=*/test_keyboard.has_assistant_key,
        /*has_function_key=*/test_keyboard.has_function_key);

    // Old CrOS keyboards supply an integer/enum as a sysfs property to identify
    // their layout type. New keyboards provide the mapping of scan codes to
    // F-Key position via an attribute.
    std::map<std::string, std::string> sysfs_properties;
    std::map<std::string, std::string> sysfs_attributes;
    if (!std::string_view(test_keyboard.layout).empty()) {
      (test_keyboard.has_custom_top_row
           ? sysfs_attributes[kKbdTopRowLayoutAttributeName]
           : sysfs_properties[kKbdTopRowPropertyName]) = test_keyboard.layout;
    }

    fake_udev_.Reset();
    fake_udev_.AddFakeDevice(keyboard.name, keyboard.sys_path.value(),
                             /*subsystem=*/"input", /*devnode=*/std::nullopt,
                             /*devtype=*/std::nullopt,
                             std::move(sysfs_attributes),
                             std::move(sysfs_properties));

    // Reset the state of the device manager.
    device_data_manager_test_api_.SetKeyboardDevices({});
    device_data_manager_test_api_.SetKeyboardDevices({keyboard});

    // Reset the state of the EventRewriter.
    event_rewriter_ash_->ResetStateForTesting();
    event_rewriter_ash_->set_last_keyboard_device_id_for_testing(
        kKeyboardDeviceId);
  }

  void SetExtensionCommands(
      std::optional<base::flat_set<std::pair<ui::KeyboardCode, int>>>
          commands) {
    delegate_->SetExtensionCommandsOverrideForTesting(std::move(commands));
  }

  std::vector<std::unique_ptr<ui::Event>> TakeEvents() {
    return sink_.TakeEvents();
  }

  void ClearNotifications() {
    message_center_.RemoveAllNotifications(
        false, message_center::FakeMessageCenter::RemoveType::ALL);
    deprecation_controller_->ResetStateForTesting();
  }

  base::test::ScopedFeatureList scoped_feature_list_;
  raw_ptr<FakeChromeUserManager, DanglingUntriaged>
      fake_user_manager_;  // Not owned.
  user_manager::ScopedUserManager user_manager_enabler_;
  raw_ptr<input_method::MockInputMethodManagerImpl, DanglingUntriaged>
      input_method_manager_mock_;
  testing::FakeUdevLoader fake_udev_;
  ui::DeviceDataManagerTestApi device_data_manager_test_api_;
  std::unique_ptr<InputDeviceSettingsController::ScopedResetterForTest>
      input_device_settings_controller_resetter_;
  std::unique_ptr<MockInputDeviceSettingsController>
      input_device_settings_controller_mock_;
  mojom::KeyboardSettingsPtr keyboard_settings;

  sync_preferences::TestingPrefServiceSyncable prefs_;
  std::unique_ptr<EventRewriterDelegateImpl> delegate_;
  std::unique_ptr<ui::StubKeyboardLayoutEngine> keyboard_layout_engine_;
  std::unique_ptr<ui::KeyboardCapability> keyboard_capability_;
  input_method::FakeImeKeyboard fake_ime_keyboard_;
  std::unique_ptr<ui::KeyboardDeviceIdEventRewriter>
      keyboard_device_id_event_rewriter_;
  std::unique_ptr<ui::KeyboardModifierEventRewriter>
      keyboard_modifier_event_rewriter_;
  std::unique_ptr<ui::CapsLockEventRewriter> caps_lock_event_rewriter_;
  std::unique_ptr<ui::EventRewriterAsh> event_rewriter_ash_;
  std::unique_ptr<ui::DiscardKeyEventRewriter> discard_key_event_rewriter_;
  TestEventSink sink_;
  ui::test::TestEventSource source_{&sink_};
  message_center::FakeMessageCenter message_center_;
  base::AutoReset<bool> ignore_modifier_split_secret_key_ =
      switches::SetIgnoreModifierSplitSecretKeyForTest();
  raw_ptr<DeprecationNotificationController>
      deprecation_controller_;  // Not owned.
  raw_ptr<InputDeviceSettingsNotificationController>
      input_device_settings_notification_controller_;  // Not owned.
};

class EventRewriterTest
    : public EventRewriterTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  void SetUp() override {
    auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam();
    if (enable_keyboard_rewriter_fix) {
      fix_feature_list_.InitAndEnableFeature(
          ash::features::kEnableKeyboardRewriterFix);
    } else {
      fix_feature_list_.InitAndDisableFeature(
          ash::features::kEnableKeyboardRewriterFix);
    }

    if (enable_modifier_split) {
      modifier_split_feature_list_.InitAndEnableFeature(
          ash::features::kModifierSplit);
    } else {
      modifier_split_feature_list_.InitAndDisableFeature(
          ash::features::kModifierSplit);
    }

    EventRewriterTestBase::SetUp();
  }

  void TearDown() override {
    EventRewriterTestBase::TearDown();
    modifier_split_feature_list_.Reset();
    fix_feature_list_.Reset();
  }

 private:
  base::test::ScopedFeatureList fix_feature_list_;
  base::test::ScopedFeatureList modifier_split_feature_list_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         EventRewriterTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

// TestKeyRewriteLatency checks that the event rewriter
// publishes a latency metric every time a key is pressed.
TEST_P(EventRewriterTest, TestKeyRewriteLatency) {
  SendKeyEvent(KeyLControl::Pressed());

  base::HistogramTester histogram_tester;
  EXPECT_EQ(std::vector({KeyB::Pressed(ui::EF_CONTROL_DOWN),
                         KeyB::Pressed(ui::EF_CONTROL_DOWN)}),
            SendKeyEvents({KeyB::Pressed(ui::EF_CONTROL_DOWN),
                           KeyB::Pressed(ui::EF_CONTROL_DOWN)}));
  histogram_tester.ExpectTotalCount(
      "ChromeOS.Inputs.EventRewriter.KeyRewriteLatency", 2);
}

TEST_P(EventRewriterTest, ModifiersNotRemappedWhenSuppressed) {
  // Remap Control -> Alt.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kAlt);

  // Pressing Control + B should now be remapped to Alt + B.
  delegate_->SuppressModifierKeyRewrites(false);
  EXPECT_EQ(KeyB::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyB::Typed(), ui::EF_CONTROL_DOWN));

  // Pressing Control + B should no longer be remapped.
  delegate_->SuppressModifierKeyRewrites(true);
  EXPECT_EQ(KeyB::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyB::Typed(), ui::EF_CONTROL_DOWN));
}

TEST_P(EventRewriterTest, TestRewriteNumPadKeys) {
  // Even if most Chrome OS keyboards do not have numpad, they should still
  // handle it the same way as generic PC keyboards.
  for (const auto& keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // XK_KP_Insert (= NumPad 0 without Num Lock), no modifier.
    EXPECT_EQ(KeyNumpad0::Typed(), RunRewriter(KeyNumpadInsert::Typed()));

    // XK_KP_Insert (= NumPad 0 without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpad0::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadInsert::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_Delete (= NumPad . without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpadDecimal::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadDelete::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_End (= NumPad 1 without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpad1::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadEnd::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_Down (= NumPad 2 without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpad2::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadArrowDown::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_Next (= NumPad 3 without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpad3::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadPageDown::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_Left (= NumPad 4 without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpad4::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadArrowLeft::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_Begin (= NumPad 5 without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpad5::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadClear::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_Right (= NumPad 6 without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpad6::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadArrowRight::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_Home (= NumPad 7 without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpad7::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadHome::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_Up (= NumPad 8 without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpad8::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadArrowUp::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_Prior (= NumPad 9 without Num Lock), Alt modifier.
    EXPECT_EQ(KeyNumpad9::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyNumpadPageUp::Typed(), ui::EF_ALT_DOWN));

    // XK_KP_{N} (= NumPad {N} with Num Lock), Num Lock modifier.
    EXPECT_EQ(KeyNumpad0::Typed(), RunRewriter(KeyNumpad0::Typed()));
    EXPECT_EQ(KeyNumpad1::Typed(), RunRewriter(KeyNumpad1::Typed()));
    EXPECT_EQ(KeyNumpad2::Typed(), RunRewriter(KeyNumpad2::Typed()));
    EXPECT_EQ(KeyNumpad3::Typed(), RunRewriter(KeyNumpad3::Typed()));
    EXPECT_EQ(KeyNumpad4::Typed(), RunRewriter(KeyNumpad4::Typed()));
    EXPECT_EQ(KeyNumpad5::Typed(), RunRewriter(KeyNumpad5::Typed()));
    EXPECT_EQ(KeyNumpad6::Typed(), RunRewriter(KeyNumpad6::Typed()));
    EXPECT_EQ(KeyNumpad7::Typed(), RunRewriter(KeyNumpad7::Typed()));
    EXPECT_EQ(KeyNumpad8::Typed(), RunRewriter(KeyNumpad8::Typed()));
    EXPECT_EQ(KeyNumpad9::Typed(), RunRewriter(KeyNumpad9::Typed()));

    // XK_KP_DECIMAL (= NumPad . with Num Lock), Num Lock modifier.
    EXPECT_EQ(KeyNumpadDecimal::Typed(),
              RunRewriter(KeyNumpadDecimal::Typed()));
  }
}

// Tests if the rewriter can handle a Command + Num Pad event.
TEST_P(EventRewriterTest, TestRewriteNumPadKeysOnAppleKeyboard) {
  // Simulate the default initialization of the Apple Command key remap pref to
  // Ctrl.
  Preferences::RegisterProfilePrefs(prefs()->registry());

  if (features::IsInputDeviceSettingsSplitEnabled()) {
    keyboard_settings->modifier_remappings[ui::mojom::ModifierKey::kMeta] =
        ui::mojom::ModifierKey::kControl;
  }

  SetUpKeyboard(kExternalAppleKeyboard);

  // XK_KP_End (= NumPad 1 without Num Lock), Win modifier.
  // The result should be "Num Pad 1 with Control + Num Lock modifiers".
  EXPECT_EQ(KeyNumpad1::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyNumpadEnd::Typed(), ui::EF_COMMAND_DOWN));

  // XK_KP_1 (= NumPad 1 with Num Lock), Win modifier.
  // The result should also be "Num Pad 1 with Control + Num Lock
  // modifiers".
  EXPECT_EQ(KeyNumpad1::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyNumpad1::Typed(), ui::EF_COMMAND_DOWN));
}

TEST_P(EventRewriterTest, TestRewriteModifiersNoRemap) {
  for (const auto& keyboard : kAllKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press Search. Confirm the event is not rewritten.
    EXPECT_EQ(KeyLMeta::Typed(), RunRewriter(KeyLMeta::Typed()));

    // Press left Control. Confirm the event is not rewritten.
    EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLControl::Typed()));

    // Press right Control. Confirm the event is not rewritten.
    EXPECT_EQ(KeyRControl::Typed(), RunRewriter(KeyRControl::Typed()));

    // Press left Alt. Confirm the event is not rewritten.
    EXPECT_EQ(KeyLAlt::Typed(), RunRewriter(KeyLAlt::Typed()));

    // Press right Alt. Confirm the event is not rewritten.
    EXPECT_EQ(KeyRAlt::Typed(), RunRewriter(KeyRAlt::Typed()));
  }
}

TEST_P(EventRewriterTest, TestRewriteModifiersNoRemapMultipleKeys) {
  for (const auto& keyboard : kAllKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press Alt with Shift. Confirm the event is not rewritten.
    EXPECT_EQ(KeyLAlt::Typed(ui::EF_SHIFT_DOWN),
              RunRewriter(KeyLAlt::Typed(), ui::EF_SHIFT_DOWN));

    // Press Escape with Alt and Shift. Confirm the event is not rewritten.
    EXPECT_EQ(
        KeyEscape::Typed(ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN),
        RunRewriter(KeyEscape::Typed(), ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN));

    // Toggling on CapsLock.
    EXPECT_EQ(KeyCapsLock::Typed(ui::EF_CAPS_LOCK_ON),
              RunRewriter(KeyCapsLock::Typed(ui::EF_CAPS_LOCK_ON)));

    // Press Search with Caps Lock mask. Confirm the event is not rewritten.
    EXPECT_EQ(KeyLMeta::Typed(ui::EF_CAPS_LOCK_ON),
              RunRewriter(KeyLMeta::Typed(ui::EF_CAPS_LOCK_ON)));

    // Toggling off CapsLock.
    EXPECT_EQ(KeyCapsLock::Typed(), RunRewriter(KeyCapsLock::Typed()));

    // Press Shift+Ctrl+Alt+Search+Escape. Confirm the event is not rewritten.
    EXPECT_EQ(KeyEscape::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
                               ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN),
              RunRewriter(KeyEscape::Typed(),
                          ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
                              ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN));

    // Press Shift+Ctrl+Alt+Search+B. Confirm the event is not rewritten.
    EXPECT_EQ(KeyB::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
                          ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN),
              RunRewriter(
                  // In this case, SHIFT modifier will be set on pressing B,
                  // thus we should use capital 'B' as DomKey, which the current
                  // factory does not support.
                  // Modifier flags will be annotated to TestKeyEvents inside
                  // RunRewriter.
                  {TestKeyEvent{ui::EventType::kKeyPressed, ui::DomCode::US_B,
                                ui::DomKey::FromCharacter('B'), ui::VKEY_B},
                   TestKeyEvent{ui::EventType::kKeyReleased, ui::DomCode::US_B,
                                ui::DomKey::FromCharacter('B'), ui::VKEY_B}},
                  ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN |
                      ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteModifiersDisableSome) {
  // Disable Search, Control and Escape keys.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember search;
  InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo,
                      ui::mojom::ModifierKey::kMeta,
                      ui::mojom::ModifierKey::kVoid);
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kVoid);
  IntegerPrefMember escape;
  InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo,
                      ui::mojom::ModifierKey::kEscape,
                      ui::mojom::ModifierKey::kVoid);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press Alt with Shift. This key press shouldn't be affected by the
    // pref. Confirm the event is not rewritten.
    EXPECT_EQ(KeyLAlt::Typed(ui::EF_SHIFT_DOWN),
              RunRewriter(KeyLAlt::Typed(ui::EF_SHIFT_DOWN)));

    // Press Search. Confirm the event is now VKEY_UNKNOWN.
    EXPECT_EQ(KeyUnknown::Typed(), RunRewriter(KeyLMeta::Typed()));

    // Press Control. Confirm the event is now VKEY_UNKNOWN.
    EXPECT_EQ(KeyUnknown::Typed(), RunRewriter(KeyLControl::Typed()));

    // Press Escape. Confirm the event is now VKEY_UNKNOWN.
    EXPECT_EQ(KeyUnknown::Typed(), RunRewriter(KeyEscape::Typed()));

    // Press Control+Search. Confirm the event is now VKEY_UNKNOWN
    // without any modifiers.
    if (ash::features::IsKeyboardRewriterFixEnabled()) {
      EXPECT_EQ(KeyUnknown::Typed(),
                RunRewriter(KeyLMeta::Typed(), ui::EF_CONTROL_DOWN));
    } else {
      // TODO(crbug.com/40265877): Release key event is not dispatched in old
      // rewriter. Remove this once the old rewriter is no longer used.
      EXPECT_EQ(std::vector({KeyUnknown::Pressed()}),
                RunRewriter(KeyLMeta::Typed(), ui::EF_CONTROL_DOWN));
    }

    // Press Control+Search+a. Confirm the event is now VKEY_A without any
    // modifiers.
    EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed(), ui::EF_CONTROL_DOWN));

    // Press Control+Search+Alt+a. Confirm the event is now VKEY_A only with
    // the Alt modifier.
    EXPECT_EQ(
        KeyA::Typed(ui::EF_ALT_DOWN),
        RunRewriter(KeyA::Typed(), ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN));
  }

  // Remap Alt to Control.
  IntegerPrefMember alt;
  InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo,
                      ui::mojom::ModifierKey::kAlt,
                      ui::mojom::ModifierKey::kControl);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press left Alt. Confirm the event is now VKEY_CONTROL
    // even though the Control key itself is disabled.
    EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLAlt::Typed()));

    // Press Alt+a. Confirm the event is now Control+a even though the Control
    // key itself is disabled.
    EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteModifiersRemapToControl) {
  // Remap Search to Control.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember search;
  InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo,
                      ui::mojom::ModifierKey::kMeta,
                      ui::mojom::ModifierKey::kControl);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press Search. Confirm the event is now VKEY_CONTROL.
    EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLMeta::Typed()));
  }

  // Remap Alt to Control too.
  IntegerPrefMember alt;
  InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo,
                      ui::mojom::ModifierKey::kAlt,
                      ui::mojom::ModifierKey::kControl);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press Alt. Confirm the event is now VKEY_CONTROL.
    EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLAlt::Typed()));

    // Press Alt+Search. Confirm the event is now VKEY_CONTROL.
    if (ash::features::IsKeyboardRewriterFixEnabled()) {
      // In this case, both pressed/released events have EF_CONTROL_DOWN,
      // because ALT key mapped to CONTROL is held.
      EXPECT_EQ(KeyLControl::Typed(ui::EF_CONTROL_DOWN),
                RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN));
    } else {
      // TODO(crbug.com/40265877): Release key event is not dispatched in old
      // rewriter. Remove this once the old rewriter is no longer used.
      EXPECT_EQ(std::vector({KeyLControl::Pressed()}),
                RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN));
    }

    // Press Control+Alt+Search. Confirm the event is now VKEY_CONTROL.
    if (ash::features::IsKeyboardRewriterFixEnabled()) {
      // In this case, both pressed/released events have EF_CONTROL_DOWN,
      // because ALT key mapped to CONTROL is held.
      EXPECT_EQ(KeyLControl::Typed(ui::EF_CONTROL_DOWN),
                RunRewriter(KeyLMeta::Typed(),
                            ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN));
    } else {
      // TODO(crbug.com/40265877): Release key event is not dispatched in old
      // rewriter. Remove this once the old rewriter is no longer used.
      EXPECT_EQ(std::vector({KeyLControl::Pressed()}),
                RunRewriter(KeyLMeta::Typed(),
                            ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN));
    }

    // Press Shift+Control+Alt+Search. Confirm the event is now Control with
    // Shift and Control modifiers.
    if (ash::features::IsKeyboardRewriterFixEnabled()) {
      // In this case, both pressed/released events have EF_CONTROL_DOWN,
      // because ALT key mapped to CONTROL is held.
      EXPECT_EQ(KeyLControl::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN),
                RunRewriter(KeyLMeta::Typed(), ui::EF_SHIFT_DOWN |
                                                   ui::EF_CONTROL_DOWN |
                                                   ui::EF_ALT_DOWN));
    } else {
      // TODO(crbug.com/40265877): Release key event is not dispatched in old
      // rewriter. Remove this once the old rewriter is no longer used.
      EXPECT_EQ(std::vector({KeyLControl::Pressed(ui::EF_SHIFT_DOWN)}),
                RunRewriter(KeyLMeta::Typed(), ui::EF_SHIFT_DOWN |
                                                   ui::EF_CONTROL_DOWN |
                                                   ui::EF_ALT_DOWN));
    }

    // Press Shift+Control+Alt+Search+B. Confirm the event is now B with Shift
    // and Control modifiers.
    EXPECT_EQ(
        KeyB::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN),
        RunRewriter(KeyB::Typed(), ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
                                       ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteModifiersRemapToEscape) {
  // Remap Search to Escape.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember search;
  InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo,
                      ui::mojom::ModifierKey::kMeta,
                      ui::mojom::ModifierKey::kEscape);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press Search. Confirm the event is now VKEY_ESCAPE.
    EXPECT_EQ(KeyEscape::Typed(), RunRewriter(KeyLMeta::Typed()));
  }
}

TEST_P(EventRewriterTest, TestRewriteModifiersRemapEscapeToAlt) {
  // Remap Escape to Alt.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember escape;
  InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo,
                      ui::mojom::ModifierKey::kEscape,
                      ui::mojom::ModifierKey::kAlt);

  for (const auto& keyboard : kAllKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press Escape. Confirm the event is now VKEY_MENU.
    EXPECT_EQ(KeyLAlt::Typed(), RunRewriter(KeyEscape::Typed()));
  }
}

TEST_P(EventRewriterTest, TestRewriteModifiersRemapAltToControl) {
  // Remap Alt to Control.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember alt;
  InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo,
                      ui::mojom::ModifierKey::kAlt,
                      ui::mojom::ModifierKey::kControl);

  for (const auto& keyboard : kAllKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press left Alt. Confirm the event is now VKEY_CONTROL.
    EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLAlt::Typed()));

    // Press Shift+comma. Verify that only the flags are changed.
    EXPECT_EQ(
        KeyComma::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN),
        RunRewriter(KeyComma::Typed(), ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN));

    // Press Shift+9. Verify that only the flags are changed.
    EXPECT_EQ(
        KeyDigit9::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN),
        RunRewriter(KeyDigit9::Typed(), ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteModifiersRemapUnderEscapeControlAlt) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  // Remap Escape to Alt.
  IntegerPrefMember escape;
  InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo,
                      ui::mojom::ModifierKey::kEscape,
                      ui::mojom::ModifierKey::kAlt);

  // Remap Alt to Control.
  IntegerPrefMember alt;
  InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo,
                      ui::mojom::ModifierKey::kAlt,
                      ui::mojom::ModifierKey::kControl);

  // Remap Control to Search.
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kMeta);

  for (const auto& keyboard : kAllKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press left Control. Confirm the event is now VKEY_LWIN.
    EXPECT_EQ(KeyLMeta::Typed(), RunRewriter(KeyLControl::Typed()));

    // Then, press all of the three, Control+Alt+Escape.
    EXPECT_EQ(
        KeyLAlt::Typed(ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN),
        RunRewriter(KeyEscape::Typed(), ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN));

    // Press Shift+Control+Alt+Escape.
    EXPECT_EQ(
        KeyLAlt::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
                       ui::EF_COMMAND_DOWN),
        RunRewriter(KeyEscape::Typed(),
                    ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN));

    // Press Shift+Control+Alt+B
    EXPECT_EQ(
        KeyB::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
                    ui::EF_COMMAND_DOWN),
        RunRewriter(KeyB::Typed(),
                    ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN));
  }
}

TEST_P(EventRewriterTest,
       TestRewriteModifiersRemapUnderEscapeControlAltSearch) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  // Remap Escape to Alt.
  IntegerPrefMember escape;
  InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo,
                      ui::mojom::ModifierKey::kEscape,
                      ui::mojom::ModifierKey::kAlt);

  // Remap Alt to Control.
  IntegerPrefMember alt;
  InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo,
                      ui::mojom::ModifierKey::kAlt,
                      ui::mojom::ModifierKey::kControl);

  // Remap Control to Search.
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kMeta);

  // Remap Search to Backspace.
  IntegerPrefMember search;
  InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo,
                      ui::mojom::ModifierKey::kMeta,
                      ui::mojom::ModifierKey::kBackspace);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Release Control and Escape, as Search and Alt would transform Backspace
    // to Delete.
    EXPECT_EQ(std::vector({KeyLMeta::Pressed()}),
              SendKeyEvent(KeyLControl::Pressed()));
    EXPECT_EQ(std::vector({KeyLAlt::Pressed(ui::EF_COMMAND_DOWN)}),
              SendKeyEvent(KeyEscape::Pressed(ui::EF_CONTROL_DOWN)));

    if (ash::features::IsKeyboardRewriterFixEnabled()) {
      EXPECT_EQ(std::vector({KeyLMeta::Released(ui::EF_ALT_DOWN)}),
                SendKeyEvent(KeyLControl::Released()));
      EXPECT_EQ(std::vector({KeyLAlt::Released()}),
                SendKeyEvent(KeyEscape::Released()));
    } else {
      // TODO(crbug.com/40265877): Due to old rewriter implementation,
      // unexpected key release events are dispatched, followed by wrongly
      // un-rewritten event is dispatched. Fix them.
      EXPECT_EQ(std::vector({KeyLMeta::Released(ui::EF_ALT_DOWN),
                             KeyLAlt::Released(ui::EF_ALT_DOWN)}),
                SendKeyEvent(KeyLControl::Released()));
      EXPECT_EQ(std::vector({KeyEscape::Released()}),
                SendKeyEvent(KeyEscape::Released()));
    }

    // Press Search. Confirm the event is now VKEY_BACK.
    EXPECT_EQ(KeyBackspace::Typed(), RunRewriter(KeyLMeta::Typed()));
  }
}

TEST_P(EventRewriterTest, TestRewriteModifiersRemapBackspaceToEscape) {
  // Remap Backspace to Escape.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember backspace;
  InitModifierKeyPref(&backspace, ::prefs::kLanguageRemapBackspaceKeyTo,
                      ui::mojom::ModifierKey::kBackspace,
                      ui::mojom::ModifierKey::kEscape);

  for (const auto& keyboard : kAllKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Press Backspace. Confirm the event is now VKEY_ESCAPE.
    EXPECT_EQ(KeyEscape::Typed(), RunRewriter(KeyBackspace::Typed()));
  }
}

TEST_P(EventRewriterTest,
       TestRewriteNonModifierToModifierWithRemapBetweenKeyEvents) {
  // Remap Escape to Alt.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember escape;
  InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo,
                      ui::mojom::ModifierKey::kEscape,
                      ui::mojom::ModifierKey::kAlt);

  SetUpKeyboard(kInternalChromeKeyboard);

  // Press Escape.
  EXPECT_EQ(std::vector({KeyLAlt::Pressed()}),
            SendKeyEvent(KeyEscape::Pressed()));

  // Remap Escape to Control before releasing Escape.
  InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo,
                      ui::mojom::ModifierKey::kEscape,
                      ui::mojom::ModifierKey::kControl);

  // Release Escape.
  if (ash::features::IsKeyboardRewriterFixEnabled()) {
    EXPECT_EQ(std::vector({KeyLAlt::Released()}),
              SendKeyEvent(KeyEscape::Released()));
  } else {
    EXPECT_EQ(std::vector({KeyEscape::Released()}),
              SendKeyEvent(KeyEscape::Released()));
  }

  // Type A, expect that Alt is not stickied.
  EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed()));
}

TEST_P(EventRewriterTest, TestRewriteModifiersRemapToCapsLock) {
  // Remap Search to Caps Lock.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember search;
  InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo,
                      ui::mojom::ModifierKey::kMeta,
                      ui::mojom::ModifierKey::kCapsLock);

  SetUpKeyboard(kInternalChromeKeyboard);
  EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Press Search.
  EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyLMeta::Pressed()));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Release Search.
  EXPECT_EQ(std::vector({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyLMeta::Released()));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Press Search.
  EXPECT_EQ(std::vector({KeyCapsLock::Pressed()}),
            SendKeyEvent(KeyLMeta::Pressed()));
  EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Release Search.
  EXPECT_EQ(std::vector({KeyCapsLock::Released()}),
            SendKeyEvent(KeyLMeta::Released()));
  EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Do the same on external Chrome OS keyboard.
  SetUpKeyboard(kExternalChromeKeyboard);

  // Press Search.
  EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyLMeta::Pressed()));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Release Search.
  EXPECT_EQ(std::vector({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyLMeta::Released()));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Press Search.
  EXPECT_EQ(std::vector({KeyCapsLock::Pressed()}),
            SendKeyEvent(KeyLMeta::Pressed()));
  EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Release Search.
  EXPECT_EQ(std::vector({KeyCapsLock::Released()}),
            SendKeyEvent(KeyLMeta::Released()));
  EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Try external keyboard with Caps Lock.
  SetUpKeyboard(kExternalGenericKeyboard);

  // Press Caps Lock.
  EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Release Caps Lock.
  EXPECT_EQ(std::vector({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());
}

TEST_P(EventRewriterTest, TestRewriteCapsLock) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  SetUpKeyboard(kExternalGenericKeyboard);
  EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled());

  // On Chrome OS, CapsLock is mapped to CapsLock with Mod3Mask.
  EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Pressed()));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Key repeating should not toggle CapsLock state.
  EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Pressed()));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  EXPECT_EQ(std::vector({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Remap Caps Lock to Control.
  IntegerPrefMember caps_lock;
  InitModifierKeyPref(&caps_lock, ::prefs::kLanguageRemapCapsLockKeyTo,
                      ui::mojom::ModifierKey::kCapsLock,
                      ui::mojom::ModifierKey::kControl);

  // Press Caps Lock. CapsLock is enabled but we have remapped the key to
  // now be Control. We want to ensure that the CapsLock modifier is still
  // active even after pressing the remapped Capslock key.
  EXPECT_EQ(std::vector({KeyLControl::Pressed(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Release Caps Lock.
  EXPECT_EQ(std::vector({KeyLControl::Released(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());
}

TEST_P(EventRewriterTest, TestRewriteExternalCapsLockWithDifferentScenarios) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  SetUpKeyboard(kExternalGenericKeyboard);
  EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Turn on CapsLock.
  EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Pressed()));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  EXPECT_EQ(std::vector({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Remap CapsLock to Search.
  IntegerPrefMember search;
  InitModifierKeyPref(&search, ::prefs::kLanguageRemapCapsLockKeyTo,
                      ui::mojom::ModifierKey::kCapsLock,
                      ui::mojom::ModifierKey::kMeta);

  // Now that CapsLock is enabled, press the remapped CapsLock button again
  // and expect to not disable CapsLock.
  EXPECT_EQ(std::vector({KeyLMeta::Pressed(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  EXPECT_EQ(std::vector({KeyLMeta::Released(ui::EF_CAPS_LOCK_ON)}),
            SendKeyEvent(KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)));
  EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

  // Remap CapsLock key back to CapsLock.
  IntegerPrefMember capslock;
  InitModifierKeyPref(&capslock, ::prefs::kLanguageRemapCapsLockKeyTo,
                      ui::mojom::ModifierKey::kCapsLock,
                      ui::mojom::ModifierKey::kCapsLock);

  // Now press CapsLock again and now expect that the CapsLock modifier is
  // removed and the key is disabled.
  EXPECT_EQ(std::vector({KeyCapsLock::Pressed()}),
            SendKeyEvent(KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)));
  EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled());
}

TEST_P(EventRewriterTest, TestRewriteCapsLockToControl) {
  // Remap CapsLock to Control.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapCapsLockKeyTo,
                      ui::mojom::ModifierKey::kCapsLock,
                      ui::mojom::ModifierKey::kControl);

  SetUpKeyboard(kExternalGenericKeyboard);

  // Press CapsLock+a. Confirm that Mod3Mask is rewritten to ControlMask.
  // On Chrome OS, CapsLock works as a Mod3 modifier.
  EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyA::Typed(), ui::EF_MOD3_DOWN));

  // Press Control+CapsLock+a. Confirm that Mod3Mask is rewritten to
  // ControlMask
  EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyA::Typed(), ui::EF_CONTROL_DOWN | ui::EF_MOD3_DOWN));

  // Press Alt+CapsLock+a. Confirm that Mod3Mask is rewritten to
  // ControlMask.
  EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN),
            RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_MOD3_DOWN));
}

TEST_P(EventRewriterTest, TestRewriteCapsLockMod3InUse) {
  // Remap CapsLock to Control.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapCapsLockKeyTo,
                      ui::mojom::ModifierKey::kCapsLock,
                      ui::mojom::ModifierKey::kControl);

  SetUpKeyboard(kExternalGenericKeyboard);
  input_method_manager_mock_->set_mod3_used(true);

  // Press CapsLock+a. Confirm that Mod3Mask is NOT rewritten to ControlMask
  // when Mod3Mask is already in use by the current XKB layout.
  EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed()));

  input_method_manager_mock_->set_mod3_used(false);
}

TEST_P(EventRewriterTest, TestRewriteToRightAlt) {
  // Remap RightAlt to Control
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kRightAlt);

  IntegerPrefMember search;
  InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo,
                      ui::mojom::ModifierKey::kMeta,
                      ui::mojom::ModifierKey::kRightAlt);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    EXPECT_EQ(KeyRightAlt::Typed(ui::EF_NONE, {kPropertyRightAlt}),
              RunRewriter(KeyLControl::Typed()));
    EXPECT_EQ(KeyRightAlt::Typed(ui::EF_NONE, {kPropertyRightAlt}),
              RunRewriter(KeyLMeta::Typed()));
  }
}

TEST_P(EventRewriterTest, FnAndRightAltKeyPressedMetrics) {
  if (!features::IsModifierSplitEnabled()) {
    GTEST_SKIP() << "Test is only valid with the modifier split flag enabled";
  }
  base::HistogramTester histogram_tester;
  scoped_feature_list_.InitAndEnableFeature(
      features::kInputDeviceSettingsSplit);
  SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard);
  SendKeyEvent(KeyFunction::Pressed());
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal",
      ui::ModifierKeyUsageMetric::kFunction, 1);
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal",
      ui::ModifierKeyUsageMetric::kFunction, 1);

  SendKeyEvent(KeyRightAlt::Pressed());
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal",
      ui::ModifierKeyUsageMetric::kRightAlt, 1);
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal",
      ui::ModifierKeyUsageMetric::kRightAlt, 1);

  // Remap RightAlt to Assistant
  InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kRightAlt,
                      ui::mojom::ModifierKey::kAssistant);

  RunRewriter(KeyRightAlt::Typed());
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal",
      ui::ModifierKeyUsageMetric::kRightAlt, 2);
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal",
      ui::ModifierKeyUsageMetric::kRightAlt, 1);
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal",
      ui::ModifierKeyUsageMetric::kAssistant, 1);
  scoped_feature_list_.Reset();
}

TEST_P(EventRewriterTest, TestRewriteToFunction) {
  if (!features::IsModifierSplitEnabled()) {
    GTEST_SKIP() << "Test is only valid with the modifier split flag enabled";
  }

  SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard);

  // Remap RightAlt to Control
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kFunction);

  IntegerPrefMember search;
  InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo,
                      ui::mojom::ModifierKey::kMeta,
                      ui::mojom::ModifierKey::kFunction);

  // Keys + rewritten modifiers produce rewritten six-pack keys.
  EXPECT_EQ(KeyPageUp::Typed(),
            RunRewriter(KeyArrowUp::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyPageDown::Typed(),
            RunRewriter(KeyArrowDown::Typed(), ui::EF_COMMAND_DOWN));

  // After command + control are released, events are not affected.
  EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed()));
}

TEST_P(EventRewriterTest, TestRewriteFromFunction) {
  // Function is only available when InputDeviceSettingsSplit is enabled.
  scoped_feature_list_.InitAndEnableFeature(
      features::kInputDeviceSettingsSplit);

  // Remap Function to Control
  InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kFunction,
                      ui::mojom::ModifierKey::kControl);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyFunction::Typed()));

    // A + rewritten modifiers produce events with function flag down.
    EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyA::Typed(), ui::EF_FUNCTION_DOWN));
    // After command + control are released, events are not affected.
    EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed()));
  }

  // Remap Function to CapsLock
  InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kFunction,
                      ui::mojom::ModifierKey::kCapsLock);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Toggle CapsLock on
    EXPECT_EQ(KeyCapsLock::Typed(ui::EF_CAPS_LOCK_ON),
              RunRewriter(KeyFunction::Typed(ui::EF_CAPS_LOCK_ON)));

    // Toggle CapsLock off
    EXPECT_EQ(KeyCapsLock::Typed(), RunRewriter(KeyFunction::Typed()));
  }

  // Remap Function to Void
  InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kFunction,
                      ui::mojom::ModifierKey::kVoid);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    EXPECT_EQ(KeyUnknown::Typed(), RunRewriter(KeyFunction::Typed()));
  }

  scoped_feature_list_.Reset();
}

TEST_P(EventRewriterTest, TestRewriteFromRightAlt) {
  if (!features::IsModifierSplitEnabled()) {
    GTEST_SKIP() << "Test is only valid with the modifier split flag enabled";
  }

  // RightAlt is only available when InputDeviceSettingsSplit is enabled.
  scoped_feature_list_.InitAndEnableFeature(
      features::kInputDeviceSettingsSplit);
  SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard);

  // Test that identity is working as expected.
  EXPECT_EQ(KeyRightAlt::Typed(ui::EF_NONE, {kPropertyRightAlt}),
            RunRewriter(KeyLaunchAssistant::Typed()));

  // Remap RightAlt to Control
  InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kRightAlt,
                      ui::mojom::ModifierKey::kControl);

  EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyRightAlt::Typed()));

  // Test RightAlt remapped to Control properly applies the flag to other
  // events.
  EXPECT_EQ((std::vector<TestKeyEvent>{KeyLControl::Pressed()}),
            (RunRewriter(std::vector<TestKeyEvent>{KeyRightAlt::Pressed()})));
  EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN), RunRewriter(KeyA::Typed()));
  EXPECT_EQ((std::vector<TestKeyEvent>{KeyLControl::Released()}),
            (RunRewriter(std::vector<TestKeyEvent>{KeyRightAlt::Released()})));

  // Remap RightAlt to CapsLock
  InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kRightAlt,
                      ui::mojom::ModifierKey::kCapsLock);
  // Toggle CapsLock on/off
  EXPECT_EQ(KeyCapsLock::Typed(ui::EF_CAPS_LOCK_ON),
            RunRewriter(KeyRightAlt::Typed(ui::EF_CAPS_LOCK_ON)));
  EXPECT_EQ(KeyCapsLock::Typed(), RunRewriter(KeyRightAlt::Typed()));

  // Remap RightAlt to Void
  InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kRightAlt,
                      ui::mojom::ModifierKey::kVoid);
  EXPECT_EQ(KeyUnknown::Typed(), RunRewriter(KeyRightAlt::Typed()));

  scoped_feature_list_.Reset();
}

// TODO(crbug.com/1179893): Remove once the feature is enabled permanently.
TEST_P(EventRewriterTest, TestRewriteExtendedKeysAltVariantsOld) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {}, {::features::kImprovedKeyboardShortcuts,
           features::kAltClickAndSixPackCustomization});

  for (const auto keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Alt+Backspace -> Delete
    EXPECT_EQ(KeyDelete::Typed(),
              RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN));

    // Control+Alt+Backspace -> Control+Delete
    EXPECT_EQ(KeyDelete::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyBackspace::Typed(),
                          ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN));

    // Search+Alt+Backspace -> Alt+Backspace
    EXPECT_EQ(KeyBackspace::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyBackspace::Typed(),
                          ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN));

    // Search+Control+Alt+Backspace -> Control+Alt+Backspace
    EXPECT_EQ(KeyBackspace::Typed(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN),
              RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN |
                                                     ui::EF_COMMAND_DOWN |
                                                     ui::EF_CONTROL_DOWN));

    // Alt+Up -> Prior
    EXPECT_EQ(KeyPageUp::Typed(),
              RunRewriter(KeyArrowUp::Typed(), ui::EF_ALT_DOWN));

    // Alt+Down -> Next
    EXPECT_EQ(KeyPageDown::Typed(),
              RunRewriter(KeyArrowDown::Typed(), ui::EF_ALT_DOWN));

    // Ctrl+Alt+Up -> Home
    EXPECT_EQ(KeyHome::Typed(),
              RunRewriter(KeyArrowUp::Typed(),
                          ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN));

    // Ctrl+Alt+Down -> End
    EXPECT_EQ(KeyEnd::Typed(),
              RunRewriter(KeyArrowDown::Typed(),
                          ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN));

    // NOTE: The following are workarounds to avoid rewriting the
    // Alt variants by additionally pressing Search.
    // Search+Ctrl+Alt+Up -> Ctrl+Alt+Up
    EXPECT_EQ(
        KeyArrowUp::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN),
        RunRewriter(KeyArrowUp::Typed(), ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN |
                                             ui::EF_COMMAND_DOWN));

    // Search+Ctrl+Alt+Down -> Ctrl+Alt+Down
    EXPECT_EQ(KeyArrowDown::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN),
              RunRewriter(KeyArrowDown::Typed(), ui::EF_ALT_DOWN |
                                                     ui::EF_CONTROL_DOWN |
                                                     ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteExtendedKeysAltVariants) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      features::kAltClickAndSixPackCustomization);
  // All the previously supported Alt based rewrites no longer have any
  // effect. The Search workarounds no longer take effect and the Search+Key
  // portion is rewritten as expected.
  for (const auto keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Alt+Backspace -> No Rewrite
    EXPECT_EQ(KeyBackspace::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN));
    EXPECT_EQ(1u, message_center_.NotificationCount());
    ClearNotifications();

    // Control+Alt+Backspace -> No Rewrite
    EXPECT_EQ(KeyBackspace::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN),
              RunRewriter(KeyBackspace::Typed(),
                          ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN));
    EXPECT_EQ(1u, message_center_.NotificationCount());
    ClearNotifications();

    // Search+Alt+Backspace -> Alt+Delete
    EXPECT_EQ(KeyDelete::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyBackspace::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN));

    // Search+Control+Alt+Backspace -> Control+Alt+Delete
    EXPECT_EQ(KeyDelete::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN),
              RunRewriter(KeyBackspace::Typed(), ui::EF_COMMAND_DOWN |
                                                     ui::EF_CONTROL_DOWN |
                                                     ui::EF_ALT_DOWN));

    // Alt+Up -> No Rewrite
    EXPECT_EQ(KeyArrowUp::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyArrowUp::Typed(), ui::EF_ALT_DOWN));
    EXPECT_EQ(1u, message_center_.NotificationCount());
    ClearNotifications();

    // Alt+Down -> No Rewrite
    EXPECT_EQ(KeyArrowDown::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyArrowDown::Typed(), ui::EF_ALT_DOWN));
    EXPECT_EQ(1u, message_center_.NotificationCount());
    ClearNotifications();

    // Ctrl+Alt+Up -> No Rewrite
    EXPECT_EQ(KeyArrowUp::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN),
              RunRewriter(KeyArrowUp::Typed(),
                          ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN));
    EXPECT_EQ(1u, message_center_.NotificationCount());
    ClearNotifications();

    // Ctrl+Alt+Down -> No Rewrite
    EXPECT_EQ(KeyArrowDown::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN),
              RunRewriter(KeyArrowDown::Typed(),
                          ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN));
    EXPECT_EQ(1u, message_center_.NotificationCount());
    ClearNotifications();

    // NOTE: The following were workarounds to avoid rewriting the
    // Alt variants by additionally pressing Search.

    // Search+Ctrl+Alt+Up -> Ctrl+Alt+PageUp(aka Prior)
    EXPECT_EQ(KeyPageUp::Typed(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN),
              RunRewriter(KeyArrowUp::Typed(), ui::EF_COMMAND_DOWN |
                                                   ui::EF_CONTROL_DOWN |
                                                   ui::EF_ALT_DOWN));
    // Search+Ctrl+Alt+Down -> Ctrl+Alt+PageDown(aka Next)
    EXPECT_EQ(KeyPageDown::Typed(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN),
              RunRewriter(KeyArrowDown::Typed(), ui::EF_COMMAND_DOWN |
                                                     ui::EF_CONTROL_DOWN |
                                                     ui::EF_ALT_DOWN));
  }
}

// TODO(crbug.com/1179893): Remove once the feature is enabled permanently.
TEST_P(EventRewriterTest, TestRewriteExtendedKeyInsertOld) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {}, {::features::kImprovedKeyboardShortcuts,
           features::kAltClickAndSixPackCustomization});
  for (const auto keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Period -> Period
    EXPECT_EQ(KeyPeriod::Typed(), RunRewriter(KeyPeriod::Typed()));

    // Search+Period -> Insert
    EXPECT_EQ(KeyInsert::Typed(),
              RunRewriter(KeyPeriod::Typed(), ui::EF_COMMAND_DOWN));

    // Control+Search+Period -> Control+Insert
    EXPECT_EQ(KeyInsert::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyPeriod::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteExtendedKeyInsertDeprecatedNotification) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {::features::kImprovedKeyboardShortcuts},
      {features::kAltClickAndSixPackCustomization});

  for (const auto keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Period -> Period
    EXPECT_EQ(KeyPeriod::Typed(), RunRewriter(KeyPeriod::Typed()));

    // Search+Period -> No rewrite (and shows notification)
    EXPECT_EQ(KeyPeriod::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyPeriod::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(1u, message_center_.NotificationCount());
    ClearNotifications();

    // Control+Search+Period -> No rewrite (and shows notification)
    EXPECT_EQ(KeyPeriod::Typed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN),
              RunRewriter(KeyPeriod::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN));
    EXPECT_EQ(1u, message_center_.NotificationCount());
    ClearNotifications();
  }
}

// TODO(crbug.com/1179893): Rename once the feature is enabled permanently.
TEST_P(EventRewriterTest, TestRewriteExtendedKeyInsertNew) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {::features::kImprovedKeyboardShortcuts},
      {features::kAltClickAndSixPackCustomization});

  for (const auto keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Search+Shift+Backspace -> Insert
    EXPECT_EQ(KeyInsert::Typed(),
              RunRewriter(KeyBackspace::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN));

    // Control+Search+Shift+Backspace -> Control+Insert
    EXPECT_EQ(KeyInsert::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyBackspace::Typed(), ui::EF_COMMAND_DOWN |
                                                     ui::EF_CONTROL_DOWN |
                                                     ui::EF_SHIFT_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteExtendedKeysSearchVariants) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      features::kAltClickAndSixPackCustomization);
  Preferences::RegisterProfilePrefs(prefs()->registry());

  for (const auto keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Search+Backspace -> Delete
    EXPECT_EQ(KeyDelete::Typed(),
              RunRewriter(KeyBackspace::Typed(), ui::EF_COMMAND_DOWN));

    // Search+Up -> Prior
    EXPECT_EQ(KeyPageUp::Typed(),
              RunRewriter(KeyArrowUp::Typed(), ui::EF_COMMAND_DOWN));

    // Search+Down -> Next
    EXPECT_EQ(KeyPageDown::Typed(),
              RunRewriter(KeyArrowDown::Typed(), ui::EF_COMMAND_DOWN));

    // Search+Left -> Home
    EXPECT_EQ(KeyHome::Typed(),
              RunRewriter(KeyArrowLeft::Typed(), ui::EF_COMMAND_DOWN));

    // Control+Search+Left -> Control+Home
    EXPECT_EQ(KeyHome::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyArrowLeft::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN));

    // Search+Right -> End
    EXPECT_EQ(KeyEnd::Typed(),
              RunRewriter(KeyArrowRight::Typed(), ui::EF_COMMAND_DOWN));

    // Control+Search+Right -> Control+End
    EXPECT_EQ(KeyEnd::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyArrowRight::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN));
  }
}

TEST_P(EventRewriterTest, TestNumberRowIsNotRewritten) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      features::kAltClickAndSixPackCustomization);
  for (const auto& keyboard : kNonAppleNonCustomLayoutKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // The number row should not be rewritten without Search key.
    EXPECT_EQ(KeyDigit1::Typed(), RunRewriter(KeyDigit1::Typed()));
    EXPECT_EQ(KeyDigit2::Typed(), RunRewriter(KeyDigit2::Typed()));
    EXPECT_EQ(KeyDigit3::Typed(), RunRewriter(KeyDigit3::Typed()));
    EXPECT_EQ(KeyDigit4::Typed(), RunRewriter(KeyDigit4::Typed()));
    EXPECT_EQ(KeyDigit5::Typed(), RunRewriter(KeyDigit5::Typed()));
    EXPECT_EQ(KeyDigit6::Typed(), RunRewriter(KeyDigit6::Typed()));
    EXPECT_EQ(KeyDigit7::Typed(), RunRewriter(KeyDigit7::Typed()));
    EXPECT_EQ(KeyDigit8::Typed(), RunRewriter(KeyDigit8::Typed()));
    EXPECT_EQ(KeyDigit9::Typed(), RunRewriter(KeyDigit9::Typed()));
    EXPECT_EQ(KeyDigit0::Typed(), RunRewriter(KeyDigit0::Typed()));
    EXPECT_EQ(KeyMinus::Typed(), RunRewriter(KeyMinus::Typed()));
    EXPECT_EQ(KeyEqual::Typed(), RunRewriter(KeyEqual::Typed()));
  }
}

// TODO(crbug.com/1179893): Remove once the feature is enabled permanently.
TEST_P(EventRewriterTest, TestRewriteSearchNumberToFunctionKeyOld) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      ::features::kImprovedKeyboardShortcuts);

  for (const auto& keyboard : kNonAppleNonCustomLayoutKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // The number row should be rewritten as the F<number> row with Search
    // key.
    EXPECT_EQ(KeyF1::Typed(),
              RunRewriter(KeyDigit1::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF2::Typed(),
              RunRewriter(KeyDigit2::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF3::Typed(),
              RunRewriter(KeyDigit3::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF4::Typed(),
              RunRewriter(KeyDigit4::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF5::Typed(),
              RunRewriter(KeyDigit5::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF6::Typed(),
              RunRewriter(KeyDigit6::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF7::Typed(),
              RunRewriter(KeyDigit7::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF8::Typed(),
              RunRewriter(KeyDigit8::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF9::Typed(),
              RunRewriter(KeyDigit9::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF10::Typed(),
              RunRewriter(KeyDigit0::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF11::Typed(),
              RunRewriter(KeyMinus::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF12::Typed(),
              RunRewriter(KeyEqual::Typed(), ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteSearchNumberToFunctionKeyNoAction) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  for (const auto& keyboard : kNonAppleNonCustomLayoutKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Search+Number should now have no effect but a notification will
    // be shown the first time F1 to F10 is pressed.
    EXPECT_EQ(KeyDigit1::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyDigit1::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyDigit2::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyDigit2::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyDigit3::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyDigit3::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyDigit4::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyDigit4::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyDigit5::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyDigit5::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyDigit6::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyDigit6::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyDigit7::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyDigit7::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyDigit8::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyDigit8::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyDigit9::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyDigit9::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyDigit0::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyDigit0::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyMinus::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyMinus::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyEqual::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyEqual::Typed(), ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterTest, TestFunctionKeysNotRewrittenBySearch) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  for (const auto& keyboard : kNonAppleNonCustomLayoutKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // The function keys should not be rewritten with Search key pressed.
    EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF2::Typed(), RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF3::Typed(), RunRewriter(KeyF3::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF4::Typed(), RunRewriter(KeyF4::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF5::Typed(), RunRewriter(KeyF5::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF6::Typed(), RunRewriter(KeyF6::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF7::Typed(), RunRewriter(KeyF7::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF8::Typed(), RunRewriter(KeyF8::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF9::Typed(), RunRewriter(KeyF9::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF10::Typed(),
              RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF11::Typed(),
              RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF12::Typed(),
              RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteFunctionKeysNonCustomLayouts) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  // Old CrOS keyboards that do not have custom layouts send F-Keys by default
  // and are translated by default to Actions based on hardcoded mappings.
  // New CrOS keyboards are not tested here because they do not remap F-Keys.
  for (const auto& keyboard : kNonAppleNonCustomLayoutKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // F1 -> Back
    EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyF1::Typed()));
    EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF1::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN));

    // F2 -> Forward
    EXPECT_EQ(KeyBrowserForward::Typed(), RunRewriter(KeyF2::Typed()));
    EXPECT_EQ(KeyBrowserForward::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF2::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyBrowserForward::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF2::Typed(), ui::EF_ALT_DOWN));

    // F3 -> Refresh
    EXPECT_EQ(KeyBrowserRefresh::Typed(), RunRewriter(KeyF3::Typed()));
    EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF3::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF3::Typed(), ui::EF_ALT_DOWN));

    // F4 -> Zoom (aka Fullscreen)
    EXPECT_EQ(KeyZoomToggle::Typed(), RunRewriter(KeyF4::Typed()));
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF4::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF4::Typed(), ui::EF_ALT_DOWN));

    // F5 -> Launch App 1
    EXPECT_EQ(KeySelectTask::Typed(), RunRewriter(KeyF5::Typed()));
    EXPECT_EQ(KeySelectTask::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF5::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeySelectTask::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF5::Typed(), ui::EF_ALT_DOWN));

    // F6 -> Brightness down
    EXPECT_EQ(KeyBrightnessDown::Typed(), RunRewriter(KeyF6::Typed()));
    EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF6::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF6::Typed(), ui::EF_ALT_DOWN));

    // F7 -> Brightness up
    EXPECT_EQ(KeyBrightnessUp::Typed(), RunRewriter(KeyF7::Typed()));
    EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF7::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF7::Typed(), ui::EF_ALT_DOWN));

    // F8 -> Volume Mute
    EXPECT_EQ(KeyVolumeMute::Typed(), RunRewriter(KeyF8::Typed()));
    EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF8::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF8::Typed(), ui::EF_ALT_DOWN));

    // F9 -> Volume Down
    EXPECT_EQ(KeyVolumeDown::Typed(), RunRewriter(KeyF9::Typed()));
    EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF9::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF9::Typed(), ui::EF_ALT_DOWN));

    // F10 -> Volume Up
    EXPECT_EQ(KeyVolumeUp::Typed(), RunRewriter(KeyF10::Typed()));
    EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF10::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF10::Typed(), ui::EF_ALT_DOWN));

    // F11 -> F11
    EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF11::Typed()));
    EXPECT_EQ(KeyF11::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF11::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF11::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF11::Typed(), ui::EF_ALT_DOWN));

    // F12 -> F12
    EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF12::Typed()));
    EXPECT_EQ(KeyF12::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF12::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF12::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF12::Typed(), ui::EF_ALT_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteFunctionKeysCustomLayoutsFKeyUnchanged) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  // On devices with custom layouts, the F-Keys are never remapped.
  for (const auto& keyboard : kChromeCustomKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    for (const auto typed :
         {KeyF1::Typed, KeyF2::Typed, KeyF3::Typed, KeyF4::Typed, KeyF5::Typed,
          KeyF6::Typed, KeyF7::Typed, KeyF8::Typed, KeyF9::Typed, KeyF10::Typed,
          KeyF11::Typed, KeyF12::Typed, KeyF13::Typed, KeyF14::Typed,
          KeyF15::Typed}) {
      EXPECT_EQ(typed(ui::EF_NONE, {}), RunRewriter(typed(ui::EF_NONE, {})));
      EXPECT_EQ(typed(ui::EF_CONTROL_DOWN, {}),
                RunRewriter(typed(ui::EF_NONE, {}), ui::EF_CONTROL_DOWN));
      EXPECT_EQ(typed(ui::EF_ALT_DOWN, {}),
                RunRewriter(typed(ui::EF_NONE, {}), ui::EF_ALT_DOWN));
      EXPECT_EQ(typed(ui::EF_COMMAND_DOWN, {}),
                RunRewriter(typed(ui::EF_NONE, {}), ui::EF_COMMAND_DOWN));
    }
  }
}

TEST_P(EventRewriterTest, TestRewriteFunctionKeysCustomLayoutsActionUnchanged) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  // An action key on these devices is one where the scan code matches an entry
  // in the layout map. It doesn't matter what the action is, as long the
  // search key isn't pressed it will pass through unchanged.
  SetUpKeyboard({.name = "Internal Custom LayoutKeyboard",
                 .layout = "a1 a2 a3",
                 .type = ui::INPUT_DEVICE_INTERNAL,
                 .has_custom_top_row = true});
  auto browser_refresh = KeyBrowserRefresh::Pressed();
  browser_refresh.scan_code = 0xa1;
  EXPECT_EQ(std::vector({browser_refresh}), SendKeyEvent(browser_refresh));

  auto volume_up = KeyVolumeUp::Pressed();
  volume_up.scan_code = 0xa2;
  EXPECT_EQ(std::vector({volume_up}), SendKeyEvent(volume_up));

  auto volume_down = KeyVolumeDown::Pressed();
  volume_down.scan_code = 0xa3;
  EXPECT_EQ(std::vector({volume_down}), SendKeyEvent(volume_down));
}

TEST_P(EventRewriterTest,
       TestRewriteFunctionKeysCustomLayoutsActionSuppressedUnchanged) {
  // For EF_COMMAND_DOWN modifier.
  SendKeyEvent(KeyLMeta::Pressed());

  Preferences::RegisterProfilePrefs(prefs()->registry());
  delegate_->SuppressMetaTopRowKeyComboRewrites(true);
  keyboard_settings->suppress_meta_fkey_rewrites = true;

  // An action key on these devices is one where the scan code matches an entry
  // in the layout map. With Meta + Top Row Key rewrites being suppressed, the
  // input should be equivalent to the output for all tested keys.
  SetUpKeyboard({.name = "Internal Custom Layout Keyboard",
                 .layout = "a1 a2 a3",
                 .type = ui::INPUT_DEVICE_INTERNAL,
                 .has_custom_top_row = true});

  auto browser_refresh = KeyBrowserRefresh::Pressed(ui::EF_COMMAND_DOWN);
  browser_refresh.scan_code = 0xa1;
  EXPECT_EQ(std::vector({browser_refresh}), SendKeyEvent(browser_refresh));

  auto volume_up = KeyVolumeUp::Pressed(ui::EF_COMMAND_DOWN);
  volume_up.scan_code = 0xa2;
  EXPECT_EQ(std::vector({volume_up}), SendKeyEvent(volume_up));

  auto volume_down = KeyVolumeDown::Pressed(ui::EF_COMMAND_DOWN);
  volume_down.scan_code = 0xa3;
  EXPECT_EQ(std::vector({volume_down}), SendKeyEvent(volume_down));
}

TEST_P(EventRewriterTest,
       TestRewriteFunctionKeysCustomLayoutsActionSuppressedWithTopRowAreFKeys) {
  // For EF_COMMAND_DOWN.
  SendKeyEvent(KeyLMeta::Pressed());

  Preferences::RegisterProfilePrefs(prefs()->registry());
  delegate_->SuppressMetaTopRowKeyComboRewrites(true);
  keyboard_settings->suppress_meta_fkey_rewrites = true;

  BooleanPrefMember send_function_keys_pref;
  send_function_keys_pref.Init(prefs::kSendFunctionKeys, prefs());
  send_function_keys_pref.SetValue(true);
  keyboard_settings->top_row_are_fkeys = true;

  // An action key on these devices is one where the scan code matches an entry
  // in the layout map. With Meta + Top Row Key rewrites being suppressed, the
  // input should be remapped to F-Keys and the Search modifier should not be
  // removed.
  SetUpKeyboard({.name = "Internal Custom Layout Keyboard",
                 .layout = "a1 a2 a3",
                 .type = ui::INPUT_DEVICE_INTERNAL,
                 .has_custom_top_row = true});

  auto browser_refresh = KeyBrowserRefresh::Pressed(ui::EF_COMMAND_DOWN);
  browser_refresh.scan_code = 0xa1;
  auto f1 = KeyF1::Pressed(ui::EF_COMMAND_DOWN);
  f1.scan_code = 0xa1;
  EXPECT_EQ(std::vector({f1}), SendKeyEvent(browser_refresh));

  auto volume_up = KeyVolumeUp::Pressed(ui::EF_COMMAND_DOWN);
  volume_up.scan_code = 0xa2;
  auto f2 = KeyF2::Pressed(ui::EF_COMMAND_DOWN);
  f2.scan_code = 0xa2;
  EXPECT_EQ(std::vector({f2}), SendKeyEvent(volume_up));

  auto volume_down = KeyVolumeDown::Pressed(ui::EF_COMMAND_DOWN);
  volume_down.scan_code = 0xa3;
  auto f3 = KeyF3::Pressed(ui::EF_COMMAND_DOWN);
  f3.scan_code = 0xa3;
  EXPECT_EQ(std::vector({f3}), SendKeyEvent(volume_down));
}

TEST_P(EventRewriterTest, TestRewriteFunctionKeysCustomLayouts) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  // On devices with custom layouts, scan codes that match the layout
  // map get mapped to F-Keys based only on the scan code. The search
  // key also gets treated as unpressed in the remapped event.
  SetUpKeyboard({.name = "Internal Custom Layout Keyboard",
                 .layout = "a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af",
                 .type = ui::INPUT_DEVICE_INTERNAL,
                 .has_custom_top_row = true});

  struct TestCase {
    std::vector<TestKeyEvent> (*pressed)(
        ui::EventFlags,
        std::vector<std::pair<std::string, std::vector<uint8_t>>>);
    uint32_t scan_code;
  };
  // Action -> F1..F15
  for (const auto& [typed, scan_code] :
       std::initializer_list<TestCase>{{KeyF1::Typed, 0xa1},
                                       {KeyF2::Typed, 0xa2},
                                       {KeyF3::Typed, 0xa3},
                                       {KeyF4::Typed, 0xa4},
                                       {KeyF5::Typed, 0xa5},
                                       {KeyF6::Typed, 0xa6},
                                       {KeyF7::Typed, 0xa7},
                                       {KeyF8::Typed, 0xa8},
                                       {KeyF9::Typed, 0xa9},
                                       {KeyF10::Typed, 0xaa},
                                       {KeyF11::Typed, 0xab},
                                       {KeyF12::Typed, 0xac},
                                       {KeyF13::Typed, 0xad},
                                       {KeyF14::Typed, 0xae},
                                       {KeyF15::Typed, 0xaf}}) {
    auto unknowns = KeyUnknown::Typed();
    for (auto& unknown : unknowns) {
      unknown.scan_code = scan_code;
    }
    auto expected_events = typed(ui::EF_NONE, {});
    for (auto& event : expected_events) {
      event.scan_code = scan_code;
    }
    EXPECT_EQ(expected_events, RunRewriter(unknowns, ui::EF_COMMAND_DOWN));

    if (features::IsModifierSplitEnabled()) {
      // With fn down, nothing should change since this keyboard uses Search
      // based rewriting.
      EXPECT_EQ(unknowns, RunRewriter(unknowns, ui::EF_FUNCTION_DOWN));
    }
  }
}

TEST_P(EventRewriterTest, TestRewriteFunctionKeysCustomLayoutsWithFunction) {
  if (!features::IsModifierSplitEnabled()) {
    GTEST_SKIP() << "Test is only valid with the modifier split flag enabled";
  }

  // On devices with custom layouts, scan codes that match the layout
  // map get mapped to F-Keys based only on the scan code. The search
  // key also gets treated as unpressed in the remapped event.
  SetUpKeyboard({.name = "Internal Custom Layout Keyboard",
                 .layout = "a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af",
                 .type = ui::INPUT_DEVICE_INTERNAL,
                 .has_custom_top_row = true,
                 .has_assistant_key = true,
                 .has_function_key = true});

  struct TestCase {
    std::vector<TestKeyEvent> (*pressed)(
        ui::EventFlags,
        std::vector<std::pair<std::string, std::vector<uint8_t>>>);
    uint32_t scan_code;
  };
  // Action -> F1..F15
  for (const auto& [typed, scan_code] :
       std::initializer_list<TestCase>{{KeyF1::Typed, 0xa1},
                                       {KeyF2::Typed, 0xa2},
                                       {KeyF3::Typed, 0xa3},
                                       {KeyF4::Typed, 0xa4},
                                       {KeyF5::Typed, 0xa5},
                                       {KeyF6::Typed, 0xa6},
                                       {KeyF7::Typed, 0xa7},
                                       {KeyF8::Typed, 0xa8},
                                       {KeyF9::Typed, 0xa9},
                                       {KeyF10::Typed, 0xaa},
                                       {KeyF11::Typed, 0xab},
                                       {KeyF12::Typed, 0xac},
                                       {KeyF13::Typed, 0xad},
                                       {KeyF14::Typed, 0xae},
                                       {KeyF15::Typed, 0xaf}}) {
    auto unknowns = KeyUnknown::Typed();
    for (auto& unknown : unknowns) {
      unknown.scan_code = scan_code;
    }
    auto unknowns_with_search = KeyUnknown::Typed(ui::EF_COMMAND_DOWN);
    for (auto& unknown : unknowns_with_search) {
      unknown.scan_code = scan_code;
    }
    auto expected_events = typed(ui::EF_NONE, {});
    for (auto& event : expected_events) {
      event.scan_code = scan_code;
    }
    // Do not rewrite when search key is down since the keyboard should use the
    // fn key.
    EXPECT_EQ(unknowns_with_search, RunRewriter(unknowns, ui::EF_COMMAND_DOWN));

    // Rewrite correctly with the fn key down.
    EXPECT_EQ(expected_events, RunRewriter(unknowns, ui::EF_FUNCTION_DOWN));
  }
  scoped_feature_list_.Reset();
}

TEST_P(EventRewriterTest, TestRewriteFunctionKeysLayout2) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  SetUpKeyboard({.name = "Internal Keyboard",
                 .layout = kKbdTopRowLayout2Tag,
                 .type = ui::INPUT_DEVICE_INTERNAL,
                 .has_custom_top_row = false});

  // F1 -> Back
  EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyF1::Typed()));
  EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF1::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN));

  // F2 -> Refresh
  EXPECT_EQ(KeyBrowserRefresh::Typed(), RunRewriter(KeyF2::Typed()));
  EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF2::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF2::Typed(), ui::EF_ALT_DOWN));

  // F3 -> Zoom (aka Fullscreen)
  EXPECT_EQ(KeyZoomToggle::Typed(), RunRewriter(KeyF3::Typed()));
  EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF3::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF3::Typed(), ui::EF_ALT_DOWN));

  // F4 -> Launch App 1
  EXPECT_EQ(KeySelectTask::Typed(), RunRewriter(KeyF4::Typed()));
  EXPECT_EQ(KeySelectTask::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF4::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeySelectTask::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF4::Typed(), ui::EF_ALT_DOWN));

  // F5 -> Brightness down
  EXPECT_EQ(KeyBrightnessDown::Typed(), RunRewriter(KeyF5::Typed()));
  EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF5::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF5::Typed(), ui::EF_ALT_DOWN));

  // F6 -> Brightness up
  EXPECT_EQ(KeyBrightnessUp::Typed(), RunRewriter(KeyF6::Typed()));
  EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF6::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF6::Typed(), ui::EF_ALT_DOWN));

  // F7 -> Media Play/Pause
  EXPECT_EQ(KeyMediaPlayPause::Typed(), RunRewriter(KeyF7::Typed()));
  EXPECT_EQ(KeyMediaPlayPause::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF7::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyMediaPlayPause::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF7::Typed(), ui::EF_ALT_DOWN));

  // F8 -> Volume Mute
  EXPECT_EQ(KeyVolumeMute::Typed(), RunRewriter(KeyF8::Typed()));
  EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF8::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF8::Typed(), ui::EF_ALT_DOWN));

  // F9 -> Volume Down
  EXPECT_EQ(KeyVolumeDown::Typed(), RunRewriter(KeyF9::Typed()));
  EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF9::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF9::Typed(), ui::EF_ALT_DOWN));

  // F10 -> Volume Up
  EXPECT_EQ(KeyVolumeUp::Typed(), RunRewriter(KeyF10::Typed()));
  EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF10::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF10::Typed(), ui::EF_ALT_DOWN));

  // F11 -> F11
  EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF11::Typed()));
  EXPECT_EQ(KeyF11::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF11::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyF11::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF11::Typed(), ui::EF_ALT_DOWN));

  // F12 -> F12
  EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF12::Typed()));
  EXPECT_EQ(KeyF12::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyF12::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyF12::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF12::Typed(), ui::EF_ALT_DOWN));
}

TEST_P(EventRewriterTest,
       TestFunctionKeysLayout2SuppressMetaTopRowKeyRewrites) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  delegate_->SuppressMetaTopRowKeyComboRewrites(true);
  keyboard_settings->suppress_meta_fkey_rewrites = true;

  // With Meta + Top Row Key rewrites suppressed, F-Keys should be translated to
  // the equivalent action key and not lose the Search modifier.
  SetUpKeyboard({.name = "Internal Keyboard",
                 .layout = kKbdTopRowLayout2Tag,
                 .type = ui::INPUT_DEVICE_INTERNAL,
                 .has_custom_top_row = false});

  // F1 -> Back
  EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN));

  // F2 -> Refresh
  EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN));

  // F3 -> Zoom (aka Fullscreen)
  EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF3::Typed(), ui::EF_COMMAND_DOWN));

  // F4 -> Launch App 1
  EXPECT_EQ(KeySelectTask::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF4::Typed(), ui::EF_COMMAND_DOWN));

  // F5 -> Brightness down
  EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF5::Typed(), ui::EF_COMMAND_DOWN));

  // F6 -> Brightness up
  EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF6::Typed(), ui::EF_COMMAND_DOWN));

  // F7 -> Media Play/Pause
  EXPECT_EQ(KeyMediaPlayPause::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF7::Typed(), ui::EF_COMMAND_DOWN));

  // F8 -> Volume Mute
  EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF8::Typed(), ui::EF_COMMAND_DOWN));

  // F9 -> Volume Down
  EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF9::Typed(), ui::EF_COMMAND_DOWN));

  // F10 -> Volume Up
  EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN));

  // F11 -> F11
  EXPECT_EQ(KeyF11::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN));

  // F12 -> F12
  EXPECT_EQ(KeyF12::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN));
}

TEST_P(EventRewriterTest, RecordEventRemappedToRightClick) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember alt_remap_to_right_click;
  IntegerPrefMember search_remap_to_right_click;
  alt_remap_to_right_click.Init(prefs::kAltEventRemappedToRightClick, prefs());
  alt_remap_to_right_click.SetValue(0);
  search_remap_to_right_click.Init(prefs::kSearchEventRemappedToRightClick,
                                   prefs());
  search_remap_to_right_click.SetValue(0);
  delegate_->RecordEventRemappedToRightClick(/*alt_based_right_click=*/false);
  EXPECT_EQ(1, prefs()->GetInteger(prefs::kSearchEventRemappedToRightClick));
  delegate_->RecordEventRemappedToRightClick(/*alt_based_right_click=*/true);
  EXPECT_EQ(1, prefs()->GetInteger(prefs::kAltEventRemappedToRightClick));
}

TEST_P(
    EventRewriterTest,
    TestFunctionKeysLayout2SuppressMetaTopRowKeyRewritesWithTreatTopRowAsFKeys) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  delegate_->SuppressMetaTopRowKeyComboRewrites(true);
  keyboard_settings->suppress_meta_fkey_rewrites = true;

  // Enable preference treat-top-row-as-function-keys.
  // That causes action keys to be mapped back to Fn keys.
  BooleanPrefMember top_row_as_fn_key;
  top_row_as_fn_key.Init(prefs::kSendFunctionKeys, prefs());
  top_row_as_fn_key.SetValue(true);
  keyboard_settings->top_row_are_fkeys = true;

  // With Meta + Top Row Key rewrites suppressed and TopRowAsFKeys enabled,
  // F-Keys should not be translated and search modifier should be kept.
  SetUpKeyboard({.name = "Internal Keyboard",
                 .layout = kKbdTopRowLayout2Tag,
                 .type = ui::INPUT_DEVICE_INTERNAL,
                 .has_custom_top_row = false});

  EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF2::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF3::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF3::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF4::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF4::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF5::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF5::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF6::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF6::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF7::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF7::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF8::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF8::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF9::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF9::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF10::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF11::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF12::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN));
}

TEST_P(EventRewriterTest, TestRewriteFunctionKeysWilcoLayouts) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  for (const auto& keyboard : kWilcoKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // F1 -> F1, Search + F1 -> Back
    EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed()));
    EXPECT_EQ(KeyBrowserBack::Typed(),
              RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF1::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF1::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF1::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN));

    // F2 -> F2, Search + F2 -> Refresh
    EXPECT_EQ(KeyF2::Typed(), RunRewriter(KeyF2::Typed()));
    EXPECT_EQ(KeyBrowserRefresh::Typed(),
              RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF2::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF2::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF2::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF2::Typed(), ui::EF_ALT_DOWN));

    // F3 -> F3, Search + F3 -> Zoom (aka Fullscreen)
    EXPECT_EQ(KeyF3::Typed(), RunRewriter(KeyF3::Typed()));
    EXPECT_EQ(KeyZoomToggle::Typed(),
              RunRewriter(KeyF3::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF3::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF3::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF3::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF3::Typed(), ui::EF_ALT_DOWN));

    // F4 -> F4, Search + F4 -> Launch App 1
    EXPECT_EQ(KeyF4::Typed(), RunRewriter(KeyF4::Typed()));
    EXPECT_EQ(
        std::vector({TestKeyEvent{ui::EventType::kKeyPressed, ui::DomCode::F4,
                                  ui::DomKey::F4, ui::VKEY_MEDIA_LAUNCH_APP1,
                                  ui::EF_NONE},
                     TestKeyEvent{ui::EventType::kKeyReleased, ui::DomCode::F4,
                                  ui::DomKey::F4, ui::VKEY_MEDIA_LAUNCH_APP1,
                                  ui::EF_NONE}}),
        RunRewriter(KeyF4::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF4::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF4::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF4::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF4::Typed(), ui::EF_ALT_DOWN));

    // F5 -> F5, Search + F5 -> Brightness down
    EXPECT_EQ(KeyF5::Typed(), RunRewriter(KeyF5::Typed()));
    EXPECT_EQ(KeyBrightnessDown::Typed(),
              RunRewriter(KeyF5::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF5::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF5::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF5::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF5::Typed(), ui::EF_ALT_DOWN));

    // F6 -> F6, Search + F6 -> Brightness up
    EXPECT_EQ(KeyF6::Typed(), RunRewriter(KeyF6::Typed()));
    EXPECT_EQ(KeyBrightnessUp::Typed(),
              RunRewriter(KeyF6::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF6::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF6::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF6::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF6::Typed(), ui::EF_ALT_DOWN));

    // F7 -> F7, Search + F7 -> Volume mute
    EXPECT_EQ(KeyF7::Typed(), RunRewriter(KeyF7::Typed()));
    EXPECT_EQ(KeyVolumeMute::Typed(),
              RunRewriter(KeyF7::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF7::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF7::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF7::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF7::Typed(), ui::EF_ALT_DOWN));

    // F8 -> F8, Search + F8 -> Volume Down
    EXPECT_EQ(KeyF8::Typed(), RunRewriter(KeyF8::Typed()));
    EXPECT_EQ(KeyVolumeDown::Typed(),
              RunRewriter(KeyF8::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF8::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF8::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF8::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF8::Typed(), ui::EF_ALT_DOWN));

    // F9 -> F9, Search + F9 -> Volume Up
    EXPECT_EQ(KeyF9::Typed(), RunRewriter(KeyF9::Typed()));
    EXPECT_EQ(KeyVolumeUp::Typed(),
              RunRewriter(KeyF9::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF9::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF9::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF9::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF9::Typed(), ui::EF_ALT_DOWN));

    // F10 -> F10, Search + F10 -> F10
    EXPECT_EQ(KeyF10::Typed(), RunRewriter(KeyF10::Typed()));
    EXPECT_EQ(KeyF10::Typed(),
              RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF10::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF10::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF10::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF10::Typed(), ui::EF_ALT_DOWN));

    // F11 -> F11, Search + F11 -> F11
    EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF11::Typed()));
    EXPECT_EQ(KeyF11::Typed(),
              RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF11::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF11::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF11::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF11::Typed(), ui::EF_ALT_DOWN));

    // F12 -> F12
    // Search + F12 differs between Wilco devices so it is tested separately.
    EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF12::Typed()));
    EXPECT_EQ(KeyF12::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyF12::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyF12::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyF12::Typed(), ui::EF_ALT_DOWN));

    // The number row should not be rewritten without Search key.
    EXPECT_EQ(KeyDigit1::Typed(), RunRewriter(KeyDigit1::Typed()));
    EXPECT_EQ(KeyDigit2::Typed(), RunRewriter(KeyDigit2::Typed()));
    EXPECT_EQ(KeyDigit3::Typed(), RunRewriter(KeyDigit3::Typed()));
    EXPECT_EQ(KeyDigit4::Typed(), RunRewriter(KeyDigit4::Typed()));
    EXPECT_EQ(KeyDigit5::Typed(), RunRewriter(KeyDigit5::Typed()));
    EXPECT_EQ(KeyDigit6::Typed(), RunRewriter(KeyDigit6::Typed()));
    EXPECT_EQ(KeyDigit7::Typed(), RunRewriter(KeyDigit7::Typed()));
    EXPECT_EQ(KeyDigit8::Typed(), RunRewriter(KeyDigit8::Typed()));
    EXPECT_EQ(KeyDigit9::Typed(), RunRewriter(KeyDigit9::Typed()));
    EXPECT_EQ(KeyDigit0::Typed(), RunRewriter(KeyDigit0::Typed()));
    EXPECT_EQ(KeyMinus::Typed(), RunRewriter(KeyMinus::Typed()));
    EXPECT_EQ(KeyEqual::Typed(), RunRewriter(KeyEqual::Typed()));
  }

  SetUpKeyboard(kWilco1_0Keyboard);
  // Search + F12 -> Ctrl + Zoom (aka Fullscreen) (Display toggle)
  EXPECT_EQ(
      std::vector(
          {TestKeyEvent{ui::EventType::kKeyPressed, ui::DomCode::F12,
                        ui::DomKey::F12, ui::VKEY_ZOOM, ui::EF_CONTROL_DOWN},
           TestKeyEvent{ui::EventType::kKeyReleased, ui::DomCode::F12,
                        ui::DomKey::F12, ui::VKEY_ZOOM, ui::EF_CONTROL_DOWN}}),
      RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN));

  SetUpKeyboard(kWilco1_5Keyboard);
  // Search + F12 -> F12 (Privacy screen not supported)
  event_rewriter_ash_->set_privacy_screen_for_testing(false);
  EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN));

  // F12 -> F12, Search + F12 -> Privacy Screen Toggle
  event_rewriter_ash_->set_privacy_screen_for_testing(true);
  EXPECT_EQ(KeyPrivacyScreenToggle::Typed(),
            RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN));
}

TEST_P(EventRewriterTest, TestRewriteActionKeysWilcoLayouts) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  for (const auto& keyboard : kWilcoKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Back -> Back, Search + Back -> F1
    EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyBrowserBack::Typed()));
    EXPECT_EQ(KeyF1::Typed(),
              RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN));

    // Refresh -> Refresh, Search + Refresh -> F2
    EXPECT_EQ(KeyBrowserRefresh::Typed(),
              RunRewriter(KeyBrowserRefresh::Typed()));
    EXPECT_EQ(KeyF2::Typed(),
              RunRewriter(KeyBrowserRefresh::Typed(), ui::EF_COMMAND_DOWN));

    // Full Screen -> Full Screen, Search + Full Screen -> F3
    EXPECT_EQ(KeyZoomToggle::Typed(), RunRewriter(KeyZoomToggle::Typed()));
    EXPECT_EQ(KeyF3::Typed(),
              RunRewriter(KeyZoomToggle::Typed(), ui::EF_COMMAND_DOWN));

    // Launch App 1 -> Launch App 1, Search + Launch App 1 -> F4
    EXPECT_EQ(KeySelectTask::Typed(), RunRewriter(KeySelectTask::Typed()));
    EXPECT_EQ(KeyF4::Typed(),
              RunRewriter(KeySelectTask::Typed(), ui::EF_COMMAND_DOWN));

    // Brightness down -> Brightness Down, Search Brightness Down -> F5
    EXPECT_EQ(KeyBrightnessDown::Typed(),
              RunRewriter(KeyBrightnessDown::Typed()));
    EXPECT_EQ(KeyF5::Typed(),
              RunRewriter(KeyBrightnessDown::Typed(), ui::EF_COMMAND_DOWN));

    // Brightness up -> Brightness Up, Search + Brightness Up -> F6
    EXPECT_EQ(KeyBrightnessUp::Typed(), RunRewriter(KeyBrightnessUp::Typed()));
    EXPECT_EQ(KeyF6::Typed(),
              RunRewriter(KeyBrightnessUp::Typed(), ui::EF_COMMAND_DOWN));

    // Volume mute -> Volume Mute, Search + Volume Mute -> F7
    EXPECT_EQ(KeyVolumeMute::Typed(), RunRewriter(KeyVolumeMute::Typed()));
    EXPECT_EQ(KeyF7::Typed(),
              RunRewriter(KeyVolumeMute::Typed(), ui::EF_COMMAND_DOWN));

    // Volume Down -> Volume Down, Search + Volume Down -> F8
    EXPECT_EQ(KeyVolumeDown::Typed(), RunRewriter(KeyVolumeDown::Typed()));
    EXPECT_EQ(KeyF8::Typed(),
              RunRewriter(KeyVolumeDown::Typed(), ui::EF_COMMAND_DOWN));

    // Volume Up -> Volume Up, Search + Volume Up -> F9
    EXPECT_EQ(KeyVolumeUp::Typed(), RunRewriter(KeyVolumeUp::Typed()));
    EXPECT_EQ(KeyF9::Typed(),
              RunRewriter(KeyVolumeUp::Typed(), ui::EF_COMMAND_DOWN));
  }

  SetUpKeyboard(kWilco1_0Keyboard);
  // Ctrl + Zoom (Display toggle) -> Unchanged
  // Search + Ctrl + Zoom (Display toggle) -> F12
  EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyZoomToggle::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyF12::Typed(),
            RunRewriter(KeyZoomToggle::Typed(),
                        ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN));

  SetUpKeyboard(kWilco1_5Keyboard);
  {
    // Drallion specific key tests (no privacy screen)
    event_rewriter_ash_->set_privacy_screen_for_testing(false);

    // Privacy Screen Toggle -> F12 (Privacy Screen not supported),
    // Search + Privacy Screen Toggle -> F12
    EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed()));
    EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed(),
                                           ui::EF_COMMAND_DOWN));

    // Ctrl + Zoom (Display toggle) -> Unchanged
    // Search + Ctrl + Zoom (Display toggle) -> Unchanged
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyZoomToggle::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyZoomToggle::Typed(),
                          ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN));
  }

  {
    // Drallion specific key tests (privacy screen supported)
    event_rewriter_ash_->set_privacy_screen_for_testing(true);

    // Privacy Screen Toggle -> Privacy Screen Toggle,
    // Search + Privacy Screen Toggle -> F12
    EXPECT_EQ(KeyPrivacyScreenToggle::Typed(),
              RunRewriter(KeyPrivacyScreenToggle::Typed()));
    EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed(),
                                           ui::EF_COMMAND_DOWN));

    // Ctrl + Zoom (Display toggle) -> Unchanged
    // Search + Ctrl + Zoom (Display toggle) -> Unchanged
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyZoomToggle::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyZoomToggle::Typed(),
                          ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterTest,
       TestRewriteActionKeysWilcoLayoutsSuppressMetaTopRowKeyRewrites) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  delegate_->SuppressMetaTopRowKeyComboRewrites(true);
  keyboard_settings->suppress_meta_fkey_rewrites = true;

  // With |SuppressMetaTopRowKeyComboRewrites|, all action keys should be
  // unchanged and keep the search modifier.

  for (const auto& keyboard : kWilcoKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyBrowserRefresh::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyZoomToggle::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeySelectTask::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeySelectTask::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyBrightnessDown::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyBrightnessUp::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyVolumeMute::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyVolumeDown::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyVolumeUp::Typed(), ui::EF_COMMAND_DOWN));

    // F-Keys do not remove Search when pressed.
    EXPECT_EQ(KeyF10::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF11::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN));
  }

  SetUpKeyboard(kWilco1_0Keyboard);
  EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN),
            RunRewriter(KeyZoomToggle::Typed(),
                        ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN));

  SetUpKeyboard(kWilco1_5Keyboard);
  {
    // Drallion specific key tests (no privacy screen)
    event_rewriter_ash_->set_privacy_screen_for_testing(false);

    // Search + Privacy Screen Toggle -> Search + F12
    EXPECT_EQ(
        KeyF12::Typed(ui::EF_COMMAND_DOWN),
        RunRewriter(KeyPrivacyScreenToggle::Typed(), ui::EF_COMMAND_DOWN));
    // Search + Ctrl + Zoom (Display toggle) -> Unchanged
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN),
              RunRewriter(KeyZoomToggle::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN));
  }

  {
    // Drallion specific key tests (privacy screen supported)
    event_rewriter_ash_->set_privacy_screen_for_testing(true);

    // Search + Privacy Screen Toggle -> F12  TODO
    EXPECT_EQ(
        KeyPrivacyScreenToggle::Typed(ui::EF_COMMAND_DOWN),
        RunRewriter(KeyPrivacyScreenToggle::Typed(), ui::EF_COMMAND_DOWN));
    // Ctrl + Zoom (Display toggle) -> Unchanged  TODO
    // Search + Ctrl + Zoom (Display toggle) -> Unchanged
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN),
              RunRewriter(KeyZoomToggle::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN));
  }
}

TEST_P(
    EventRewriterTest,
    TestRewriteActionKeysWilcoLayoutsSuppressMetaTopRowKeyRewritesWithTopRowAreFkeys) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  delegate_->SuppressMetaTopRowKeyComboRewrites(true);
  keyboard_settings->suppress_meta_fkey_rewrites = true;

  // Enable preference treat-top-row-as-function-keys.
  // That causes action keys to be mapped back to Fn keys.
  BooleanPrefMember top_row_as_fn_key;
  top_row_as_fn_key.Init(prefs::kSendFunctionKeys, prefs());
  top_row_as_fn_key.SetValue(true);
  keyboard_settings->top_row_are_fkeys = true;

  // With |SuppressMetaTopRowKeyComboRewrites| and TopRowAreFKeys, all action
  // keys should be remapped to F-Keys and keep the Search modifier.
  for (const auto& keyboard : kWilcoKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF2::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyBrowserRefresh::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF3::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyZoomToggle::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF4::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeySelectTask::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF5::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyBrightnessDown::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF6::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyBrightnessUp::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF7::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyVolumeMute::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF8::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyVolumeDown::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF9::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyVolumeUp::Typed(), ui::EF_COMMAND_DOWN));

    EXPECT_EQ(KeyF10::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN));
    EXPECT_EQ(KeyF11::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN));
  }

  SetUpKeyboard(kWilco1_0Keyboard);
  EXPECT_EQ(KeyF12::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyZoomToggle::Typed(),
                        ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN));

  SetUpKeyboard(kWilco1_5Keyboard);
  {
    // Drallion specific key tests (no privacy screen)
    event_rewriter_ash_->set_privacy_screen_for_testing(false);

    // Search + Privacy Screen Toggle -> Search + F12
    EXPECT_EQ(
        KeyF12::Typed(ui::EF_COMMAND_DOWN),
        RunRewriter(KeyPrivacyScreenToggle::Typed(), ui::EF_COMMAND_DOWN));
    // Search + Ctrl + Zoom (Display toggle) -> Unchanged
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN),
              RunRewriter(KeyZoomToggle::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN));
  }

  {
    // Drallion specific key tests (privacy screen supported)
    event_rewriter_ash_->set_privacy_screen_for_testing(true);

    // Search + Privacy Screen Toggle -> F12
    EXPECT_EQ(
        KeyF12::Typed(ui::EF_COMMAND_DOWN),
        RunRewriter(KeyPrivacyScreenToggle::Typed(), ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterTest, TestTopRowAsFnKeysForKeyboardWilcoLayouts) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  // Enable preference treat-top-row-as-function-keys.
  // That causes action keys to be mapped back to Fn keys, unless the search
  // key is pressed.
  BooleanPrefMember top_row_as_fn_key;
  top_row_as_fn_key.Init(prefs::kSendFunctionKeys, prefs());
  top_row_as_fn_key.SetValue(true);
  keyboard_settings->top_row_are_fkeys = true;

  for (const auto& keyboard : kWilcoKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Back -> F1, Search + Back -> Back
    EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyBrowserBack::Typed()));
    EXPECT_EQ(KeyBrowserBack::Typed(),
              RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN));

    // Refresh -> F2, Search + Refresh -> Refresh
    EXPECT_EQ(KeyF2::Typed(), RunRewriter(KeyBrowserRefresh::Typed()));
    EXPECT_EQ(KeyBrowserRefresh::Typed(),
              RunRewriter(KeyBrowserRefresh::Typed(), ui::EF_COMMAND_DOWN));

    // Full Screen -> F3, Search + Full Screen -> Full Screen
    EXPECT_EQ(KeyF3::Typed(), RunRewriter(KeyZoomToggle::Typed()));
    EXPECT_EQ(KeyZoomToggle::Typed(),
              RunRewriter(KeyZoomToggle::Typed(), ui::EF_COMMAND_DOWN));

    // Launch App 1 -> F4, Search + Launch App 1 -> Launch App 1
    EXPECT_EQ(KeyF4::Typed(), RunRewriter(KeySelectTask::Typed()));
    EXPECT_EQ(KeySelectTask::Typed(),
              RunRewriter(KeySelectTask::Typed(), ui::EF_COMMAND_DOWN));

    // Brightness down -> F5, Search Brightness Down -> Brightness Down
    EXPECT_EQ(KeyF5::Typed(), RunRewriter(KeyBrightnessDown::Typed()));
    EXPECT_EQ(KeyBrightnessDown::Typed(),
              RunRewriter(KeyBrightnessDown::Typed(), ui::EF_COMMAND_DOWN));

    // Brightness up -> F6, Search + Brightness Up -> Brightness Up
    EXPECT_EQ(KeyF6::Typed(), RunRewriter(KeyBrightnessUp::Typed()));
    EXPECT_EQ(KeyBrightnessUp::Typed(),
              RunRewriter(KeyBrightnessUp::Typed(), ui::EF_COMMAND_DOWN));

    // Volume mute -> F7, Search + Volume Mute -> Volume Mute
    EXPECT_EQ(KeyF7::Typed(), RunRewriter(KeyVolumeMute::Typed()));
    EXPECT_EQ(KeyVolumeMute::Typed(),
              RunRewriter(KeyVolumeMute::Typed(), ui::EF_COMMAND_DOWN));

    // Volume Down -> F8, Search + Volume Down -> Volume Down
    EXPECT_EQ(KeyF8::Typed(), RunRewriter(KeyVolumeDown::Typed()));
    EXPECT_EQ(KeyVolumeDown::Typed(),
              RunRewriter(KeyVolumeDown::Typed(), ui::EF_COMMAND_DOWN));

    // Volume Up -> F9, Search + Volume Up -> Volume Up
    EXPECT_EQ(KeyF9::Typed(), RunRewriter(KeyVolumeUp::Typed()));
    EXPECT_EQ(KeyVolumeUp::Typed(),
              RunRewriter(KeyVolumeUp::Typed(), ui::EF_COMMAND_DOWN));

    // F10 -> F10
    EXPECT_EQ(KeyF10::Typed(), RunRewriter(KeyF10::Typed()));
    EXPECT_EQ(KeyF10::Typed(),
              RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN));

    // F11 -> F11
    EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF11::Typed()));
    EXPECT_EQ(KeyF11::Typed(),
              RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN));
  }

  SetUpKeyboard(kWilco1_0Keyboard);
  // Ctrl + Zoom (Display toggle) -> F12
  // Search + Ctrl + Zoom (Display toggle) -> Search modifier should be removed
  EXPECT_EQ(KeyF12::Typed(),
            RunRewriter(KeyZoomToggle::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyZoomToggle::Typed(),
                        ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN));

  SetUpKeyboard(kWilco1_5Keyboard);
  {
    // Drallion specific key tests (no privacy screen)
    event_rewriter_ash_->set_privacy_screen_for_testing(false);

    // Privacy Screen Toggle -> F12,
    // Search + Privacy Screen Toggle -> F12 (Privacy screen not supported)
    EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed()));
    EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed(),
                                           ui::EF_COMMAND_DOWN));

    // Ctrl + Zoom (Display toggle) -> Unchanged
    // Search + Ctrl + Zoom (Display toggle) -> Unchanged
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyZoomToggle::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyZoomToggle::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN));
  }

  {
    // Drallion specific key tests (privacy screen supported)
    event_rewriter_ash_->set_privacy_screen_for_testing(true);

    // Privacy Screen Toggle -> F12,
    // Search + Privacy Screen Toggle -> Unchanged
    EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed()));
    EXPECT_EQ(
        KeyPrivacyScreenToggle::Typed(),
        RunRewriter(KeyPrivacyScreenToggle::Typed(), ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteFunctionKeysInvalidLayout) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  // Not adding a keyboard simulates a failure in getting top row layout, which
  // will fallback to Layout1 in which case keys are rewritten to their default
  // values.
  EXPECT_EQ(KeyBrowserForward::Typed(), RunRewriter(KeyF2::Typed()));
  EXPECT_EQ(KeyBrowserRefresh::Typed(), RunRewriter(KeyF3::Typed()));
  EXPECT_EQ(KeyZoomToggle::Typed(), RunRewriter(KeyF4::Typed()));
  EXPECT_EQ(KeyBrightnessUp::Typed(), RunRewriter(KeyF7::Typed()));

  // Adding a keyboard with a valid layout will take effect.
  SetUpKeyboard({.name = "Internal Keyboard",
                 .layout = kKbdTopRowLayout2Tag,
                 .type = ui::INPUT_DEVICE_INTERNAL,
                 .has_custom_top_row = false});
  EXPECT_EQ(KeyBrowserRefresh::Typed(), RunRewriter(KeyF2::Typed()));
  EXPECT_EQ(KeyZoomToggle::Typed(), RunRewriter(KeyF3::Typed()));
  EXPECT_EQ(KeySelectTask::Typed(), RunRewriter(KeyF4::Typed()));
  EXPECT_EQ(KeyMediaPlayPause::Typed(), RunRewriter(KeyF7::Typed()));
}

// Tests that event rewrites still work even if modifiers are remapped.
TEST_P(EventRewriterTest, TestRewriteExtendedKeysWithControlRemapped) {
  // Remap Control to Search.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      features::kAltClickAndSixPackCustomization);
  IntegerPrefMember search;
  InitModifierKeyPref(&search, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kMeta);

  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    EXPECT_EQ(KeyEnd::Typed(),
              RunRewriter(KeyArrowRight::Typed(), ui::EF_CONTROL_DOWN));
    EXPECT_EQ(KeyEnd::Typed(ui::EF_SHIFT_DOWN),
              RunRewriter(KeyArrowRight::Typed(),
                          ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN));
  }
}

TEST_P(EventRewriterTest, TestRewriteKeyEventSentByXSendEvent) {
  // Remap Control to Alt.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kAlt);

  SetUpKeyboard(kInternalChromeKeyboard);

  // Send left control press.
  {
    // Control should NOT be remapped to Alt if EF_FINAL is set.
    EXPECT_EQ(KeyLControl::Typed(ui::EF_FINAL),
              SendKeyEvents(KeyLControl::Typed(ui::EF_FINAL)));
  }
}

TEST_P(EventRewriterTest, TestRewriteNonNativeEvent) {
  // Remap Control to Alt.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kAlt);

  SetUpKeyboard(kInternalChromeKeyboard);

  // For EF_CONTROL_DOWN modifier.
  SendKeyEvent(KeyLControl::Pressed());

  const int kTouchId = 2;
  gfx::Point location(0, 0);
  ui::TouchEvent press(
      ui::EventType::kTouchPressed, location, base::TimeTicks(),
      ui::PointerDetails(ui::EventPointerType::kTouch, kTouchId));
  press.SetFlags(ui::EF_CONTROL_DOWN);

  source().Send(&press);
  auto events = TakeEvents();
  ASSERT_EQ(1u, events.size());
  // Control should be remapped to Alt.
  EXPECT_EQ(ui::EF_ALT_DOWN,
            events[0]->flags() & (ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN));
}

TEST_P(EventRewriterTest, TopRowKeysAreFunctionKeys) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(1));
  wm::ActivateWindow(window.get());

  // Create a simulated keypress of F1 targetted at the window.
  ui::KeyEvent press_f1(ui::EventType::kKeyPressed, ui::VKEY_F1,
                        ui::DomCode::F1, ui::EF_NONE, ui::DomKey::F1,
                        ui::EventTimeForNow());

  // The event should also not be rewritten if the send-function-keys pref is
  // additionally set, for both apps v2 and regular windows.
  BooleanPrefMember send_function_keys_pref;
  send_function_keys_pref.Init(prefs::kSendFunctionKeys, prefs());
  send_function_keys_pref.SetValue(true);
  keyboard_settings->top_row_are_fkeys = true;
  EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed()));

  // If the pref isn't set when an event is sent to a regular window, F1 is
  // rewritten to the back key.
  send_function_keys_pref.SetValue(false);
  keyboard_settings->top_row_are_fkeys = false;
  EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyF1::Typed()));
}

// Parameterized version of test with the same name that accepts the
// event flags that correspond to a right-click. This will be either
// Alt+Click or Search+Click. After a transition period this will
// default to Search+Click and the Alt+Click logic will be removed.
void EventRewriterTestBase::DontRewriteIfNotRewritten(int right_click_flags) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  ui::DeviceDataManager* device_data_manager =
      ui::DeviceDataManager::GetInstance();
  std::vector<ui::TouchpadDevice> touchpad_devices(2);
  touchpad_devices[0].id = kTouchpadId1;
  touchpad_devices[1].id = kTouchpadId2;
  static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager)
      ->OnTouchpadDevicesUpdated(touchpad_devices);
  std::vector<ui::InputDevice> mouse_devices(1);
  constexpr int kMouseId = 12;
  touchpad_devices[0].id = kMouseId;
  static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager)
      ->OnMouseDevicesUpdated(mouse_devices);

  // Test (Alt|Search) + Left click.
  {
    ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(),
                         gfx::Point(), ui::EventTimeForNow(), right_click_flags,
                         ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_press(&press);
    test_press.set_source_device_id(kTouchpadId1);
    // Sanity check.
    EXPECT_EQ(ui::EventType::kMousePressed, press.type());
    EXPECT_EQ(right_click_flags, press.flags());
    const ui::MouseEvent result = RewriteMouseButtonEvent(press);
    EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags());
    EXPECT_NE(right_click_flags, right_click_flags & result.flags());
    EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags());
  }
  {
    ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(),
                           gfx::Point(), ui::EventTimeForNow(),
                           right_click_flags, ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_release(&release);
    test_release.set_source_device_id(kTouchpadId1);
    const ui::MouseEvent result = RewriteMouseButtonEvent(release);
    EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags());
    EXPECT_NE(right_click_flags, right_click_flags & result.flags());
    EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags());
  }

  // No (ALT|SEARCH) in first click.
  {
    ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(),
                         gfx::Point(), ui::EventTimeForNow(),
                         ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_press(&press);
    test_press.set_source_device_id(kTouchpadId1);
    const ui::MouseEvent result = RewriteMouseButtonEvent(press);
    EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());
  }
  {
    ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(),
                           gfx::Point(), ui::EventTimeForNow(),
                           right_click_flags, ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_release(&release);
    test_release.set_source_device_id(kTouchpadId1);
    const ui::MouseEvent result = RewriteMouseButtonEvent(release);
    EXPECT_EQ(right_click_flags, right_click_flags & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());
  }

  // ALT on different device.
  {
    ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(),
                         gfx::Point(), ui::EventTimeForNow(), right_click_flags,
                         ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_press(&press);
    test_press.set_source_device_id(kTouchpadId2);
    const ui::MouseEvent result = RewriteMouseButtonEvent(press);
    EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags());
    EXPECT_NE(right_click_flags, right_click_flags & result.flags());
    EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags());
  }
  {
    ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(),
                           gfx::Point(), ui::EventTimeForNow(),
                           right_click_flags, ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_release(&release);
    test_release.set_source_device_id(kTouchpadId1);
    const ui::MouseEvent result = RewriteMouseButtonEvent(release);
    EXPECT_EQ(right_click_flags, right_click_flags & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());
  }
  {
    ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(),
                           gfx::Point(), ui::EventTimeForNow(),
                           right_click_flags, ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_release(&release);
    test_release.set_source_device_id(kTouchpadId2);
    const ui::MouseEvent result = RewriteMouseButtonEvent(release);
    EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags());
    EXPECT_NE(right_click_flags, right_click_flags & result.flags());
    EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags());
  }

  // No rewrite for non-touchpad devices.
  {
    ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(),
                         gfx::Point(), ui::EventTimeForNow(), right_click_flags,
                         ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_press(&press);
    test_press.set_source_device_id(kMouseId);
    EXPECT_EQ(ui::EventType::kMousePressed, press.type());
    EXPECT_EQ(right_click_flags, press.flags());
    const ui::MouseEvent result = RewriteMouseButtonEvent(press);
    EXPECT_EQ(right_click_flags, right_click_flags & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());
  }
  {
    ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(),
                           gfx::Point(), ui::EventTimeForNow(),
                           right_click_flags, ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_release(&release);
    test_release.set_source_device_id(kMouseId);
    const ui::MouseEvent result = RewriteMouseButtonEvent(release);
    EXPECT_EQ(right_click_flags, right_click_flags & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());
  }

  // Still rewrite to right button, even if the modifier key is already
  // released when the mouse release event happens
  // This is for regressions such as:
  // https://crbug.com/1399284
  // https://crbug.com/1417079
  {
    ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(),
                         gfx::Point(), ui::EventTimeForNow(), right_click_flags,
                         ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_press(&press);
    test_press.set_source_device_id(kTouchpadId1);
    // Sanity check.
    EXPECT_EQ(ui::EventType::kMousePressed, press.type());
    EXPECT_EQ(right_click_flags, press.flags());
    const ui::MouseEvent result = RewriteMouseButtonEvent(press);
    EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags());
    EXPECT_NE(right_click_flags, right_click_flags & result.flags());
    EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags());
  }
  {
    ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(),
                           gfx::Point(), ui::EventTimeForNow(),
                           ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_release(&release);
    test_release.set_source_device_id(kTouchpadId1);
    const ui::MouseEvent result = RewriteMouseButtonEvent(release);
    EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags());
    EXPECT_NE(right_click_flags, right_click_flags & result.flags());
    EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags());
  }
}

TEST_P(EventRewriterTest, DontRewriteIfNotRewritten_AltClickIsRightClick) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      features::kAltClickAndSixPackCustomization);
  DontRewriteIfNotRewritten(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_ALT_DOWN);
  EXPECT_EQ(message_center_.NotificationCount(), 0u);
}

TEST_P(EventRewriterTest, DontRewriteIfNotRewritten_AltClickIsRightClick_New) {
  // Enabling the kImprovedKeyboardShortcuts feature does not change alt+click
  // behavior or create a notification.
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {::features::kImprovedKeyboardShortcuts},
      {features::kAltClickAndSixPackCustomization});
  DontRewriteIfNotRewritten(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_ALT_DOWN);
  EXPECT_EQ(message_center_.NotificationCount(), 0u);
}

TEST_P(EventRewriterTest, DontRewriteIfNotRewritten_SearchClickIsRightClick) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {features::kUseSearchClickForRightClick},
      {features::kAltClickAndSixPackCustomization});
  DontRewriteIfNotRewritten(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_COMMAND_DOWN);
  EXPECT_EQ(message_center_.NotificationCount(), 0u);
}

TEST_P(EventRewriterTest, DontRewriteIfNotRewritten_AltClickDeprecated) {
  // Pressing search+click with alt+click deprecated works, but does not
  // generate a notification.
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {::features::kDeprecateAltClick},
      {features::kAltClickAndSixPackCustomization});
  DontRewriteIfNotRewritten(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_COMMAND_DOWN);
  EXPECT_EQ(message_center_.NotificationCount(), 0u);
}

TEST_P(EventRewriterTest, DeprecatedAltClickGeneratesNotification) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {::features::kDeprecateAltClick},
      {features::kAltClickAndSixPackCustomization});
  ui::DeviceDataManager* device_data_manager =
      ui::DeviceDataManager::GetInstance();
  std::vector<ui::TouchpadDevice> touchpad_devices(1);
  constexpr int kTouchpadId1 = 10;
  touchpad_devices[0].id = kTouchpadId1;
  static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager)
      ->OnTouchpadDevicesUpdated(touchpad_devices);
  std::vector<ui::InputDevice> mouse_devices(1);
  constexpr int kMouseId = 12;
  touchpad_devices[0].id = kMouseId;
  static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager)
      ->OnMouseDevicesUpdated(mouse_devices);

  const int deprecated_flags = ui::EF_LEFT_MOUSE_BUTTON | ui::EF_ALT_DOWN;

  // Alt + Left click => No rewrite.
  {
    ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(),
                         gfx::Point(), ui::EventTimeForNow(), deprecated_flags,
                         ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_press(&press);
    test_press.set_source_device_id(kTouchpadId1);
    // Sanity check.
    EXPECT_EQ(ui::EventType::kMousePressed, press.type());
    EXPECT_EQ(deprecated_flags, press.flags());
    const ui::MouseEvent result = RewriteMouseButtonEvent(press);

    // No rewrite occurred.
    EXPECT_EQ(deprecated_flags, deprecated_flags & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());

    // Expect a deprecation notification.
    EXPECT_EQ(message_center_.NotificationCount(), 1u);
    ClearNotifications();
  }
  {
    ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(),
                           gfx::Point(), ui::EventTimeForNow(),
                           deprecated_flags, ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_release(&release);
    test_release.set_source_device_id(kTouchpadId1);
    const ui::MouseEvent result = RewriteMouseButtonEvent(release);

    // No rewrite occurred.
    EXPECT_EQ(deprecated_flags, deprecated_flags & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());

    // Don't expect a new notification on release.
    EXPECT_EQ(message_center_.NotificationCount(), 0u);
  }

  // No rewrite or notification for non-touchpad devices.
  {
    ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(),
                         gfx::Point(), ui::EventTimeForNow(), deprecated_flags,
                         ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_press(&press);
    test_press.set_source_device_id(kMouseId);
    EXPECT_EQ(ui::EventType::kMousePressed, press.type());
    EXPECT_EQ(deprecated_flags, press.flags());
    const ui::MouseEvent result = RewriteMouseButtonEvent(press);
    EXPECT_EQ(deprecated_flags, deprecated_flags & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());

    // No notification expected for this case.
    EXPECT_EQ(message_center_.NotificationCount(), 0u);
  }
  {
    ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(),
                           gfx::Point(), ui::EventTimeForNow(),
                           deprecated_flags, ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_release(&release);
    test_release.set_source_device_id(kMouseId);
    const ui::MouseEvent result = RewriteMouseButtonEvent(release);
    EXPECT_EQ(deprecated_flags, deprecated_flags & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());

    // No notification expected for this case.
    EXPECT_EQ(message_center_.NotificationCount(), 0u);
  }
}

TEST_P(EventRewriterTest, StickyKeyEventDispatchImpl) {
  Shell::Get()->sticky_keys_controller()->Enable(true);
  // Test the actual key event dispatch implementation.
  {
    auto events = SendKeyEvents(KeyLControl::Typed());
    ASSERT_EQ(1u, events.size());
    EXPECT_EQ(KeyLControl::Pressed(), events[0]);
  }

  // Test key press event is correctly modified and modifier release
  // event is sent.
  {
    auto events = SendKeyEvent(KeyC::Pressed());
    ASSERT_EQ(2u, events.size());
    EXPECT_EQ(KeyC::Pressed(ui::EF_CONTROL_DOWN), events[0]);
    EXPECT_EQ(KeyLControl::Released(), events[1]);
  }

  // Test key release event is not modified.
  {
    auto events = SendKeyEvent(KeyC::Released());
    ASSERT_EQ(1u, events.size());
    EXPECT_EQ(KeyC::Released(), events[0]);
  }
}

TEST_P(EventRewriterTest, MouseEventDispatchImpl) {
  Shell::Get()->sticky_keys_controller()->Enable(true);
  SendKeyEvents(KeyLControl::Typed());

  // Test mouse press event is correctly modified.
  gfx::Point location(0, 0);
  ui::MouseEvent press(ui::EventType::kMousePressed, location, location,
                       ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
                       ui::EF_LEFT_MOUSE_BUTTON);
  ui::EventDispatchDetails details = source().Send(&press);
  ASSERT_FALSE(details.dispatcher_destroyed);
  {
    auto events = TakeEvents();
    ASSERT_EQ(1u, events.size());
    EXPECT_EQ(ui::EventType::kMousePressed, events[0]->type());
    EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN);
  }

  // Test mouse release event is correctly modified and modifier release
  // event is sent. The mouse event should have the correct DIP location.
  ui::MouseEvent release(ui::EventType::kMouseReleased, location, location,
                         ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
                         ui::EF_LEFT_MOUSE_BUTTON);
  details = source().Send(&release);
  ASSERT_FALSE(details.dispatcher_destroyed);
  {
    auto events = TakeEvents();
    ASSERT_EQ(2u, events.size());
    EXPECT_EQ(ui::EventType::kMouseReleased, events[0]->type());
    EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN);

    ASSERT_EQ(ui::EventType::kKeyReleased, events[1]->type());
    EXPECT_EQ(ui::VKEY_CONTROL, events[1]->AsKeyEvent()->key_code());
  }
}

TEST_P(EventRewriterTest, MouseWheelEventDispatchImpl) {
  Shell::Get()->sticky_keys_controller()->Enable(true);
  // Test positive mouse wheel event is correctly modified and modifier release
  // event is sent.
  SendKeyEvents(KeyLControl::Typed());

  gfx::Point location(0, 0);
  ui::MouseWheelEvent positive(
      gfx::Vector2d(0, ui::MouseWheelEvent::kWheelDelta), location, location,
      ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
      ui::EF_LEFT_MOUSE_BUTTON);
  ui::EventDispatchDetails details = source().Send(&positive);
  ASSERT_FALSE(details.dispatcher_destroyed);
  {
    auto events = TakeEvents();
    ASSERT_EQ(2u, events.size());
    EXPECT_TRUE(events[0]->IsMouseWheelEvent());
    EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN);

    ASSERT_EQ(ui::EventType::kKeyReleased, events[1]->type());
    EXPECT_EQ(ui::VKEY_CONTROL, events[1]->AsKeyEvent()->key_code());
  }

  // Test negative mouse wheel event is correctly modified and modifier release
  // event is sent.
  SendKeyEvents({KeyLControl::Pressed(), KeyLControl::Released()});

  ui::MouseWheelEvent negative(
      gfx::Vector2d(0, -ui::MouseWheelEvent::kWheelDelta), location, location,
      ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
      ui::EF_LEFT_MOUSE_BUTTON);
  details = source().Send(&negative);
  ASSERT_FALSE(details.dispatcher_destroyed);
  {
    auto events = TakeEvents();
    ASSERT_EQ(2u, events.size());
    EXPECT_TRUE(events[0]->IsMouseWheelEvent());
    EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN);

    ASSERT_EQ(ui::EventType::kKeyReleased, events[1]->type());
    EXPECT_EQ(ui::VKEY_CONTROL, events[1]->AsKeyEvent()->key_code());
  }
}

// Tests that if modifier keys are remapped, the flags of a mouse wheel event
// will be rewritten properly.
TEST_P(EventRewriterTest, MouseWheelEventModifiersRewritten) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  // For EF_CONTROL_DOWN modifier.
  SendKeyEvent(KeyLControl::Pressed());

  // Generate a mouse wheel event that has a CONTROL_DOWN modifier flag and
  // expect that no rewriting happens as no modifier remapping is active.
  gfx::Point location(0, 0);
  ui::MouseWheelEvent positive(
      gfx::Vector2d(0, ui::MouseWheelEvent::kWheelDelta), location, location,
      ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON | ui::EF_CONTROL_DOWN,
      ui::EF_LEFT_MOUSE_BUTTON);
  ui::EventDispatchDetails details = source().Send(&positive);
  ASSERT_FALSE(details.dispatcher_destroyed);
  {
    auto events = TakeEvents();
    ASSERT_EQ(1u, events.size());
    EXPECT_TRUE(events[0]->IsMouseWheelEvent());
    EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN);
  }

  // Remap Control to Alt.
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kAlt);

  // Sends the same events once again and expect that it will be rewritten to
  // ALT_DOWN in older implementation, or not rewritten (as Control is held)
  // in the new implementation.
  details = source().Send(&positive);
  ASSERT_FALSE(details.dispatcher_destroyed);
  {
    auto events = TakeEvents();
    ASSERT_EQ(1u, events.size());
    EXPECT_TRUE(events[0]->IsMouseWheelEvent());
    if (ash::features::IsKeyboardRewriterFixEnabled()) {
      EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN);
      EXPECT_FALSE(events[0]->flags() & ui::EF_ALT_DOWN);
    } else {
      EXPECT_FALSE(events[0]->flags() & ui::EF_CONTROL_DOWN);
      EXPECT_TRUE(events[0]->flags() & ui::EF_ALT_DOWN);
    }
  }
}

TEST_P(EventRewriterTest, MouseEventMaintainNativeEvent) {
  if (!features::IsKeyboardRewriterFixEnabled()) {
    GTEST_SKIP() << "Test is only valid with keyboard rewriter fix enabled";
  }

  Preferences::RegisterProfilePrefs(prefs()->registry());

  gfx::Point location(0, 0);
  ui::MouseEvent native_event(ui::EventType::kMouseMoved, location, location,
                              /*time_stamp=*/{}, ui::EF_CONTROL_DOWN,
                              ui::EF_NONE);
  ui::MouseEvent mouse_event(&native_event);
  // Remap Control to Alt.
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kAlt);

  SendKeyEvent(KeyLControl::Pressed());

  // Sends the same events once again and expect that it will be rewritten to
  // ALT_DOWN in older implementation, or not rewritten (as Control is held)
  // in the new implementation.
  ui::EventDispatchDetails details = source().Send(&mouse_event);
  ASSERT_FALSE(details.dispatcher_destroyed);
  {
    auto events = TakeEvents();
    ASSERT_EQ(1u, events.size());
    EXPECT_TRUE(events[0]->IsMouseEvent());
    EXPECT_FALSE(events[0]->flags() & ui::EF_CONTROL_DOWN);
    EXPECT_TRUE(events[0]->flags() & ui::EF_ALT_DOWN);
    EXPECT_TRUE(events[0]->HasNativeEvent());
  }
}

// Tests edge cases of key event rewriting (see https://crbug.com/913209).
TEST_P(EventRewriterTest, KeyEventRewritingEdgeCases) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kAltClickAndSixPackCustomization);

  // Edge case 1: Press the Launcher button first. Then press the Up Arrow
  // button.
  {
    auto events = SendKeyEvents(
        {KeyLMeta::Pressed(), KeyArrowUp::Pressed(ui::EF_COMMAND_DOWN)});
    EXPECT_EQ(2u, events.size());
  }

  // When releasing the Launcher button, the rewritten event should be released
  // as well.
  if (features::IsKeyboardRewriterFixEnabled()) {
    EXPECT_EQ(std::vector({KeyLMeta::Released()}),
              SendKeyEvent(KeyLMeta::Released()));
    EXPECT_EQ(std::vector({KeyPageUp::Released(), KeyArrowUp::Pressed()}),
              SendKeyEvent(KeyArrowUp::Pressed()));
  } else {
    auto events = SendKeyEvent(KeyLMeta::Released());
    ASSERT_EQ(2u, events.size());
    EXPECT_EQ(KeyLMeta::Released(), events[0]);
    EXPECT_EQ(KeyPageUp::Released(), events[1]);
  }

  // Edge case 2: Press the Up Arrow button first. Then press the Launch button.
  {
    auto events = SendKeyEvents({KeyArrowUp::Pressed(), KeyLMeta::Pressed()});
    EXPECT_EQ(2u, events.size());
  }

  // When releasing the Up Arrow button, the rewritten event should be blocked.
  {
    auto events = SendKeyEvent(KeyArrowUp::Released(ui::EF_COMMAND_DOWN));
    ASSERT_EQ(1u, events.size());
    EXPECT_EQ(KeyArrowUp::Released(ui::EF_COMMAND_DOWN), events[0]);
  }
}

TEST_P(EventRewriterTest, ScrollEventDispatchImpl) {
  Shell::Get()->sticky_keys_controller()->Enable(true);
  // Test scroll event is correctly modified.
  SendKeyEvents(KeyLControl::Typed());

  gfx::PointF location(0, 0);
  ui::ScrollEvent scroll(ui::EventType::kScroll, location, location,
                         ui::EventTimeForNow(), 0 /* flag */, 0 /* x_offset */,
                         1 /* y_offset */, 0 /* x_offset_ordinal */,
                         1 /* y_offset_ordinal */, 2 /* finger */);
  ui::EventDispatchDetails details = source().Send(&scroll);
  ASSERT_FALSE(details.dispatcher_destroyed);
  {
    auto events = TakeEvents();
    ASSERT_EQ(1u, events.size());
    EXPECT_TRUE(events[0]->IsScrollEvent());
    EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN);
  }

  // Test FLING_START event deactivates the sticky key, but is modified.
  ui::ScrollEvent fling_start(
      ui::EventType::kScrollFlingStart, location, location,
      ui::EventTimeForNow(), 0 /* flag */, 0 /* x_offset */, 0 /* y_offset */,
      0 /* x_offset_ordinal */, 0 /* y_offset_ordinal */, 2 /* finger */);
  details = source().Send(&fling_start);
  {
    auto events = TakeEvents();
    ASSERT_EQ(2u, events.size());
    EXPECT_TRUE(events[0]->IsScrollEvent());
    EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN);

    ASSERT_EQ(ui::EventType::kKeyReleased, events[1]->type());
    EXPECT_EQ(ui::VKEY_CONTROL, events[1]->AsKeyEvent()->key_code());
  }

  // Test scroll direction change causes that modifier release event is sent.
  SendKeyEvents(KeyLControl::Typed());

  details = source().Send(&scroll);
  ASSERT_FALSE(details.dispatcher_destroyed);
  std::ignore = TakeEvents();

  ui::ScrollEvent scroll2(ui::EventType::kScroll, location, location,
                          ui::EventTimeForNow(), 0 /* flag */, 0 /* x_offset */,
                          -1 /* y_offset */, 0 /* x_offset_ordinal */,
                          -1 /* y_offset_ordinal */, 2 /* finger */);
  details = source().Send(&scroll2);
  ASSERT_FALSE(details.dispatcher_destroyed);
  {
    auto events = TakeEvents();
    ASSERT_EQ(2u, events.size());
    EXPECT_TRUE(events[0]->IsScrollEvent());
    EXPECT_FALSE(events[0]->flags() & ui::EF_CONTROL_DOWN);

    ASSERT_EQ(ui::EventType::kKeyReleased, events[1]->type());
    EXPECT_EQ(ui::VKEY_CONTROL, events[1]->AsKeyEvent()->key_code());
  }
}

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
TEST_P(EventRewriterTest, RemapHangulOnCros1p) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember alt;
  InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo,
                      ui::mojom::ModifierKey::kAlt,
                      ui::mojom::ModifierKey::kAlt);

  scoped_refptr<input_method::MockInputMethodManagerImpl::State> state =
      base::MakeRefCounted<input_method::MockInputMethodManagerImpl::State>(
          input_method_manager_mock_);
  input_method_manager_mock_->SetState(state);

  for (const auto& keyboard : kAllKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);
    state->current_input_method_id =
        base::StrCat({kCros1pInputMethodIdPrefix, "ko-t-i0-und"});
    EXPECT_EQ(KeyHangulMode::Typed(), RunRewriter(KeyHangulMode::Typed()));
    EXPECT_EQ(KeyLAlt::Typed(), RunRewriter(KeyLAlt::Typed()));
    EXPECT_EQ(KeyRAlt::Typed(), RunRewriter(KeyRAlt::Typed()));

    state->current_input_method_id =
        base::StrCat({kCros1pInputMethodIdPrefix, "xkb:us::eng"});
    EXPECT_EQ(KeyRAlt::Typed(), RunRewriter(KeyHangulMode::Typed()));
    EXPECT_EQ(KeyLAlt::Typed(), RunRewriter(KeyLAlt::Typed()));
    EXPECT_EQ(KeyRAlt::Typed(), RunRewriter(KeyRAlt::Typed()));
  }
}
#endif

class EventRewriterInputSettingsSplitDisabledTest : public EventRewriterTest {
 public:
  void SetUp() override {
    settings_split_disable_feature_list_.InitAndDisableFeature(
        features::kInputDeviceSettingsSplit);
    EventRewriterTest::SetUp();
  }

  void TearDown() override {
    EventRewriterTest::TearDown();
    settings_split_disable_feature_list_.Reset();
  }

 private:
  base::test::ScopedFeatureList settings_split_disable_feature_list_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         EventRewriterInputSettingsSplitDisabledTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

TEST_P(EventRewriterInputSettingsSplitDisabledTest,
       TestRewriteCommandToControl) {
  // First, test non Apple keyboards, they should all behave the same.
  for (const auto& keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // VKEY_A, Alt modifier.
    EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN));

    // VKEY_A, Win modifier.
    EXPECT_EQ(KeyA::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN));

    // VKEY_A, Alt+Win modifier.
    EXPECT_EQ(
        KeyA::Typed(ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN),
        RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN));

    // VKEY_LWIN (left Windows key), Alt modifier.
    EXPECT_EQ(KeyLMeta::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN));

    // VKEY_RWIN (right Windows key), Alt modifier.
    EXPECT_EQ(KeyRMeta::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN));
  }

  // Simulate the default initialization of the Apple Command key remap pref to
  // Ctrl.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  {
    SCOPED_TRACE(kExternalAppleKeyboard.name);
    SetUpKeyboard(kExternalAppleKeyboard);

    // VKEY_A, Alt modifier.
    EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN));

    // VKEY_A, Win modifier.
    EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN));

    // VKEY_A, Alt+Win modifier.
    EXPECT_EQ(
        KeyA::Typed(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN),
        RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN));

    // VKEY_LWIN (left Windows key), Alt modifier.
    EXPECT_EQ(KeyLControl::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN));

    // VKEY_RWIN (right Windows key), Alt modifier.
    EXPECT_EQ(KeyRControl::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN));
  }

  // Now simulate the user remapped the Command key back to Search.
  IntegerPrefMember command;
  InitModifierKeyPref(&command, ::prefs::kLanguageRemapExternalCommandKeyTo,
                      ui::mojom::ModifierKey::kMeta,
                      ui::mojom::ModifierKey::kMeta);
  {
    SCOPED_TRACE(kExternalAppleKeyboard.name);
    SetUpKeyboard(kExternalAppleKeyboard);

    // VKEY_A, Alt modifier.
    EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN));

    // VKEY_A, Win modifier.
    EXPECT_EQ(KeyA::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN));

    // VKEY_A, Alt+Win modifier.
    EXPECT_EQ(
        KeyA::Typed(ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN),
        RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN));

    // VKEY_LWIN (left Windows key), Alt modifier.
    EXPECT_EQ(KeyLMeta::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN));

    // VKEY_RWIN (right Windows key), Alt modifier.
    EXPECT_EQ(KeyRMeta::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN));
  }
}

TEST_P(EventRewriterInputSettingsSplitDisabledTest,
       TestRewriteExternalMetaKey) {
  // Simulate the default initialization of the Meta key on external keyboards
  // remap pref to Search.
  Preferences::RegisterProfilePrefs(prefs()->registry());

  // By default, the Meta key on all keyboards, internal, external Chrome OS
  // branded keyboards, and Generic keyboards should produce Search.
  for (const auto& keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // VKEY_A, Win modifier.
    EXPECT_EQ(KeyA::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN));

    // VKEY_A, Alt+Win modifier.
    EXPECT_EQ(
        KeyA::Typed(ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN),
        RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN));

    // VKEY_LWIN (left Windows key), Alt modifier.
    EXPECT_EQ(KeyLMeta::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN));

    // VKEY_RWIN (right Windows key), Alt modifier.
    EXPECT_EQ(KeyRMeta::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN));
  }

  // Both preferences for Search on Chrome keyboards, and external Meta on
  // generic external keyboards are independent, even if one or both are
  // modified.

  // Remap Chrome OS Search to Ctrl.
  IntegerPrefMember internal_search;
  InitModifierKeyPref(&internal_search, ::prefs::kLanguageRemapSearchKeyTo,
                      ui::mojom::ModifierKey::kMeta,
                      ui::mojom::ModifierKey::kControl);

  // Remap external Meta to Alt.
  IntegerPrefMember meta;
  InitModifierKeyPref(&meta, ::prefs::kLanguageRemapExternalMetaKeyTo,
                      ui::mojom::ModifierKey::kMeta,
                      ui::mojom::ModifierKey::kAlt);
  for (const auto& keyboard : kChromeKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // VKEY_A, Win modifier.
    EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN));

    // VKEY_A, Alt+Win modifier.
    EXPECT_EQ(
        KeyA::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN),
        RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN));

    // VKEY_LWIN (left Windows key), Alt modifier.
    EXPECT_EQ(KeyLControl::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN));

    // VKEY_RWIN (right Windows key), Alt modifier.
    EXPECT_EQ(KeyRControl::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN));
  }

  SetUpKeyboard(kExternalGenericKeyboard);

  // VKEY_A, Win modifier.
  EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN));

  // VKEY_A, Alt+Win modifier.
  EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN));

  if (ash::features::IsKeyboardRewriterFixEnabled()) {
    // VKEY_LWIN (left Windows key), Alt modifier.
    EXPECT_EQ(KeyLAlt::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN));
    // VKEY_RWIN (right Windows key), Alt modifier.
    EXPECT_EQ(KeyRAlt::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN));
  } else {
    // VKEY_LWIN (left Windows key), Alt modifier.
    // Older implementation has an issue that release event is not dispatched.
    EXPECT_EQ(std::vector({KeyLAlt::Pressed()}),
              RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN));
    // VKEY_RWIN (right Windows key), Alt modifier.
    EXPECT_EQ(KeyRAlt::Typed(),
              RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN));
  }
}

// For crbug.com/133896.
TEST_P(EventRewriterInputSettingsSplitDisabledTest,
       TestRewriteCommandToControlWithControlRemapped) {
  // Remap Control to Alt.
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kAlt);

  for (const auto& keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    EXPECT_EQ(KeyLAlt::Typed(), RunRewriter(KeyLControl::Typed()));
  }

  // Now verify that remapping does not affect Apple keyboard.
  SetUpKeyboard(kExternalAppleKeyboard);

  // VKEY_LWIN (left Command key) with  Alt modifier. The remapped Command
  // key should never be re-remapped to Alt.
  EXPECT_EQ(KeyLControl::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN));

  // VKEY_RWIN (right Command key) with  Alt modifier. The remapped Command
  // key should never be re-remapped to Alt.
  EXPECT_EQ(KeyRControl::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN));
}

class StickyKeysOverlayTest
    : public EventRewriterTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  StickyKeysOverlayTest() : overlay_(nullptr) {}

  ~StickyKeysOverlayTest() override {}

  void SetUp() override {
    auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam();
    if (enable_keyboard_rewriter_fix) {
      fix_feature_list_.InitAndEnableFeature(
          ash::features::kEnableKeyboardRewriterFix);
    } else {
      fix_feature_list_.InitAndDisableFeature(
          ash::features::kEnableKeyboardRewriterFix);
    }

    if (enable_modifier_split) {
      modifier_split_feature_list_.InitAndEnableFeature(
          ash::features::kModifierSplit);
    } else {
      modifier_split_feature_list_.InitAndDisableFeature(
          ash::features::kModifierSplit);
    }

    EventRewriterTestBase::SetUp();
    auto* sticky_keys_controller = Shell::Get()->sticky_keys_controller();
    sticky_keys_controller->Enable(true);
    overlay_ = sticky_keys_controller->GetOverlayForTest();
    ASSERT_TRUE(overlay_);
  }

  raw_ptr<StickyKeysOverlay, DanglingUntriaged> overlay_;

 private:
  base::test::ScopedFeatureList fix_feature_list_;
  base::test::ScopedFeatureList modifier_split_feature_list_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         StickyKeysOverlayTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

TEST_P(StickyKeysOverlayTest, OneModifierEnabled) {
  EXPECT_FALSE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));

  // Pressing modifier key should show overlay.
  SendKeyEvents(KeyLControl::Typed());
  EXPECT_TRUE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_ENABLED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));

  // Pressing a normal key should hide overlay.
  SendKeyEvents(KeyT::Typed());
  EXPECT_FALSE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));
}

TEST_P(StickyKeysOverlayTest, TwoModifiersEnabled) {
  EXPECT_FALSE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN));

  // Pressing two modifiers should show overlay.
  SendKeyEvents(KeyLShift::Typed());
  SendKeyEvents(KeyLControl::Typed());
  EXPECT_TRUE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_ENABLED,
            overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_ENABLED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));

  // Pressing a normal key should hide overlay.
  SendKeyEvents(KeyN::Typed());
  EXPECT_FALSE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN));
}

TEST_P(StickyKeysOverlayTest, LockedModifier) {
  EXPECT_FALSE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_ALT_DOWN));

  // Pressing a modifier key twice should lock modifier and show overlay.
  SendKeyEvents(KeyLAlt::Typed());
  SendKeyEvents(KeyLAlt::Typed());
  EXPECT_TRUE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_LOCKED,
            overlay_->GetModifierKeyState(ui::EF_ALT_DOWN));

  // Pressing a normal key should not hide overlay.
  SendKeyEvents(KeyD::Typed());
  EXPECT_TRUE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_LOCKED,
            overlay_->GetModifierKeyState(ui::EF_ALT_DOWN));
}

TEST_P(StickyKeysOverlayTest, LockedAndNormalModifier) {
  EXPECT_FALSE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN));

  // Pressing a modifier key twice should lock modifier and show overlay.
  SendKeyEvents(KeyLControl::Typed());
  SendKeyEvents(KeyLControl::Typed());
  EXPECT_TRUE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_LOCKED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));

  // Pressing another modifier key should still show overlay.
  SendKeyEvents(KeyLShift::Typed());
  EXPECT_TRUE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_LOCKED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_ENABLED,
            overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN));

  // Pressing a normal key should not hide overlay but disable normal modifier.
  SendKeyEvents(KeyD::Typed());
  EXPECT_TRUE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_LOCKED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN));
}

TEST_P(StickyKeysOverlayTest, ModifiersDisabled) {
  EXPECT_FALSE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_ALT_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_COMMAND_DOWN));

  // Enable modifiers.
  SendKeyEvents(KeyLControl::Typed());
  SendKeyEvents(KeyLShift::Typed());
  SendKeyEvents(KeyLShift::Typed());
  SendKeyEvents(KeyLAlt::Typed());
  SendKeyEvents(KeyLMeta::Typed());
  SendKeyEvents(KeyLMeta::Typed());

  EXPECT_TRUE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_ENABLED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_LOCKED,
            overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_ENABLED,
            overlay_->GetModifierKeyState(ui::EF_ALT_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_LOCKED,
            overlay_->GetModifierKeyState(ui::EF_COMMAND_DOWN));

  // Disable modifiers and overlay should be hidden.
  SendKeyEvents(KeyLControl::Typed());
  SendKeyEvents(KeyLControl::Typed());
  SendKeyEvents(KeyLShift::Typed());
  SendKeyEvents(KeyLAlt::Typed());
  SendKeyEvents(KeyLAlt::Typed());
  SendKeyEvents(KeyLMeta::Typed());

  EXPECT_FALSE(overlay_->is_visible());
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_ALT_DOWN));
  EXPECT_EQ(STICKY_KEY_STATE_DISABLED,
            overlay_->GetModifierKeyState(ui::EF_COMMAND_DOWN));
}

TEST_P(StickyKeysOverlayTest, ModifierVisibility) {
  // All but AltGr and Mod3 should initially be visible.
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_CONTROL_DOWN));
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_SHIFT_DOWN));
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_ALT_DOWN));
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_COMMAND_DOWN));
  EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_ALTGR_DOWN));
  EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_MOD3_DOWN));
  EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_FUNCTION_DOWN));

  // Turn all modifiers on.
  auto* sticky_keys_controller = Shell::Get()->sticky_keys_controller();
  sticky_keys_controller->SetMod3AndAltGrModifiersEnabled(true, true);
  sticky_keys_controller->SetFnModifierEnabled(true);
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_CONTROL_DOWN));
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_SHIFT_DOWN));
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_ALT_DOWN));
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_COMMAND_DOWN));
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_ALTGR_DOWN));
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_MOD3_DOWN));
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_FUNCTION_DOWN));

  // Turn off Fn.
  sticky_keys_controller->SetFnModifierEnabled(false);
  EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_FUNCTION_DOWN));

  // Turn off Mod3.
  sticky_keys_controller->SetMod3AndAltGrModifiersEnabled(false, true);
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_ALTGR_DOWN));
  EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_MOD3_DOWN));

  // Turn off AltGr.
  sticky_keys_controller->SetMod3AndAltGrModifiersEnabled(true, false);
  EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_ALTGR_DOWN));
  EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_MOD3_DOWN));

  // Turn off AltGr and Mod3.
  sticky_keys_controller->SetMod3AndAltGrModifiersEnabled(false, false);
  EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_ALTGR_DOWN));
  EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_MOD3_DOWN));
}

TEST_P(EventRewriterTest, RewrittenModifier) {
  Preferences::RegisterProfilePrefs(prefs()->registry());

  // Register Control + B as an extension shortcut.
  SetExtensionCommands({{{ui::VKEY_B, ui::EF_CONTROL_DOWN}}});

  // Check that standard extension input has no rewritten modifiers.
  EXPECT_EQ(KeyB::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyB::Typed(), ui::EF_CONTROL_DOWN));

  // Remap Control -> Alt.
  IntegerPrefMember control;
  InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo,
                      ui::mojom::ModifierKey::kControl,
                      ui::mojom::ModifierKey::kAlt);
  // Pressing Control + B should now be remapped to Alt + B.
  EXPECT_EQ(KeyB::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyB::Typed(), ui::EF_CONTROL_DOWN));

  // Remap Alt -> Control.
  IntegerPrefMember alt;
  InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo,
                      ui::mojom::ModifierKey::kAlt,
                      ui::mojom::ModifierKey::kControl);
  // Pressing Alt + B should now be remapped to Control + B.
  EXPECT_EQ(KeyB::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyB::Typed(), ui::EF_ALT_DOWN));

  // Remove all extension shortcuts and still expect the remapping to work.
  SetExtensionCommands(std::nullopt);

  EXPECT_EQ(KeyB::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyB::Typed(), ui::EF_CONTROL_DOWN));
  EXPECT_EQ(KeyB::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyB::Typed(), ui::EF_ALT_DOWN));
}

TEST_P(EventRewriterTest, RewriteNumpadExtensionCommand) {
  // Register Control + NUMPAD1 as an extension shortcut.
  SetExtensionCommands({{{ui::VKEY_NUMPAD1, ui::EF_CONTROL_DOWN}}});
  // Check that extension shortcuts that involve numpads keys are properly
  // rewritten. Note that VKEY_END is associated with NUMPAD1 if Num Lock is
  // disabled. The result should be "NumPad 1 with Control".
  EXPECT_EQ(KeyNumpad1::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyNumpadEnd::Typed(), ui::EF_CONTROL_DOWN));

  // Remove the extension shortcut and expect the numpad event to still be
  // rewritten.
  SetExtensionCommands(std::nullopt);
  EXPECT_EQ(KeyNumpad1::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyNumpadEnd::Typed(), ui::EF_CONTROL_DOWN));
}

TEST_P(EventRewriterTest, RecordRewritingToFunctionKeys) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      {features::kInputDeviceSettingsSplit},
      {::features::kImprovedKeyboardShortcuts});

  base::HistogramTester histogram_tester;
  histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 0);

  // Search + back -> F1.
  SetUpKeyboard(kWilco1_0Keyboard);
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*SearchTopRowTranslated*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kSearchTopRowTranslated),
      0);
  EXPECT_EQ(KeyF1::Typed(),
            RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN));
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*SearchTopRowTranslated*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kSearchTopRowTranslated),
      1u);
  histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 1u);

  mojom::KeyboardSettings settings;
  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kKeyboardDeviceId))
      .WillRepeatedly(testing::Return(&settings));

  // Back + Search -> F1 + Search. Back was automatically translated to F1,
  // with search key unaffected so it should be mapped to
  // kTopRowAutoTranslated.
  settings.top_row_are_fkeys = true;
  settings.suppress_meta_fkey_rewrites = true;
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*TopRowAutoTranslated*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kTopRowAutoTranslated),
      0);
  EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN));
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*TopRowAutoTranslated*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kTopRowAutoTranslated),
      1u);
  histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 2u);

  SetUpKeyboard(kExternalGenericKeyboard);

  // F1 + search -> F1.
  settings.top_row_are_fkeys = false;
  settings.suppress_meta_fkey_rewrites = false;

  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*DirectlyWithSearch*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kDirectlyWithSearch), 0);
  // The keyboard sends F1 + search and the result is F1 only without search so
  // it should be mapped to kDirectlyWithSearch.
  EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN));
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*DirectlyWithSearch*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kDirectlyWithSearch),
      1u);
  histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 3u);

  // No change.
  settings.top_row_are_fkeys = true;
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*DirectlyFromKeyboard*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kDirectlyFromKeyboard),
      0);
  EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed()));
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*DirectlyFromKeyboard*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kDirectlyFromKeyboard),
      1u);
  histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 4u);

  // Search + number.
  SetUpKeyboard(kInternalChromeKeyboard);
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*SearchDigitTranslated*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kSearchDigitTranslated),
      0);
  EXPECT_EQ(KeyF1::Typed(),
            RunRewriter(KeyDigit1::Typed(), ui::EF_COMMAND_DOWN));
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*SearchDigitTranslated*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kSearchDigitTranslated),
      1u);
  histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 5u);

  // F1 + search to F1 + search
  settings.suppress_meta_fkey_rewrites = true;
  EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN));
  histogram_tester.ExpectBucketCount(
      "ChromeOS.Inputs.Keyboard.F1Pressed",
      /*DirectlyFromKeyboard*/
      static_cast<int>(ui::InputKeyEventToFunctionKey::kDirectlyFromKeyboard),
      2u);
  histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 6u);
}

TEST_P(EventRewriterTest, AltgrLatch) {
  // TODO(b/331906341): Consider to use real latvian layout.
  keyboard_layout_engine_->SetCustomLookupTableForTesting({
      {ui::DomCode::QUOTE, ui::DomKey::ALT_GRAPH_LATCH,
       ui::DomKey::FromCharacter(u'"'), ui::VKEY_ALTGR},
  });

  // Use fake latvian quote key.
  using KeyLatvianQuote =
      TestKey<ui::DomCode::QUOTE, ui::DomKey::ALT_GRAPH_LATCH, ui::VKEY_ALTGR,
              ui::EF_ALTGR_DOWN>;
  EXPECT_EQ(std::vector<TestKeyEvent>(
                {{ui::EventType::kKeyPressed, ui::DomCode::QUOTE,
                  ui::DomKey::ALT_GRAPH, ui::VKEY_ALTGR, ui::EF_ALTGR_DOWN},
                 // EF_ALTGR_DOWN is still here, because it's latched.
                 {ui::EventType::kKeyReleased, ui::DomCode::QUOTE,
                  ui::DomKey::ALT_GRAPH, ui::VKEY_ALTGR, ui::EF_ALTGR_DOWN}}),
            SendKeyEvents(KeyLatvianQuote::Typed()));
  EXPECT_EQ(std::vector({KeyA::Pressed(ui::EF_ALTGR_DOWN), KeyA::Released()}),
            SendKeyEvents(KeyA::Typed()));

  // Hold the quote.
  EXPECT_EQ(std::vector<TestKeyEvent>(
                {{ui::EventType::kKeyPressed, ui::DomCode::QUOTE,
                  ui::DomKey::ALT_GRAPH, ui::VKEY_ALTGR, ui::EF_ALTGR_DOWN}}),
            SendKeyEvent(KeyLatvianQuote::Pressed()));

  // Type A followed by C.
  EXPECT_EQ(KeyA::Typed(ui::EF_ALTGR_DOWN),
            SendKeyEvents(KeyA::Typed(ui::EF_ALTGR_DOWN)));
  EXPECT_EQ(KeyC::Typed(ui::EF_ALTGR_DOWN),
            SendKeyEvents(KeyC::Typed(ui::EF_ALTGR_DOWN)));

  // Release the quote, where EF_ALTGR_DOWN should not be set.
  EXPECT_EQ(std::vector<TestKeyEvent>(
                {{ui::EventType::kKeyReleased, ui::DomCode::QUOTE,
                  ui::DomKey::ALT_GRAPH, ui::VKEY_ALTGR}}),
            SendKeyEvent(KeyLatvianQuote::Released()));
}

TEST_P(EventRewriterTest, SixPackRemappingsFnBased) {
  if (!features::IsModifierSplitEnabled()) {
    GTEST_SKIP() << "Test is only valid with the modifier split flag enabled";
  }

  SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard);

  // Test each case while applying additional flags to confirm flags get
  // properly applied to rewritten events.
  for (const auto flag : {ui::EF_NONE, ui::EF_COMMAND_DOWN, ui::EF_CONTROL_DOWN,
                          ui::EF_ALT_DOWN, ui::EF_SHIFT_DOWN}) {
    EXPECT_EQ(KeyDelete::Typed(flag),
              RunRewriter(KeyBackspace::Typed(), ui::EF_FUNCTION_DOWN | flag));
    EXPECT_EQ(KeyHome::Typed(flag),
              RunRewriter(KeyArrowLeft::Typed(), ui::EF_FUNCTION_DOWN | flag));
    EXPECT_EQ(KeyEnd::Typed(flag),
              RunRewriter(KeyArrowRight::Typed(), ui::EF_FUNCTION_DOWN | flag));
    EXPECT_EQ(KeyPageUp::Typed(flag),
              RunRewriter(KeyArrowUp::Typed(), ui::EF_FUNCTION_DOWN | flag));
    EXPECT_EQ(KeyPageDown::Typed(flag),
              RunRewriter(KeyArrowDown::Typed(), ui::EF_FUNCTION_DOWN | flag));
  }
}

TEST_P(EventRewriterTest, NotifyShortcutEventRewriteBlockedByFnKey) {
  if (!features::IsModifierSplitEnabled()) {
    GTEST_SKIP() << "Test is only valid with the modifier split flag enabled";
  }

  AnchoredNudgeManagerImpl* nudge_manager =
      Shell::Get()->anchored_nudge_manager();
  ASSERT_TRUE(nudge_manager);
  SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard);
  EXPECT_EQ(KeyArrowLeft::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyArrowLeft::Typed(), ui::EF_COMMAND_DOWN));

  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId));
  nudge_manager->Cancel(kSixPackKeyNoMatchNudgeId);

  // Set the scan code so the key event is recognized as top row key.
  std::vector<TestKeyEvent> key_events;
  for (auto event : KeyF1::Typed()) {
    event.scan_code = 1;
    key_events.push_back(std::move(event));
  }

  std::vector<TestKeyEvent> expected_events;
  for (auto event : KeyF1::Typed(ui::EF_COMMAND_DOWN)) {
    event.scan_code = 1;
    expected_events.push_back(std::move(event));
  }

  EXPECT_EQ(expected_events, RunRewriter(key_events, ui::EF_COMMAND_DOWN));
  EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kTopRowKeyNoMatchNudgeId));
  nudge_manager->Cancel(kTopRowKeyNoMatchNudgeId);
}

TEST_P(EventRewriterTest, CapsLockRemappingFnBased) {
  if (!features::IsModifierSplitEnabled()) {
    GTEST_SKIP() << "Test is only valid with the modifier split flag enabled";
  }

  SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard);

  for (const auto flag :
       {ui::EF_NONE, ui::EF_CONTROL_DOWN, ui::EF_SHIFT_DOWN, ui::EF_ALT_DOWN}) {
    EXPECT_EQ(KeyCapsLock::Typed(flag | ui::EF_CAPS_LOCK_ON),
              RunRewriter(KeyRightAlt::Typed(ui::EF_NONE, {kPropertyRightAlt}),
                          ui::EF_FUNCTION_DOWN | flag));
    EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled());

    EXPECT_EQ(KeyCapsLock::Typed(flag),
              RunRewriter(
                  KeyRightAlt::Typed(ui::EF_CAPS_LOCK_ON, {kPropertyRightAlt}),
                  ui::EF_FUNCTION_DOWN | flag));
    EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled());
  }
}

TEST_P(EventRewriterTest, FnDiscarded) {
  if (!features::IsModifierSplitEnabled()) {
    GTEST_SKIP() << "Test is only valid with the modifier split flag enabled";
  }

  SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard);

  EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed(), ui::EF_FUNCTION_DOWN));
  EXPECT_EQ(
      KeyA::Typed(ui::EF_CONTROL_DOWN),
      RunRewriter(KeyA::Typed(), ui::EF_FUNCTION_DOWN | ui::EF_CONTROL_DOWN));

  EXPECT_EQ(std::vector<TestKeyEvent>(), RunRewriter(KeyFunction::Typed()));
}

// Tests that when you press Fn -> Right Alt -> Release Fn -> Release Right Alt
// that the release of right alt is remapped to CapsLock to match the remapped
// press.
TEST_P(EventRewriterTest, CapsLockRemappingFnBasedReleaseOrdering) {
  if (!features::IsModifierSplitEnabled()) {
    GTEST_SKIP() << "Test is only valid with the modifier split flag enabled";
  }

  SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard);

  EXPECT_EQ(std::vector<TestKeyEvent>(),
            RunRewriter(std::vector<TestKeyEvent>{KeyFunction::Pressed()}));
  EXPECT_EQ(
      std::vector<TestKeyEvent>({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}),
      RunRewriter(std::vector<TestKeyEvent>{
          KeyRightAlt::Pressed(ui::EF_FUNCTION_DOWN, {kPropertyRightAlt})}));
  EXPECT_EQ(std::vector<TestKeyEvent>(),
            RunRewriter(std::vector<TestKeyEvent>{
                KeyFunction::Released(ui::EF_CAPS_LOCK_ON)}));
  EXPECT_EQ(
      std::vector<TestKeyEvent>({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}),
      RunRewriter(std::vector<TestKeyEvent>{
          KeyRightAlt::Released(ui::EF_CAPS_LOCK_ON, {kPropertyRightAlt})}));
}

class ModifierPressedMetricsTest
    : public EventRewriterTestBase,
      public testing::WithParamInterface<
          std::tuple<bool,
                     std::tuple<TestKeyEvent,
                                ui::ModifierKeyUsageMetric,
                                std::vector<std::string>>>> {
 public:
  void SetUp() override {
    bool fix_enabled;
    auto tuple = std::tie(event_, modifier_key_usage_mapping_, key_pref_names_);
    std::tie(fix_enabled, tuple) = GetParam();
    std::vector<base::test::FeatureRef> enabled_features, disabled_features;
    (fix_enabled ? enabled_features : disabled_features)
        .push_back(ash::features::kEnableKeyboardRewriterFix);
    disabled_features.push_back(features::kInputDeviceSettingsSplit);
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
    EventRewriterTestBase::SetUp();
  }

 protected:
  TestKeyEvent event_;
  ui::ModifierKeyUsageMetric modifier_key_usage_mapping_;
  std::vector<std::string> key_pref_names_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    ModifierPressedMetricsTest,
    testing::Combine(
        testing::Bool(),
        testing::ValuesIn(std::vector<std::tuple<TestKeyEvent,
                                                 ui::ModifierKeyUsageMetric,
                                                 std::vector<std::string>>>{
            {KeyLMeta::Pressed(),
             ui::ModifierKeyUsageMetric::kMetaLeft,
             {::prefs::kLanguageRemapSearchKeyTo,
              ::prefs::kLanguageRemapExternalCommandKeyTo,
              ::prefs::kLanguageRemapExternalMetaKeyTo}},
            {KeyRMeta::Pressed(),
             ui::ModifierKeyUsageMetric::kMetaRight,
             {::prefs::kLanguageRemapSearchKeyTo,
              ::prefs::kLanguageRemapExternalCommandKeyTo,
              ::prefs::kLanguageRemapExternalMetaKeyTo}},
            {KeyLControl::Pressed(),
             ui::ModifierKeyUsageMetric::kControlLeft,
             {::prefs::kLanguageRemapControlKeyTo}},
            {KeyRControl::Pressed(),
             ui::ModifierKeyUsageMetric::kControlRight,
             {::prefs::kLanguageRemapControlKeyTo}},
            {KeyLAlt::Pressed(),
             ui::ModifierKeyUsageMetric::kAltLeft,
             {::prefs::kLanguageRemapAltKeyTo}},
            {KeyRAlt::Pressed(),
             ui::ModifierKeyUsageMetric::kAltRight,
             {::prefs::kLanguageRemapAltKeyTo}},
            {KeyLShift::Pressed(),
             ui::ModifierKeyUsageMetric::kShiftLeft,
             // Shift keys cannot be remapped and therefore do not have a real
             // "pref" path.
             {"fakePrefPath"}},
            {KeyRShift::Pressed(),
             ui::ModifierKeyUsageMetric::kShiftRight,
             // Shift keys cannot be remapped and therefore do not have a real
             // "pref" path.
             {"fakePrefPath"}},
            {KeyCapsLock::Pressed(),
             ui::ModifierKeyUsageMetric::kCapsLock,
             {::prefs::kLanguageRemapCapsLockKeyTo}},
            {KeyBackspace::Pressed(),
             ui::ModifierKeyUsageMetric::kBackspace,
             {::prefs::kLanguageRemapBackspaceKeyTo}},
            {KeyEscape::Pressed(),
             ui::ModifierKeyUsageMetric::kEscape,
             {::prefs::kLanguageRemapEscapeKeyTo}},
            {KeyLaunchAssistant::Pressed(),
             ui::ModifierKeyUsageMetric::kAssistant,
             {::prefs::kLanguageRemapAssistantKeyTo}}})));

TEST_P(ModifierPressedMetricsTest, KeyPressedTest) {
  auto expected = event_;
  if (expected.code == ui::DomCode::CAPS_LOCK) {
    expected.flags |= ui::EF_CAPS_LOCK_ON;
  }

  base::HistogramTester histogram_tester;
  SetUpKeyboard(kInternalChromeKeyboard);
  EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal",
      modifier_key_usage_mapping_, 1);
  // Unset CapsLock for each press.
  SendKeyEvent(TestKeyEvent{ui::EventType::kKeyReleased, event_.code,
                            event_.key, event_.keycode, event_.flags,
                            event_.scan_code});
  fake_ime_keyboard_.SetCapsLockEnabled(false);

  SetUpKeyboard(kExternalChromeKeyboard);
  EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.CrOSExternal",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.CrOSExternal",
      modifier_key_usage_mapping_, 1);
  SendKeyEvent(TestKeyEvent{ui::EventType::kKeyReleased, event_.code,
                            event_.key, event_.keycode, event_.flags,
                            event_.scan_code});
  fake_ime_keyboard_.SetCapsLockEnabled(false);

  SetUpKeyboard(kExternalAppleKeyboard);
  EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.AppleExternal",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.AppleExternal",
      modifier_key_usage_mapping_, 1);
  SendKeyEvent(TestKeyEvent{ui::EventType::kKeyReleased, event_.code,
                            event_.key, event_.keycode, event_.flags,
                            event_.scan_code});
  fake_ime_keyboard_.SetCapsLockEnabled(false);

  SetUpKeyboard(kExternalGenericKeyboard);
  EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.External",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.External",
      modifier_key_usage_mapping_, 1);
  SendKeyEvent(TestKeyEvent{ui::EventType::kKeyReleased, event_.code,
                            event_.key, event_.keycode, event_.flags,
                            event_.scan_code});
  fake_ime_keyboard_.SetCapsLockEnabled(false);
}

TEST_P(ModifierPressedMetricsTest, KeyPressedWithRemappingToBackspaceTest) {
  if (event_.keycode == ui::VKEY_SHIFT) {
    GTEST_SKIP() << "Shift cannot be remapped";
  }

  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::HistogramTester histogram_tester;
  for (const auto& pref_name : key_pref_names_) {
    IntegerPrefMember pref_member;
    InitModifierKeyPref(&pref_member, pref_name,
                        ui::mojom::ModifierKey::kControl,
                        ui::mojom::ModifierKey::kBackspace);
  }

  SetUpKeyboard(kInternalChromeKeyboard);
  EXPECT_EQ(std::vector({KeyBackspace::Pressed()}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal",
      ui::ModifierKeyUsageMetric::kBackspace, 1);

  SetUpKeyboard(kExternalChromeKeyboard);
  EXPECT_EQ(std::vector({KeyBackspace::Pressed()}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.CrOSExternal",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.CrOSExternal",
      ui::ModifierKeyUsageMetric::kBackspace, 1);

  SetUpKeyboard(kExternalAppleKeyboard);
  EXPECT_EQ(std::vector({KeyBackspace::Pressed()}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.AppleExternal",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.AppleExternal",
      ui::ModifierKeyUsageMetric::kBackspace, 1);

  SetUpKeyboard(kExternalGenericKeyboard);
  EXPECT_EQ(std::vector({KeyBackspace::Pressed()}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.External",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.External",
      ui::ModifierKeyUsageMetric::kBackspace, 1);
}

TEST_P(ModifierPressedMetricsTest, KeyPressedWithRemappingToControlTest) {
  if (event_.keycode == ui::VKEY_SHIFT) {
    GTEST_SKIP() << "Shift cannot be remapped";
  }

  Preferences::RegisterProfilePrefs(prefs()->registry());
  base::HistogramTester histogram_tester;

  const bool right = ui::KeycodeConverter::DomCodeToLocation(event_.code) ==
                     ui::DomKeyLocation::RIGHT;
  const ui::ModifierKeyUsageMetric remapped_modifier_key_usage_mapping =
      right ? ui::ModifierKeyUsageMetric::kControlRight
            : ui::ModifierKeyUsageMetric::kControlLeft;
  const auto control_event =
      right ? KeyRControl::Pressed() : KeyLControl::Pressed();

  for (const auto& pref_name : key_pref_names_) {
    IntegerPrefMember pref_member;
    InitModifierKeyPref(&pref_member, pref_name,
                        ui::mojom::ModifierKey::kControl,
                        ui::mojom::ModifierKey::kControl);
  }

  SetUpKeyboard(kInternalChromeKeyboard);
  EXPECT_EQ(std::vector({control_event}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal",
      remapped_modifier_key_usage_mapping, 1);

  SetUpKeyboard(kExternalChromeKeyboard);
  EXPECT_EQ(std::vector({control_event}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.CrOSExternal",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.CrOSExternal",
      remapped_modifier_key_usage_mapping, 1);

  SetUpKeyboard(kExternalAppleKeyboard);
  EXPECT_EQ(std::vector({control_event}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.AppleExternal",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.AppleExternal",
      remapped_modifier_key_usage_mapping, 1);

  SetUpKeyboard(kExternalGenericKeyboard);
  EXPECT_EQ(std::vector({control_event}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.External",
      modifier_key_usage_mapping_, 1);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.External",
      remapped_modifier_key_usage_mapping, 1);
}

TEST_P(ModifierPressedMetricsTest, KeyRepeatTest) {
  if (event_.code == ui::DomCode::CAPS_LOCK) {
    GTEST_SKIP() << "CapsLock Key will not be marked as EF_IS_REPEAT";
  }

  base::HistogramTester histogram_tester;
  // No metrics should be published if it is a repeated key.
  event_.flags |= ui::EF_IS_REPEAT;

  SetUpKeyboard(kInternalChromeKeyboard);
  EXPECT_EQ(std::vector({event_}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal",
      modifier_key_usage_mapping_, 0);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal",
      modifier_key_usage_mapping_, 0);

  SetUpKeyboard(kExternalChromeKeyboard);
  EXPECT_EQ(std::vector({event_}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.CrOSExternal",
      modifier_key_usage_mapping_, 0);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.CrOSExternal",
      modifier_key_usage_mapping_, 0);

  SetUpKeyboard(kExternalAppleKeyboard);
  EXPECT_EQ(std::vector({event_}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.AppleExternal",
      modifier_key_usage_mapping_, 0);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.AppleExternal",
      modifier_key_usage_mapping_, 0);

  SetUpKeyboard(kExternalGenericKeyboard);
  EXPECT_EQ(std::vector({event_}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.External",
      modifier_key_usage_mapping_, 0);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.External",
      modifier_key_usage_mapping_, 0);
}

TEST_P(ModifierPressedMetricsTest, KeyReleasedTest) {
  if (event_.code == ui::DomCode::CAPS_LOCK) {
    GTEST_SKIP() << "CapsLock Key will not be marked as EF_IS_REPEAT";
  }

  base::HistogramTester histogram_tester;
  // No metrics should be published if it is a repeated key.
  event_.flags |= ui::EF_IS_REPEAT;

  auto expected = event_;
  if (expected.code == ui::DomCode::CAPS_LOCK) {
    expected.flags |= ui::EF_CAPS_LOCK_ON;
  }

  SetUpKeyboard(kInternalChromeKeyboard);
  EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal",
      modifier_key_usage_mapping_, 0);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal",
      modifier_key_usage_mapping_, 0);

  SetUpKeyboard(kExternalChromeKeyboard);
  EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.CrOSExternal",
      modifier_key_usage_mapping_, 0);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.CrOSExternal",
      modifier_key_usage_mapping_, 0);

  SetUpKeyboard(kExternalAppleKeyboard);
  EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.AppleExternal",
      modifier_key_usage_mapping_, 0);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.AppleExternal",
      modifier_key_usage_mapping_, 0);

  SetUpKeyboard(kExternalGenericKeyboard);
  EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_));
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.ModifierPressed.External",
      modifier_key_usage_mapping_, 0);
  histogram_tester.ExpectUniqueSample(
      "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.External",
      modifier_key_usage_mapping_, 0);
}

class EventRewriterSixPackKeysTest
    : public EventRewriterTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  void SetUp() override {
    auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam();

    std::vector<base::test::FeatureRef> enabled_features, disabled_features;
    enabled_features.push_back(features::kInputDeviceSettingsSplit);
    enabled_features.push_back(features::kAltClickAndSixPackCustomization);
    (enable_keyboard_rewriter_fix ? enabled_features : disabled_features)
        .push_back(ash::features::kEnableKeyboardRewriterFix);
    (enable_modifier_split ? enabled_features : disabled_features)
        .push_back(ash::features::kModifierSplit);
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);

    EventRewriterTestBase::SetUp();
  }
};

INSTANTIATE_TEST_SUITE_P(All,
                         EventRewriterSixPackKeysTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

TEST_P(EventRewriterSixPackKeysTest, TestRewriteSixPackKeysSearchVariants) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  mojom::KeyboardSettings settings;
  settings.six_pack_key_remappings = ash::mojom::SixPackKeyInfo::New();
  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kKeyboardDeviceId))
      .WillRepeatedly(testing::Return(&settings));
  for (const auto& keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Search+Shift+Backspace -> Insert
    EXPECT_EQ(KeyInsert::Typed(),
              RunRewriter(KeyBackspace::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN));
    // Search+Backspace -> Delete
    EXPECT_EQ(KeyDelete::Typed(),
              RunRewriter(KeyBackspace::Typed(), ui::EF_COMMAND_DOWN));
    // Search+Up -> Prior (aka PageUp)
    EXPECT_EQ(KeyPageUp::Typed(),
              RunRewriter(KeyArrowUp::Typed(), ui::EF_COMMAND_DOWN));
    // Search+Down -> Next (aka PageDown)
    EXPECT_EQ(KeyPageDown::Typed(),
              RunRewriter(KeyArrowDown::Typed(), ui::EF_COMMAND_DOWN));
    // Search+Left -> Home
    EXPECT_EQ(KeyHome::Typed(),
              RunRewriter(KeyArrowLeft::Typed(), ui::EF_COMMAND_DOWN));
    // Search+Right -> End
    EXPECT_EQ(KeyEnd::Typed(),
              RunRewriter(KeyArrowRight::Typed(), ui::EF_COMMAND_DOWN));
    // Search+Shift+Down -> Shift+Next (aka PageDown)
    EXPECT_EQ(KeyPageDown::Typed(ui::EF_SHIFT_DOWN),
              RunRewriter(KeyArrowDown::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN));
    // Search+Ctrl+Up -> Ctrl+Prior (aka PageUp)
    EXPECT_EQ(KeyPageUp::Typed(ui::EF_CONTROL_DOWN),
              RunRewriter(KeyArrowUp::Typed(),
                          ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN));
    // Search+Alt+Left -> Alt+Home
    EXPECT_EQ(KeyHome::Typed(ui::EF_ALT_DOWN),
              RunRewriter(KeyArrowLeft::Typed(),
                          ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterSixPackKeysTest, TestRewriteSixPackKeysAltVariants) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  mojom::KeyboardSettings settings;
  settings.six_pack_key_remappings = ash::mojom::SixPackKeyInfo::New();
  settings.six_pack_key_remappings->del =
      ui::mojom::SixPackShortcutModifier::kAlt;
  settings.six_pack_key_remappings->end =
      ui::mojom::SixPackShortcutModifier::kAlt;
  settings.six_pack_key_remappings->home =
      ui::mojom::SixPackShortcutModifier::kAlt;
  settings.six_pack_key_remappings->page_down =
      ui::mojom::SixPackShortcutModifier::kAlt;
  settings.six_pack_key_remappings->page_up =
      ui::mojom::SixPackShortcutModifier::kAlt;

  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kKeyboardDeviceId))
      .WillRepeatedly(testing::Return(&settings));
  for (const auto& keyboard : kNonAppleKeyboardVariants) {
    SCOPED_TRACE(keyboard.name);
    SetUpKeyboard(keyboard);

    // Alt+Backspace -> Delete
    EXPECT_EQ(KeyDelete::Typed(),
              RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN));
    // Alt+Up -> Prior
    EXPECT_EQ(KeyPageUp::Typed(),
              RunRewriter(KeyArrowUp::Typed(), ui::EF_ALT_DOWN));
    // Alt+Down -> Next
    EXPECT_EQ(KeyPageDown::Typed(),
              RunRewriter(KeyArrowDown::Typed(), ui::EF_ALT_DOWN));
    // Ctrl+Alt+Up -> Home
    EXPECT_EQ(KeyHome::Typed(),
              RunRewriter(KeyArrowUp::Typed(),
                          ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN));
    // Ctrl+Alt+Down -> End
    EXPECT_EQ(KeyEnd::Typed(),
              RunRewriter(KeyArrowDown::Typed(),
                          ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN));
    // Ctrl+Alt+Shift+Up -> Shift+Home
    EXPECT_EQ(
        KeyHome::Typed(ui::EF_SHIFT_DOWN),
        RunRewriter(KeyArrowUp::Typed(),
                    ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN));
    // Ctrl+Alt+Search+Down -> Search+End
    EXPECT_EQ(KeyEnd::Typed(ui::EF_COMMAND_DOWN),
              RunRewriter(KeyArrowDown::Typed(), ui::EF_CONTROL_DOWN |
                                                     ui::EF_ALT_DOWN |
                                                     ui::EF_COMMAND_DOWN));
  }
}

TEST_P(EventRewriterSixPackKeysTest, TestRewriteSixPackKeysBlockedBySetting) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  mojom::KeyboardSettings settings;
  // "six pack" key settings use the search modifier by default.
  settings.six_pack_key_remappings = ash::mojom::SixPackKeyInfo::New();
  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kKeyboardDeviceId))
      .WillRepeatedly(testing::Return(&settings));
  // No rewrite should occur since the search-based rewrite is the setting for
  // the "Delete" 6-pack key.
  SetUpKeyboard(kInternalChromeKeyboard);
  EXPECT_EQ(KeyBackspace::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN));
  EXPECT_EQ(1u, message_center_.NotificationCount());
  ClearNotifications();

  settings.six_pack_key_remappings->del =
      ui::mojom::SixPackShortcutModifier::kAlt;
  // Rewrite should occur now that the alt rewrite is the current setting.
  // Alt+Backspace -> Delete
  EXPECT_EQ(KeyDelete::Typed(),
            RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN));

  settings.six_pack_key_remappings->del =
      ui::mojom::SixPackShortcutModifier::kNone;
  // No rewrite should occur since remapping a key event to the "Delete"
  // 6-pack key is disabled.
  EXPECT_EQ(KeyBackspace::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN));
  EXPECT_EQ(1u, message_center_.NotificationCount());
  ClearNotifications();
}

class EventRewriterExtendedFkeysTest
    : public EventRewriterTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  void SetUp() override {
    auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam();
    std::vector<base::test::FeatureRef> enabled_features, disabled_features;
    enabled_features.push_back(ash::features::kInputDeviceSettingsSplit);
    enabled_features.push_back(::features::kSupportF11AndF12KeyShortcuts);
    (enable_keyboard_rewriter_fix ? enabled_features : disabled_features)
        .push_back(ash::features::kEnableKeyboardRewriterFix);
    (enable_modifier_split ? enabled_features : disabled_features)
        .push_back(ash::features::kModifierSplit);
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);

    EventRewriterTestBase::SetUp();
  }
};

INSTANTIATE_TEST_SUITE_P(All,
                         EventRewriterExtendedFkeysTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

TEST_P(EventRewriterExtendedFkeysTest, TestRewriteExtendedFkeys) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  mojom::KeyboardSettings settings;
  settings.f11 = ui::mojom::ExtendedFkeysModifier::kAlt;
  settings.f12 = ui::mojom::ExtendedFkeysModifier::kShift;
  settings.top_row_are_fkeys = true;

  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kKeyboardDeviceId))
      .WillRepeatedly(testing::Return(&settings));

  SetUpKeyboard(kInternalChromeKeyboard);
  EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN));
  EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF2::Typed(), ui::EF_SHIFT_DOWN));

  settings.f11 = ui::mojom::ExtendedFkeysModifier::kCtrlShift;
  settings.f12 = ui::mojom::ExtendedFkeysModifier::kAlt;

  EXPECT_EQ(
      KeyF11::Typed(),
      RunRewriter(KeyF1::Typed(), ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN));
  EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF2::Typed(), ui::EF_ALT_DOWN));
}

TEST_P(EventRewriterExtendedFkeysTest,
       TestRewriteExtendedFkeysBlockedBySetting) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  mojom::KeyboardSettings settings;
  settings.f11 = ui::mojom::ExtendedFkeysModifier::kDisabled;
  settings.f12 = ui::mojom::ExtendedFkeysModifier::kDisabled;
  settings.top_row_are_fkeys = true;

  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kKeyboardDeviceId))
      .WillRepeatedly(testing::Return(&settings));
  SetUpKeyboard(kInternalChromeKeyboard);

  EXPECT_EQ(KeyF1::Typed(ui::EF_ALT_DOWN),
            RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN));
}

TEST_P(EventRewriterExtendedFkeysTest, TestRewriteExtendedFkeysTopRowAreFkeys) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  mojom::KeyboardSettings settings;
  settings.f11 = ui::mojom::ExtendedFkeysModifier::kAlt;
  settings.f12 = ui::mojom::ExtendedFkeysModifier::kShift;
  settings.top_row_are_fkeys = true;

  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kKeyboardDeviceId))
      .WillRepeatedly(testing::Return(&settings));
  SetUpKeyboard(kInternalChromeKeyboard);
  EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN));
  EXPECT_EQ(
      KeyF11::Typed(ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN),
      RunRewriter(KeyF1::Typed(),
                  ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN));
  EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF2::Typed(), ui::EF_SHIFT_DOWN));

  settings.top_row_are_fkeys = false;
  EXPECT_EQ(KeyF11::Typed(),
            RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN));
  EXPECT_EQ(
      KeyF12::Typed(),
      RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN));
}

class EventRewriterSettingsSplitTest
    : public EventRewriterTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  void SetUp() override {
    auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam();

    std::vector<base::test::FeatureRef> enabled_features, disabled_features;
    enabled_features.push_back(ash::features::kInputDeviceSettingsSplit);
    (enable_keyboard_rewriter_fix ? enabled_features : disabled_features)
        .push_back(ash::features::kEnableKeyboardRewriterFix);
    (enable_modifier_split ? enabled_features : disabled_features)
        .push_back(ash::features::kModifierSplit);
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);

    EventRewriterTestBase::SetUp();
  }
};

INSTANTIATE_TEST_SUITE_P(All,
                         EventRewriterSettingsSplitTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

TEST_P(EventRewriterSettingsSplitTest, TopRowAreFKeys) {
  mojom::KeyboardSettings settings;
  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kKeyboardDeviceId))
      .WillRepeatedly(testing::Return(&settings));
  SetUpKeyboard(kExternalGenericKeyboard);

  settings.top_row_are_fkeys = false;
  settings.suppress_meta_fkey_rewrites = false;

  EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyF1::Typed()));

  settings.top_row_are_fkeys = true;
  EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed()));
}

TEST_P(EventRewriterSettingsSplitTest, RewriteMetaTopRowKeyComboEvents) {
  mojom::KeyboardSettings settings;
  settings.top_row_are_fkeys = true;
  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kKeyboardDeviceId))
      .WillRepeatedly(testing::Return(&settings));
  SetUpKeyboard(kExternalGenericKeyboard);

  settings.suppress_meta_fkey_rewrites = false;
  EXPECT_EQ(KeyBrowserBack::Typed(),
            RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN));

  settings.suppress_meta_fkey_rewrites = true;
  EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN));
}

TEST_P(EventRewriterSettingsSplitTest, ModifierRemapping) {
  mojom::KeyboardSettings settings;
  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kKeyboardDeviceId))
      .WillRepeatedly(testing::Return(&settings));
  SetUpKeyboard(kExternalGenericKeyboard);

  settings.modifier_remappings = {
      {ui::mojom::ModifierKey::kAlt, ui::mojom::ModifierKey::kControl},
      {ui::mojom::ModifierKey::kMeta, ui::mojom::ModifierKey::kBackspace}};

  // Test remapping modifier keys.
  EXPECT_EQ(KeyRControl::Typed(), RunRewriter(KeyRAlt::Typed()));
  EXPECT_EQ(KeyBackspace::Typed(), RunRewriter(KeyLMeta::Typed()));
  EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLControl::Typed()));

  // Test remapping modifier flags.
  EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN));
  EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN),
            RunRewriter(KeyA::Typed(), ui::EF_CONTROL_DOWN));
}

class KeyEventRemappedToSixPackKeyTest
    : public EventRewriterTestBase,
      public testing::WithParamInterface<
          std::tuple<bool,
                     std::tuple<ui::KeyboardCode, bool, int, const char*>>> {
 public:
  void SetUp() override {
    bool fix_enabled;
    auto tuple =
        std::tie(key_code_, alt_based_, expected_pref_value_, pref_name_);
    std::tie(fix_enabled, tuple) = GetParam();
    if (fix_enabled) {
      scoped_feature_list_.InitAndEnableFeature(
          ash::features::kEnableKeyboardRewriterFix);
    } else {
      scoped_feature_list_.InitAndDisableFeature(
          ash::features::kEnableKeyboardRewriterFix);
    }
    EventRewriterTestBase::SetUp();
  }

 protected:
  ui::KeyboardCode key_code_;
  bool alt_based_;
  int expected_pref_value_;
  const char* pref_name_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    KeyEventRemappedToSixPackKeyTest,
    testing::Combine(
        testing::Bool(),
        testing::ValuesIn(
            std::vector<std::tuple<ui::KeyboardCode, bool, int, const char*>>{
                {ui::VKEY_DELETE, false, -1,
                 prefs::kKeyEventRemappedToSixPackDelete},
                {ui::VKEY_HOME, true, 1, prefs::kKeyEventRemappedToSixPackHome},
                {ui::VKEY_PRIOR, false, -1,
                 prefs::kKeyEventRemappedToSixPackPageDown},
                {ui::VKEY_END, true, 1, prefs::kKeyEventRemappedToSixPackEnd},
                {ui::VKEY_NEXT, false, -1,
                 prefs::kKeyEventRemappedToSixPackPageUp}})));

TEST_P(KeyEventRemappedToSixPackKeyTest, KeyEventRemappedTest) {
  Preferences::RegisterProfilePrefs(prefs()->registry());
  IntegerPrefMember int_pref;
  int_pref.Init(pref_name_, prefs());
  int_pref.SetValue(0);
  delegate_->RecordSixPackEventRewrite(key_code_, alt_based_);
  EXPECT_EQ(expected_pref_value_, prefs()->GetInteger(pref_name_));
}

class EventRewriterRemapToRightClickTest
    : public EventRewriterTestBase,
      public message_center::MessageCenterObserver,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  void SetUp() override {
    auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam();
    std::vector<base::test::FeatureRef> enabled_features, disabled_features;
    enabled_features.push_back(features::kInputDeviceSettingsSplit);
    enabled_features.push_back(features::kAltClickAndSixPackCustomization);
    (enable_keyboard_rewriter_fix ? enabled_features : disabled_features)
        .push_back(ash::features::kEnableKeyboardRewriterFix);
    (enable_modifier_split ? enabled_features : disabled_features)
        .push_back(ash::features::kModifierSplit);
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);

    EventRewriterTestBase::SetUp();

    Preferences::RegisterProfilePrefs(prefs()->registry());
    ui::DeviceDataManager* device_data_manager =
        ui::DeviceDataManager::GetInstance();
    std::vector<ui::TouchpadDevice> touchpad_devices(1);
    touchpad_devices[0].id = kTouchpadId1;
    static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager)
        ->OnTouchpadDevicesUpdated(touchpad_devices);

    EXPECT_CALL(*input_device_settings_controller_mock_,
                GetTouchpadSettings(kTouchpadId1))
        .WillRepeatedly(testing::Return(&settings_));

    observation_.Observe(&message_center_);
  }

  void TearDown() override {
    observation_.Reset();
    EventRewriterTestBase::TearDown();
  }

  // message_center::MessageCenterObserver:
  void OnNotificationAdded(const std::string& notification_id) override {
    ++notification_count_;
  }

  void SetSimulateRightClickSetting(
      ui::mojom::SimulateRightClickModifier modifier) {
    settings_.simulate_right_click = modifier;
  }

  int notification_count() { return notification_count_; }

 private:
  mojom::TouchpadSettings settings_;
  int notification_count_ = 0;
  base::ScopedObservation<message_center::MessageCenter,
                          message_center::MessageCenterObserver>
      observation_{this};
};

INSTANTIATE_TEST_SUITE_P(All,
                         EventRewriterRemapToRightClickTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

TEST_P(EventRewriterRemapToRightClickTest, AltClickRemappedToRightClick) {
  SetSimulateRightClickSetting(ui::mojom::SimulateRightClickModifier::kAlt);
  int flag_masks = ui::EF_ALT_DOWN | ui::EF_LEFT_MOUSE_BUTTON;

  ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
                       ui::EventTimeForNow(), flag_masks,
                       ui::EF_LEFT_MOUSE_BUTTON);
  ui::EventTestApi test_press(&press);
  test_press.set_source_device_id(kTouchpadId1);
  EXPECT_EQ(ui::EventType::kMousePressed, press.type());
  EXPECT_EQ(flag_masks, press.flags());
  const ui::MouseEvent result = RewriteMouseButtonEvent(press);
  EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags());
  EXPECT_NE(flag_masks, flag_masks & result.flags());
  EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags());
}

TEST_P(EventRewriterRemapToRightClickTest, SearchClickRemappedToRightClick) {
  SetSimulateRightClickSetting(ui::mojom::SimulateRightClickModifier::kSearch);
  int flag_masks = ui::EF_COMMAND_DOWN | ui::EF_LEFT_MOUSE_BUTTON;

  ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
                       ui::EventTimeForNow(), flag_masks,
                       ui::EF_LEFT_MOUSE_BUTTON);
  ui::EventTestApi test_press(&press);
  test_press.set_source_device_id(kTouchpadId1);
  EXPECT_EQ(ui::EventType::kMousePressed, press.type());
  EXPECT_EQ(flag_masks, press.flags());
  const ui::MouseEvent result = RewriteMouseButtonEvent(press);
  EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags());
  EXPECT_NE(flag_masks, flag_masks & result.flags());
  EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags());
}

TEST_P(EventRewriterRemapToRightClickTest, RemapToRightClickBlockedBySetting) {
  ui::DeviceDataManager* device_data_manager =
      ui::DeviceDataManager::GetInstance();
  std::vector<ui::TouchpadDevice> touchpad_devices(1);
  touchpad_devices[0].id = kTouchpadId1;
  static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager)
      ->OnTouchpadDevicesUpdated(touchpad_devices);
  SetSimulateRightClickSetting(ui::mojom::SimulateRightClickModifier::kAlt);

  {
    ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(),
                         gfx::Point(), ui::EventTimeForNow(),
                         ui::EF_COMMAND_DOWN | ui::EF_LEFT_MOUSE_BUTTON,
                         ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_press(&press);
    test_press.set_source_device_id(kTouchpadId1);
    const ui::MouseEvent result = RewriteMouseButtonEvent(press);
    EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());
    EXPECT_EQ(notification_count(), 1);
  }
  {
    SetSimulateRightClickSetting(
        ui::mojom::SimulateRightClickModifier::kSearch);
    ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(),
                         gfx::Point(), ui::EventTimeForNow(),
                         ui::EF_ALT_DOWN | ui::EF_LEFT_MOUSE_BUTTON,
                         ui::EF_LEFT_MOUSE_BUTTON);
    ui::EventTestApi test_press(&press);
    test_press.set_source_device_id(kTouchpadId1);
    const ui::MouseEvent result = RewriteMouseButtonEvent(press);
    EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & result.flags());
    EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());
    EXPECT_EQ(notification_count(), 2);
  }
}

TEST_P(EventRewriterRemapToRightClickTest, RemapToRightClickIsDisabled) {
  ui::DeviceDataManager* device_data_manager =
      ui::DeviceDataManager::GetInstance();
  std::vector<ui::TouchpadDevice> touchpad_devices(1);
  touchpad_devices[0].id = kTouchpadId1;
  static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager)
      ->OnTouchpadDevicesUpdated(touchpad_devices);
  SetSimulateRightClickSetting(ui::mojom::SimulateRightClickModifier::kNone);

  ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
                       ui::EventTimeForNow(),
                       ui::EF_COMMAND_DOWN | ui::EF_LEFT_MOUSE_BUTTON,
                       ui::EF_LEFT_MOUSE_BUTTON);
  ui::EventTestApi test_press(&press);
  test_press.set_source_device_id(kTouchpadId1);
  const ui::MouseEvent result = RewriteMouseButtonEvent(press);
  EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & result.flags());
  EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags());
  EXPECT_EQ(notification_count(), 1);
}

class FKeysRewritingPeripheralCustomizationTest
    : public EventRewriterTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  void SetUp() override {
    auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam();

    std::vector<base::test::FeatureRef> enabled_features, disabled_features;
    enabled_features.push_back(features::kInputDeviceSettingsSplit);
    enabled_features.push_back(features::kPeripheralCustomization);
    (enable_keyboard_rewriter_fix ? enabled_features : disabled_features)
        .push_back(ash::features::kEnableKeyboardRewriterFix);
    (enable_modifier_split ? enabled_features : disabled_features)
        .push_back(ash::features::kModifierSplit);
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);

    EventRewriterTestBase::SetUp();
  }

 protected:
  mojom::MouseSettings mouse_settings_;
  mojom::KeyboardSettings keyboard_settings_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         FKeysRewritingPeripheralCustomizationTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

TEST_P(FKeysRewritingPeripheralCustomizationTest, FKeysNotRewritten) {
  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetKeyboardSettings(kMouseDeviceId))
      .WillRepeatedly(testing::Return(nullptr));
  EXPECT_CALL(*input_device_settings_controller_mock_,
              GetMouseSettings(kMouseDeviceId))
      .WillRepeatedly(testing::Return(&mouse_settings_));

  SetUpKeyboard(kExternalGenericKeyboard);

  // Mice that press F-Keys do not get rewritten to actions.
  EXPECT_EQ(KeyF1::Typed(),
            RunRewriter(KeyF1::Typed(), ui::EF_NONE, kMouseDeviceId));
  EXPECT_EQ(KeyF2::Typed(),
            RunRewriter(KeyF2::Typed(), ui::EF_NONE, kMouseDeviceId));
  EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN, kMouseDeviceId));
  EXPECT_EQ(KeyF2::Typed(ui::EF_COMMAND_DOWN),
            RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN, kMouseDeviceId));

  // Keyboards that press F-Keys do get rewritten to actions.
  EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyF1::Typed()));
  EXPECT_EQ(KeyBrowserForward::Typed(), RunRewriter(KeyF2::Typed()));
  EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN));
  EXPECT_EQ(KeyF2::Typed(), RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN));
}

}  // namespace ash