blob: 071443dc93d01494a9bbcb625da3cabf2ddd2d3d [file] [log] [blame]
// Copyright 2020 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/events/accessibility_event_rewriter.h"
#include <memory>
#include <optional>
#include <vector>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/accessibility_event_rewriter_delegate.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/check_op.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/ime/ash/fake_ime_keyboard.h"
#include "ui/events/ash/event_rewriter_ash.h"
#include "ui/events/ash/fake_event_rewriter_ash_delegate.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/six_pack_shortcut_modifier.mojom-shared.h"
#include "ui/events/ash/pref_names.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_handler.h"
#include "ui/events/event_rewriter.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/test/test_event_rewriter.h"
#include "ui/events/types/event_type.h"
namespace ash {
namespace {
// A test implementation of the spoken feedback delegate interface.
class TestAccessibilityEventRewriterDelegate
: public AccessibilityEventRewriterDelegate {
public:
TestAccessibilityEventRewriterDelegate() = default;
TestAccessibilityEventRewriterDelegate(
const TestAccessibilityEventRewriterDelegate&) = delete;
TestAccessibilityEventRewriterDelegate& operator=(
const TestAccessibilityEventRewriterDelegate&) = delete;
~TestAccessibilityEventRewriterDelegate() override = default;
// Count of events sent to the delegate.
size_t chromevox_recorded_event_count_ = 0;
// Count of captured events sent to the delegate.
size_t chromevox_captured_event_count_ = 0;
// Last key event sent to ChromeVox.
ui::Event* GetLastChromeVoxKeyEvent() {
return last_chromevox_key_event_.get();
}
const std::vector<SwitchAccessCommand>& switch_access_commands() const {
return switch_access_commands_;
}
void ClearSwitchAccessCommands() { switch_access_commands_.clear(); }
const std::vector<MagnifierCommand>& magnifier_commands() const {
return magnifier_commands_;
}
void ClearMagnifierCommands() { magnifier_commands_.clear(); }
private:
// AccessibilityEventRewriterDelegate:
void DispatchKeyEventToChromeVox(std::unique_ptr<ui::Event> event,
bool capture) override {
chromevox_recorded_event_count_++;
if (capture)
chromevox_captured_event_count_++;
last_chromevox_key_event_ = std::move(event);
}
void DispatchMouseEvent(std::unique_ptr<ui::Event> event) override {
chromevox_recorded_event_count_++;
}
void SendSwitchAccessCommand(SwitchAccessCommand command) override {
switch_access_commands_.push_back(command);
}
void SendPointScanPoint(const gfx::PointF& point) override {}
void SendMagnifierCommand(MagnifierCommand command) override {
magnifier_commands_.push_back(command);
}
std::unique_ptr<ui::Event> last_chromevox_key_event_;
std::vector<SwitchAccessCommand> switch_access_commands_;
std::vector<MagnifierCommand> magnifier_commands_;
};
// Records all key events for testing.
class EventCapturer : public ui::EventHandler {
public:
EventCapturer() = default;
EventCapturer(const EventCapturer&) = delete;
EventCapturer& operator=(const EventCapturer&) = delete;
~EventCapturer() override = default;
void Reset() { last_key_event_.reset(); }
ui::KeyEvent* last_key_event() { return last_key_event_.get(); }
private:
void OnKeyEvent(ui::KeyEvent* event) override {
last_key_event_ = std::make_unique<ui::KeyEvent>(*event);
}
std::unique_ptr<ui::KeyEvent> last_key_event_;
};
class KeyboardModifierEventRewriterDelegate
: public ui::KeyboardModifierEventRewriter::Delegate {
public:
explicit KeyboardModifierEventRewriterDelegate(
ui::EventRewriterAsh::Delegate* delegate)
: delegate_(delegate) {}
absl::optional<ui::mojom::ModifierKey> GetKeyboardRemappedModifierValue(
int device_id,
ui::mojom::ModifierKey modifier_key,
const std::string& pref_name) const override {
return delegate_->GetKeyboardRemappedModifierValue(device_id, modifier_key,
pref_name);
}
bool RewriteModifierKeys() override {
return delegate_->RewriteModifierKeys();
}
private:
raw_ptr<ui::EventRewriterAsh::Delegate> delegate_;
};
// Common set up for AccessibilityEventRewriter related unit tests.
class AccessibilityEventRewriterTestBase : public ash::AshTestBase {
protected:
AccessibilityEventRewriterTestBase() = default;
AccessibilityEventRewriterTestBase(
const AccessibilityEventRewriterTestBase&) = delete;
AccessibilityEventRewriterTestBase& operator=(
const AccessibilityEventRewriterTestBase&) = delete;
~AccessibilityEventRewriterTestBase() override = default;
void SetUp() override {
ash::AshTestBase::SetUp();
generator_ = AshTestBase::GetEventGenerator();
accessibility_event_rewriter_ =
std::make_unique<AccessibilityEventRewriter>(
&event_rewriter_ash_, &accessibility_event_rewriter_delegate_);
GetContext()->AddPreTargetHandler(&event_capturer_);
GetAccessibilityController()->SetAccessibilityEventRewriter(
accessibility_event_rewriter_.get());
auto* event_source = GetContext()->GetHost()->GetEventSource();
if (ash::features::IsKeyboardRewriterFixEnabled()) {
event_source->AddEventRewriter(&keyboard_device_id_event_rewriter_);
event_source->AddEventRewriter(&keyboard_modifier_event_rewriter_);
}
event_source->AddEventRewriter(accessibility_event_rewriter_.get());
}
void TearDown() override {
auto* event_source = GetContext()->GetHost()->GetEventSource();
event_source->RemoveEventRewriter(accessibility_event_rewriter_.get());
if (ash::features::IsKeyboardRewriterFixEnabled()) {
event_source->RemoveEventRewriter(&keyboard_modifier_event_rewriter_);
event_source->RemoveEventRewriter(&keyboard_device_id_event_rewriter_);
}
GetAccessibilityController()->SetAccessibilityEventRewriter(nullptr);
GetContext()->RemovePreTargetHandler(&event_capturer_);
accessibility_event_rewriter_.reset();
generator_ = nullptr;
ash::AshTestBase::TearDown();
}
protected:
EventCapturer& event_capturer() { return event_capturer_; }
TestAccessibilityEventRewriterDelegate&
accessibility_event_rewriter_delegate() {
return accessibility_event_rewriter_delegate_;
}
AccessibilityEventRewriter& accessibility_event_rewriter() {
return *accessibility_event_rewriter_;
}
ui::test::EventGenerator& generator() { return *generator_; }
AccessibilityController* GetAccessibilityController() {
return Shell::Get()->accessibility_controller();
}
void SetModifierRemapping(const std::string& pref_name,
ui::mojom::ModifierKey value) {
event_rewriter_ash_delegate_.SetModifierRemapping(pref_name, value);
}
private:
EventCapturer event_capturer_;
ui::test::FakeEventRewriterAshDelegate event_rewriter_ash_delegate_;
std::unique_ptr<ui::KeyboardCapability> keyboard_capability_{
ui::KeyboardCapability::CreateStubKeyboardCapability()};
input_method::FakeImeKeyboard fake_ime_keyboard_;
ui::KeyboardDeviceIdEventRewriter keyboard_device_id_event_rewriter_{
keyboard_capability_.get()};
ui::KeyboardModifierEventRewriter keyboard_modifier_event_rewriter_{
std::make_unique<KeyboardModifierEventRewriterDelegate>(
&event_rewriter_ash_delegate_),
keyboard_capability_.get(), &fake_ime_keyboard_};
ui::EventRewriterAsh event_rewriter_ash_{&event_rewriter_ash_delegate_,
keyboard_capability_.get(), nullptr,
false, &fake_ime_keyboard_};
TestAccessibilityEventRewriterDelegate accessibility_event_rewriter_delegate_;
std::unique_ptr<AccessibilityEventRewriter> accessibility_event_rewriter_;
// Generates ui::Events from simulated user input.
raw_ptr<ui::test::EventGenerator> generator_ = nullptr;
};
} // namespace
class ChromeVoxAccessibilityEventRewriterTest
: public AccessibilityEventRewriterTestBase,
public testing::WithParamInterface<bool> {
public:
ChromeVoxAccessibilityEventRewriterTest() {
if (GetParam()) {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kEnableKeyboardRewriterFix);
} else {
scoped_feature_list_.InitAndDisableFeature(
ash::features::kEnableKeyboardRewriterFix);
}
}
void SetUp() override {
AccessibilityEventRewriterTestBase::SetUp();
GetContext()->GetHost()->GetEventSource()->AddEventRewriter(
&event_recorder_);
}
void TearDown() override {
GetContext()->GetHost()->GetEventSource()->RemoveEventRewriter(
&event_recorder_);
AccessibilityEventRewriterTestBase::TearDown();
}
ui::test::TestEventRewriter& event_recorder() { return event_recorder_; }
size_t delegate_chromevox_recorded_event_count() {
return accessibility_event_rewriter_delegate()
.chromevox_recorded_event_count_;
}
size_t delegate_chromevox_captured_event_count() {
return accessibility_event_rewriter_delegate()
.chromevox_captured_event_count_;
}
void SetDelegateChromeVoxCaptureAllKeys(bool value) {
accessibility_event_rewriter().set_chromevox_capture_all_keys(value);
}
void ExpectCounts(size_t expected_recorded_count,
size_t expected_delegate_count,
size_t expected_captured_count) {
EXPECT_EQ(expected_recorded_count,
static_cast<size_t>(event_recorder().events_seen()));
EXPECT_EQ(expected_delegate_count,
delegate_chromevox_recorded_event_count());
EXPECT_EQ(expected_captured_count,
delegate_chromevox_captured_event_count());
}
bool RewriteEventForChromeVox(
const ui::Event& event,
const AccessibilityEventRewriter::Continuation continuation) {
return accessibility_event_rewriter().RewriteEventForChromeVox(
event, continuation);
}
ui::KeyEvent* GetLastChromeVoxKeyEvent() {
if (accessibility_event_rewriter_delegate().GetLastChromeVoxKeyEvent()) {
return accessibility_event_rewriter_delegate()
.GetLastChromeVoxKeyEvent()
->AsKeyEvent();
}
return nullptr;
}
void SetRemapPositionalKeys(bool value) {
accessibility_event_rewriter()
.try_rewriting_positional_keys_for_chromevox_ = value;
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
// Records events delivered to the next event rewriter after spoken feedback.
ui::test::TestEventRewriter event_recorder_;
};
INSTANTIATE_TEST_SUITE_P(All,
ChromeVoxAccessibilityEventRewriterTest,
testing::Bool());
// The delegate should not intercept events when spoken feedback is disabled.
TEST_P(ChromeVoxAccessibilityEventRewriterTest, EventsNotConsumedWhenDisabled) {
AccessibilityController* controller = GetAccessibilityController();
EXPECT_FALSE(controller->spoken_feedback().enabled());
generator().PressKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(1, event_recorder().events_seen());
EXPECT_EQ(0U, delegate_chromevox_recorded_event_count());
generator().ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(2, event_recorder().events_seen());
EXPECT_EQ(0U, delegate_chromevox_recorded_event_count());
generator().ClickLeftButton();
EXPECT_EQ(4, event_recorder().events_seen());
EXPECT_EQ(0U, delegate_chromevox_recorded_event_count());
generator().GestureTapAt(gfx::Point());
EXPECT_EQ(6, event_recorder().events_seen());
EXPECT_EQ(0U, delegate_chromevox_recorded_event_count());
}
// The delegate should intercept key events when spoken feedback is enabled.
TEST_P(ChromeVoxAccessibilityEventRewriterTest, KeyEventsConsumedWhenEnabled) {
AccessibilityController* controller = GetAccessibilityController();
controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
EXPECT_TRUE(controller->spoken_feedback().enabled());
generator().PressKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(1, event_recorder().events_seen());
EXPECT_EQ(1U, delegate_chromevox_recorded_event_count());
EXPECT_EQ(0U, delegate_chromevox_captured_event_count());
generator().ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(2, event_recorder().events_seen());
EXPECT_EQ(2U, delegate_chromevox_recorded_event_count());
EXPECT_EQ(0U, delegate_chromevox_captured_event_count());
generator().ClickLeftButton();
EXPECT_EQ(4, event_recorder().events_seen());
EXPECT_EQ(2U, delegate_chromevox_recorded_event_count());
EXPECT_EQ(0U, delegate_chromevox_captured_event_count());
generator().GestureTapAt(gfx::Point());
EXPECT_EQ(6, event_recorder().events_seen());
EXPECT_EQ(2U, delegate_chromevox_recorded_event_count());
EXPECT_EQ(0U, delegate_chromevox_captured_event_count());
}
// Asynchronously unhandled events should be sent to subsequent rewriters.
TEST_P(ChromeVoxAccessibilityEventRewriterTest,
UnhandledEventsSentToOtherRewriters) {
// Before it can forward unhandled events, AccessibilityEventRewriter
// must have seen at least one event in the first place.
generator().PressKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(1, event_recorder().events_seen());
generator().ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(2, event_recorder().events_seen());
accessibility_event_rewriter().OnUnhandledSpokenFeedbackEvent(
std::make_unique<ui::KeyEvent>(ui::ET_KEY_PRESSED, ui::VKEY_A,
ui::EF_NONE));
EXPECT_EQ(3, event_recorder().events_seen());
accessibility_event_rewriter().OnUnhandledSpokenFeedbackEvent(
std::make_unique<ui::KeyEvent>(ui::ET_KEY_RELEASED, ui::VKEY_A,
ui::EF_NONE));
EXPECT_EQ(4, event_recorder().events_seen());
}
TEST_P(ChromeVoxAccessibilityEventRewriterTest,
KeysNotEatenWithChromeVoxDisabled) {
AccessibilityController* controller = GetAccessibilityController();
EXPECT_FALSE(controller->spoken_feedback().enabled());
// Send Search+Shift+Right.
generator().PressKey(ui::VKEY_LWIN, ui::EF_COMMAND_DOWN);
EXPECT_EQ(1, event_recorder().events_seen());
generator().PressKey(ui::VKEY_SHIFT, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
EXPECT_EQ(2, event_recorder().events_seen());
// Mock successful commands lookup and dispatch; shouldn't matter either way.
generator().PressKey(ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
EXPECT_EQ(3, event_recorder().events_seen());
// Released keys shouldn't get eaten.
generator().ReleaseKey(ui::VKEY_RIGHT,
ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
generator().ReleaseKey(ui::VKEY_SHIFT, ui::EF_COMMAND_DOWN);
generator().ReleaseKey(ui::VKEY_LWIN, 0);
EXPECT_EQ(6, event_recorder().events_seen());
// Try releasing more keys.
generator().ReleaseKey(ui::VKEY_RIGHT, 0);
generator().ReleaseKey(ui::VKEY_SHIFT, 0);
generator().ReleaseKey(ui::VKEY_LWIN, 0);
EXPECT_EQ(9, event_recorder().events_seen());
EXPECT_EQ(0U, delegate_chromevox_recorded_event_count());
}
TEST_P(ChromeVoxAccessibilityEventRewriterTest, KeyEventsCaptured) {
AccessibilityController* controller = GetAccessibilityController();
controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
EXPECT_TRUE(controller->spoken_feedback().enabled());
// Initialize expected counts as variables for easier maintaiblity.
size_t recorded_count = 0;
size_t delegate_count = 0;
size_t captured_count = 0;
// Anything with Search gets captured.
generator().PressKey(ui::VKEY_LWIN, ui::EF_COMMAND_DOWN);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
generator().ReleaseKey(ui::VKEY_LWIN, ui::EF_COMMAND_DOWN);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
// Tab never gets captured.
generator().PressKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
generator().ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
// A client requested capture of all keys.
SetDelegateChromeVoxCaptureAllKeys(true);
generator().PressKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
generator().ReleaseKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
// Tab never gets captured even with explicit client request for all keys.
generator().PressKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
generator().ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
// A client requested to not capture all keys.
SetDelegateChromeVoxCaptureAllKeys(false);
generator().PressKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
generator().ReleaseKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
}
TEST_P(ChromeVoxAccessibilityEventRewriterTest,
KeyEventsCapturedWithModifierRemapping) {
AccessibilityController* controller = GetAccessibilityController();
controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
EXPECT_TRUE(controller->spoken_feedback().enabled());
// Initialize expected counts as variables for easier maintaiblity.
size_t recorded_count = 0;
size_t delegate_count = 0;
size_t captured_count = 0;
// Map Control key to Search.
SetModifierRemapping(prefs::kLanguageRemapControlKeyTo,
ui::mojom::ModifierKey::kMeta);
// Anything with Search gets captured.
generator().PressKey(ui::VKEY_CONTROL, ui::EF_CONTROL_DOWN);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
// EventRewriterAsh actually omits the modifier flag.
generator().ReleaseKey(ui::VKEY_CONTROL, 0);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
// Search itself should also work.
generator().PressKey(ui::VKEY_LWIN, ui::EF_COMMAND_DOWN);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
generator().ReleaseKey(ui::VKEY_LWIN, 0);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
// Remapping should have no effect on all other expectations.
// Tab never gets captured.
generator().PressKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
generator().ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
// A client requested capture of all keys.
SetDelegateChromeVoxCaptureAllKeys(true);
generator().PressKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
generator().ReleaseKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
// Tab never gets captured even with explicit client request for all keys.
generator().PressKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
generator().ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
// A client requested to not capture all keys.
SetDelegateChromeVoxCaptureAllKeys(false);
generator().PressKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
generator().ReleaseKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
}
TEST_P(ChromeVoxAccessibilityEventRewriterTest,
PositionalInputMethodKeysMightBeRewritten) {
AccessibilityController* controller = GetAccessibilityController();
controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
EXPECT_TRUE(controller->spoken_feedback().enabled());
// Ensure remapping of positional keys (back to their absolute key codes) is
// in effect.
SetRemapPositionalKeys(true);
// Bracket right is a dom code which always causes an absolute key code to be
// sent to ChromeVox. The key code below is irrelevant.
ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A,
ui::DomCode::BRACKET_RIGHT, ui::EF_NONE);
AccessibilityEventRewriter::Continuation continuation;
RewriteEventForChromeVox(key_event, continuation);
auto* last_key_event = GetLastChromeVoxKeyEvent();
ASSERT_TRUE(last_key_event);
// This is key cap label "]" in a English input method.
EXPECT_EQ(ui::VKEY_OEM_6, last_key_event->key_code());
// Sanity check the flag, when off, using the same key event.
SetRemapPositionalKeys(false);
RewriteEventForChromeVox(key_event, continuation);
last_key_event = GetLastChromeVoxKeyEvent();
ASSERT_TRUE(last_key_event);
// Unmodified.
EXPECT_EQ(ui::VKEY_A, last_key_event->key_code());
}
class SwitchAccessAccessibilityEventRewriterTest
: public AccessibilityEventRewriterTestBase,
public testing::WithParamInterface<bool> {
public:
SwitchAccessAccessibilityEventRewriterTest() {
if (GetParam()) {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kEnableKeyboardRewriterFix);
} else {
scoped_feature_list_.InitAndDisableFeature(
ash::features::kEnableKeyboardRewriterFix);
}
}
void SetUp() override {
AccessibilityEventRewriterTestBase::SetUp();
// This test triggers a resize of WindowTreeHost, which will end up
// throttling events. set_throttle_input_on_resize_for_testing() disables
// this.
aura::Env::GetInstance()->set_throttle_input_on_resize_for_testing(false);
GetAccessibilityController()->switch_access().SetEnabled(true);
std::vector<ui::KeyboardDevice> keyboards;
ui::DeviceDataManagerTestApi device_data_test_api;
keyboards.emplace_back(1, ui::INPUT_DEVICE_INTERNAL, "");
keyboards.emplace_back(2, ui::INPUT_DEVICE_USB, "");
keyboards.emplace_back(3, ui::INPUT_DEVICE_BLUETOOTH, "");
keyboards.emplace_back(4, ui::INPUT_DEVICE_UNKNOWN, "");
device_data_test_api.SetKeyboardDevices(keyboards);
}
void SetKeyCodesForSwitchAccessCommand(
std::map<int, std::set<std::string>> key_codes,
SwitchAccessCommand command) {
accessibility_event_rewriter().SetKeyCodesForSwitchAccessCommand(key_codes,
command);
}
const std::map<int, std::set<ui::InputDeviceType>> GetKeyCodesToCapture() {
return accessibility_event_rewriter()
.switch_access_key_codes_to_capture_for_test();
}
const std::map<int, SwitchAccessCommand> GetCommandForKeyCodeMap() {
return accessibility_event_rewriter()
.key_code_to_switch_access_command_map_for_test();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All,
SwitchAccessAccessibilityEventRewriterTest,
testing::Bool());
TEST_P(SwitchAccessAccessibilityEventRewriterTest, CaptureSpecifiedKeys) {
// Set keys for Switch Access to capture.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_1, {kSwitchAccessInternalDevice}},
{ui::VKEY_2, {kSwitchAccessUsbDevice}}},
SwitchAccessCommand::kSelect);
EXPECT_FALSE(event_capturer().last_key_event());
// Press 1 from the internal keyboard.
generator().PressKey(ui::VKEY_1, ui::EF_NONE, 1 /* keyboard id */);
generator().ReleaseKey(ui::VKEY_1, ui::EF_NONE, 1 /* keyboard id */);
// The event was captured by AccessibilityEventRewriter.
EXPECT_FALSE(event_capturer().last_key_event());
EXPECT_EQ(
SwitchAccessCommand::kSelect,
accessibility_event_rewriter_delegate().switch_access_commands().back());
accessibility_event_rewriter_delegate().ClearSwitchAccessCommands();
// Press 1 from the bluetooth keyboard.
generator().PressKey(ui::VKEY_1, ui::EF_NONE, 3 /* keyboard id */);
generator().ReleaseKey(ui::VKEY_1, ui::EF_NONE, 3 /* keyboard id */);
// The event was not captured by AccessibilityEventRewriter.
EXPECT_TRUE(event_capturer().last_key_event());
EXPECT_EQ(
0u,
accessibility_event_rewriter_delegate().switch_access_commands().size());
// Press the "2" key.
generator().PressKey(ui::VKEY_2, ui::EF_NONE, 2 /* keyboard id */);
generator().ReleaseKey(ui::VKEY_2, ui::EF_NONE, 2 /* keyboard id */);
// The event was captured by AccessibilityEventRewriter.
EXPECT_TRUE(event_capturer().last_key_event());
EXPECT_EQ(
SwitchAccessCommand::kSelect,
accessibility_event_rewriter_delegate().switch_access_commands().back());
accessibility_event_rewriter_delegate().ClearSwitchAccessCommands();
// Press the "3" key.
generator().PressKey(ui::VKEY_3, ui::EF_NONE, 1 /* keyboard id */);
generator().ReleaseKey(ui::VKEY_3, ui::EF_NONE, 1 /* keyboard id */);
// The event was not captured by AccessibilityEventRewriter.
EXPECT_TRUE(event_capturer().last_key_event());
EXPECT_EQ(
0u,
accessibility_event_rewriter_delegate().switch_access_commands().size());
}
TEST_P(SwitchAccessAccessibilityEventRewriterTest,
KeysNoLongerCaptureAfterUpdate) {
// Set Switch Access to capture the keys {1, 2, 3}.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_1, {kSwitchAccessInternalDevice}},
{ui::VKEY_2, {kSwitchAccessInternalDevice}},
{ui::VKEY_3, {kSwitchAccessInternalDevice}}},
SwitchAccessCommand::kSelect);
EXPECT_FALSE(event_capturer().last_key_event());
// Press the "1" key.
generator().PressKey(ui::VKEY_1, ui::EF_NONE, 1 /* keyboard id */);
generator().ReleaseKey(ui::VKEY_1, ui::EF_NONE, 1 /* keyboard id */);
// The event was captured by AccessibilityEventRewriter.
EXPECT_FALSE(event_capturer().last_key_event());
EXPECT_EQ(
SwitchAccessCommand::kSelect,
accessibility_event_rewriter_delegate().switch_access_commands().back());
// Update the Switch Access keys to capture {2, 3, 4}.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_2, {kSwitchAccessInternalDevice}},
{ui::VKEY_3, {kSwitchAccessInternalDevice}},
{ui::VKEY_4, {kSwitchAccessInternalDevice}}},
SwitchAccessCommand::kSelect);
// Press the "1" key.
generator().PressKey(ui::VKEY_1, ui::EF_NONE, 1 /* keyboard id */);
generator().ReleaseKey(ui::VKEY_1, ui::EF_NONE, 1 /* keyboard id */);
// We received a new event.
// The event was NOT captured by AccessibilityEventRewriter.
EXPECT_TRUE(event_capturer().last_key_event());
EXPECT_FALSE(event_capturer().last_key_event()->handled());
// Press the "4" key.
event_capturer().Reset();
generator().PressKey(ui::VKEY_4, ui::EF_NONE, 1 /* keyboard id */);
generator().ReleaseKey(ui::VKEY_4, ui::EF_NONE, 1 /* keyboard id */);
// The event was captured by AccessibilityEventRewriter.
EXPECT_FALSE(event_capturer().last_key_event());
EXPECT_EQ(
SwitchAccessCommand::kSelect,
accessibility_event_rewriter_delegate().switch_access_commands().back());
}
TEST_P(SwitchAccessAccessibilityEventRewriterTest,
SetKeyCodesForSwitchAccessCommand) {
// Both the key codes to capture and the command map should be empty.
EXPECT_EQ(0u, GetKeyCodesToCapture().size());
EXPECT_EQ(0u, GetCommandForKeyCodeMap().size());
// Set key codes for Select command.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_0, {kSwitchAccessInternalDevice}},
{ui::VKEY_S, {kSwitchAccessInternalDevice}}},
SwitchAccessCommand::kSelect);
// Check that values are added to both data structures.
std::map<int, std::set<ui::InputDeviceType>> kc_to_capture =
GetKeyCodesToCapture();
EXPECT_EQ(2u, kc_to_capture.size());
EXPECT_EQ(1u, kc_to_capture.count(48));
EXPECT_EQ(1u, kc_to_capture.count(83));
std::map<int, SwitchAccessCommand> command_map = GetCommandForKeyCodeMap();
EXPECT_EQ(2u, command_map.size());
EXPECT_EQ(SwitchAccessCommand::kSelect, command_map.at(48));
EXPECT_EQ(SwitchAccessCommand::kSelect, command_map.at(83));
// Set key codes for the Next command.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_1, {kSwitchAccessInternalDevice}},
{ui::VKEY_N, {kSwitchAccessInternalDevice}}},
SwitchAccessCommand::kNext);
// Check that the new values are added and old values are not changed.
kc_to_capture = GetKeyCodesToCapture();
EXPECT_EQ(4u, kc_to_capture.size());
EXPECT_EQ(1u, kc_to_capture.count(49));
EXPECT_EQ(1u, kc_to_capture.count(78));
command_map = GetCommandForKeyCodeMap();
EXPECT_EQ(4u, command_map.size());
EXPECT_EQ(SwitchAccessCommand::kNext, command_map.at(49));
EXPECT_EQ(SwitchAccessCommand::kNext, command_map.at(78));
// Set key codes for the Previous command. Re-use a key code from above.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_1, {kSwitchAccessInternalDevice}},
{ui::VKEY_P, {kSwitchAccessInternalDevice}}},
SwitchAccessCommand::kPrevious);
// Check that '1' has been remapped to Previous.
kc_to_capture = GetKeyCodesToCapture();
EXPECT_EQ(5u, kc_to_capture.size());
EXPECT_EQ(1u, kc_to_capture.count(49));
EXPECT_EQ(1u, kc_to_capture.count(80));
command_map = GetCommandForKeyCodeMap();
EXPECT_EQ(5u, command_map.size());
EXPECT_EQ(SwitchAccessCommand::kPrevious, command_map.at(49));
EXPECT_EQ(SwitchAccessCommand::kPrevious, command_map.at(80));
EXPECT_EQ(SwitchAccessCommand::kNext, command_map.at(78));
// Set a new key code for the Select command.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_3, {kSwitchAccessInternalDevice}},
{ui::VKEY_S, {kSwitchAccessInternalDevice}}},
SwitchAccessCommand::kSelect);
// Check that the previously set values for Select have been cleared.
kc_to_capture = GetKeyCodesToCapture();
EXPECT_EQ(5u, kc_to_capture.size());
EXPECT_EQ(0u, kc_to_capture.count(48));
EXPECT_EQ(1u, kc_to_capture.count(51));
EXPECT_EQ(1u, kc_to_capture.count(83));
command_map = GetCommandForKeyCodeMap();
EXPECT_EQ(5u, command_map.size());
EXPECT_EQ(SwitchAccessCommand::kSelect, command_map.at(51));
EXPECT_EQ(SwitchAccessCommand::kSelect, command_map.at(83));
EXPECT_EQ(command_map.end(), command_map.find(48));
}
TEST_P(SwitchAccessAccessibilityEventRewriterTest, RespectsModifierRemappings) {
// Set Control to be Switch Access' next button.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_CONTROL, {kSwitchAccessInternalDevice}}},
SwitchAccessCommand::kNext);
// Set Alt to be Switch Access' select button.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_MENU /* Alt key */, {kSwitchAccessInternalDevice}}},
SwitchAccessCommand::kSelect);
// Map Control key to Alt.
SetModifierRemapping(prefs::kLanguageRemapControlKeyTo,
ui::mojom::ModifierKey::kAlt);
// Send a key event for Control.
generator().PressKey(ui::VKEY_CONTROL, ui::EF_CONTROL_DOWN,
1 /* keyboard id */);
// EventRewriterAsh actually omits the modifier flag on release.
generator().ReleaseKey(ui::VKEY_CONTROL, ui::EF_NONE, 1 /* keyboard id */);
// Verify Switch Access treated it like Alt.
EXPECT_EQ(
1u,
accessibility_event_rewriter_delegate().switch_access_commands().size());
EXPECT_EQ(
SwitchAccessCommand::kSelect,
accessibility_event_rewriter_delegate().switch_access_commands().back());
// Send a key event for Alt.
generator().PressKey(ui::VKEY_MENU, ui::EF_ALT_DOWN, 1 /* keyboard id */);
// EventRewriterAsh actually omits the modifier flag on release.
generator().ReleaseKey(ui::VKEY_MENU, ui::EF_NONE, 1 /* keyboard id */);
// Verify Switch Access also treats that like Alt.
EXPECT_EQ(
2u,
accessibility_event_rewriter_delegate().switch_access_commands().size());
EXPECT_EQ(
SwitchAccessCommand::kSelect,
accessibility_event_rewriter_delegate().switch_access_commands().back());
}
TEST_P(SwitchAccessAccessibilityEventRewriterTest, UseFunctionKeyRemappings) {
// Set BrowserForward to be Switch Access' next button.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_BROWSER_FORWARD, {kSwitchAccessInternalDevice}}},
SwitchAccessCommand::kNext);
// Set F2 (the underlying value for BrowserForward) to be Switch Access'
// select button.
SetKeyCodesForSwitchAccessCommand(
{{ui::VKEY_F2, {kSwitchAccessInternalDevice}}},
SwitchAccessCommand::kSelect);
// Send a key event for F2.
generator().PressKey(ui::VKEY_F2, ui::EF_NONE, 1 /* keyboard id */);
generator().ReleaseKey(ui::VKEY_F2, ui::EF_NONE, 1 /* keyboard id */);
// Verify Switch Access treated it like BrowserForward.
EXPECT_EQ(
1u,
accessibility_event_rewriter_delegate().switch_access_commands().size());
EXPECT_EQ(
SwitchAccessCommand::kNext,
accessibility_event_rewriter_delegate().switch_access_commands().back());
// Send a key event for BrowserForward.
generator().PressKey(ui::VKEY_BROWSER_FORWARD, ui::EF_NONE,
1 /* keyboard id */);
generator().ReleaseKey(ui::VKEY_BROWSER_FORWARD, ui::EF_NONE,
1 /* keyboard id */);
// Verify Switch Access also treats that like BrowserForward.
EXPECT_EQ(
2u,
accessibility_event_rewriter_delegate().switch_access_commands().size());
EXPECT_EQ(
SwitchAccessCommand::kNext,
accessibility_event_rewriter_delegate().switch_access_commands().back());
}
class MagnifierAccessibilityEventRewriterTest
: public AccessibilityEventRewriterTestBase,
public testing::WithParamInterface<bool> {
public:
MagnifierAccessibilityEventRewriterTest() {
if (GetParam()) {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kEnableKeyboardRewriterFix);
} else {
scoped_feature_list_.InitAndDisableFeature(
ash::features::kEnableKeyboardRewriterFix);
}
}
void SetUp() override {
AccessibilityEventRewriterTestBase::SetUp();
// This test triggers a resize of WindowTreeHost, which will end up
// throttling events. set_throttle_input_on_resize_for_testing() disables
// this.
aura::Env::GetInstance()->set_throttle_input_on_resize_for_testing(false);
GetAccessibilityController()->fullscreen_magnifier().SetEnabled(true);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All,
MagnifierAccessibilityEventRewriterTest,
testing::Bool());
TEST_P(MagnifierAccessibilityEventRewriterTest, CaptureKeys) {
// Press and release Ctrl+Alt+Up.
// Verify that the events are captured by AccessibilityEventRewriter.
generator().PressModifierKeys(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
event_capturer().Reset();
generator().PressKey(ui::VKEY_UP, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
EXPECT_FALSE(event_capturer().last_key_event());
EXPECT_EQ(
MagnifierCommand::kMoveUp,
accessibility_event_rewriter_delegate().magnifier_commands().back());
generator().ReleaseKey(ui::VKEY_UP, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
EXPECT_FALSE(event_capturer().last_key_event());
EXPECT_EQ(
MagnifierCommand::kMoveStop,
accessibility_event_rewriter_delegate().magnifier_commands().back());
// Press and release Ctrl+Alt+Down.
// Verify that the events are captured by AccessibilityEventRewriter.
generator().PressKey(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
EXPECT_FALSE(event_capturer().last_key_event());
EXPECT_EQ(
MagnifierCommand::kMoveDown,
accessibility_event_rewriter_delegate().magnifier_commands().back());
generator().ReleaseKey(ui::VKEY_DOWN, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
EXPECT_FALSE(event_capturer().last_key_event());
EXPECT_EQ(
MagnifierCommand::kMoveStop,
accessibility_event_rewriter_delegate().magnifier_commands().back());
generator().ReleaseModifierKeys(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
event_capturer().Reset();
// Press and release the "3" key.
// Verify that the events are not captured by AccessibilityEventRewriter.
generator().PressKey(ui::VKEY_3, ui::EF_NONE);
EXPECT_TRUE(event_capturer().last_key_event());
generator().ReleaseKey(ui::VKEY_3, ui::EF_NONE);
EXPECT_TRUE(event_capturer().last_key_event());
}
} // namespace ash