blob: 8e7a5b3c1e3da52caa40c2164396483b08adff09 [file] [log] [blame]
// 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/accessibility/disable_touchpad_event_rewriter.h"
#include <memory>
#include <vector>
#include "ash/accessibility/test_event_recorder.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/accessibility_controller_enums.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/touchpad_device.h"
#include "ui/events/event_rewriter.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/test/event_generator.h"
#include "ui/wm/core/cursor_manager.h"
namespace ash {
namespace {
const int kUsbMouseDeviceId = 20;
const int kBluetoothMouseDeviceId = 25;
const int kInternalTouchpadDeviceId = 30;
const uint16_t kLogitechVID = 0x046d;
const uint16_t kMousePID = 0xb034;
ui::InputDevice GetSampleMouseUsb() {
return {kUsbMouseDeviceId, ui::INPUT_DEVICE_USB, "SampleMouseUsb"};
}
ui::InputDevice GetSampleMouseBluetooth() {
return {kBluetoothMouseDeviceId,
ui::INPUT_DEVICE_BLUETOOTH,
"SampleMouseBluetooth",
/* phys= */ "",
base::FilePath(),
kLogitechVID,
kMousePID,
/* version= */ 0};
}
ui::TouchpadDevice GetSampleTouchpadInternal() {
return {kInternalTouchpadDeviceId, ui::INPUT_DEVICE_INTERNAL, "touchpad"};
}
void SimulateOnlyInternalTouchpadConnected() {
ui::DeviceDataManagerTestApi().SetTouchpadDevices(
{GetSampleTouchpadInternal()});
}
void SimulateExternalMouseConnected() {
ui::DeviceDataManagerTestApi().SetMouseDevices(
{GetSampleMouseUsb(), GetSampleMouseBluetooth()});
SimulateOnlyInternalTouchpadConnected();
}
void SetDisableTouchpadMode(DisableTouchpadMode mode) {
PrefService* prefs =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
prefs->SetInteger(prefs::kAccessibilityDisableTrackpadMode,
static_cast<int>(mode));
}
} // namespace
class DisableTouchpadEventRewriterTest : public AshTestBase {
public:
DisableTouchpadEventRewriterTest() = default;
DisableTouchpadEventRewriterTest(const DisableTouchpadEventRewriterTest&) =
delete;
DisableTouchpadEventRewriterTest& operator=(
const DisableTouchpadEventRewriterTest&) = delete;
~DisableTouchpadEventRewriterTest() override = default;
void PressAndReleaseShiftKey() {
generator()->PressKey(ui::VKEY_SHIFT, ui::EF_NONE);
generator()->ReleaseKey(ui::VKEY_SHIFT, ui::EF_NONE);
}
void SetUp() override {
AshTestBase::SetUp();
event_rewriter_ = std::make_unique<DisableTouchpadEventRewriter>();
generator_ = AshTestBase::GetEventGenerator();
GetContext()->GetHost()->GetEventSource()->AddEventRewriter(
event_rewriter());
GetContext()->GetHost()->GetEventSource()->AddEventRewriter(
&event_recorder_);
event_rewriter()->SetEnabled(true);
}
void TearDown() override {
GetContext()->GetHost()->GetEventSource()->RemoveEventRewriter(
&event_recorder_);
GetContext()->GetHost()->GetEventSource()->RemoveEventRewriter(
event_rewriter());
event_rewriter_.reset();
generator_ = nullptr;
AshTestBase::TearDown();
}
ui::test::EventGenerator* generator() { return generator_; }
TestEventRecorder* event_recorder() { return &event_recorder_; }
DisableTouchpadEventRewriter* event_rewriter() {
return event_rewriter_.get();
}
private:
// Generates ui::Events to simulate user input.
raw_ptr<ui::test::EventGenerator> generator_ = nullptr;
// Records events delivered to the next event rewriter after
// DisableTouchpadEventRewriter.
TestEventRecorder event_recorder_;
// The DisableTouchpadEventRewriter instance.
std::unique_ptr<DisableTouchpadEventRewriter> event_rewriter_;
};
TEST_F(DisableTouchpadEventRewriterTest, KeyboardEventsNotCanceledIfDisabled) {
event_rewriter()->SetEnabled(false);
generator()->PressKey(ui::VKEY_A, ui::EF_NONE);
ASSERT_EQ(1U, event_recorder()->events().size());
ASSERT_EQ(ui::EventType::kKeyPressed,
event_recorder()->events().back()->type());
generator()->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
ASSERT_EQ(2u, event_recorder()->events().size());
ASSERT_EQ(ui::EventType::kKeyReleased,
event_recorder()->events().back()->type());
}
TEST_F(DisableTouchpadEventRewriterTest, MouseButtonsNotCanceledIfDisabled) {
event_rewriter()->SetEnabled(false);
generator()->PressLeftButton();
EXPECT_EQ(1U, event_recorder()->events().size());
EXPECT_EQ(ui::EventType::kMousePressed,
event_recorder()->events().back()->type());
generator()->ReleaseLeftButton();
EXPECT_EQ(2U, event_recorder()->events().size());
EXPECT_EQ(ui::EventType::kMouseReleased,
event_recorder()->events().back()->type());
}
TEST_F(DisableTouchpadEventRewriterTest, KeyboardEventsNotCanceled) {
generator()->PressKey(ui::VKEY_A, ui::EF_NONE);
ASSERT_EQ(1U, event_recorder()->events().size());
ASSERT_EQ(ui::EventType::kKeyPressed,
event_recorder()->events().back()->type());
generator()->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
ASSERT_EQ(2u, event_recorder()->events().size());
ASSERT_EQ(ui::EventType::kKeyReleased,
event_recorder()->events().back()->type());
}
TEST_F(DisableTouchpadEventRewriterTest, MouseButtonsCanceledInAlwaysMode) {
event_rewriter()->SetEnabled(true);
SetDisableTouchpadMode(DisableTouchpadMode::kAlways);
generator()->set_mouse_source_device_id(kInternalTouchpadDeviceId);
SimulateOnlyInternalTouchpadConnected();
generator()->PressLeftButton();
EXPECT_FALSE(Shell::Get()->cursor_manager()->IsCursorVisible());
EXPECT_EQ(0U, event_recorder()->events().size());
generator()->ReleaseLeftButton();
EXPECT_EQ(0U, event_recorder()->events().size());
}
TEST_F(DisableTouchpadEventRewriterTest, DisableAfterFiveShiftKeyPresses) {
event_rewriter()->SetEnabled(true);
int shiftKeyPressCount = 0;
// Simulate pressing and releasing the shift key 5 times.
for (int i = 0; i < 5; ++i) {
PressAndReleaseShiftKey();
generator()->AdvanceClock(base::Milliseconds(100));
++shiftKeyPressCount;
// After the 5th press, check if the rewriter is disabled.
if (shiftKeyPressCount == 5) {
EXPECT_FALSE(event_rewriter()->IsEnabled());
} else {
EXPECT_TRUE(event_rewriter()->IsEnabled());
}
}
}
TEST_F(DisableTouchpadEventRewriterTest,
ShiftPressesExceedTimeWindowStaysEnabled) {
event_rewriter()->SetEnabled(true);
// Simulate pressing and releasing the shift key 4 times.
for (int i = 0; i < 4; ++i) {
PressAndReleaseShiftKey();
generator()->AdvanceClock(base::Milliseconds(100));
EXPECT_TRUE(event_rewriter()->IsEnabled());
}
// Exceed shift key time window on final key press.
generator()->AdvanceClock(base::Seconds(3));
PressAndReleaseShiftKey();
EXPECT_TRUE(event_rewriter()->IsEnabled());
}
TEST_F(DisableTouchpadEventRewriterTest, ResetShiftKeyCountOnNonShiftKeyPress) {
event_rewriter()->SetEnabled(true);
// Simulate pressing shift key and releasing the shift key 3 times.
for (int i = 0; i < 3; ++i) {
PressAndReleaseShiftKey();
EXPECT_TRUE(event_rewriter()->IsEnabled());
}
// Press non shift key resets the shift key count.
generator()->PressKey(ui::VKEY_A, ui::EF_NONE);
generator()->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_TRUE(event_rewriter()->IsEnabled());
int shiftKeyPressCount = 0;
for (int i = 0; i < 5; ++i) {
PressAndReleaseShiftKey();
generator()->AdvanceClock(base::Milliseconds(100));
++shiftKeyPressCount;
if (shiftKeyPressCount == 5) {
EXPECT_FALSE(event_rewriter()->IsEnabled());
} else {
EXPECT_TRUE(event_rewriter()->IsEnabled());
}
}
}
TEST_F(DisableTouchpadEventRewriterTest,
InternalMouseCanceledWithExternalMouse) {
event_rewriter()->SetEnabled(true);
SetDisableTouchpadMode(DisableTouchpadMode::kOnExternalMouseConnected);
generator()->set_mouse_source_device_id(kInternalTouchpadDeviceId);
SimulateOnlyInternalTouchpadConnected();
generator()->PressLeftButton();
EXPECT_TRUE(Shell::Get()->cursor_manager()->IsCursorVisible());
EXPECT_EQ(1U, event_recorder()->events().size());
generator()->ReleaseLeftButton();
EXPECT_EQ(2U, event_recorder()->events().size());
SimulateExternalMouseConnected();
generator()->PressLeftButton();
EXPECT_FALSE(Shell::Get()->cursor_manager()->IsCursorVisible());
EXPECT_EQ(2U, event_recorder()->events().size());
generator()->ReleaseLeftButton();
EXPECT_EQ(2U, event_recorder()->events().size());
}
TEST_F(DisableTouchpadEventRewriterTest, ExternalMouseAllowedWhenConnected) {
event_rewriter()->SetEnabled(true);
SetDisableTouchpadMode(DisableTouchpadMode::kOnExternalMouseConnected);
generator()->set_mouse_source_device_id(kInternalTouchpadDeviceId);
SimulateOnlyInternalTouchpadConnected();
EXPECT_TRUE(Shell::Get()->cursor_manager()->IsCursorVisible());
generator()->PressLeftButton();
EXPECT_EQ(1U, event_recorder()->events().size());
generator()->ReleaseLeftButton();
EXPECT_EQ(2U, event_recorder()->events().size());
SimulateExternalMouseConnected();
generator()->set_mouse_source_device_id(kUsbMouseDeviceId);
generator()->PressLeftButton();
EXPECT_TRUE(Shell::Get()->cursor_manager()->IsCursorVisible());
EXPECT_EQ(3U, event_recorder()->events().size());
generator()->ReleaseLeftButton();
EXPECT_EQ(4U, event_recorder()->events().size());
}
} // namespace ash