summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/hardware/input.h2
-rw-r--r--modules/input/evdev/Android.mk30
-rw-r--r--modules/input/evdev/EvdevModule.cpp80
-rw-r--r--modules/input/evdev/InputDevice.cpp102
-rw-r--r--modules/input/evdev/InputDevice.h60
-rw-r--r--modules/input/evdev/InputDeviceManager.cpp50
-rw-r--r--modules/input/evdev/InputDeviceManager.h53
-rw-r--r--modules/input/evdev/InputHost.cpp78
-rw-r--r--modules/input/evdev/InputHost.h133
-rw-r--r--modules/input/evdev/InputHub.cpp802
-rw-r--r--modules/input/evdev/InputHub.h204
-rw-r--r--tests/input/Android.mk19
-rw-r--r--tests/input/evdev/Android.mk23
-rw-r--r--tests/input/evdev/InputDevice_test.cpp134
-rw-r--r--tests/input/evdev/InputHub_test.cpp259
-rw-r--r--tests/input/evdev/TestHelpers.cpp93
-rw-r--r--tests/input/evdev/TestHelpers.h83
17 files changed, 2197 insertions, 8 deletions
diff --git a/include/hardware/input.h b/include/hardware/input.h
index dad1bc0..d2f2f79 100644
--- a/include/hardware/input.h
+++ b/include/hardware/input.h
@@ -164,7 +164,7 @@ struct input_module {
* Sends an output report with a new set of state the host would like the given device to
* assume.
*/
- void (*notify_report)(input_report_t* report);
+ void (*notify_report)(const input_module_t* module, input_report_t* report);
};
static inline int input_open(const struct hw_module_t** module, const char* type) {
diff --git a/modules/input/evdev/Android.mk b/modules/input/evdev/Android.mk
index ad05af3..d3c49e7 100644
--- a/modules/input/evdev/Android.mk
+++ b/modules/input/evdev/Android.mk
@@ -14,6 +14,29 @@
LOCAL_PATH := $(call my-dir)
+# Evdev module implementation
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ InputHub.cpp \
+ InputDevice.cpp \
+ InputDeviceManager.cpp \
+ InputHost.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libhardware_legacy \
+ liblog \
+ libutils
+
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS += -std=c++14 -Wno-unused-parameter
+
+LOCAL_MODULE := libinput_evdev
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
+
+# HAL module
include $(CLEAR_VARS)
LOCAL_MODULE := input.evdev.default
@@ -22,7 +45,12 @@ LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_SRC_FILES := \
EvdevModule.cpp
-LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_SHARED_LIBRARIES := \
+ libinput_evdev \
+ liblog
+
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS += -std=c++14 -Wno-unused-parameter
LOCAL_MODULE_TAGS := optional
diff --git a/modules/input/evdev/EvdevModule.cpp b/modules/input/evdev/EvdevModule.cpp
index f56842a..e9c8222 100644
--- a/modules/input/evdev/EvdevModule.cpp
+++ b/modules/input/evdev/EvdevModule.cpp
@@ -17,27 +17,95 @@
#define LOG_NDEBUG 0
#define LOG_TAG "EvdevModule"
+#include <memory>
+#include <string>
+#include <thread>
+
#include <assert.h>
#include <hardware/hardware.h>
#include <hardware/input.h>
-namespace input {
+#include <utils/Log.h>
+
+#include "InputHub.h"
+#include "InputDeviceManager.h"
+#include "InputHost.h"
+
+namespace android {
+
+static const char kDevInput[] = "/dev/input";
+
+class EvdevModule {
+public:
+ explicit EvdevModule(InputHost inputHost);
+
+ void init();
+ void notifyReport(input_report_t* r);
+
+private:
+ void loop();
+
+ InputHost mInputHost;
+ std::shared_ptr<InputDeviceManager> mDeviceManager;
+ std::shared_ptr<InputHub> mInputHub;
+ std::thread mPollThread;
+};
+
+static std::shared_ptr<EvdevModule> gEvdevModule;
+
+EvdevModule::EvdevModule(InputHost inputHost) :
+ mInputHost(inputHost),
+ mDeviceManager(std::make_shared<InputDeviceManager>()),
+ mInputHub(std::make_shared<InputHub>(mDeviceManager)) {}
+
+void EvdevModule::init() {
+ ALOGV("%s", __func__);
+
+ mInputHub->registerDevicePath(kDevInput);
+ mPollThread = std::thread(&EvdevModule::loop, this);
+}
+
+void EvdevModule::notifyReport(input_report_t* r) {
+ ALOGV("%s", __func__);
+
+ // notifyReport() will be called from an arbitrary thread within the input
+ // host. Since InputHub is not threadsafe, this is how I expect this to
+ // work:
+ // * notifyReport() will queue up the output report in the EvdevModule and
+ // call wake() on the InputHub.
+ // * In the main loop thread, after returning from poll(), the queue will
+ // be processed with any pending work.
+}
+
+void EvdevModule::loop() {
+ ALOGV("%s", __func__);
+ for (;;) {
+ mInputHub->poll();
+
+ // TODO: process any pending work, like notify reports
+ }
+}
extern "C" {
static int dummy_open(const hw_module_t __unused *module, const char __unused *id,
- hw_device_t __unused **device) {
- assert(false);
+ hw_device_t __unused **device) {
+ ALOGW("open not implemented in the input HAL!");
return 0;
}
static void input_init(const input_module_t* module,
input_host_t* host, input_host_callbacks_t cb) {
- return;
+ LOG_ALWAYS_FATAL_IF(strcmp(module->common.id, INPUT_HARDWARE_MODULE_ID) != 0);
+ InputHost inputHost = {host, cb};
+ gEvdevModule = std::make_shared<EvdevModule>(inputHost);
+ gEvdevModule->init();
}
-static void input_notify_report(input_report_t* r) {
- return;
+static void input_notify_report(const input_module_t* module, input_report_t* r) {
+ LOG_ALWAYS_FATAL_IF(strcmp(module->common.id, INPUT_HARDWARE_MODULE_ID) != 0);
+ LOG_ALWAYS_FATAL_IF(gEvdevModule == nullptr);
+ gEvdevModule->notifyReport(r);
}
static struct hw_module_methods_t input_module_methods = {
diff --git a/modules/input/evdev/InputDevice.cpp b/modules/input/evdev/InputDevice.cpp
new file mode 100644
index 0000000..c0b59d7
--- /dev/null
+++ b/modules/input/evdev/InputDevice.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#define LOG_TAG "InputDevice"
+#define LOG_NDEBUG 0
+
+#include <linux/input.h>
+
+#define __STDC_FORMAT_MACROS
+#include <cinttypes>
+#include <string>
+
+#include <utils/Log.h>
+#include <utils/Timers.h>
+
+#include "InputHub.h"
+#include "InputDevice.h"
+
+#define MSC_ANDROID_TIME_SEC 0x6
+#define MSC_ANDROID_TIME_USEC 0x7
+
+namespace android {
+
+EvdevDevice::EvdevDevice(std::shared_ptr<InputDeviceNode> node) :
+ mDeviceNode(node) {}
+
+void EvdevDevice::processInput(InputEvent& event, nsecs_t currentTime) {
+ std::string log;
+ log.append("---InputEvent for device %s---\n");
+ log.append(" when: %" PRId64 "\n");
+ log.append(" type: %d\n");
+ log.append(" code: %d\n");
+ log.append(" value: %d\n");
+ ALOGV(log.c_str(), mDeviceNode->getPath().c_str(), event.when, event.type, event.code,
+ event.value);
+
+ if (event.type == EV_MSC) {
+ if (event.code == MSC_ANDROID_TIME_SEC) {
+ mOverrideSec = event.value;
+ } else if (event.code == MSC_ANDROID_TIME_USEC) {
+ mOverrideUsec = event.value;
+ }
+ return;
+ }
+
+ if (mOverrideSec || mOverrideUsec) {
+ event.when = s2ns(mOverrideSec) + us2ns(mOverrideUsec);
+ ALOGV("applied override time %d.%06d", mOverrideSec, mOverrideUsec);
+
+ if (event.type == EV_SYN && event.code == SYN_REPORT) {
+ mOverrideSec = 0;
+ mOverrideUsec = 0;
+ }
+ }
+
+ // Bug 7291243: Add a guard in case the kernel generates timestamps
+ // that appear to be far into the future because they were generated
+ // using the wrong clock source.
+ //
+ // This can happen because when the input device is initially opened
+ // it has a default clock source of CLOCK_REALTIME. Any input events
+ // enqueued right after the device is opened will have timestamps
+ // generated using CLOCK_REALTIME. We later set the clock source
+ // to CLOCK_MONOTONIC but it is already too late.
+ //
+ // Invalid input event timestamps can result in ANRs, crashes and
+ // and other issues that are hard to track down. We must not let them
+ // propagate through the system.
+ //
+ // Log a warning so that we notice the problem and recover gracefully.
+ if (event.when >= currentTime + s2ns(10)) {
+ // Double-check. Time may have moved on.
+ auto time = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (event.when > time) {
+ ALOGW("An input event from %s has a timestamp that appears to have "
+ "been generated using the wrong clock source (expected "
+ "CLOCK_MONOTONIC): event time %" PRId64 ", current time %" PRId64
+ ", call time %" PRId64 ". Using current time instead.",
+ mDeviceNode->getPath().c_str(), event.when, time, currentTime);
+ event.when = time;
+ } else {
+ ALOGV("Event time is ok but failed the fast path and required an extra "
+ "call to systemTime: event time %" PRId64 ", current time %" PRId64
+ ", call time %" PRId64 ".", event.when, time, currentTime);
+ }
+ }
+}
+
+} // namespace android
diff --git a/modules/input/evdev/InputDevice.h b/modules/input/evdev/InputDevice.h
new file mode 100644
index 0000000..3aa16cc
--- /dev/null
+++ b/modules/input/evdev/InputDevice.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 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 ANDROID_INPUT_DEVICE_H_
+#define ANDROID_INPUT_DEVICE_H_
+
+#include <memory>
+
+#include <utils/Timers.h>
+
+#include "InputHub.h"
+
+namespace android {
+
+/**
+ * InputDeviceInterface represents an input device in the HAL. It processes
+ * input events before passing them to the input host.
+ */
+class InputDeviceInterface {
+public:
+ virtual void processInput(InputEvent& event, nsecs_t currentTime) = 0;
+
+protected:
+ InputDeviceInterface() = default;
+ virtual ~InputDeviceInterface() = default;
+};
+
+/**
+ * EvdevDevice is an input device backed by a Linux evdev node.
+ */
+class EvdevDevice : public InputDeviceInterface {
+public:
+ explicit EvdevDevice(std::shared_ptr<InputDeviceNode> node);
+ virtual ~EvdevDevice() override = default;
+
+ virtual void processInput(InputEvent& event, nsecs_t currentTime) override;
+
+private:
+ std::shared_ptr<InputDeviceNode> mDeviceNode;
+
+ int32_t mOverrideSec = 0;
+ int32_t mOverrideUsec = 0;
+};
+
+} // namespace android
+
+#endif // ANDROID_INPUT_DEVICE_H_
diff --git a/modules/input/evdev/InputDeviceManager.cpp b/modules/input/evdev/InputDeviceManager.cpp
new file mode 100644
index 0000000..ceddd90
--- /dev/null
+++ b/modules/input/evdev/InputDeviceManager.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#define LOG_TAG "InputDeviceManager"
+//#define LOG_NDEBUG 0
+
+#include <utils/Log.h>
+
+#include "InputDevice.h"
+#include "InputDeviceManager.h"
+
+namespace android {
+
+void InputDeviceManager::onInputEvent(std::shared_ptr<InputDeviceNode> node, InputEvent& event,
+ nsecs_t event_time) {
+ if (mDevices[node] == nullptr) {
+ ALOGE("got input event for unknown node %s", node->getPath().c_str());
+ return;
+ }
+ mDevices[node]->processInput(event, event_time);
+}
+
+void InputDeviceManager::onDeviceAdded(std::shared_ptr<InputDeviceNode> node) {
+ mDevices[node] = std::make_shared<EvdevDevice>(node);
+}
+
+void InputDeviceManager::onDeviceRemoved(std::shared_ptr<InputDeviceNode> node) {
+ if (mDevices[node] == nullptr) {
+ ALOGE("could not remove unknown node %s", node->getPath().c_str());
+ return;
+ }
+ // TODO: tell the InputDevice and InputDeviceNode that they are being
+ // removed so they can run any cleanup.
+ mDevices.erase(node);
+}
+
+} // namespace android
diff --git a/modules/input/evdev/InputDeviceManager.h b/modules/input/evdev/InputDeviceManager.h
new file mode 100644
index 0000000..b652155
--- /dev/null
+++ b/modules/input/evdev/InputDeviceManager.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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 ANDROID_INPUT_DEVICE_MANAGER_H_
+#define ANDROID_INPUT_DEVICE_MANAGER_H_
+
+#include <memory>
+#include <unordered_map>
+
+#include <utils/Timers.h>
+
+#include "InputDevice.h"
+#include "InputHub.h"
+
+namespace android {
+
+/**
+ * InputDeviceManager keeps the mapping of InputDeviceNodes to
+ * InputDeviceInterfaces and handles the callbacks from the InputHub, delegating
+ * them to the appropriate InputDeviceInterface.
+ */
+class InputDeviceManager : public InputCallbackInterface {
+public:
+ virtual ~InputDeviceManager() override = default;
+
+ virtual void onInputEvent(std::shared_ptr<InputDeviceNode> node, InputEvent& event,
+ nsecs_t event_time) override;
+ virtual void onDeviceAdded(std::shared_ptr<InputDeviceNode> node) override;
+ virtual void onDeviceRemoved(std::shared_ptr<InputDeviceNode> node) override;
+
+private:
+ template<class T, class U>
+ using DeviceMap = std::unordered_map<std::shared_ptr<T>, std::shared_ptr<U>>;
+
+ DeviceMap<InputDeviceNode, InputDeviceInterface> mDevices;
+};
+
+} // namespace android
+
+#endif // ANDROID_INPUT_DEVICE_MANAGER_H_
diff --git a/modules/input/evdev/InputHost.cpp b/modules/input/evdev/InputHost.cpp
new file mode 100644
index 0000000..0903f47
--- /dev/null
+++ b/modules/input/evdev/InputHost.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 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 "InputHost.h"
+
+namespace android {
+
+void InputReport::reportEvent(InputDeviceHandle d) {
+ mCallbacks.report_event(mHost, d, mReport);
+}
+
+void InputReportDefinition::addCollection(InputCollectionId id, int32_t arity) {
+ mCallbacks.input_report_definition_add_collection(mHost, mReportDefinition, id, arity);
+}
+
+void InputReportDefinition::declareUsage(InputCollectionId id, InputUsage usage,
+ int32_t min, int32_t max, float resolution) {
+ mCallbacks.input_report_definition_declare_usage_int(mHost, mReportDefinition,
+ id, usage, min, max, resolution);
+}
+
+void InputReportDefinition::declareUsage(InputCollectionId id, InputUsage* usage,
+ size_t usageCount) {
+ mCallbacks.input_report_definition_declare_usages_bool(mHost, mReportDefinition,
+ id, usage, usageCount);
+}
+
+InputReport InputReportDefinition::allocateReport() {
+ return InputReport(mHost, mCallbacks,
+ mCallbacks.input_allocate_report(mHost, mReportDefinition));
+}
+
+void InputDeviceDefinition::addReport(InputReportDefinition r) {
+ mCallbacks.input_device_definition_add_report(mHost, mDeviceDefinition, r);
+}
+
+InputDeviceIdentifier InputHost::createDeviceIdentifier(const char* name, int32_t productId,
+ int32_t vendorId, InputBus bus, const char* uniqueId) {
+ return mCallbacks.create_device_identifier(mHost, name, productId, vendorId, bus, uniqueId);
+}
+
+InputDeviceDefinition InputHost::createDeviceDefinition() {
+ return InputDeviceDefinition(mHost, mCallbacks, mCallbacks.create_device_definition(mHost));
+}
+
+InputReportDefinition InputHost::createInputReportDefinition() {
+ return InputReportDefinition(mHost, mCallbacks,
+ mCallbacks.create_input_report_definition(mHost));
+}
+
+InputReportDefinition InputHost::createOutputReportDefinition() {
+ return InputReportDefinition(mHost, mCallbacks,
+ mCallbacks.create_output_report_definition(mHost));
+}
+
+InputDeviceHandle InputHost::registerDevice(InputDeviceIdentifier id,
+ InputDeviceDefinition d) {
+ return mCallbacks.register_device(mHost, id, d);
+}
+
+void InputHost::unregisterDevice(InputDeviceHandle handle) {
+ return mCallbacks.unregister_device(mHost, handle);
+}
+
+} // namespace android
diff --git a/modules/input/evdev/InputHost.h b/modules/input/evdev/InputHost.h
new file mode 100644
index 0000000..129443e
--- /dev/null
+++ b/modules/input/evdev/InputHost.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 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 ANDROID_INPUT_HOST_H_
+#define ANDROID_INPUT_HOST_H_
+
+#include <hardware/input.h>
+
+namespace android {
+
+/**
+ * Classes in this file wrap the corresponding interfaces in the Input HAL. They
+ * are intended to be lightweight and passed by value. It is still important not
+ * to use an object after a HAL-specific method has freed the underlying
+ * representation.
+ *
+ * See hardware/input.h for details about each of these methods.
+ */
+
+using InputBus = input_bus_t;
+using InputCollectionId = input_collection_id_t;
+using InputDeviceHandle = input_device_handle_t*;
+using InputDeviceIdentifier = input_device_identifier_t*;
+using InputUsage = input_usage_t;
+
+class InputHostBase {
+protected:
+ InputHostBase(input_host_t* host, input_host_callbacks_t cb) : mHost(host), mCallbacks(cb) {}
+ virtual ~InputHostBase() = default;
+
+ input_host_t* mHost;
+ input_host_callbacks_t mCallbacks;
+};
+
+class InputReport : private InputHostBase {
+public:
+ virtual ~InputReport() = default;
+
+ InputReport(const InputReport& rhs) = default;
+ InputReport& operator=(const InputReport& rhs) = default;
+ operator input_report_t*() const { return mReport; }
+
+ void reportEvent(InputDeviceHandle d);
+
+private:
+ friend class InputReportDefinition;
+
+ InputReport(input_host_t* host, input_host_callbacks_t cb, input_report_t* r) :
+ InputHostBase(host, cb), mReport(r) {}
+
+ input_report_t* mReport;
+};
+
+class InputReportDefinition : private InputHostBase {
+public:
+ virtual ~InputReportDefinition() = default;
+
+ InputReportDefinition(const InputReportDefinition& rhs) = default;
+ InputReportDefinition& operator=(const InputReportDefinition& rhs) = default;
+ operator input_report_definition_t*() { return mReportDefinition; }
+
+ void addCollection(InputCollectionId id, int32_t arity);
+ void declareUsage(InputCollectionId id, InputUsage usage, int32_t min, int32_t max,
+ float resolution);
+ void declareUsage(InputCollectionId id, InputUsage* usage, size_t usageCount);
+
+ InputReport allocateReport();
+
+private:
+ friend class InputHost;
+
+ InputReportDefinition(
+ input_host_t* host, input_host_callbacks_t cb, input_report_definition_t* r) :
+ InputHostBase(host, cb), mReportDefinition(r) {}
+
+ input_report_definition_t* mReportDefinition;
+};
+
+class InputDeviceDefinition : private InputHostBase {
+public:
+ virtual ~InputDeviceDefinition() = default;
+
+ InputDeviceDefinition(const InputDeviceDefinition& rhs) = default;
+ InputDeviceDefinition& operator=(const InputDeviceDefinition& rhs) = default;
+ operator input_device_definition_t*() { return mDeviceDefinition; }
+
+ void addReport(InputReportDefinition r);
+
+private:
+ friend class InputHost;
+
+ InputDeviceDefinition(
+ input_host_t* host, input_host_callbacks_t cb, input_device_definition_t* d) :
+ InputHostBase(host, cb), mDeviceDefinition(d) {}
+
+ input_device_definition_t* mDeviceDefinition;
+};
+
+class InputHost : private InputHostBase {
+public:
+ InputHost(input_host_t* host, input_host_callbacks_t cb) : InputHostBase(host, cb) {}
+ virtual ~InputHost() = default;
+
+ InputHost(const InputHost& rhs) = default;
+ InputHost& operator=(const InputHost& rhs) = default;
+
+ InputDeviceIdentifier createDeviceIdentifier(const char* name, int32_t productId,
+ int32_t vendorId, InputBus bus, const char* uniqueId);
+
+ InputDeviceDefinition createDeviceDefinition();
+ InputReportDefinition createInputReportDefinition();
+ InputReportDefinition createOutputReportDefinition();
+
+ InputDeviceHandle registerDevice(InputDeviceIdentifier id, InputDeviceDefinition d);
+ void unregisterDevice(InputDeviceHandle handle);
+};
+
+} // namespace android
+
+#endif // ANDROID_INPUT_HOST_H_
diff --git a/modules/input/evdev/InputHub.cpp b/modules/input/evdev/InputHub.cpp
new file mode 100644
index 0000000..e72ac2e
--- /dev/null
+++ b/modules/input/evdev/InputHub.cpp
@@ -0,0 +1,802 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#define LOG_TAG "InputHub"
+#define LOG_NDEBUG 0
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <sys/inotify.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "InputHub.h"
+
+#include <android/input.h>
+#include <hardware_legacy/power.h>
+#include <linux/input.h>
+
+#include <utils/Log.h>
+
+namespace android {
+
+static const char WAKE_LOCK_ID[] = "KeyEvents";
+static const int NO_TIMEOUT = -1;
+static const int EPOLL_MAX_EVENTS = 16;
+static const int INPUT_MAX_EVENTS = 128;
+
+static constexpr bool testBit(int bit, const uint8_t arr[]) {
+ return arr[bit / 8] & (1 << (bit % 8));
+}
+
+static constexpr size_t sizeofBitArray(size_t bits) {
+ return (bits + 7) / 8;
+}
+
+static void getLinuxRelease(int* major, int* minor) {
+ struct utsname info;
+ if (uname(&info) || sscanf(info.release, "%d.%d", major, minor) <= 0) {
+ *major = 0, *minor = 0;
+ ALOGE("Could not get linux version: %s", strerror(errno));
+ }
+}
+
+static bool processHasCapability(int capability) {
+ LOG_ALWAYS_FATAL_IF(!cap_valid(capability), "invalid linux capability: %d", capability);
+ struct __user_cap_header_struct cap_header_data;
+ struct __user_cap_data_struct cap_data_data[2];
+ cap_user_header_t caphdr = &cap_header_data;
+ cap_user_data_t capdata = cap_data_data;
+ caphdr->pid = 0;
+ caphdr->version = _LINUX_CAPABILITY_VERSION_3;
+ LOG_ALWAYS_FATAL_IF(capget(caphdr, capdata) != 0,
+ "Could not get process capabilities. errno=%d", errno);
+ ALOGV("effective capabilities: %08x %08x", capdata[0].effective, capdata[1].effective);
+ int idx = CAP_TO_INDEX(capability);
+ return capdata[idx].effective & CAP_TO_MASK(capability);
+}
+
+class EvdevDeviceNode : public InputDeviceNode {
+public:
+ static EvdevDeviceNode* openDeviceNode(const std::string& path);
+
+ virtual ~EvdevDeviceNode() {
+ ALOGV("closing %s (fd=%d)", mPath.c_str(), mFd);
+ if (mFd >= 0) {
+ ::close(mFd);
+ }
+ }
+
+ virtual int getFd() const { return mFd; }
+ virtual const std::string& getPath() const override { return mPath; }
+ virtual const std::string& getName() const override { return mName; }
+ virtual const std::string& getLocation() const override { return mLocation; }
+ virtual const std::string& getUniqueId() const override { return mUniqueId; }
+
+ virtual uint16_t getBusType() const override { return mBusType; }
+ virtual uint16_t getVendorId() const override { return mVendorId; }
+ virtual uint16_t getProductId() const override { return mProductId; }
+ virtual uint16_t getVersion() const override { return mVersion; }
+
+ virtual bool hasKey(int32_t key) const override;
+ virtual bool hasRelativeAxis(int axis) const override;
+ virtual const AbsoluteAxisInfo* getAbsoluteAxisInfo(int32_t axis) const override;
+ virtual bool hasInputProperty(int property) const override;
+
+ virtual int32_t getKeyState(int32_t key) const override;
+ virtual int32_t getSwitchState(int32_t sw) const override;
+ virtual status_t getAbsoluteAxisValue(int32_t axis, int32_t* outValue) const override;
+
+ virtual void vibrate(nsecs_t duration) override;
+ virtual void cancelVibrate(int32_t deviceId) override;
+
+ virtual void disableDriverKeyRepeat() override;
+
+private:
+ EvdevDeviceNode(const std::string& path, int fd) :
+ mFd(fd), mPath(path) {}
+
+ status_t queryProperties();
+ void queryAxisInfo();
+
+ int mFd;
+ std::string mPath;
+
+ std::string mName;
+ std::string mLocation;
+ std::string mUniqueId;
+
+ uint16_t mBusType;
+ uint16_t mVendorId;
+ uint16_t mProductId;
+ uint16_t mVersion;
+
+ uint8_t mKeyBitmask[KEY_CNT / 8];
+ uint8_t mAbsBitmask[ABS_CNT / 8];
+ uint8_t mRelBitmask[REL_CNT / 8];
+ uint8_t mSwBitmask[SW_CNT / 8];
+ uint8_t mLedBitmask[LED_CNT / 8];
+ uint8_t mFfBitmask[FF_CNT / 8];
+ uint8_t mPropBitmask[INPUT_PROP_CNT / 8];
+
+ std::unordered_map<uint32_t, std::unique_ptr<AbsoluteAxisInfo>> mAbsInfo;
+
+ bool mFfEffectPlaying = false;
+ int16_t mFfEffectId = -1;
+};
+
+EvdevDeviceNode* EvdevDeviceNode::openDeviceNode(const std::string& path) {
+ auto fd = TEMP_FAILURE_RETRY(::open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC));
+ if (fd < 0) {
+ ALOGE("could not open evdev device %s. err=%d", path.c_str(), errno);
+ return nullptr;
+ }
+
+ // Tell the kernel that we want to use the monotonic clock for reporting
+ // timestamps associated with input events. This is important because the
+ // input system uses the timestamps extensively and assumes they were
+ // recorded using the monotonic clock.
+ //
+ // The EVIOCSCLOCKID ioctl was introduced in Linux 3.4.
+ int clockId = CLOCK_MONOTONIC;
+ if (TEMP_FAILURE_RETRY(ioctl(fd, EVIOCSCLOCKID, &clockId)) < 0) {
+ ALOGW("Could not set input clock id to CLOCK_MONOTONIC. errno=%d", errno);
+ }
+
+ auto node = new EvdevDeviceNode(path, fd);
+ status_t ret = node->queryProperties();
+ if (ret != OK) {
+ ALOGE("could not open evdev device %s: failed to read properties. errno=%d",
+ path.c_str(), ret);
+ delete node;
+ return nullptr;
+ }
+ return node;
+}
+
+status_t EvdevDeviceNode::queryProperties() {
+ char buffer[80];
+
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGNAME(sizeof(buffer) - 1), buffer)) < 1) {
+ ALOGV("could not get device name for %s.", mPath.c_str());
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ mName = buffer;
+ }
+
+ int driverVersion;
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGVERSION, &driverVersion))) {
+ ALOGE("could not get driver version for %s. err=%d", mPath.c_str(), errno);
+ return -errno;
+ }
+
+ struct input_id inputId;
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGID, &inputId))) {
+ ALOGE("could not get device input id for %s. err=%d", mPath.c_str(), errno);
+ return -errno;
+ }
+ mBusType = inputId.bustype;
+ mVendorId = inputId.vendor;
+ mProductId = inputId.product;
+ mVersion = inputId.version;
+
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGPHYS(sizeof(buffer) - 1), buffer)) < 1) {
+ ALOGV("could not get location for %s.", mPath.c_str());
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ mLocation = buffer;
+ }
+
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGUNIQ(sizeof(buffer) - 1), buffer)) < 1) {
+ ALOGV("could not get unique id for %s.", mPath.c_str());
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ mUniqueId = buffer;
+ }
+
+ ALOGV("add device %s", mPath.c_str());
+ ALOGV(" bus: %04x\n"
+ " vendor: %04x\n"
+ " product: %04x\n"
+ " version: %04x\n",
+ mBusType, mVendorId, mProductId, mVersion);
+ ALOGV(" name: \"%s\"\n"
+ " location: \"%s\"\n"
+ " unique_id: \"%s\"\n"
+ " descriptor: (TODO)\n"
+ " driver: v%d.%d.%d",
+ mName.c_str(), mLocation.c_str(), mUniqueId.c_str(),
+ driverVersion >> 16, (driverVersion >> 8) & 0xff, (driverVersion >> 16) & 0xff);
+
+ TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_KEY, sizeof(mKeyBitmask)), mKeyBitmask));
+ TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_ABS, sizeof(mAbsBitmask)), mAbsBitmask));
+ TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_REL, sizeof(mRelBitmask)), mRelBitmask));
+ TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_SW, sizeof(mSwBitmask)), mSwBitmask));
+ TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_LED, sizeof(mLedBitmask)), mLedBitmask));
+ TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_FF, sizeof(mFfBitmask)), mFfBitmask));
+ TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGPROP(sizeof(mPropBitmask)), mPropBitmask));
+
+ queryAxisInfo();
+
+ return OK;
+}
+
+void EvdevDeviceNode::queryAxisInfo() {
+ for (int32_t axis = 0; axis < ABS_MAX; ++axis) {
+ if (testBit(axis, mAbsBitmask)) {
+ struct input_absinfo info;
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGABS(axis), &info))) {
+ ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
+ axis, mPath.c_str(), mFd, errno);
+ continue;
+ }
+
+ mAbsInfo[axis] = std::unique_ptr<AbsoluteAxisInfo>(new AbsoluteAxisInfo{
+ .minValue = info.minimum,
+ .maxValue = info.maximum,
+ .flat = info.flat,
+ .fuzz = info.fuzz,
+ .resolution = info.resolution
+ });
+ }
+ }
+}
+
+bool EvdevDeviceNode::hasKey(int32_t key) const {
+ if (key >= 0 && key <= KEY_MAX) {
+ return testBit(key, mKeyBitmask);
+ }
+ return false;
+}
+
+bool EvdevDeviceNode::hasRelativeAxis(int axis) const {
+ if (axis >= 0 && axis <= REL_MAX) {
+ return testBit(axis, mRelBitmask);
+ }
+ return false;
+}
+
+const AbsoluteAxisInfo* EvdevDeviceNode::getAbsoluteAxisInfo(int32_t axis) const {
+ if (axis < 0 || axis > ABS_MAX) {
+ return nullptr;
+ }
+
+ const auto absInfo = mAbsInfo.find(axis);
+ if (absInfo != mAbsInfo.end()) {
+ return absInfo->second.get();
+ }
+ return nullptr;
+}
+
+bool EvdevDeviceNode::hasInputProperty(int property) const {
+ if (property >= 0 && property <= INPUT_PROP_MAX) {
+ return testBit(property, mPropBitmask);
+ }
+ return false;
+}
+
+int32_t EvdevDeviceNode::getKeyState(int32_t key) const {
+ if (key >= 0 && key <= KEY_MAX) {
+ if (testBit(key, mKeyBitmask)) {
+ uint8_t keyState[sizeofBitArray(KEY_CNT)];
+ memset(keyState, 0, sizeof(keyState));
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGKEY(sizeof(keyState)), keyState)) >= 0) {
+ return testBit(key, keyState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EvdevDeviceNode::getSwitchState(int32_t sw) const {
+ if (sw >= 0 && sw <= SW_MAX) {
+ if (testBit(sw, mSwBitmask)) {
+ uint8_t swState[sizeofBitArray(SW_CNT)];
+ memset(swState, 0, sizeof(swState));
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGSW(sizeof(swState)), swState)) >= 0) {
+ return testBit(sw, swState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+status_t EvdevDeviceNode::getAbsoluteAxisValue(int32_t axis, int32_t* outValue) const {
+ *outValue = 0;
+
+ if (axis >= 0 && axis <= ABS_MAX) {
+ if (testBit(axis, mAbsBitmask)) {
+ struct input_absinfo info;
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGABS(axis), &info))) {
+ ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
+ axis, mPath.c_str(), mFd, errno);
+ return -errno;
+ }
+
+ *outValue = info.value;
+ return OK;
+ }
+ }
+ return -1;
+}
+
+void EvdevDeviceNode::vibrate(nsecs_t duration) {
+ ff_effect effect{};
+ effect.type = FF_RUMBLE;
+ effect.id = mFfEffectId;
+ effect.u.rumble.strong_magnitude = 0xc000;
+ effect.u.rumble.weak_magnitude = 0xc000;
+ effect.replay.length = (duration + 999'999LL) / 1'000'000LL;
+ effect.replay.delay = 0;
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCSFF, &effect))) {
+ ALOGW("Could not upload force feedback effect to device %s due to error %d.",
+ mPath.c_str(), errno);
+ return;
+ }
+ mFfEffectId = effect.id;
+
+ struct input_event ev{};
+ ev.type = EV_FF;
+ ev.code = mFfEffectId;
+ ev.value = 1;
+ size_t written = TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(ev)));
+ if (written != sizeof(ev)) {
+ ALOGW("Could not start force feedback effect on device %s due to error %d.",
+ mPath.c_str(), errno);
+ return;
+ }
+ mFfEffectPlaying = true;
+}
+
+void EvdevDeviceNode::cancelVibrate(int32_t deviceId) {
+ if (mFfEffectPlaying) {
+ mFfEffectPlaying = false;
+
+ struct input_event ev{};
+ ev.type = EV_FF;
+ ev.code = mFfEffectId;
+ ev.value = 0;
+ size_t written = TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(ev)));
+ if (written != sizeof(ev)) {
+ ALOGW("Could not stop force feedback effect on device %s due to error %d.",
+ mPath.c_str(), errno);
+ return;
+ }
+ }
+}
+
+void EvdevDeviceNode::disableDriverKeyRepeat() {
+ unsigned int repeatRate[] = {0, 0};
+ if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCSREP, repeatRate))) {
+ ALOGW("Unable to disable kernel key repeat for %s due to error %d.",
+ mPath.c_str(), errno);
+ }
+}
+
+InputHub::InputHub(std::shared_ptr<InputCallbackInterface> cb) :
+ mInputCallback(cb) {
+ // Determine the type of suspend blocking we can do on this device. There
+ // are 3 options, in decreasing order of preference:
+ // 1) EPOLLWAKEUP: introduced in Linux kernel 3.5, this flag can be set on
+ // an epoll event to indicate that a wake lock should be held from the
+ // time an fd has data until the next epoll_wait (or the epoll fd is
+ // closed).
+ // 2) EVIOCSSUSPENDBLOCK: introduced into the Android kernel's evdev
+ // driver, this ioctl blocks suspend while the event queue for the fd is
+ // not empty. This was never accepted into the mainline kernel, and it was
+ // replaced by EPOLLWAKEUP.
+ // 3) explicit wake locks: use acquire_wake_lock to manage suspend
+ // blocking explicitly in the InputHub code.
+ //
+ // (1) can be checked by simply observing the Linux kernel version. (2)
+ // requires an fd from an evdev node, which cannot be done in the InputHub
+ // constructor. So we assume (3) unless (1) is true, and we can verify
+ // whether (2) is true once we have an evdev fd (and we're not in (1)).
+ int major, minor;
+ getLinuxRelease(&major, &minor);
+ if (major > 3 || (major == 3 && minor >= 5)) {
+ ALOGI("Using EPOLLWAKEUP to block suspend while processing input events.");
+ mWakeupMechanism = WakeMechanism::EPOLL_WAKEUP;
+ mNeedToCheckSuspendBlockIoctl = false;
+ }
+ if (manageWakeLocks()) {
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+ }
+
+ // epoll_create argument is ignored, but it must be > 0.
+ mEpollFd = epoll_create(1);
+ LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
+
+ mINotifyFd = inotify_init();
+ LOG_ALWAYS_FATAL_IF(mINotifyFd < 0, "Could not create inotify instance. errno=%d", errno);
+
+ struct epoll_event eventItem;
+ memset(&eventItem, 0, sizeof(eventItem));
+ eventItem.events = EPOLLIN;
+ if (mWakeupMechanism == WakeMechanism::EPOLL_WAKEUP) {
+ eventItem.events |= EPOLLWAKEUP;
+ }
+ eventItem.data.u32 = mINotifyFd;
+ int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
+
+ int wakeFds[2];
+ result = pipe(wakeFds);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
+
+ mWakeEventFd = eventfd(0, EFD_NONBLOCK);
+ LOG_ALWAYS_FATAL_IF(mWakeEventFd == -1, "Could not create wake event fd. errno=%d", errno);
+
+ eventItem.data.u32 = mWakeEventFd;
+ result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, &eventItem);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance. errno=%d", errno);
+}
+
+InputHub::~InputHub() {
+ ::close(mEpollFd);
+ ::close(mINotifyFd);
+ ::close(mWakeEventFd);
+
+ if (manageWakeLocks()) {
+ release_wake_lock(WAKE_LOCK_ID);
+ }
+}
+
+status_t InputHub::registerDevicePath(const std::string& path) {
+ ALOGV("registering device path %s", path.c_str());
+ int wd = inotify_add_watch(mINotifyFd, path.c_str(), IN_DELETE | IN_CREATE);
+ if (wd < 0) {
+ ALOGE("Could not add %s to INotify watch. errno=%d", path.c_str(), errno);
+ return -errno;
+ }
+ mWatchedPaths[wd] = path;
+ scanDir(path);
+ return OK;
+}
+
+status_t InputHub::unregisterDevicePath(const std::string& path) {
+ int wd = -1;
+ for (auto pair : mWatchedPaths) {
+ if (pair.second == path) {
+ wd = pair.first;
+ break;
+ }
+ }
+
+ if (wd == -1) {
+ return BAD_VALUE;
+ }
+ mWatchedPaths.erase(wd);
+ if (inotify_rm_watch(mINotifyFd, wd) != 0) {
+ return -errno;
+ }
+ return OK;
+}
+
+status_t InputHub::poll() {
+ bool deviceChange = false;
+
+ if (manageWakeLocks()) {
+ // Mind the wake lock dance!
+ // If we're relying on wake locks, we hold a wake lock at all times
+ // except during epoll_wait(). This works due to some subtle
+ // choreography. When a device driver has pending (unread) events, it
+ // acquires a kernel wake lock. However, once the last pending event
+ // has been read, the device driver will release the kernel wake lock.
+ // To prevent the system from going to sleep when this happens, the
+ // InputHub holds onto its own user wake lock while the client is
+ // processing events. Thus the system can only sleep if there are no
+ // events pending or currently being processed.
+ release_wake_lock(WAKE_LOCK_ID);
+ }
+
+ struct epoll_event pendingEventItems[EPOLL_MAX_EVENTS];
+ int pollResult = epoll_wait(mEpollFd, pendingEventItems, EPOLL_MAX_EVENTS, NO_TIMEOUT);
+
+ if (manageWakeLocks()) {
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+ }
+
+ if (pollResult == 0) {
+ ALOGW("epoll_wait should not return 0 with no timeout");
+ return UNKNOWN_ERROR;
+ }
+ if (pollResult < 0) {
+ // An error occurred. Return even if it's EINTR, and let the caller
+ // restart the poll.
+ ALOGE("epoll_wait returned with errno=%d", errno);
+ return -errno;
+ }
+
+ // pollResult > 0: there are events to process
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ std::vector<int> removedDeviceFds;
+ int inputFd = -1;
+ std::shared_ptr<InputDeviceNode> deviceNode;
+ for (int i = 0; i < pollResult; ++i) {
+ const struct epoll_event& eventItem = pendingEventItems[i];
+
+ int dataFd = static_cast<int>(eventItem.data.u32);
+ if (dataFd == mINotifyFd) {
+ if (eventItem.events & EPOLLIN) {
+ deviceChange = true;
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
+ }
+ continue;
+ }
+
+ if (dataFd == mWakeEventFd) {
+ if (eventItem.events & EPOLLIN) {
+ ALOGV("awoken after wake()");
+ uint64_t u;
+ ssize_t nRead = TEMP_FAILURE_RETRY(read(mWakeEventFd, &u, sizeof(uint64_t)));
+ if (nRead != sizeof(uint64_t)) {
+ ALOGW("Could not read event fd; waking anyway.");
+ }
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for wake event.",
+ eventItem.events);
+ }
+ continue;
+ }
+
+ // Update the fd and device node when the fd changes. When several
+ // events are read back-to-back with the same fd, this saves many reads
+ // from the hash table.
+ if (inputFd != dataFd) {
+ inputFd = dataFd;
+ deviceNode = mDeviceNodes[inputFd];
+ }
+ if (deviceNode == nullptr) {
+ ALOGE("could not find device node for fd %d", inputFd);
+ continue;
+ }
+ if (eventItem.events & EPOLLIN) {
+ struct input_event ievs[INPUT_MAX_EVENTS];
+ for (;;) {
+ ssize_t readSize = TEMP_FAILURE_RETRY(read(inputFd, ievs, sizeof(ievs)));
+ if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
+ ALOGW("could not get event, removed? (fd: %d, size: %d errno: %d)",
+ inputFd, readSize, errno);
+
+ removedDeviceFds.push_back(inputFd);
+ break;
+ } else if (readSize < 0) {
+ if (errno != EAGAIN && errno != EINTR) {
+ ALOGW("could not get event. errno=%d", errno);
+ }
+ break;
+ } else if (readSize % sizeof(input_event) != 0) {
+ ALOGE("could not get event. wrong size=%d", readSize);
+ break;
+ } else {
+ size_t count = static_cast<size_t>(readSize) / sizeof(struct input_event);
+ for (size_t i = 0; i < count; ++i) {
+ auto& iev = ievs[i];
+ auto when = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec);
+ InputEvent inputEvent = { when, iev.type, iev.code, iev.value };
+ mInputCallback->onInputEvent(deviceNode, inputEvent, now);
+ }
+ }
+ }
+ } else if (eventItem.events & EPOLLHUP) {
+ ALOGI("Removing device fd %d due to epoll hangup event.", inputFd);
+ removedDeviceFds.push_back(inputFd);
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for device fd %d",
+ eventItem.events, inputFd);
+ }
+ }
+
+ if (removedDeviceFds.size()) {
+ for (auto deviceFd : removedDeviceFds) {
+ auto deviceNode = mDeviceNodes[deviceFd];
+ if (deviceNode != nullptr) {
+ status_t ret = closeNodeByFd(deviceFd);
+ if (ret != OK) {
+ ALOGW("Could not close device with fd %d. errno=%d", deviceFd, ret);
+ } else {
+ mInputCallback->onDeviceRemoved(deviceNode);
+ }
+ }
+ }
+ }
+
+ if (deviceChange) {
+ readNotify();
+ }
+
+ return OK;
+}
+
+status_t InputHub::wake() {
+ ALOGV("wake() called");
+
+ uint64_t u = 1;
+ ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &u, sizeof(uint64_t)));
+
+ if (nWrite != sizeof(uint64_t) && errno != EAGAIN) {
+ ALOGW("Could not write wake signal, errno=%d", errno);
+ return -errno;
+ }
+ return OK;
+}
+
+void InputHub::dump(String8& dump) {
+ // TODO
+}
+
+status_t InputHub::readNotify() {
+ char event_buf[512];
+ struct inotify_event* event;
+
+ ssize_t res = TEMP_FAILURE_RETRY(read(mINotifyFd, event_buf, sizeof(event_buf)));
+ if (res < static_cast<int>(sizeof(*event))) {
+ ALOGW("could not get inotify event, %s\n", strerror(errno));
+ return -errno;
+ }
+
+ size_t event_pos = 0;
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ while (res >= static_cast<int>(sizeof(*event))) {
+ event = reinterpret_cast<struct inotify_event*>(event_buf + event_pos);
+ if (event->len) {
+ std::string path = mWatchedPaths[event->wd];
+ path.append("/").append(event->name);
+ ALOGV("inotify event for path %s", path.c_str());
+
+ if (event->mask & IN_CREATE) {
+ std::shared_ptr<InputDeviceNode> deviceNode;
+ status_t res = openNode(path, &deviceNode);
+ if (res != OK) {
+ ALOGE("could not open device node %s. err=%d", path.c_str(), res);
+ } else {
+ mInputCallback->onDeviceAdded(deviceNode);
+ }
+ } else {
+ auto deviceNode = findNodeByPath(path);
+ if (deviceNode != nullptr) {
+ status_t ret = closeNode(deviceNode);
+ if (ret != OK) {
+ ALOGW("Could not close device %s. errno=%d", path.c_str(), ret);
+ } else {
+ mInputCallback->onDeviceRemoved(deviceNode);
+ }
+ } else {
+ ALOGW("could not find device node for %s", path.c_str());
+ }
+ }
+ }
+ int event_size = sizeof(*event) + event->len;
+ res -= event_size;
+ event_pos += event_size;
+ }
+
+ return OK;
+}
+
+status_t InputHub::scanDir(const std::string& path) {
+ auto dir = ::opendir(path.c_str());
+ if (dir == nullptr) {
+ ALOGE("could not open device path %s to scan for devices. err=%d", path.c_str(), errno);
+ return -errno;
+ }
+
+ while (auto dirent = readdir(dir)) {
+ if (strcmp(dirent->d_name, ".") == 0 ||
+ strcmp(dirent->d_name, "..") == 0) {
+ continue;
+ }
+ std::string filename = path + "/" + dirent->d_name;
+ std::shared_ptr<InputDeviceNode> node;
+ if (openNode(filename, &node) != OK) {
+ ALOGE("could not open device node %s", filename.c_str());
+ } else {
+ mInputCallback->onDeviceAdded(node);
+ }
+ }
+ ::closedir(dir);
+ return OK;
+}
+
+status_t InputHub::openNode(const std::string& path,
+ std::shared_ptr<InputDeviceNode>* outNode) {
+ ALOGV("opening %s...", path.c_str());
+ auto evdevNode = std::shared_ptr<EvdevDeviceNode>(EvdevDeviceNode::openDeviceNode(path));
+ if (evdevNode == nullptr) {
+ return UNKNOWN_ERROR;
+ }
+
+ auto fd = evdevNode->getFd();
+ ALOGV("opened %s with fd %d", path.c_str(), fd);
+ *outNode = std::static_pointer_cast<InputDeviceNode>(evdevNode);
+ mDeviceNodes[fd] = *outNode;
+ struct epoll_event eventItem{};
+ eventItem.events = EPOLLIN;
+ if (mWakeupMechanism == WakeMechanism::EPOLL_WAKEUP) {
+ eventItem.events |= EPOLLWAKEUP;
+ }
+ eventItem.data.u32 = fd;
+ if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
+ ALOGE("Could not add device fd to epoll instance. errno=%d", errno);
+ return -errno;
+ }
+
+ if (mNeedToCheckSuspendBlockIoctl) {
+#ifndef EVIOCSSUSPENDBLOCK
+ // uapi headers don't include EVIOCSSUSPENDBLOCK, and future kernels
+ // will use an epoll flag instead, so as long as we want to support this
+ // feature, we need to be prepared to define the ioctl ourselves.
+#define EVIOCSSUSPENDBLOCK _IOW('E', 0x91, int)
+#endif
+ if (TEMP_FAILURE_RETRY(ioctl(fd, EVIOCSSUSPENDBLOCK, 1))) {
+ // no wake mechanism, continue using explicit wake locks
+ ALOGI("Using explicit wakelocks to block suspend while processing input events.");
+ } else {
+ mWakeupMechanism = WakeMechanism::LEGACY_EVDEV_SUSPENDBLOCK_IOCTL;
+ // release any held wakelocks since we won't need them anymore
+ release_wake_lock(WAKE_LOCK_ID);
+ ALOGI("Using EVIOCSSUSPENDBLOCK to block suspend while processing input events.");
+ }
+ mNeedToCheckSuspendBlockIoctl = false;
+ }
+
+ return OK;
+}
+
+status_t InputHub::closeNode(const std::shared_ptr<InputDeviceNode>& node) {
+ for (auto pair : mDeviceNodes) {
+ if (pair.second.get() == node.get()) {
+ return closeNodeByFd(pair.first);
+ }
+ }
+ return BAD_VALUE;
+}
+
+status_t InputHub::closeNodeByFd(int fd) {
+ status_t ret = OK;
+ if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, NULL)) {
+ ALOGW("Could not remove device fd from epoll instance. errno=%d", errno);
+ ret = -errno;
+ }
+ mDeviceNodes.erase(fd);
+ ::close(fd);
+ return ret;
+}
+
+std::shared_ptr<InputDeviceNode> InputHub::findNodeByPath(const std::string& path) {
+ for (auto pair : mDeviceNodes) {
+ if (pair.second->getPath() == path) return pair.second;
+ }
+ return nullptr;
+}
+
+bool InputHub::manageWakeLocks() const {
+ return mWakeupMechanism != WakeMechanism::EPOLL_WAKEUP;
+}
+
+} // namespace android
diff --git a/modules/input/evdev/InputHub.h b/modules/input/evdev/InputHub.h
new file mode 100644
index 0000000..bec327a
--- /dev/null
+++ b/modules/input/evdev/InputHub.h
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2015 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 ANDROID_INPUT_HUB_H_
+#define ANDROID_INPUT_HUB_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include <utils/String8.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+/**
+ * InputEvent represents an event from the kernel. The fields largely mirror
+ * those found in linux/input.h.
+ */
+struct InputEvent {
+ nsecs_t when;
+
+ int32_t type;
+ int32_t code;
+ int32_t value;
+};
+
+/** Describes an absolute axis. */
+struct AbsoluteAxisInfo {
+ int32_t minValue = 0; // minimum value
+ int32_t maxValue = 0; // maximum value
+ int32_t flat = 0; // center flat position, e.g. flat == 8 means center is between -8 and 8
+ int32_t fuzz = 0; // error tolerance, e.g. fuzz == 4 means value is +/- 4 due to noise
+ int32_t resolution = 0; // resolution in units per mm or radians per mm
+};
+
+/**
+ * An InputDeviceNode represents a device node in the Linux system. It can be
+ * used to interact with the device, setting and getting property values.
+ *
+ * An InputDeviceNode should only be used on the same thread that is polling for
+ * input events.
+ */
+class InputDeviceNode {
+public:
+ virtual const std::string& getPath() const = 0;
+
+ virtual const std::string& getName() const = 0;
+ virtual const std::string& getLocation() const = 0;
+ virtual const std::string& getUniqueId() const = 0;
+
+ virtual uint16_t getBusType() const = 0;
+ virtual uint16_t getVendorId() const = 0;
+ virtual uint16_t getProductId() const = 0;
+ virtual uint16_t getVersion() const = 0;
+
+ virtual bool hasKey(int32_t key) const = 0;
+ virtual bool hasRelativeAxis(int axis) const = 0;
+ virtual const AbsoluteAxisInfo* getAbsoluteAxisInfo(int32_t axis) const = 0;
+ virtual bool hasInputProperty(int property) const = 0;
+
+ virtual int32_t getKeyState(int32_t key) const = 0;
+ virtual int32_t getSwitchState(int32_t sw) const = 0;
+ virtual status_t getAbsoluteAxisValue(int32_t axis, int32_t* outValue) const = 0;
+
+ virtual void vibrate(nsecs_t duration) = 0;
+ virtual void cancelVibrate(int32_t deviceId) = 0;
+
+ virtual void disableDriverKeyRepeat() = 0;
+
+protected:
+ InputDeviceNode() = default;
+ virtual ~InputDeviceNode() = default;
+};
+
+/** Callback interface for receiving input events, including device changes. */
+class InputCallbackInterface {
+public:
+ virtual void onInputEvent(std::shared_ptr<InputDeviceNode> node, InputEvent& event,
+ nsecs_t event_time) = 0;
+ virtual void onDeviceAdded(std::shared_ptr<InputDeviceNode> node) = 0;
+ virtual void onDeviceRemoved(std::shared_ptr<InputDeviceNode> node) = 0;
+
+protected:
+ InputCallbackInterface() = default;
+ virtual ~InputCallbackInterface() = default;
+};
+
+/**
+ * InputHubInterface is responsible for monitoring a set of device paths and
+ * executing callbacks when events occur. Before calling poll(), you should set
+ * the device and input callbacks, and register your device path(s).
+ */
+class InputHubInterface {
+public:
+ virtual status_t registerDevicePath(const std::string& path) = 0;
+ virtual status_t unregisterDevicePath(const std::string& path) = 0;
+
+ virtual status_t poll() = 0;
+ virtual status_t wake() = 0;
+
+ virtual void dump(String8& dump) = 0;
+
+protected:
+ InputHubInterface() = default;
+ virtual ~InputHubInterface() = default;
+};
+
+/**
+ * An implementation of InputHubInterface that uses epoll to wait for events.
+ *
+ * This class is not threadsafe. Any functions called on the InputHub should be
+ * called on the same thread that is used to call poll(). The only exception is
+ * wake(), which may be used to return from poll() before an input or device
+ * event occurs.
+ */
+class InputHub : public InputHubInterface {
+public:
+ explicit InputHub(std::shared_ptr<InputCallbackInterface> cb);
+ virtual ~InputHub() override;
+
+ virtual status_t registerDevicePath(const std::string& path) override;
+ virtual status_t unregisterDevicePath(const std::string& path) override;
+
+ virtual status_t poll() override;
+ virtual status_t wake() override;
+
+ virtual void dump(String8& dump) override;
+
+private:
+ status_t readNotify();
+ status_t scanDir(const std::string& path);
+ status_t openNode(const std::string& path, std::shared_ptr<InputDeviceNode>* outNode);
+ status_t closeNode(const std::shared_ptr<InputDeviceNode>& node);
+ status_t closeNodeByFd(int fd);
+ std::shared_ptr<InputDeviceNode> findNodeByPath(const std::string& path);
+
+ enum class WakeMechanism {
+ /**
+ * The kernel supports the EPOLLWAKEUP flag for epoll_ctl.
+ *
+ * When using this mechanism, epoll_wait will internally acquire a wake
+ * lock whenever one of the FDs it is monitoring becomes ready. The wake
+ * lock is held automatically by the kernel until the next call to
+ * epoll_wait.
+ *
+ * This mechanism only exists in Linux kernel 3.5+.
+ */
+ EPOLL_WAKEUP,
+ /**
+ * The kernel evdev driver supports the EVIOCSSUSPENDBLOCK ioctl.
+ *
+ * When using this mechanism, the InputHub asks evdev to acquire and
+ * hold a wake lock whenever its buffer is non-empty. We must take care
+ * to acquire our own userspace wake lock before draining the buffer to
+ * prevent actually going back into suspend before we have fully
+ * processed all of the events.
+ *
+ * This mechanism only exists in older Android Linux kernels.
+ */
+ LEGACY_EVDEV_SUSPENDBLOCK_IOCTL,
+ /**
+ * The kernel doesn't seem to support any special wake mechanism.
+ *
+ * We explicitly acquire and release wake locks when processing input
+ * events.
+ */
+ LEGACY_EVDEV_EXPLICIT_WAKE_LOCKS,
+ };
+ WakeMechanism mWakeupMechanism = WakeMechanism::LEGACY_EVDEV_EXPLICIT_WAKE_LOCKS;
+ bool manageWakeLocks() const;
+ bool mNeedToCheckSuspendBlockIoctl = true;
+
+ int mEpollFd;
+ int mINotifyFd;
+ int mWakeEventFd;
+ int mWakeReadPipeFd;
+ int mWakeWritePipeFd;
+
+ // Callback for input events
+ std::shared_ptr<InputCallbackInterface> mInputCallback;
+
+ // Map from watch descriptors to watched paths
+ std::unordered_map<int, std::string> mWatchedPaths;
+ // Map from file descriptors to InputDeviceNodes
+ std::unordered_map<int, std::shared_ptr<InputDeviceNode>> mDeviceNodes;
+};
+
+} // namespace android
+
+#endif // ANDROID_INPUT_HUB_H_
diff --git a/tests/input/Android.mk b/tests/input/Android.mk
new file mode 100644
index 0000000..3011b2e
--- /dev/null
+++ b/tests/input/Android.mk
@@ -0,0 +1,19 @@
+# Copyright (C) 2015 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/input/evdev/Android.mk b/tests/input/evdev/Android.mk
new file mode 100644
index 0000000..167cbc2
--- /dev/null
+++ b/tests/input/evdev/Android.mk
@@ -0,0 +1,23 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES += hardware/libhardware/modules/input/evdev
+
+LOCAL_SRC_FILES:= \
+ InputDevice_test.cpp \
+ InputHub_test.cpp \
+ TestHelpers.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libinput_evdev \
+ liblog \
+ libutils
+
+LOCAL_CLANG := true
+LOCAL_CFLAGS += -Wall -Wextra -Wno-unused-parameter
+LOCAL_CPPFLAGS += -std=c++14
+
+LOCAL_MODULE := libinput_evdevtests
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_NATIVE_TEST)
diff --git a/tests/input/evdev/InputDevice_test.cpp b/tests/input/evdev/InputDevice_test.cpp
new file mode 100644
index 0000000..a96d664
--- /dev/null
+++ b/tests/input/evdev/InputDevice_test.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#define LOG_TAG "InputHub_test"
+//#define LOG_NDEBUG 0
+
+#include <linux/input.h>
+
+#include <gtest/gtest.h>
+
+#include <utils/Timers.h>
+
+#include "InputDevice.h"
+#include "InputHub.h"
+
+// # of milliseconds to allow for timing measurements
+#define TIMING_TOLERANCE_MS 25
+
+#define MSC_ANDROID_TIME_SEC 0x6
+#define MSC_ANDROID_TIME_USEC 0x7
+
+namespace android {
+namespace tests {
+
+class MockInputDeviceNode : public InputDeviceNode {
+ virtual const std::string& getPath() const override { return mPath; }
+
+ virtual const std::string& getName() const override { return mName; }
+ virtual const std::string& getLocation() const override { return mLocation; }
+ virtual const std::string& getUniqueId() const override { return mUniqueId; }
+
+ virtual uint16_t getBusType() const override { return 0; }
+ virtual uint16_t getVendorId() const override { return 0; }
+ virtual uint16_t getProductId() const override { return 0; }
+ virtual uint16_t getVersion() const override { return 0; }
+
+ virtual bool hasKey(int32_t key) const { return false; }
+ virtual bool hasRelativeAxis(int axis) const { return false; }
+ virtual bool hasInputProperty(int property) const { return false; }
+
+ virtual int32_t getKeyState(int32_t key) const { return 0; }
+ virtual int32_t getSwitchState(int32_t sw) const { return 0; }
+ virtual const AbsoluteAxisInfo* getAbsoluteAxisInfo(int32_t axis) const { return nullptr; }
+ virtual status_t getAbsoluteAxisValue(int32_t axis, int32_t* outValue) const { return 0; }
+
+ virtual void vibrate(nsecs_t duration) {}
+ virtual void cancelVibrate(int32_t deviceId) {}
+
+ virtual void disableDriverKeyRepeat() {}
+
+private:
+ std::string mPath = "/test";
+ std::string mName = "Test Device";
+ std::string mLocation = "test/0";
+ std::string mUniqueId = "test-id";
+};
+
+TEST(EvdevDeviceTest, testOverrideTime) {
+ auto node = std::make_shared<MockInputDeviceNode>();
+ auto device = std::make_unique<EvdevDevice>(node);
+ ASSERT_TRUE(device != nullptr);
+
+ // Send two timestamp override events before an input event.
+ nsecs_t when = 2ULL;
+ InputEvent msc1 = { when, EV_MSC, MSC_ANDROID_TIME_SEC, 1 };
+ InputEvent msc2 = { when, EV_MSC, MSC_ANDROID_TIME_USEC, 900000 };
+
+ // Send a key down and syn. Should get the overridden timestamp.
+ InputEvent keyDown = { when, EV_KEY, KEY_HOME, 1 };
+ InputEvent syn = { when, EV_SYN, SYN_REPORT, 0 };
+
+ // Send a key up, which should be at the reported timestamp.
+ InputEvent keyUp = { when, EV_KEY, KEY_HOME, 0 };
+
+ device->processInput(msc1, when);
+ device->processInput(msc2, when);
+ device->processInput(keyDown, when);
+ device->processInput(syn, when);
+ device->processInput(keyUp, when);
+
+ nsecs_t expectedWhen = s2ns(1) + us2ns(900000);
+ EXPECT_EQ(expectedWhen, keyDown.when);
+ EXPECT_EQ(expectedWhen, syn.when);
+ EXPECT_EQ(when, keyUp.when);
+}
+
+TEST(EvdevDeviceTest, testWrongClockCorrection) {
+ auto node = std::make_shared<MockInputDeviceNode>();
+ auto device = std::make_unique<EvdevDevice>(node);
+ ASSERT_TRUE(device != nullptr);
+
+ auto now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ // Input event that supposedly comes from 1 minute in the future. In
+ // reality, the timestamps would be much further off.
+ InputEvent event = { now + s2ns(60), EV_KEY, KEY_HOME, 1 };
+
+ device->processInput(event, now);
+
+ EXPECT_NEAR(now, event.when, ms2ns(TIMING_TOLERANCE_MS));
+}
+
+TEST(EvdevDeviceTest, testClockCorrectionOk) {
+ auto node = std::make_shared<MockInputDeviceNode>();
+ auto device = std::make_unique<EvdevDevice>(node);
+ ASSERT_TRUE(device != nullptr);
+
+ auto now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ // Input event from now, but will be reported as if it came early.
+ InputEvent event = { now, EV_KEY, KEY_HOME, 1 };
+
+ // event_time parameter is 11 seconds in the past, so it looks like we used
+ // the wrong clock.
+ device->processInput(event, now - s2ns(11));
+
+ EXPECT_NEAR(now, event.when, ms2ns(TIMING_TOLERANCE_MS));
+}
+
+} // namespace tests
+} // namespace android
diff --git a/tests/input/evdev/InputHub_test.cpp b/tests/input/evdev/InputHub_test.cpp
new file mode 100644
index 0000000..f2c8edf
--- /dev/null
+++ b/tests/input/evdev/InputHub_test.cpp
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#define LOG_TAG "InputHub_test"
+//#define LOG_NDEBUG 0
+
+#include <linux/input.h>
+
+#include <chrono>
+#include <memory>
+#include <mutex>
+
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+#include <utils/StopWatch.h>
+#include <utils/Timers.h>
+
+#include "InputHub.h"
+#include "TestHelpers.h"
+
+// # of milliseconds to fudge stopwatch measurements
+#define TIMING_TOLERANCE_MS 25
+#define NO_TIMEOUT (-1)
+
+namespace android {
+namespace tests {
+
+using namespace std::literals::chrono_literals;
+
+using InputCbFunc = std::function<void(std::shared_ptr<InputDeviceNode>, InputEvent&, nsecs_t)>;
+using DeviceCbFunc = std::function<void(std::shared_ptr<InputDeviceNode>)>;
+
+static const InputCbFunc kNoopInputCb = [](std::shared_ptr<InputDeviceNode>, InputEvent&, nsecs_t){};
+static const DeviceCbFunc kNoopDeviceCb = [](std::shared_ptr<InputDeviceNode>){};
+
+class TestInputCallback : public InputCallbackInterface {
+public:
+ TestInputCallback() :
+ mInputCb(kNoopInputCb), mDeviceAddedCb(kNoopDeviceCb), mDeviceRemovedCb(kNoopDeviceCb) {}
+ virtual ~TestInputCallback() = default;
+
+ void setInputCallback(InputCbFunc cb) { mInputCb = cb; }
+ void setDeviceAddedCallback(DeviceCbFunc cb) { mDeviceAddedCb = cb; }
+ void setDeviceRemovedCallback(DeviceCbFunc cb) { mDeviceRemovedCb = cb; }
+
+ virtual void onInputEvent(std::shared_ptr<InputDeviceNode> node, InputEvent& event,
+ nsecs_t event_time) override {
+ mInputCb(node, event, event_time);
+ }
+ virtual void onDeviceAdded(std::shared_ptr<InputDeviceNode> node) override {
+ mDeviceAddedCb(node);
+ }
+ virtual void onDeviceRemoved(std::shared_ptr<InputDeviceNode> node) override {
+ mDeviceRemovedCb(node);
+ }
+
+private:
+ InputCbFunc mInputCb;
+ DeviceCbFunc mDeviceAddedCb;
+ DeviceCbFunc mDeviceRemovedCb;
+};
+
+class InputHubTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ mCallback = std::make_shared<TestInputCallback>();
+ mInputHub = std::make_shared<InputHub>(mCallback);
+ }
+
+ std::shared_ptr<TestInputCallback> mCallback;
+ std::shared_ptr<InputHub> mInputHub;
+};
+
+TEST_F(InputHubTest, testWake) {
+ // Call wake() after 100ms.
+ auto f = delay_async(100ms, [&]() { EXPECT_EQ(OK, mInputHub->wake()); });
+
+ StopWatch stopWatch("poll");
+ EXPECT_EQ(OK, mInputHub->poll());
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS);
+}
+
+TEST_F(InputHubTest, DISABLED_testDeviceAdded) {
+ auto tempDir = std::make_shared<TempDir>();
+ std::string pathname;
+ // Expect that this callback will run and set handle and pathname.
+ mCallback->setDeviceAddedCallback(
+ [&](std::shared_ptr<InputDeviceNode> node) {
+ pathname = node->getPath();
+ });
+
+ ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName()));
+
+ // Create a new file in tempDir after 100ms.
+ std::unique_ptr<TempFile> tempFile;
+ std::mutex tempFileMutex;
+ auto f = delay_async(100ms,
+ [&]() {
+ std::lock_guard<std::mutex> lock(tempFileMutex);
+ tempFile.reset(tempDir->newTempFile());
+ });
+
+ StopWatch stopWatch("poll");
+ EXPECT_EQ(OK, mInputHub->poll());
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+
+ EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS);
+ std::lock_guard<std::mutex> lock(tempFileMutex);
+ EXPECT_EQ(tempFile->getName(), pathname);
+}
+
+TEST_F(InputHubTest, DISABLED_testDeviceRemoved) {
+ // Create a temp dir and file. Save its name and handle (to be filled in
+ // once InputHub scans the dir).
+ auto tempDir = std::make_unique<TempDir>();
+ auto deviceFile = std::unique_ptr<TempFile>(tempDir->newTempFile());
+ std::string tempFileName(deviceFile->getName());
+
+ std::shared_ptr<InputDeviceNode> tempNode;
+ // Expect that these callbacks will run for the above device file.
+ mCallback->setDeviceAddedCallback(
+ [&](std::shared_ptr<InputDeviceNode> node) {
+ tempNode = node;
+ });
+ mCallback->setDeviceRemovedCallback(
+ [&](std::shared_ptr<InputDeviceNode> node) {
+ EXPECT_EQ(tempNode, node);
+ });
+
+ ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName()));
+ // Ensure that tempDir was scanned to find the device.
+ ASSERT_TRUE(tempNode != nullptr);
+
+ auto f = delay_async(100ms, [&]() { deviceFile.reset(); });
+
+ StopWatch stopWatch("poll");
+ EXPECT_EQ(OK, mInputHub->poll());
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS);
+}
+
+TEST_F(InputHubTest, DISABLED_testInputEvent) {
+ // Create a temp dir and file. Save its name and handle (to be filled in
+ // once InputHub scans the dir.)
+ auto tempDir = std::make_unique<TempDir>();
+ auto deviceFile = std::unique_ptr<TempFile>(tempDir->newTempFile());
+ std::string tempFileName(deviceFile->getName());
+
+ // Send a key event corresponding to HOME.
+ struct input_event iev;
+ iev.time = { 1, 0 };
+ iev.type = EV_KEY;
+ iev.code = KEY_HOME;
+ iev.value = 0x01;
+
+ auto inputDelayMs = 100ms;
+ auto f = delay_async(inputDelayMs, [&] {
+ ssize_t nWrite = TEMP_FAILURE_RETRY(write(deviceFile->getFd(), &iev, sizeof(iev)));
+
+ ASSERT_EQ(static_cast<ssize_t>(sizeof(iev)), nWrite) << "could not write to "
+ << deviceFile->getFd() << ". errno: " << errno;
+ });
+
+ // Expect this callback to run when the input event is read.
+ nsecs_t expectedWhen = systemTime(CLOCK_MONOTONIC) + ms2ns(inputDelayMs.count());
+ mCallback->setInputCallback(
+ [&](std::shared_ptr<InputDeviceNode> node, InputEvent& event, nsecs_t event_time) {
+ EXPECT_NEAR(expectedWhen, event_time, ms2ns(TIMING_TOLERANCE_MS));
+ EXPECT_EQ(s2ns(1), event.when);
+ EXPECT_EQ(tempFileName, node->getPath());
+ EXPECT_EQ(EV_KEY, event.type);
+ EXPECT_EQ(KEY_HOME, event.code);
+ EXPECT_EQ(0x01, event.value);
+ });
+ ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName()));
+
+ StopWatch stopWatch("poll");
+ EXPECT_EQ(OK, mInputHub->poll());
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS);
+}
+
+TEST_F(InputHubTest, DISABLED_testCallbackOrder) {
+ // Create two "devices": one to receive input and the other to go away.
+ auto tempDir = std::make_unique<TempDir>();
+ auto deviceFile1 = std::unique_ptr<TempFile>(tempDir->newTempFile());
+ auto deviceFile2 = std::unique_ptr<TempFile>(tempDir->newTempFile());
+ std::string tempFileName(deviceFile2->getName());
+
+ bool inputCallbackFinished = false, deviceCallbackFinished = false;
+
+ // Setup the callback for input events. Should run before the device
+ // callback.
+ mCallback->setInputCallback(
+ [&](std::shared_ptr<InputDeviceNode>, InputEvent&, nsecs_t) {
+ ASSERT_FALSE(deviceCallbackFinished);
+ inputCallbackFinished = true;
+ });
+
+ // Setup the callback for device removal. Should run after the input
+ // callback.
+ mCallback->setDeviceRemovedCallback(
+ [&](std::shared_ptr<InputDeviceNode> node) {
+ ASSERT_TRUE(inputCallbackFinished)
+ << "input callback did not run before device changed callback";
+ // Make sure the correct device was removed.
+ EXPECT_EQ(tempFileName, node->getPath());
+ deviceCallbackFinished = true;
+ });
+ ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName()));
+
+ auto f = delay_async(100ms,
+ [&]() {
+ // Delete the second device file first.
+ deviceFile2.reset();
+
+ // Then inject an input event into the first device.
+ struct input_event iev;
+ iev.time = { 1, 0 };
+ iev.type = EV_KEY;
+ iev.code = KEY_HOME;
+ iev.value = 0x01;
+
+ ssize_t nWrite = TEMP_FAILURE_RETRY(write(deviceFile1->getFd(), &iev, sizeof(iev)));
+
+ ASSERT_EQ(static_cast<ssize_t>(sizeof(iev)), nWrite) << "could not write to "
+ << deviceFile1->getFd() << ". errno: " << errno;
+ });
+
+ StopWatch stopWatch("poll");
+ EXPECT_EQ(OK, mInputHub->poll());
+ int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+ EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS);
+ EXPECT_TRUE(inputCallbackFinished);
+ EXPECT_TRUE(deviceCallbackFinished);
+}
+
+} // namespace tests
+} // namespace android
diff --git a/tests/input/evdev/TestHelpers.cpp b/tests/input/evdev/TestHelpers.cpp
new file mode 100644
index 0000000..63b579e
--- /dev/null
+++ b/tests/input/evdev/TestHelpers.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#define LOG_TAG "TestHelpers"
+#define LOG_NDEBUG 0
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <utils/Log.h>
+
+#include "TestHelpers.h"
+
+namespace android {
+
+static const char kTmpDirTemplate[] = "/data/local/tmp/XXXXXX";
+
+TempFile::TempFile(const char* path) {
+ bool needTrailingSlash = path[strlen(path) - 1] != '/';
+ // name = path + XXXXXX + \0
+ size_t nameLen = strlen(path) + 6 + 1;
+ if (needTrailingSlash) nameLen++;
+
+ mName = new char[nameLen];
+ strcpy(mName, path);
+ if (needTrailingSlash) {
+ strcat(mName, "/");
+ }
+ strcat(mName, "XXXXXX");
+ mName = mktemp(mName);
+ LOG_FATAL_IF(mName == nullptr, "could not create temp filename %s. errno=%d", mName, errno);
+
+ int result = TEMP_FAILURE_RETRY(mkfifo(mName, S_IRWXU));
+ LOG_FATAL_IF(result < 0, "could not create fifo %s. errno=%d", mName, errno);
+
+ mFd = TEMP_FAILURE_RETRY(open(mName, O_RDWR | O_NONBLOCK));
+ LOG_FATAL_IF(mFd < 0, "could not open fifo %s. errno=%d", mName, errno);
+}
+
+TempFile::~TempFile() {
+ if (unlink(mName) < 0) {
+ ALOGE("could not unlink %s. errno=%d", mName, errno);
+ }
+ if (close(mFd) < 0) {
+ ALOGE("could not close %d. errno=%d", mFd, errno);
+ }
+ delete[] mName;
+}
+
+TempDir::TempDir() {
+ mName = new char[sizeof(kTmpDirTemplate)];
+ strcpy(mName, kTmpDirTemplate);
+ mName = mkdtemp(mName);
+ LOG_FATAL_IF(mName == nullptr, "could not allocate tempdir %s", mName);
+}
+
+TempDir::~TempDir() {
+ auto tmpDir = opendir(mName);
+ while (auto entry = readdir(tmpDir)) {
+ if (strcmp(entry->d_name, ".") == 0 ||
+ strcmp(entry->d_name, "..") == 0) {
+ continue;
+ }
+ ALOGD("stale file %s, removing", entry->d_name);
+ unlink(entry->d_name);
+ }
+ closedir(tmpDir);
+ rmdir(mName);
+ delete mName;
+}
+
+TempFile* TempDir::newTempFile() {
+ return new TempFile(mName);
+}
+
+} // namespace android
diff --git a/tests/input/evdev/TestHelpers.h b/tests/input/evdev/TestHelpers.h
new file mode 100644
index 0000000..461db04
--- /dev/null
+++ b/tests/input/evdev/TestHelpers.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 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 ANDROID_TEST_HELPERS_H_
+#define ANDROID_TEST_HELPERS_H_
+
+#include <future>
+#include <thread>
+
+namespace android {
+
+/**
+ * Runs the given function after the specified delay.
+ * NOTE: if the std::future returned from std::async is not bound, this function
+ * will block until the task completes. This is almost certainly NOT what you
+ * want, so save the return value from delay_async into a variable:
+ *
+ * auto f = delay_async(100ms, []{ ALOGD("Hello world"); });
+ */
+template<class Function, class Duration>
+decltype(auto) delay_async(Duration&& delay, Function&& task)
+{
+ return std::async(std::launch::async, [=]{ std::this_thread::sleep_for(delay); task(); });
+}
+
+/**
+ * Creates and opens a temporary file at the given path. The file is unlinked
+ * and closed in the destructor.
+ */
+class TempFile {
+public:
+ TempFile(const char* path);
+ ~TempFile();
+
+ // No copy or assign
+ TempFile(const TempFile&) = delete;
+ TempFile& operator=(const TempFile&) = delete;
+
+ const char* getName() const { return mName; }
+ int getFd() const { return mFd; }
+
+private:
+ char* mName;
+ int mFd;
+};
+
+/**
+ * Creates a temporary directory that can create temporary files. The directory
+ * is emptied and deleted in the destructor.
+ */
+class TempDir {
+public:
+ TempDir();
+ ~TempDir();
+
+ // No copy or assign
+ TempDir(const TempDir&) = delete;
+ TempDir& operator=(const TempDir&) = delete;
+
+ const char* getName() const { return mName; }
+
+ TempFile* newTempFile();
+
+private:
+ char* mName;
+};
+
+} // namespace android
+
+#endif // ANDROID_TEST_HELPERS_H_