Commit f297c517 authored by Jamie Walch's avatar Jamie Walch Committed by Commit Bot

Add support for keyboard input for the terminate_on_input option.

Change-Id: I402f691a57d73ca287441d202034418f5a9a3920
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1726905
Commit-Queue: Jamie Walch <jamiewalch@chromium.org>
Reviewed-by: default avatarGary Kacmarcik <garykac@chromium.org>
Reviewed-by: default avatarDenis Kuznetsov <antrim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#688721}
parent 5bd8b22f
......@@ -454,6 +454,13 @@ void ClientSession::DisconnectSession(protocol::ErrorCode error) {
connection_->Disconnect(error);
}
void ClientSession::OnLocalKeyPressed(uint32_t usb_keycode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool is_local = remote_input_filter_.LocalKeyPressed(usb_keycode);
if (is_local && desktop_environment_options_.terminate_upon_input())
DisconnectSession(protocol::OK);
}
void ClientSession::OnLocalPointerMoved(const webrtc::DesktopVector& position,
ui::EventType type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
......
......@@ -134,6 +134,7 @@ class ClientSession : public protocol::HostStub,
// ClientSessionControl interface.
const std::string& client_jid() const override;
void DisconnectSession(protocol::ErrorCode error) override;
void OnLocalKeyPressed(uint32_t usb_keycode) override;
void OnLocalPointerMoved(const webrtc::DesktopVector& position,
ui::EventType type) override;
void SetDisableInputs(bool disable_inputs) override;
......
......@@ -36,6 +36,9 @@ class ClientSessionControl {
virtual void OnLocalPointerMoved(const webrtc::DesktopVector& position,
ui::EventType type) = 0;
// Called when a local key press or release is detected.
virtual void OnLocalKeyPressed(uint32_t usb_keycode) = 0;
// Disables or enables the remote input in the client session.
virtual void SetDisableInputs(bool disable_inputs) = 0;
......
......@@ -313,6 +313,12 @@ void DesktopSessionAgent::DisconnectSession(protocol::ErrorCode error) {
std::make_unique<ChromotingDesktopNetworkMsg_DisconnectSession>(error));
}
void DesktopSessionAgent::OnLocalKeyPressed(uint32_t usb_keycode) {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
remote_input_filter_->LocalKeyPressed(usb_keycode);
}
void DesktopSessionAgent::OnLocalPointerMoved(
const webrtc::DesktopVector& new_pos,
ui::EventType type) {
......
......@@ -127,6 +127,7 @@ class DesktopSessionAgent
// ClientSessionControl interface.
const std::string& client_jid() const override;
void DisconnectSession(protocol::ErrorCode error) override;
void OnLocalKeyPressed(uint32_t usb_keycode) override;
void OnLocalPointerMoved(const webrtc::DesktopVector& position,
ui::EventType type) override;
void SetDisableInputs(bool disable_inputs) override;
......
......@@ -108,7 +108,7 @@ class DisconnectWindowWin : public HostWindow {
ui::EventType type);
// Called when local keyboard event is seen and shows the dialog (if hidden).
void OnLocalKeyboardEvent();
void OnLocalKeyPressed(uint32_t usb_keycode);
// Used to disconnect the client session.
base::WeakPtr<ClientSessionControl> client_session_control_;
......@@ -195,7 +195,7 @@ void DisconnectWindowWin::Start(
local_input_monitor_->StartMonitoring(
base::BindRepeating(&DisconnectWindowWin::OnLocalMouseEvent,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&DisconnectWindowWin::OnLocalKeyboardEvent,
base::BindRepeating(&DisconnectWindowWin::OnLocalKeyPressed,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&DisconnectWindowWin::StopAutoHideBehavior,
weak_factory_.GetWeakPtr()));
......@@ -405,7 +405,7 @@ void DisconnectWindowWin::OnLocalMouseEvent(
mouse_position_ = position;
}
void DisconnectWindowWin::OnLocalKeyboardEvent() {
void DisconnectWindowWin::OnLocalKeyPressed(uint32_t usb_keycode) {
// Show the dialog before setting |local_input_seen_|. That way the dialog
// will be shown in the center position and subsequent reshows will honor
// the new position (if any) the dialog is moved to.
......
......@@ -71,6 +71,7 @@ class MockClientSessionControl : public ClientSessionControl {
MOCK_METHOD1(DisconnectSession, void(protocol::ErrorCode error));
MOCK_METHOD2(OnLocalPointerMoved,
void(const webrtc::DesktopVector&, ui::EventType));
MOCK_METHOD1(OnLocalKeyPressed, void(uint32_t));
MOCK_METHOD1(SetDisableInputs, void(bool));
MOCK_METHOD0(ResetVideoPipeline, void());
MOCK_METHOD1(OnDesktopDisplayChanged,
......
......@@ -44,6 +44,7 @@ class HostWindowProxy::Core
// ClientSessionControl interface.
const std::string& client_jid() const override;
void DisconnectSession(protocol::ErrorCode error) override;
void OnLocalKeyPressed(uint32_t usb_keycode) override;
void OnLocalPointerMoved(const webrtc::DesktopVector& position,
ui::EventType type) override;
void SetDisableInputs(bool disable_inputs) override;
......@@ -161,6 +162,17 @@ void HostWindowProxy::Core::DisconnectSession(protocol::ErrorCode error) {
client_session_control_->DisconnectSession(error);
}
void HostWindowProxy::Core::OnLocalKeyPressed(uint32_t usb_keycode) {
if (!caller_task_runner_->BelongsToCurrentThread()) {
caller_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::OnLocalKeyPressed, this, usb_keycode));
return;
}
if (client_session_control_.get())
client_session_control_->OnLocalKeyPressed(usb_keycode);
}
void HostWindowProxy::Core::OnLocalPointerMoved(
const webrtc::DesktopVector& position,
ui::EventType type) {
......
......@@ -30,7 +30,7 @@ class LocalInputMonitorImpl : public LocalInputMonitor {
void StartMonitoringForClientSession(
base::WeakPtr<ClientSessionControl> client_session_control) override;
void StartMonitoring(PointerMoveCallback on_pointer_input,
base::RepeatingClosure on_keyboard_input,
KeyPressedCallback on_keyboard_input,
base::RepeatingClosure on_error) override;
private:
......@@ -90,12 +90,19 @@ void LocalInputMonitorImpl::StartMonitoringForClientSession(
base::BindOnce(&ClientSessionControl::DisconnectSession,
client_session_control, protocol::OK));
keyboard_input_monitor_ = LocalKeyboardInputMonitor::Create(
caller_task_runner_, input_task_runner_, ui_task_runner_,
base::BindRepeating(&ClientSessionControl::OnLocalKeyPressed,
client_session_control),
base::BindOnce(&ClientSessionControl::DisconnectSession,
client_session_control, protocol::OK));
OnMonitoringStarted();
}
void LocalInputMonitorImpl::StartMonitoring(
PointerMoveCallback on_pointer_input,
base::RepeatingClosure on_keyboard_input,
KeyPressedCallback on_keyboard_input,
base::RepeatingClosure on_error) {
DCHECK(!monitoring_);
DCHECK(on_error);
......
......@@ -31,6 +31,7 @@ class LocalInputMonitor {
using PointerMoveCallback =
base::RepeatingCallback<void(const webrtc::DesktopVector&,
ui::EventType)>;
using KeyPressedCallback = base::RepeatingCallback<void(uint32_t)>;
virtual ~LocalInputMonitor() = default;
......@@ -43,7 +44,7 @@ class LocalInputMonitor {
// Start monitoring and notify using |client_session_control|. In this mode
// the LocalInputMonitor will listen for session disconnect hotkeys and mouse
// events (and touch, on some platforms) for input filtering.
// and keyboard events (and touch, on some platforms) for input filtering.
virtual void StartMonitoringForClientSession(
base::WeakPtr<ClientSessionControl> client_session_control) = 0;
......@@ -54,7 +55,7 @@ class LocalInputMonitor {
// |on_keyboard_input| is called for each keypress detected.
// |on_error| is called if any of the child input monitors fail.
virtual void StartMonitoring(PointerMoveCallback on_pointer_input,
base::RepeatingClosure on_keyboard_input,
KeyPressedCallback on_keyboard_input,
base::RepeatingClosure on_error) = 0;
protected:
......
......@@ -10,6 +10,7 @@
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "remoting/host/input_monitor/local_input_monitor.h"
namespace base {
class SingleThreadTaskRunner;
......@@ -30,7 +31,7 @@ class LocalKeyboardInputMonitor {
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::RepeatingClosure on_key_event_callback,
LocalInputMonitor::KeyPressedCallback on_key_event_callback,
base::OnceClosure disconnect_callback);
protected:
......
......@@ -6,18 +6,119 @@
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "remoting/host/chromeos/point_transformer.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/platform/platform_event_observer.h"
#include "ui/events/platform/platform_event_source.h"
namespace remoting {
namespace {
class LocalKeyboardInputMonitorChromeos : public LocalKeyboardInputMonitor {
public:
LocalKeyboardInputMonitorChromeos(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
LocalInputMonitor::KeyPressedCallback key_pressed_callback);
~LocalKeyboardInputMonitorChromeos() override;
private:
class Core : ui::PlatformEventObserver {
public:
Core(scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
LocalInputMonitor::KeyPressedCallback on_key_event_callback);
~Core() override;
void Start();
// ui::PlatformEventObserver interface.
void WillProcessEvent(const ui::PlatformEvent& event) override;
void DidProcessEvent(const ui::PlatformEvent& event) override;
private:
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
LocalInputMonitor::KeyPressedCallback key_pressed_callback_;
DISALLOW_COPY_AND_ASSIGN(Core);
};
// Task runner on which ui::events are received.
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner_;
std::unique_ptr<Core> core_;
DISALLOW_COPY_AND_ASSIGN(LocalKeyboardInputMonitorChromeos);
};
LocalKeyboardInputMonitorChromeos::LocalKeyboardInputMonitorChromeos(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
LocalInputMonitor::KeyPressedCallback key_pressed_callback)
: input_task_runner_(input_task_runner),
core_(new Core(caller_task_runner, std::move(key_pressed_callback))) {
input_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::Start, base::Unretained(core_.get())));
}
LocalKeyboardInputMonitorChromeos::~LocalKeyboardInputMonitorChromeos() {
input_task_runner_->DeleteSoon(FROM_HERE, core_.release());
}
LocalKeyboardInputMonitorChromeos::Core::Core(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
LocalInputMonitor::KeyPressedCallback key_pressed_callback)
: caller_task_runner_(caller_task_runner),
key_pressed_callback_(std::move(key_pressed_callback)) {}
void LocalKeyboardInputMonitorChromeos::Core::Start() {
// TODO(erg): Need to handle the mus case where PlatformEventSource is null
// because we are in mus. This class looks like it can be rewritten with mus
// EventMatchers.
if (ui::PlatformEventSource::GetInstance()) {
ui::PlatformEventSource::GetInstance()->AddPlatformEventObserver(this);
}
}
LocalKeyboardInputMonitorChromeos::Core::~Core() {
if (ui::PlatformEventSource::GetInstance()) {
ui::PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this);
}
}
void LocalKeyboardInputMonitorChromeos::Core::WillProcessEvent(
const ui::PlatformEvent& event) {
// No need to handle this callback.
}
void LocalKeyboardInputMonitorChromeos::Core::DidProcessEvent(
const ui::PlatformEvent& event) {
ui::EventType type = ui::EventTypeFromNative(event);
if (type == ui::ET_KEY_PRESSED) {
ui::DomCode dom_code = ui::CodeFromNative(event);
uint32_t usb_keycode = ui::KeycodeConverter::DomCodeToUsbKeycode(dom_code);
caller_task_runner_->PostTask(
FROM_HERE, base::BindOnce(key_pressed_callback_, usb_keycode));
}
}
} // namespace
std::unique_ptr<LocalKeyboardInputMonitor> LocalKeyboardInputMonitor::Create(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::RepeatingClosure on_key_event_callback,
LocalInputMonitor::KeyPressedCallback on_key_event_callback,
base::OnceClosure disconnect_callback) {
return nullptr;
return std::make_unique<LocalKeyboardInputMonitorChromeos>(
caller_task_runner, input_task_runner, std::move(on_key_event_callback));
}
} // namespace remoting
......@@ -15,7 +15,7 @@ std::unique_ptr<LocalKeyboardInputMonitor> LocalKeyboardInputMonitor::Create(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::RepeatingClosure on_key_event_callback,
LocalInputMonitor::KeyPressedCallback on_key_event_callback,
base::OnceClosure disconnect_callback) {
return nullptr;
}
......
......@@ -11,6 +11,7 @@
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "remoting/host/input_monitor/local_input_monitor_win.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
namespace remoting {
......@@ -26,7 +27,7 @@ class KeyboardRawInputHandlerWin
KeyboardRawInputHandlerWin(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::RepeatingClosure on_key_event_callback,
LocalInputMonitor::KeyPressedCallback on_key_event_callback,
base::OnceClosure disconnect_callback);
~KeyboardRawInputHandlerWin() override;
......@@ -39,7 +40,7 @@ class KeyboardRawInputHandlerWin
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
base::RepeatingClosure on_key_event_callback_;
LocalInputMonitor::KeyPressedCallback on_key_event_callback_;
base::OnceClosure disconnect_callback_;
// Tracks whether the instance is registered to receive raw input events.
......@@ -51,7 +52,7 @@ class KeyboardRawInputHandlerWin
KeyboardRawInputHandlerWin::KeyboardRawInputHandlerWin(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::RepeatingClosure on_key_event_callback,
LocalInputMonitor::KeyPressedCallback on_key_event_callback,
base::OnceClosure disconnect_callback)
: caller_task_runner_(caller_task_runner),
ui_task_runner_(ui_task_runner),
......@@ -99,7 +100,12 @@ void KeyboardRawInputHandlerWin::OnInputEvent(const RAWINPUT* input) {
if (input->header.dwType == RIM_TYPEKEYBOARD &&
input->header.hDevice != nullptr) {
caller_task_runner_->PostTask(FROM_HERE, on_key_event_callback_);
USHORT vkey = input->data.keyboard.VKey;
UINT scancode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC);
uint32_t usb_keycode =
ui::KeycodeConverter::NativeKeycodeToUsbKeycode(scancode);
caller_task_runner_->PostTask(
FROM_HERE, base::BindOnce(on_key_event_callback_, usb_keycode));
}
}
......@@ -133,7 +139,7 @@ std::unique_ptr<LocalKeyboardInputMonitor> LocalKeyboardInputMonitor::Create(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::RepeatingClosure on_key_event_callback,
LocalInputMonitor::KeyPressedCallback on_key_event_callback,
base::OnceClosure disconnect_callback) {
auto raw_input_handler = std::make_unique<KeyboardRawInputHandlerWin>(
caller_task_runner, ui_task_runner, std::move(on_key_event_callback),
......
......@@ -15,7 +15,7 @@ std::unique_ptr<LocalKeyboardInputMonitor> LocalKeyboardInputMonitor::Create(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::RepeatingClosure on_key_event_callback,
LocalInputMonitor::KeyPressedCallback on_key_event_callback,
base::OnceClosure disconnect_callback) {
return nullptr;
}
......
......@@ -17,6 +17,12 @@ namespace {
// any echoes are detected.
const unsigned int kNumRemoteMousePositions = 50;
// The number of remote keypress events to record for the purpose of eliminating
// "echoes" detected by the local input detector. The value should be large
// enough to cope with the fact that multiple events might be injected before
// any echoes are detected.
const unsigned int kNumRemoteKeyPresses = 20;
// The number of milliseconds for which to block remote input when local input
// is received.
const int64_t kRemoteBlockTimeoutMillis = 2000;
......@@ -61,10 +67,26 @@ bool RemoteInputFilter::LocalPointerMoved(const webrtc::DesktopVector& pos,
}
}
// Release all pressed buttons or keys, disable inputs, and note the time.
LocalInputDetected();
return true;
}
bool RemoteInputFilter::LocalKeyPressed(uint32_t usb_keycode) {
// If local echo is expected and |usb_keycode| is the oldest unechoed injected
// keypress, then ignore it.
if (expect_local_echo_ && !injected_key_presses_.empty() &&
injected_key_presses_.front() == usb_keycode) {
injected_key_presses_.pop_front();
return false;
}
LocalInputDetected();
return true;
}
void RemoteInputFilter::LocalInputDetected() {
event_tracker_->ReleaseAll();
latest_local_input_time_ = base::TimeTicks::Now();
return true;
}
void RemoteInputFilter::SetExpectLocalEcho(bool expect_local_echo) {
......@@ -76,6 +98,13 @@ void RemoteInputFilter::SetExpectLocalEcho(bool expect_local_echo) {
void RemoteInputFilter::InjectKeyEvent(const protocol::KeyEvent& event) {
if (ShouldIgnoreInput())
return;
if (expect_local_echo_ && event.pressed() && event.has_usb_keycode()) {
injected_key_presses_.push_back(event.usb_keycode());
if (injected_key_presses_.size() > kNumRemoteKeyPresses) {
VLOG(1) << "Injected key press queue full.";
injected_key_presses_.clear();
}
}
event_tracker_->InjectKeyEvent(event);
}
......
......@@ -31,6 +31,12 @@ class RemoteInputFilter : public protocol::InputStub {
// input was local, or false if it was rejected as an echo.
bool LocalPointerMoved(const webrtc::DesktopVector& pos, ui::EventType type);
// Informs the filter that a local keypress event has been detected. If the
// key does not correspond to one we injected then we assume that it is local,
// and block remote input for a short while. Returns true if the input was
// local, or false if it was rejected as an echo.
bool LocalKeyPressed(uint32_t usb_keycode);
// Informs the filter that injecting input causes an echo.
void SetExpectLocalEcho(bool expect_local_echo);
......@@ -42,12 +48,15 @@ class RemoteInputFilter : public protocol::InputStub {
private:
bool ShouldIgnoreInput() const;
void LocalInputDetected();
protocol::InputEventTracker* event_tracker_;
// Queue of recently-injected mouse positions used to distinguish echoes of
// injected events from movements from a local input device.
// Queue of recently-injected mouse positions and keypresses used to
// distinguish echoes of injected events from movements from a local
// input device.
std::list<webrtc::DesktopVector> injected_mouse_positions_;
std::list<uint32_t> injected_key_presses_;
// Time at which local input events were most recently observed.
base::TimeTicks latest_local_input_time_;
......
......@@ -133,6 +133,59 @@ TEST(RemoteInputFilterTest, LocalEchosAndLocalActivity) {
}
}
// Verify that local keyboard input blocks activity.
TEST(RemoteInputFilterTest, LocalKeyPressEventBlocksInput) {
MockInputStub mock_stub;
InputEventTracker input_tracker(&mock_stub);
RemoteInputFilter input_filter(&input_tracker);
input_filter.LocalKeyPressed(0);
input_filter.InjectKeyEvent(UsbKeyEvent(1, true));
}
// Verify that local echoes of remote keyboard activity does not block input
TEST(RemoteInputFilterTest, LocalEchoOfKeyPressEventDoesNotBlockInput) {
MockInputStub mock_stub;
InputEventTracker input_tracker(&mock_stub);
RemoteInputFilter input_filter(&input_tracker);
EXPECT_CALL(mock_stub, InjectKeyEvent(_)).Times(4);
input_filter.InjectKeyEvent(UsbKeyEvent(1, true));
input_filter.InjectKeyEvent(UsbKeyEvent(1, false));
input_filter.LocalKeyPressed(1);
input_filter.InjectKeyEvent(UsbKeyEvent(2, true));
input_filter.InjectKeyEvent(UsbKeyEvent(2, false));
}
// Verify that local input matching remote keyboard activity that has already
// been discarded as an echo blocks input.
TEST(RemoteInputFilterTest, LocalKeyPressEventMatchingPreviousEchoBlocksInput) {
MockInputStub mock_stub;
InputEventTracker input_tracker(&mock_stub);
RemoteInputFilter input_filter(&input_tracker);
EXPECT_CALL(mock_stub, InjectKeyEvent(_)).Times(2);
input_filter.InjectKeyEvent(UsbKeyEvent(1, true));
input_filter.InjectKeyEvent(UsbKeyEvent(1, false));
input_filter.LocalKeyPressed(1);
input_filter.LocalKeyPressed(1);
input_filter.InjectKeyEvent(UsbKeyEvent(2, true));
input_filter.InjectKeyEvent(UsbKeyEvent(2, false));
}
// Verify that local input matching remote keyboard activity blocks input if
// local echo is not expected
TEST(RemoteInputFilterTest,
LocalDuplicateKeyPressEventBlocksInputIfEchoDisabled) {
MockInputStub mock_stub;
InputEventTracker input_tracker(&mock_stub);
RemoteInputFilter input_filter(&input_tracker);
input_filter.SetExpectLocalEcho(false);
EXPECT_CALL(mock_stub, InjectKeyEvent(_)).Times(2);
input_filter.InjectKeyEvent(UsbKeyEvent(1, true));
input_filter.InjectKeyEvent(UsbKeyEvent(1, false));
input_filter.LocalKeyPressed(1);
input_filter.InjectKeyEvent(UsbKeyEvent(2, true));
input_filter.InjectKeyEvent(UsbKeyEvent(2, false));
}
// Verify that local activity also causes buttons, keys, and touches to be
// released.
TEST(RemoteInputFilterTest, LocalActivityReleasesAll) {
......
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