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