chromium/ash/accelerators/modifier_key_combo_recorder_unittest.cc

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

#include "ash/accelerators/modifier_key_combo_recorder.h"

#include "ash/public/cpp/accelerators_util.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/test/metrics/histogram_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/ash/keyboard_capability.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/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/types/event_type.h"

namespace ash {

namespace {

using ModifierFlag = ModifierKeyComboRecorder::ModifierFlag;

}  // namespace

class ModifierKeyComboRecorderTest : public AshTestBase {
 public:
  void SetUp() override {
    AshTestBase::SetUp();
    modifier_key_combo_recorder_ = std::make_unique<ModifierKeyComboRecorder>();
    histogram_tester_ = std::make_unique<base::HistogramTester>();
  }

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

 protected:
  std::unique_ptr<ModifierKeyComboRecorder> modifier_key_combo_recorder_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;
};

TEST_F(ModifierKeyComboRecorderTest, ModifierLocation) {
  ui::KeyboardDevice keyboard(1, ui::INPUT_DEVICE_INTERNAL, "Keyboard");
  ui::DeviceDataManagerTestApi().SetKeyboardDevices({keyboard});

  ui::KeyboardCapability* keyboard_capability =
      Shell::Get()->keyboard_capability();
  ui::KeyboardCapability::KeyboardInfo keyboard_info;
  keyboard_info.device_type =
      ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
  keyboard_capability->DisableKeyboardInfoTrimmingForTesting();
  keyboard_capability->SetKeyboardInfoForTesting(keyboard,
                                                 std::move(keyboard_info));

  ui::KeyEvent control_event(ui::EventType::kKeyPressed, ui::VKEY_RCONTROL,
                             ui::DomCode::CONTROL_RIGHT, ui::EF_CONTROL_DOWN);
  control_event.set_source_device_id(keyboard.id);
  modifier_key_combo_recorder_->OnPrerewriteKeyInputEvent(control_event);
  histogram_tester_->ExpectBucketCount(
      "ChromeOS.Inputs.ModifierKeyCombo.Internal",
      static_cast<uint32_t>(AcceleratorKeyInputType::kControlRight), 1);

  ui::KeyEvent alpha_event(ui::EventType::kKeyPressed, ui::VKEY_Z,
                           ui::EF_CONTROL_DOWN);
  alpha_event.set_source_device_id(keyboard.id);
  modifier_key_combo_recorder_->OnPrerewriteKeyInputEvent(alpha_event);

  const uint32_t modifier_flag =
      1 << static_cast<uint32_t>(ModifierFlag::kControlRight);
  const uint32_t expected_hash =
      static_cast<uint32_t>(AcceleratorKeyInputType::kAlpha) +
      (modifier_flag << 16);
  histogram_tester_->ExpectBucketCount(
      "ChromeOS.Inputs.ModifierKeyCombo.Internal", expected_hash, 1);
}

TEST_F(ModifierKeyComboRecorderTest, AltGrModifier) {
  ui::KeyboardDevice keyboard(1, ui::INPUT_DEVICE_INTERNAL, "Keyboard");
  ui::DeviceDataManagerTestApi().SetKeyboardDevices({keyboard});

  ui::KeyboardCapability* keyboard_capability =
      Shell::Get()->keyboard_capability();
  ui::KeyboardCapability::KeyboardInfo keyboard_info;
  keyboard_info.device_type =
      ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
  keyboard_capability->DisableKeyboardInfoTrimmingForTesting();
  keyboard_capability->SetKeyboardInfoForTesting(keyboard,
                                                 std::move(keyboard_info));

  ui::KeyEvent altgr_event(ui::EventType::kKeyPressed, ui::VKEY_ALTGR,
                           ui::DomCode::ALT_RIGHT, ui::EF_ALTGR_DOWN);
  altgr_event.set_source_device_id(keyboard.id);
  modifier_key_combo_recorder_->OnPrerewriteKeyInputEvent(altgr_event);
  histogram_tester_->ExpectBucketCount(
      "ChromeOS.Inputs.ModifierKeyCombo.Internal",
      static_cast<uint32_t>(AcceleratorKeyInputType::kAltGr), 1);

  ui::KeyEvent alpha_event(ui::EventType::kKeyPressed, ui::VKEY_Z,
                           ui::EF_ALTGR_DOWN);
  alpha_event.set_source_device_id(keyboard.id);
  modifier_key_combo_recorder_->OnPrerewriteKeyInputEvent(alpha_event);

  const uint32_t modifier_flag = 1
                                 << static_cast<uint32_t>(ModifierFlag::kAltGr);
  const uint32_t expected_hash =
      static_cast<uint32_t>(AcceleratorKeyInputType::kAlpha) +
      (modifier_flag << 16);
  histogram_tester_->ExpectBucketCount(
      "ChromeOS.Inputs.ModifierKeyCombo.Internal", expected_hash, 1);
}

TEST_F(ModifierKeyComboRecorderTest, AlphaOrDigitKeysWithShift) {
  ui::KeyboardDevice keyboard(1, ui::INPUT_DEVICE_INTERNAL, "Keyboard");
  ui::DeviceDataManagerTestApi().SetKeyboardDevices({keyboard});

  ui::KeyboardCapability* keyboard_capability =
      Shell::Get()->keyboard_capability();
  ui::KeyboardCapability::KeyboardInfo keyboard_info;
  keyboard_info.device_type =
      ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
  keyboard_capability->DisableKeyboardInfoTrimmingForTesting();
  keyboard_capability->SetKeyboardInfoForTesting(keyboard,
                                                 std::move(keyboard_info));

  ui::KeyEvent shift_c_event(ui::EventType::kKeyPressed, ui::VKEY_C,
                             ui::EF_SHIFT_DOWN);
  shift_c_event.set_source_device_id(keyboard.id);
  modifier_key_combo_recorder_->OnPrerewriteKeyInputEvent(shift_c_event);
  // No metric should be recorded if the input was an alpha key + shift.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.ModifierKeyCombo.Internal", 0);

  ui::KeyEvent shift_nine_event(ui::EventType::kKeyPressed, ui::VKEY_9,
                                ui::EF_SHIFT_DOWN);
  shift_nine_event.set_source_device_id(keyboard.id);
  modifier_key_combo_recorder_->OnPrerewriteKeyInputEvent(shift_nine_event);
  // No metric should be recorded if the input was a digit key + shift.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.ModifierKeyCombo.Internal", 0);

  ui::KeyEvent ctrl_shift_c_event(ui::EventType::kKeyPressed, ui::VKEY_C,
                                  ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
  ctrl_shift_c_event.set_source_device_id(keyboard.id);
  modifier_key_combo_recorder_->OnPrerewriteKeyInputEvent(ctrl_shift_c_event);
  // ShiftLeft and ControlLeft are the default locations for these modifiers.
  const uint32_t modifier_flag =
      1 << static_cast<uint32_t>(ModifierFlag::kShiftLeft) |
      1 << static_cast<uint32_t>(ModifierFlag::kControlLeft);
  const uint32_t expected_hash =
      static_cast<uint32_t>(AcceleratorKeyInputType::kAlpha) +
      (modifier_flag << 16);
  // Metric should be recorded if shift and another modifier are held.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.ModifierKeyCombo.Internal", 1);
  histogram_tester_->ExpectBucketCount(
      "ChromeOS.Inputs.ModifierKeyCombo.Internal", expected_hash, 1);
}

class ModifierKeyComboRecorderParameterizedTest
    : public ModifierKeyComboRecorderTest,
      public testing::WithParamInterface<std::tuple<ui::KeyEvent, uint32_t>> {};

INSTANTIATE_TEST_SUITE_P(
    All,
    ModifierKeyComboRecorderParameterizedTest,
    testing::ValuesIn(std::vector<std::tuple<ui::KeyEvent, uint32_t>>{
        {ui::KeyEvent(ui::EventType::kKeyPressed,
                      ui::VKEY_NUMPAD0,
                      ui::EF_NONE),
         static_cast<uint32_t>(AcceleratorKeyInputType::kNumberPad)},
        {ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_HOME, ui::EF_NONE),
         static_cast<uint32_t>(AcceleratorKeyInputType::kSixPack)},
        {ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_ESCAPE, ui::EF_NONE),
         static_cast<uint32_t>(AcceleratorKeyInputType::kEscape)},
        {ui::KeyEvent(ui::EventType::kKeyPressed,
                      ui::VKEY_BROWSER_BACK,
                      ui::EF_NONE),
         static_cast<uint32_t>(AcceleratorKeyInputType::kTopRow)},
        {ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_BACK, ui::EF_NONE),
         static_cast<uint32_t>(AcceleratorKeyInputType::kBackspace)},
        {ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_UP, ui::EF_NONE),
         static_cast<uint32_t>(AcceleratorKeyInputType::kUpArrow)},
        {ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_TAB, ui::EF_NONE),
         static_cast<uint32_t>(AcceleratorKeyInputType::kTab)},
        {ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_ALTGR, ui::EF_NONE),
         static_cast<uint32_t>(AcceleratorKeyInputType::kAltGr)},
        {ui::KeyEvent(ui::EventType::kKeyPressed,
                      ui::VKEY_LWIN,
                      ui::DomCode::META_LEFT,
                      ui::EF_NONE),
         static_cast<uint32_t>(AcceleratorKeyInputType::kMetaLeft)},
        {ui::KeyEvent(ui::EventType::kKeyPressed,
                      ui::VKEY_RWIN,
                      ui::DomCode::META_RIGHT,
                      ui::EF_NONE),
         static_cast<uint32_t>(AcceleratorKeyInputType::kMetaRight)}}));

TEST_P(ModifierKeyComboRecorderParameterizedTest, InternalKeyboard) {
  auto [key_event, hash] = GetParam();

  ui::KeyboardDevice keyboard(1, ui::INPUT_DEVICE_INTERNAL, "Keyboard");
  ui::DeviceDataManagerTestApi().SetKeyboardDevices({keyboard});

  ui::KeyboardCapability* keyboard_capability =
      Shell::Get()->keyboard_capability();
  ui::KeyboardCapability::KeyboardInfo keyboard_info;
  keyboard_info.device_type =
      ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
  keyboard_capability->DisableKeyboardInfoTrimmingForTesting();
  keyboard_capability->SetKeyboardInfoForTesting(keyboard,
                                                 std::move(keyboard_info));

  key_event.set_source_device_id(keyboard.id);
  modifier_key_combo_recorder_->OnPrerewriteKeyInputEvent(key_event);
  histogram_tester_->ExpectUniqueSample(
      "ChromeOS.Inputs.ModifierKeyCombo.Internal", hash, 1);
}

TEST_P(ModifierKeyComboRecorderParameterizedTest, ExternalKeyboard) {
  auto [key_event, hash] = GetParam();

  ui::KeyboardDevice keyboard(1, ui::INPUT_DEVICE_INTERNAL, "Keyboard");
  ui::DeviceDataManagerTestApi().SetKeyboardDevices({keyboard});

  ui::KeyboardCapability* keyboard_capability =
      Shell::Get()->keyboard_capability();
  ui::KeyboardCapability::KeyboardInfo keyboard_info;
  keyboard_info.device_type =
      ui::KeyboardCapability::DeviceType::kDeviceExternalGenericKeyboard;
  keyboard_capability->SetKeyboardInfoForTesting(keyboard,
                                                 std::move(keyboard_info));

  key_event.set_source_device_id(keyboard.id);
  modifier_key_combo_recorder_->OnPrerewriteKeyInputEvent(key_event);
  histogram_tester_->ExpectUniqueSample(
      "ChromeOS.Inputs.ModifierKeyCombo.External", hash, 1);
}

TEST_P(ModifierKeyComboRecorderParameterizedTest, ExternalChromeOSKeyboard) {
  auto [key_event, hash] = GetParam();

  ui::KeyboardDevice keyboard(1, ui::INPUT_DEVICE_INTERNAL, "Keyboard");
  ui::DeviceDataManagerTestApi().SetKeyboardDevices({keyboard});

  ui::KeyboardCapability* keyboard_capability =
      Shell::Get()->keyboard_capability();
  ui::KeyboardCapability::KeyboardInfo keyboard_info;
  keyboard_info.device_type =
      ui::KeyboardCapability::DeviceType::kDeviceExternalChromeOsKeyboard;
  keyboard_capability->SetKeyboardInfoForTesting(keyboard,
                                                 std::move(keyboard_info));

  key_event.set_source_device_id(keyboard.id);
  modifier_key_combo_recorder_->OnPrerewriteKeyInputEvent(key_event);
  histogram_tester_->ExpectUniqueSample(
      "ChromeOS.Inputs.ModifierKeyCombo.CrOSExternal", hash, 1);
}

}  // namespace ash