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 @@ ...@@ -8,6 +8,7 @@
#include "ash/public/cpp/accessibility_event_rewriter_delegate.h" #include "ash/public/cpp/accessibility_event_rewriter_delegate.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ui/chromeos/events/event_rewriter_chromeos.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.h"
#include "ui/events/event_utils.h" #include "ui/events/event_utils.h"
#include "ui/events/types/event_type.h" #include "ui/events/types/event_type.h"
...@@ -19,6 +20,16 @@ AccessibilityEventRewriter::AccessibilityEventRewriter( ...@@ -19,6 +20,16 @@ AccessibilityEventRewriter::AccessibilityEventRewriter(
AccessibilityEventRewriterDelegate* delegate) AccessibilityEventRewriterDelegate* delegate)
: delegate_(delegate), event_rewriter_chromeos_(event_rewriter_chromeos) { : delegate_(delegate), event_rewriter_chromeos_(event_rewriter_chromeos) {
Shell::Get()->accessibility_controller()->SetAccessibilityEventRewriter(this); 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() { AccessibilityEventRewriter::~AccessibilityEventRewriter() {
...@@ -52,23 +63,58 @@ void AccessibilityEventRewriter::OnUnhandledSpokenFeedbackEvent( ...@@ -52,23 +63,58 @@ void AccessibilityEventRewriter::OnUnhandledSpokenFeedbackEvent(
} }
} }
ui::EventDispatchDetails AccessibilityEventRewriter::RewriteEvent( bool AccessibilityEventRewriter::SetKeyCodesForSwitchAccessCommand(
const ui::Event& event, std::set<int> new_key_codes,
const Continuation continuation) { SwitchAccessCommand command) {
bool captured = false; bool has_changed = false;
if (!delegate_) std::set<int> to_clear;
return SendEvent(continuation, &event);
if (Shell::Get()->accessibility_controller()->IsSwitchAccessRunning()) { // Clear old values that conflict with the new assignment.
captured = RewriteEventForSwitchAccess(event, continuation); // 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) { if (new_key_codes.size() == 0)
captured = RewriteEventForChromeVox(event, continuation); 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) return true;
: SendEvent(continuation, &event); }
void AccessibilityEventRewriter::SetKeyboardInputDeviceTypes(
const std::set<ui::InputDeviceType>& keyboard_input_device_types) {
keyboard_input_device_types_ = keyboard_input_device_types;
UpdateKeyboardDeviceIds();
} }
bool AccessibilityEventRewriter::RewriteEventForChromeVox( bool AccessibilityEventRewriter::RewriteEventForChromeVox(
...@@ -143,52 +189,43 @@ bool AccessibilityEventRewriter::RewriteEventForSwitchAccess( ...@@ -143,52 +189,43 @@ bool AccessibilityEventRewriter::RewriteEventForSwitchAccess(
return capture; return capture;
} }
bool AccessibilityEventRewriter::SetKeyCodesForSwitchAccessCommand( void AccessibilityEventRewriter::UpdateKeyboardDeviceIds() {
std::set<int> new_key_codes, keyboard_device_ids_.clear();
SwitchAccessCommand command) { for (auto& keyboard :
bool has_changed = false; ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) {
std::set<int> to_clear; if (keyboard_input_device_types_.count(keyboard.type))
keyboard_device_ids_.insert(keyboard.id);
}
}
// Clear old values that conflict with the new assignment. ui::EventDispatchDetails AccessibilityEventRewriter::RewriteEvent(
// TODO(anastasi): convert to use iterators directly and remove has_changed as const ui::Event& event,
// an extra step. const Continuation continuation) {
for (const auto& val : key_code_to_switch_access_command_) { if (event.IsKeyEvent() && event.source_device_id() != ui::ED_UNKNOWN_DEVICE &&
int old_key_code = val.first; keyboard_device_ids_.count(event.source_device_id()) == 0) {
SwitchAccessCommand old_command = val.second; return SendEvent(continuation, &event);
}
if (new_key_codes.count(old_key_code) > 0) { bool captured = false;
if (old_command != command) { if (!delegate_)
has_changed = true; return SendEvent(continuation, &event);
// 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 (Shell::Get()->accessibility_controller()->IsSwitchAccessRunning()) {
if (old_command == command) { captured = RewriteEventForSwitchAccess(event, continuation);
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 (new_key_codes.size() == 0) if (!captured) {
return has_changed; captured = RewriteEventForChromeVox(event, continuation);
// 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 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 } // namespace ash
...@@ -10,10 +10,14 @@ ...@@ -10,10 +10,14 @@
#include <set> #include <set>
#include "ash/ash_export.h" #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" #include "ui/events/event_rewriter.h"
namespace ui { namespace ui {
class EventRewriterChromeOS; class EventRewriterChromeOS;
enum InputDeviceType;
} }
namespace ash { namespace ash {
...@@ -24,7 +28,9 @@ enum class SwitchAccessCommand; ...@@ -24,7 +28,9 @@ enum class SwitchAccessCommand;
// AccessibilityEventRewriter sends key events to Accessibility extensions (such // AccessibilityEventRewriter sends key events to Accessibility extensions (such
// as ChromeVox and Switch Access) via the delegate when the corresponding // as ChromeVox and Switch Access) via the delegate when the corresponding
// extension is enabled. Continues dispatch of unhandled key events. // 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: public:
AccessibilityEventRewriter(ui::EventRewriterChromeOS* event_rewriter_chromeos, AccessibilityEventRewriter(ui::EventRewriterChromeOS* event_rewriter_chromeos,
AccessibilityEventRewriterDelegate* delegate); AccessibilityEventRewriterDelegate* delegate);
...@@ -37,10 +43,15 @@ class ASH_EXPORT AccessibilityEventRewriter : public ui::EventRewriter { ...@@ -37,10 +43,15 @@ class ASH_EXPORT AccessibilityEventRewriter : public ui::EventRewriter {
// NOTE: These events may be delivered out-of-order from non-ChromeVox events. // NOTE: These events may be delivered out-of-order from non-ChromeVox events.
void OnUnhandledSpokenFeedbackEvent(std::unique_ptr<ui::Event> event) const; 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, bool SetKeyCodesForSwitchAccessCommand(std::set<int> key_codes,
SwitchAccessCommand command); 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) { void set_chromevox_capture_all_keys(bool value) {
chromevox_capture_all_keys_ = value; chromevox_capture_all_keys_ = value;
} }
...@@ -58,16 +69,25 @@ class ASH_EXPORT AccessibilityEventRewriter : public ui::EventRewriter { ...@@ -58,16 +69,25 @@ class ASH_EXPORT AccessibilityEventRewriter : public ui::EventRewriter {
} }
private: 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, bool RewriteEventForChromeVox(const ui::Event& event,
const Continuation continuation); const Continuation continuation);
bool RewriteEventForSwitchAccess(const ui::Event& event, bool RewriteEventForSwitchAccess(const ui::Event& event,
const Continuation continuation); 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::EventRewriter:
ui::EventDispatchDetails RewriteEvent( ui::EventDispatchDetails RewriteEvent(
const ui::Event& event, const ui::Event& event,
const Continuation continuation) override; const Continuation continuation) override;
// ui::InputDeviceObserver:
void OnInputDeviceConfigurationChanged(uint8_t input_device_types) override;
// Continuation saved for OnUnhandledSpokenFeedbackEvent(). // Continuation saved for OnUnhandledSpokenFeedbackEvent().
Continuation chromevox_continuation_; Continuation chromevox_continuation_;
...@@ -78,13 +98,29 @@ class ASH_EXPORT AccessibilityEventRewriter : public ui::EventRewriter { ...@@ -78,13 +98,29 @@ class ASH_EXPORT AccessibilityEventRewriter : public ui::EventRewriter {
// Whether to send mouse events to the ChromeVox extension. // Whether to send mouse events to the ChromeVox extension.
bool chromevox_send_mouse_events_ = false; bool chromevox_send_mouse_events_ = false;
// Whether to capture all keys. // Whether to capture all keys for ChromeVox.
bool chromevox_capture_all_keys_ = false; bool chromevox_capture_all_keys_ = false;
// Set of keys to capture for Switch Access.
std::set<int> switch_access_key_codes_to_capture_; 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_; 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_; 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 } // namespace ash
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "ui/chromeos/events/event_rewriter_chromeos.h" #include "ui/chromeos/events/event_rewriter_chromeos.h"
#include "ui/chromeos/events/modifier_key.h" #include "ui/chromeos/events/modifier_key.h"
#include "ui/chromeos/events/pref_names.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.h"
#include "ui/events/event_constants.h" #include "ui/events/event_constants.h"
#include "ui/events/event_handler.h" #include "ui/events/event_handler.h"
...@@ -646,5 +647,85 @@ TEST_F(SwitchAccessAccessibilityEventRewriterTest, ...@@ -646,5 +647,85 @@ TEST_F(SwitchAccessAccessibilityEventRewriterTest,
EXPECT_EQ(command_map.end(), command_map.find(48)); 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
} // namespace ash } // namespace ash
...@@ -593,12 +593,16 @@ void EventGenerator::CancelTrackpadRest() { ...@@ -593,12 +593,16 @@ void EventGenerator::CancelTrackpadRest() {
Dispatch(&scroll); Dispatch(&scroll);
} }
void EventGenerator::PressKey(ui::KeyboardCode key_code, int flags) { void EventGenerator::PressKey(ui::KeyboardCode key_code,
DispatchKeyEvent(true, key_code, flags); int flags,
int source_device_id) {
DispatchKeyEvent(true, key_code, flags, source_device_id);
} }
void EventGenerator::ReleaseKey(ui::KeyboardCode key_code, int flags) { void EventGenerator::ReleaseKey(ui::KeyboardCode key_code,
DispatchKeyEvent(false, key_code, flags); int flags,
int source_device_id) {
DispatchKeyEvent(false, key_code, flags, source_device_id);
} }
void EventGenerator::Dispatch(ui::Event* event) { void EventGenerator::Dispatch(ui::Event* event) {
...@@ -640,7 +644,8 @@ void EventGenerator::Init(gfx::NativeWindow root_window, ...@@ -640,7 +644,8 @@ void EventGenerator::Init(gfx::NativeWindow root_window,
void EventGenerator::DispatchKeyEvent(bool is_press, void EventGenerator::DispatchKeyEvent(bool is_press,
ui::KeyboardCode key_code, ui::KeyboardCode key_code,
int flags) { int flags,
int source_device_id) {
#if defined(OS_WIN) #if defined(OS_WIN)
UINT key_press = WM_KEYDOWN; UINT key_press = WM_KEYDOWN;
uint16_t character = ui::DomCodeToUsLayoutCharacter( uint16_t character = ui::DomCodeToUsLayoutCharacter(
...@@ -666,6 +671,7 @@ void EventGenerator::DispatchKeyEvent(bool is_press, ...@@ -666,6 +671,7 @@ void EventGenerator::DispatchKeyEvent(bool is_press,
ui::EventType type = is_press ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED; ui::EventType type = is_press ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED;
ui::KeyEvent keyev(type, key_code, flags); ui::KeyEvent keyev(type, key_code, flags);
#endif // OS_WIN #endif // OS_WIN
keyev.set_source_device_id(source_device_id);
Dispatch(&keyev); Dispatch(&keyev);
} }
......
...@@ -443,13 +443,17 @@ class EventGenerator { ...@@ -443,13 +443,17 @@ class EventGenerator {
// event without native_event() is generated. Note that ui::EF_ flags should // 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>. // be passed as |flags|, not the native ones like 'ShiftMask' in <X11/X.h>.
// TODO(yusukes): Support native_event() on all platforms. // 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 // 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 // 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>. // be passed as |flags|, not the native ones like 'ShiftMask' in <X11/X.h>.
// TODO(yusukes): Support native_event() on all platforms. // 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. // Dispatch the event to the WindowEventDispatcher.
void Dispatch(Event* event); void Dispatch(Event* event);
...@@ -466,7 +470,10 @@ class EventGenerator { ...@@ -466,7 +470,10 @@ class EventGenerator {
void Init(gfx::NativeWindow root_window, gfx::NativeWindow target_window); void Init(gfx::NativeWindow root_window, gfx::NativeWindow target_window);
// Dispatch a key event to the WindowEventDispatcher. // 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 UpdateCurrentDispatcher(const gfx::Point& point);
void PressButton(int flag); 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