Refactor EventHubTests to use UinputDevice
EventHubTests uses uinput to emulate input devices. We refactor the
system calls into a untility file UinputDevice.h so it can be reused for
other integration tests.
Test: atest inputflinger_tests
Change-Id: If6b1b94b14c0bc71193cbc69ce1239a01990559b
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 09ecb13..f913d82 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -10,6 +10,7 @@
"InputClassifierConverter_test.cpp",
"InputDispatcher_test.cpp",
"InputReader_test.cpp",
+ "UinputDevice.cpp",
],
cflags: [
"-Wall",
diff --git a/services/inputflinger/tests/EventHub_test.cpp b/services/inputflinger/tests/EventHub_test.cpp
index 6504738..be2e19e 100644
--- a/services/inputflinger/tests/EventHub_test.cpp
+++ b/services/inputflinger/tests/EventHub_test.cpp
@@ -16,7 +16,8 @@
#include "EventHub.h"
-#include <android-base/stringprintf.h>
+#include "UinputDevice.h"
+
#include <gtest/gtest.h>
#include <inttypes.h>
#include <linux/uinput.h>
@@ -25,16 +26,16 @@
#define TAG "EventHub_test"
+using android::createUinputDevice;
using android::EventHub;
using android::EventHubInterface;
using android::InputDeviceIdentifier;
using android::RawEvent;
using android::sp;
-using android::base::StringPrintf;
+using android::UinputHomeKey;
using std::chrono_literals::operator""ms;
static constexpr bool DEBUG = false;
-static const char* DEVICE_NAME = "EventHub Test Device";
static void dumpEvents(const std::vector<RawEvent>& events) {
for (const RawEvent& event : events) {
@@ -62,27 +63,26 @@
protected:
std::unique_ptr<EventHubInterface> mEventHub;
// We are only going to emulate a single input device currently.
- android::base::unique_fd mDeviceFd;
+ std::unique_ptr<UinputHomeKey> mKeyboard;
int32_t mDeviceId;
+
virtual void SetUp() override {
mEventHub = std::make_unique<EventHub>();
consumeInitialDeviceAddedEvents();
- createDevice();
+ mKeyboard = createUinputDevice<UinputHomeKey>();
mDeviceId = waitForDeviceCreation();
}
virtual void TearDown() override {
- mDeviceFd.reset();
+ mKeyboard.reset();
waitForDeviceClose(mDeviceId);
}
- void createDevice();
/**
* Return the device id of the created device.
*/
int32_t waitForDeviceCreation();
void waitForDeviceClose(int32_t deviceId);
void consumeInitialDeviceAddedEvents();
- void sendEvent(uint16_t type, uint16_t code, int32_t value);
std::vector<RawEvent> getEvents(std::chrono::milliseconds timeout = 5ms);
};
@@ -105,48 +105,6 @@
return events;
}
-void EventHubTest::createDevice() {
- mDeviceFd = android::base::unique_fd(open("/dev/uinput", O_WRONLY | O_NONBLOCK));
- if (mDeviceFd < 0) {
- FAIL() << "Can't open /dev/uinput :" << strerror(errno);
- }
-
- /**
- * Signal which type of events this input device supports.
- * We will emulate a keyboard here.
- */
- // enable key press/release event
- if (ioctl(mDeviceFd, UI_SET_EVBIT, EV_KEY)) {
- ADD_FAILURE() << "Error in ioctl : UI_SET_EVBIT : EV_KEY: " << strerror(errno);
- }
-
- // enable set of KEY events
- if (ioctl(mDeviceFd, UI_SET_KEYBIT, KEY_HOME)) {
- ADD_FAILURE() << "Error in ioctl : UI_SET_KEYBIT : KEY_HOME: " << strerror(errno);
- }
-
- // enable synchronization event
- if (ioctl(mDeviceFd, UI_SET_EVBIT, EV_SYN)) {
- ADD_FAILURE() << "Error in ioctl : UI_SET_EVBIT : EV_SYN: " << strerror(errno);
- }
-
- struct uinput_user_dev keyboard = {};
- strlcpy(keyboard.name, DEVICE_NAME, UINPUT_MAX_NAME_SIZE);
- keyboard.id.bustype = BUS_USB;
- keyboard.id.vendor = 0x01;
- keyboard.id.product = 0x01;
- keyboard.id.version = 1;
-
- if (write(mDeviceFd, &keyboard, sizeof(keyboard)) < 0) {
- FAIL() << "Could not write uinput_user_dev struct into uinput file descriptor: "
- << strerror(errno);
- }
-
- if (ioctl(mDeviceFd, UI_DEV_CREATE)) {
- FAIL() << "Error in ioctl : UI_DEV_CREATE: " << strerror(errno);
- }
-}
-
/**
* Since the test runs on a real platform, there will be existing devices
* in addition to the test devices being added. Therefore, when EventHub is first created,
@@ -176,7 +134,7 @@
EXPECT_EQ(static_cast<int32_t>(EventHubInterface::DEVICE_ADDED), deviceAddedEvent.type);
InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceAddedEvent.deviceId);
const int32_t deviceId = deviceAddedEvent.deviceId;
- EXPECT_EQ(identifier.name, DEVICE_NAME);
+ EXPECT_EQ(identifier.name, mKeyboard->getName());
const RawEvent& finishedDeviceScanEvent = events[1];
EXPECT_EQ(static_cast<int32_t>(EventHubInterface::FINISHED_DEVICE_SCAN),
finishedDeviceScanEvent.type);
@@ -194,22 +152,6 @@
finishedDeviceScanEvent.type);
}
-void EventHubTest::sendEvent(uint16_t type, uint16_t code, int32_t value) {
- struct input_event event = {};
- event.type = type;
- event.code = code;
- event.value = value;
- event.time = {}; // uinput ignores the timestamp
-
- if (write(mDeviceFd, &event, sizeof(input_event)) < 0) {
- std::string msg = StringPrintf("Could not write event %" PRIu16 " %" PRIu16
- " with value %" PRId32 " : %s",
- type, code, value, strerror(errno));
- ALOGE("%s", msg.c_str());
- ADD_FAILURE() << msg.c_str();
- }
-}
-
/**
* Ensure that input_events are generated with monotonic clock.
* That means input_event should receive a timestamp that is in the future of the time
@@ -218,13 +160,7 @@
*/
TEST_F(EventHubTest, InputEvent_TimestampIsMonotonic) {
nsecs_t lastEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
- // key press
- sendEvent(EV_KEY, KEY_HOME, 1);
- sendEvent(EV_SYN, SYN_REPORT, 0);
-
- // key release
- sendEvent(EV_KEY, KEY_HOME, 0);
- sendEvent(EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mKeyboard->pressAndReleaseHomeKey());
std::vector<RawEvent> events = getEvents();
ASSERT_EQ(4U, events.size()) << "Expected to receive 2 keys and 2 syncs, total of 4 events";
diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp
new file mode 100644
index 0000000..2775d21
--- /dev/null
+++ b/services/inputflinger/tests/UinputDevice.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "UinputDevice.h"
+
+#include <android-base/stringprintf.h>
+
+namespace android {
+
+// --- UinputDevice ---
+
+UinputDevice::UinputDevice(const char* name) : mName(name) {}
+
+UinputDevice::~UinputDevice() {
+ if (ioctl(mDeviceFd, UI_DEV_DESTROY)) {
+ ALOGE("Error while destroying uinput device: %s", strerror(errno));
+ }
+ mDeviceFd.reset();
+}
+
+void UinputDevice::init() {
+ mDeviceFd = android::base::unique_fd(open("/dev/uinput", O_WRONLY | O_NONBLOCK));
+ if (mDeviceFd < 0) {
+ FAIL() << "Can't open /dev/uinput :" << strerror(errno);
+ }
+
+ struct uinput_user_dev device = {};
+ strlcpy(device.name, mName, UINPUT_MAX_NAME_SIZE);
+ device.id.bustype = BUS_USB;
+ device.id.vendor = 0x01;
+ device.id.product = 0x01;
+ device.id.version = 1;
+
+ // Using EXPECT instead of ASSERT to allow the device creation to continue even when
+ // some failures are reported when configuring the device.
+ EXPECT_NO_FATAL_FAILURE(configureDevice(mDeviceFd, &device));
+
+ if (write(mDeviceFd, &device, sizeof(device)) < 0) {
+ FAIL() << "Could not write uinput_user_dev struct into uinput file descriptor: "
+ << strerror(errno);
+ }
+
+ if (ioctl(mDeviceFd, UI_DEV_CREATE)) {
+ FAIL() << "Error in ioctl : UI_DEV_CREATE: " << strerror(errno);
+ }
+}
+
+void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) {
+ struct input_event event = {};
+ event.type = type;
+ event.code = code;
+ event.value = value;
+ event.time = {}; // uinput ignores the timestamp
+
+ if (write(mDeviceFd, &event, sizeof(input_event)) < 0) {
+ std::string msg = base::StringPrintf("Could not write event %" PRIu16 " %" PRIu16
+ " with value %" PRId32 " : %s",
+ type, code, value, strerror(errno));
+ ALOGE("%s", msg.c_str());
+ ADD_FAILURE() << msg.c_str();
+ }
+}
+
+// --- UinputKeyboard ---
+
+UinputKeyboard::UinputKeyboard(std::initializer_list<int> keys)
+ : UinputDevice(UinputKeyboard::KEYBOARD_NAME), mKeys(keys.begin(), keys.end()) {}
+
+void UinputKeyboard::configureDevice(int fd, uinput_user_dev* device) {
+ // enable key press/release event
+ if (ioctl(fd, UI_SET_EVBIT, EV_KEY)) {
+ ADD_FAILURE() << "Error in ioctl : UI_SET_EVBIT : EV_KEY: " << strerror(errno);
+ }
+
+ // enable set of KEY events
+ std::for_each(mKeys.begin(), mKeys.end(), [fd](int key) {
+ if (ioctl(fd, UI_SET_KEYBIT, key)) {
+ ADD_FAILURE() << "Error in ioctl : UI_SET_KEYBIT : " << key << " : " << strerror(errno);
+ }
+ });
+
+ // enable synchronization event
+ if (ioctl(fd, UI_SET_EVBIT, EV_SYN)) {
+ ADD_FAILURE() << "Error in ioctl : UI_SET_EVBIT : EV_SYN: " << strerror(errno);
+ }
+}
+
+void UinputKeyboard::pressKey(int key) {
+ if (mKeys.find(key) == mKeys.end()) {
+ ADD_FAILURE() << mName << ": Cannot inject key press: Key not found: " << key;
+ }
+ EXPECT_NO_FATAL_FAILURE(injectEvent(EV_KEY, key, 1));
+ EXPECT_NO_FATAL_FAILURE(injectEvent(EV_SYN, SYN_REPORT, 0));
+}
+
+void UinputKeyboard::releaseKey(int key) {
+ if (mKeys.find(key) == mKeys.end()) {
+ ADD_FAILURE() << mName << ": Cannot inject key release: Key not found: " << key;
+ }
+ EXPECT_NO_FATAL_FAILURE(injectEvent(EV_KEY, key, 0));
+ EXPECT_NO_FATAL_FAILURE(injectEvent(EV_SYN, SYN_REPORT, 0));
+}
+
+void UinputKeyboard::pressAndReleaseKey(int key) {
+ EXPECT_NO_FATAL_FAILURE(pressKey(key));
+ EXPECT_NO_FATAL_FAILURE(releaseKey(key));
+}
+
+// --- UinputHomeKey---
+
+UinputHomeKey::UinputHomeKey() : UinputKeyboard({KEY_HOME}) {}
+
+void UinputHomeKey::pressAndReleaseHomeKey() {
+ EXPECT_NO_FATAL_FAILURE(pressAndReleaseKey(KEY_HOME));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h
new file mode 100644
index 0000000..57d9011
--- /dev/null
+++ b/services/inputflinger/tests/UinputDevice.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_TEST_INPUT_UINPUT_INJECTOR_H
+#define _UI_TEST_INPUT_UINPUT_INJECTOR_H
+
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <inttypes.h>
+#include <linux/uinput.h>
+#include <log/log.h>
+
+#include <memory>
+
+namespace android {
+
+// This is the factory method that must be used to create a UinputDevice.
+template <class D, class... Ts>
+std::unique_ptr<D> createUinputDevice(Ts... args) {
+ // Using `new` to access non-public constructors.
+ std::unique_ptr<D> dev(new D(&args...));
+ EXPECT_NO_FATAL_FAILURE(dev->init());
+ return dev;
+}
+
+// --- UinputDevice ---
+
+class UinputDevice {
+public:
+ virtual ~UinputDevice();
+
+ inline const char* getName() const { return mName; }
+
+ // Subclasses must either provide a public constructor or must be-friend the factory method.
+ template <class D, class... Ts>
+ friend std::unique_ptr<D> createUinputDevice(Ts... args);
+
+protected:
+ const char* mName;
+
+ UinputDevice(const char* name);
+
+ // Signals which types of events this device supports before it is created.
+ // This must be overridden by subclasses.
+ virtual void configureDevice(int fd, uinput_user_dev* device) = 0;
+
+ void injectEvent(uint16_t type, uint16_t code, int32_t value);
+
+private:
+ base::unique_fd mDeviceFd;
+
+ // This is called once by the factory method createUinputDevice().
+ void init();
+};
+
+// --- UinputKeyboard ---
+
+class UinputKeyboard : public UinputDevice {
+public:
+ static constexpr const char* KEYBOARD_NAME = "Test Keyboard Device";
+
+ // Injects key press and sync.
+ void pressKey(int key);
+ // Injects key release and sync.
+ void releaseKey(int key);
+ // Injects 4 events: key press, sync, key release, and sync.
+ void pressAndReleaseKey(int key);
+
+ template <class D, class... Ts>
+ friend std::unique_ptr<D> createUinputDevice(Ts... args);
+
+protected:
+ UinputKeyboard(std::initializer_list<int> keys = {});
+
+private:
+ void configureDevice(int fd, uinput_user_dev* device) override;
+
+ std::set<int> mKeys;
+};
+
+// --- UinputHomeKey---
+
+// A keyboard device that has a single HOME key.
+class UinputHomeKey : public UinputKeyboard {
+public:
+ // Injects 4 events: key press, sync, key release, and sync.
+ void pressAndReleaseHomeKey();
+
+ template <class D, class... Ts>
+ friend std::unique_ptr<D> createUinputDevice(Ts... args);
+
+private:
+ UinputHomeKey();
+};
+
+} // namespace android
+
+#endif // _UI_TEST_INPUT_UINPUT_INJECTOR_H