Commit 77c1a6d5 authored by David Tseng's avatar David Tseng Committed by Commit Bot

Adds AccessibilityEventRewriter::SetKeyboardInputTypes

This change enables AccessibilityEventRewriter to only process key events from specific types of keyboard input devices.
The new public method can be used by clients such as Switch Access to only consider key events from usb or bluetooth switches which show up as keyboard devices.

Bug: none
Test: ash_unittests --gtest_filter=Switch*.SetKeyboardInputTypes. Requires manual verification with switch devices.
Change-Id: I7496b95cb380b9ddf301d7fa7bebb9748bdf40e7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2376035
Commit-Queue: David Tseng <dtseng@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarAnastasia Helfinstein <anastasi@google.com>
Cr-Commit-Position: refs/heads/master@{#801794}
parent 5bbb5ddd
......@@ -8,6 +8,7 @@
#include "ash/public/cpp/accessibility_event_rewriter_delegate.h"
#include "ash/shell.h"
#include "ui/chromeos/events/event_rewriter_chromeos.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/types/event_type.h"
......@@ -19,6 +20,16 @@ AccessibilityEventRewriter::AccessibilityEventRewriter(
AccessibilityEventRewriterDelegate* delegate)
: delegate_(delegate), event_rewriter_chromeos_(event_rewriter_chromeos) {
Shell::Get()->accessibility_controller()->SetAccessibilityEventRewriter(this);
// By default, observe all input device types.
keyboard_input_device_types_.insert(ui::INPUT_DEVICE_INTERNAL);
keyboard_input_device_types_.insert(ui::INPUT_DEVICE_USB);
keyboard_input_device_types_.insert(ui::INPUT_DEVICE_BLUETOOTH);
keyboard_input_device_types_.insert(ui::INPUT_DEVICE_UNKNOWN);
UpdateKeyboardDeviceIds();
observer_.Add(ui::DeviceDataManager::GetInstance());
}
AccessibilityEventRewriter::~AccessibilityEventRewriter() {
......@@ -52,23 +63,58 @@ void AccessibilityEventRewriter::OnUnhandledSpokenFeedbackEvent(
}
}
ui::EventDispatchDetails AccessibilityEventRewriter::RewriteEvent(
const ui::Event& event,
const Continuation continuation) {
bool captured = false;
if (!delegate_)
return SendEvent(continuation, &event);
bool AccessibilityEventRewriter::SetKeyCodesForSwitchAccessCommand(
std::set<int> new_key_codes,
SwitchAccessCommand command) {
bool has_changed = false;
std::set<int> to_clear;
if (Shell::Get()->accessibility_controller()->IsSwitchAccessRunning()) {
captured = RewriteEventForSwitchAccess(event, continuation);
// Clear old values that conflict with the new assignment.
// TODO(anastasi): convert to use iterators directly and remove has_changed as
// an extra step.
for (const auto& val : key_code_to_switch_access_command_) {
int old_key_code = val.first;
SwitchAccessCommand old_command = val.second;
if (new_key_codes.count(old_key_code) > 0) {
if (old_command != command) {
has_changed = true;
// Modifying the map while iterating through it causes reference
// failures.
to_clear.insert(old_key_code);
} else {
new_key_codes.erase(old_key_code);
}
continue;
}
// This value was previously mapped to the command, but is no longer.
if (old_command == command) {
has_changed = true;
to_clear.insert(old_key_code);
switch_access_key_codes_to_capture_.erase(old_key_code);
}
}
for (int key_code : to_clear) {
key_code_to_switch_access_command_.erase(key_code);
}
if (!captured) {
captured = RewriteEventForChromeVox(event, continuation);
if (new_key_codes.size() == 0)
return has_changed;
// Add any new key codes to the map.
for (int key_code : new_key_codes) {
switch_access_key_codes_to_capture_.insert(key_code);
key_code_to_switch_access_command_[key_code] = command;
}
return captured ? DiscardEvent(continuation)
: SendEvent(continuation, &event);
return true;
}
void AccessibilityEventRewriter::SetKeyboardInputDeviceTypes(
const std::set<ui::InputDeviceType>& keyboard_input_device_types) {
keyboard_input_device_types_ = keyboard_input_device_types;
UpdateKeyboardDeviceIds();
}
bool AccessibilityEventRewriter::RewriteEventForChromeVox(
......@@ -143,52 +189,43 @@ bool AccessibilityEventRewriter::RewriteEventForSwitchAccess(
return capture;
}
bool AccessibilityEventRewriter::SetKeyCodesForSwitchAccessCommand(
std::set<int> new_key_codes,
SwitchAccessCommand command) {
bool has_changed = false;
std::set<int> to_clear;
void AccessibilityEventRewriter::UpdateKeyboardDeviceIds() {
keyboard_device_ids_.clear();
for (auto& keyboard :
ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) {
if (keyboard_input_device_types_.count(keyboard.type))
keyboard_device_ids_.insert(keyboard.id);
}
}
// Clear old values that conflict with the new assignment.
// TODO(anastasi): convert to use iterators directly and remove has_changed as
// an extra step.
for (const auto& val : key_code_to_switch_access_command_) {
int old_key_code = val.first;
SwitchAccessCommand old_command = val.second;
ui::EventDispatchDetails AccessibilityEventRewriter::RewriteEvent(
const ui::Event& event,
const Continuation continuation) {
if (event.IsKeyEvent() && event.source_device_id() != ui::ED_UNKNOWN_DEVICE &&
keyboard_device_ids_.count(event.source_device_id()) == 0) {
return SendEvent(continuation, &event);
}
if (new_key_codes.count(old_key_code) > 0) {
if (old_command != command) {
has_changed = true;
// Modifying the map while iterating through it causes reference
// failures.
to_clear.insert(old_key_code);
} else {
new_key_codes.erase(old_key_code);
}
continue;
}
bool captured = false;
if (!delegate_)
return SendEvent(continuation, &event);
// This value was previously mapped to the command, but is no longer.
if (old_command == command) {
has_changed = true;
to_clear.insert(old_key_code);
switch_access_key_codes_to_capture_.erase(old_key_code);
}
}
for (int key_code : to_clear) {
key_code_to_switch_access_command_.erase(key_code);
if (Shell::Get()->accessibility_controller()->IsSwitchAccessRunning()) {
captured = RewriteEventForSwitchAccess(event, continuation);
}
if (new_key_codes.size() == 0)
return has_changed;
// Add any new key codes to the map.
for (int key_code : new_key_codes) {
switch_access_key_codes_to_capture_.insert(key_code);
key_code_to_switch_access_command_[key_code] = command;
if (!captured) {
captured = RewriteEventForChromeVox(event, continuation);
}
return true;
return captured ? DiscardEvent(continuation)
: SendEvent(continuation, &event);
}
void AccessibilityEventRewriter::OnInputDeviceConfigurationChanged(
uint8_t input_device_types) {
if (input_device_types & ui::InputDeviceEventObserver::kKeyboard)
UpdateKeyboardDeviceIds();
}
} // namespace ash
......@@ -10,10 +10,14 @@
#include <set>
#include "ash/ash_export.h"
#include "base/scoped_observer.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/input_device_event_observer.h"
#include "ui/events/event_rewriter.h"
namespace ui {
class EventRewriterChromeOS;
enum InputDeviceType;
}
namespace ash {
......@@ -24,7 +28,9 @@ enum class SwitchAccessCommand;
// AccessibilityEventRewriter sends key events to Accessibility extensions (such
// as ChromeVox and Switch Access) via the delegate when the corresponding
// extension is enabled. Continues dispatch of unhandled key events.
class ASH_EXPORT AccessibilityEventRewriter : public ui::EventRewriter {
class ASH_EXPORT AccessibilityEventRewriter
: public ui::EventRewriter,
public ui::InputDeviceEventObserver {
public:
AccessibilityEventRewriter(ui::EventRewriterChromeOS* event_rewriter_chromeos,
AccessibilityEventRewriterDelegate* delegate);
......@@ -37,10 +43,15 @@ class ASH_EXPORT AccessibilityEventRewriter : public ui::EventRewriter {
// NOTE: These events may be delivered out-of-order from non-ChromeVox events.
void OnUnhandledSpokenFeedbackEvent(std::unique_ptr<ui::Event> event) const;
// Sets what key_codes are captured for a given Switch Access command.
// Sets what |key_codes| are captured for a given Switch Access command;
// returns true if any mapping changed.
bool SetKeyCodesForSwitchAccessCommand(std::set<int> key_codes,
SwitchAccessCommand command);
// Set the types of keyboard input types processed by this rewriter.
void SetKeyboardInputDeviceTypes(
const std::set<ui::InputDeviceType>& keyboard_input_device_types);
void set_chromevox_capture_all_keys(bool value) {
chromevox_capture_all_keys_ = value;
}
......@@ -58,16 +69,25 @@ class ASH_EXPORT AccessibilityEventRewriter : public ui::EventRewriter {
}
private:
// Internal helpers to rewrite an event for a given accessibility feature.
// Returns true if the event is captured.
bool RewriteEventForChromeVox(const ui::Event& event,
const Continuation continuation);
bool RewriteEventForSwitchAccess(const ui::Event& event,
const Continuation continuation);
// Updates the list of allowed keyboard device ids based on the current set of
// keyboard input types.
void UpdateKeyboardDeviceIds();
// ui::EventRewriter:
ui::EventDispatchDetails RewriteEvent(
const ui::Event& event,
const Continuation continuation) override;
// ui::InputDeviceObserver:
void OnInputDeviceConfigurationChanged(uint8_t input_device_types) override;
// Continuation saved for OnUnhandledSpokenFeedbackEvent().
Continuation chromevox_continuation_;
......@@ -78,13 +98,29 @@ class ASH_EXPORT AccessibilityEventRewriter : public ui::EventRewriter {
// Whether to send mouse events to the ChromeVox extension.
bool chromevox_send_mouse_events_ = false;
// Whether to capture all keys.
// Whether to capture all keys for ChromeVox.
bool chromevox_capture_all_keys_ = false;
// Set of keys to capture for Switch Access.
std::set<int> switch_access_key_codes_to_capture_;
// Maps a captured key from above to a Switch Access command.
std::map<int, SwitchAccessCommand> key_code_to_switch_access_command_;
// Used to rewrite events in special cases such as function keys for ChromeVox
// taylored behavior.
ui::EventRewriterChromeOS* const event_rewriter_chromeos_;
// A set of keyboard device ids who's key events we want to process.
std::set<int> keyboard_device_ids_;
// A set of input device types used to filter the list of keyboard devices
// above.
std::set<ui::InputDeviceType> keyboard_input_device_types_;
// Used to refresh state when keyboard devices change.
ScopedObserver<ui::DeviceDataManager, ui::InputDeviceEventObserver> observer_{
this};
};
} // namespace ash
......
......@@ -20,6 +20,7 @@
#include "ui/chromeos/events/event_rewriter_chromeos.h"
#include "ui/chromeos/events/modifier_key.h"
#include "ui/chromeos/events/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"
......@@ -646,5 +647,85 @@ TEST_F(SwitchAccessAccessibilityEventRewriterTest,
EXPECT_EQ(command_map.end(), command_map.find(48));
}
TEST_F(SwitchAccessAccessibilityEventRewriterTest, SetKeyboardInputTypes) {
AccessibilityEventRewriter* rewriter =
controller_->GetAccessibilityEventRewriterForTest();
EXPECT_NE(nullptr, rewriter);
// Set Switch Access to capture these keys as the select command.
SetKeyCodesForSwitchAccessCommand(
{ui::VKEY_1, ui::VKEY_2, ui::VKEY_3, ui::VKEY_4},
SwitchAccessCommand::kSelect);
std::vector<ui::InputDevice> keyboards;
ui::DeviceDataManagerTestApi device_data_test_api;
keyboards.emplace_back(ui::InputDevice(1, ui::INPUT_DEVICE_INTERNAL, ""));
keyboards.emplace_back(ui::InputDevice(2, ui::INPUT_DEVICE_USB, ""));
keyboards.emplace_back(ui::InputDevice(3, ui::INPUT_DEVICE_BLUETOOTH, ""));
keyboards.emplace_back(ui::InputDevice(4, ui::INPUT_DEVICE_UNKNOWN, ""));
device_data_test_api.SetKeyboardDevices(keyboards);
// Press the "1" key with no source device id.
generator_->PressKey(ui::VKEY_1, ui::EF_NONE);
generator_->ReleaseKey(ui::VKEY_1, ui::EF_NONE);
// The event was captured by AccessibilityEventRewriter.
EXPECT_FALSE(event_capturer_.last_key_event());
EXPECT_EQ(SwitchAccessCommand::kSelect, delegate_->last_command());
// Press the "1" key from the internal keyboard which is captured by
// AccessibilityEventRewriter.
generator_->PressKey(ui::VKEY_1, ui::EF_NONE, 1);
generator_->ReleaseKey(ui::VKEY_1, ui::EF_NONE, 1);
EXPECT_FALSE(event_capturer_.last_key_event());
// Press the "2" key from the usb keyboard which is captured by
// AccessibilityEventRewriter.
generator_->PressKey(ui::VKEY_2, ui::EF_NONE, 2);
generator_->ReleaseKey(ui::VKEY_2, ui::EF_NONE, 2);
EXPECT_FALSE(event_capturer_.last_key_event());
// Press the "3" key from the bluetooth keyboard which is captured by
// AccessibilityEventRewriter.
generator_->PressKey(ui::VKEY_3, ui::EF_NONE, 3);
generator_->ReleaseKey(ui::VKEY_3, ui::EF_NONE, 3);
EXPECT_FALSE(event_capturer_.last_key_event());
// Press the "4" key from the unknown keyboard which is captured by
// AccessibilityEventRewriter.
generator_->PressKey(ui::VKEY_4, ui::EF_NONE, 4);
generator_->ReleaseKey(ui::VKEY_4, ui::EF_NONE, 2);
EXPECT_FALSE(event_capturer_.last_key_event());
// Now, exclude some device types.
rewriter->SetKeyboardInputDeviceTypes(
{ui::INPUT_DEVICE_USB, ui::INPUT_DEVICE_BLUETOOTH});
// Press the "1" key from the internal keyboard which is not captured by
// AccessibilityEventRewriter.
generator_->PressKey(ui::VKEY_1, ui::EF_NONE, 1);
generator_->ReleaseKey(ui::VKEY_1, ui::EF_NONE, 1);
EXPECT_TRUE(event_capturer_.last_key_event());
event_capturer_.Reset();
// Press the "2" key from the usb keyboard which is captured by
// AccessibilityEventRewriter.
generator_->PressKey(ui::VKEY_2, ui::EF_NONE, 2);
generator_->ReleaseKey(ui::VKEY_2, ui::EF_NONE, 2);
EXPECT_FALSE(event_capturer_.last_key_event());
// Press the "3" key from the bluetooth keyboard which is captured by
// AccessibilityEventRewriter.
generator_->PressKey(ui::VKEY_3, ui::EF_NONE, 3);
generator_->ReleaseKey(ui::VKEY_3, ui::EF_NONE, 3);
EXPECT_FALSE(event_capturer_.last_key_event());
// Press the "4" key from the unknown keyboard which is not captured by
// AccessibilityEventRewriter.
generator_->PressKey(ui::VKEY_4, ui::EF_NONE, 4);
generator_->ReleaseKey(ui::VKEY_4, ui::EF_NONE, 2);
EXPECT_TRUE(event_capturer_.last_key_event());
}
} // namespace
} // namespace ash
......@@ -593,12 +593,16 @@ void EventGenerator::CancelTrackpadRest() {
Dispatch(&scroll);
}
void EventGenerator::PressKey(ui::KeyboardCode key_code, int flags) {
DispatchKeyEvent(true, key_code, flags);
void EventGenerator::PressKey(ui::KeyboardCode key_code,
int flags,
int source_device_id) {
DispatchKeyEvent(true, key_code, flags, source_device_id);
}
void EventGenerator::ReleaseKey(ui::KeyboardCode key_code, int flags) {
DispatchKeyEvent(false, key_code, flags);
void EventGenerator::ReleaseKey(ui::KeyboardCode key_code,
int flags,
int source_device_id) {
DispatchKeyEvent(false, key_code, flags, source_device_id);
}
void EventGenerator::Dispatch(ui::Event* event) {
......@@ -640,7 +644,8 @@ void EventGenerator::Init(gfx::NativeWindow root_window,
void EventGenerator::DispatchKeyEvent(bool is_press,
ui::KeyboardCode key_code,
int flags) {
int flags,
int source_device_id) {
#if defined(OS_WIN)
UINT key_press = WM_KEYDOWN;
uint16_t character = ui::DomCodeToUsLayoutCharacter(
......@@ -666,6 +671,7 @@ void EventGenerator::DispatchKeyEvent(bool is_press,
ui::EventType type = is_press ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED;
ui::KeyEvent keyev(type, key_code, flags);
#endif // OS_WIN
keyev.set_source_device_id(source_device_id);
Dispatch(&keyev);
}
......
......@@ -443,13 +443,17 @@ class EventGenerator {
// event without native_event() is generated. Note that ui::EF_ flags should
// be passed as |flags|, not the native ones like 'ShiftMask' in <X11/X.h>.
// TODO(yusukes): Support native_event() on all platforms.
void PressKey(KeyboardCode key_code, int flags);
void PressKey(KeyboardCode key_code,
int flags,
int source_device_id = ED_UNKNOWN_DEVICE);
// Generates a key release event. On platforms except Windows and X11, a key
// event without native_event() is generated. Note that ui::EF_ flags should
// be passed as |flags|, not the native ones like 'ShiftMask' in <X11/X.h>.
// TODO(yusukes): Support native_event() on all platforms.
void ReleaseKey(KeyboardCode key_code, int flags);
void ReleaseKey(KeyboardCode key_code,
int flags,
int source_device_id = ED_UNKNOWN_DEVICE);
// Dispatch the event to the WindowEventDispatcher.
void Dispatch(Event* event);
......@@ -466,7 +470,10 @@ class EventGenerator {
void Init(gfx::NativeWindow root_window, gfx::NativeWindow target_window);
// Dispatch a key event to the WindowEventDispatcher.
void DispatchKeyEvent(bool is_press, KeyboardCode key_code, int flags);
void DispatchKeyEvent(bool is_press,
KeyboardCode key_code,
int flags,
int source_device_id);
void UpdateCurrentDispatcher(const gfx::Point& point);
void PressButton(int flag);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment