OSDN Git Service

Input event hub for evdev input HAL module.
authorTim Kilbourn <tkilbourn@google.com>
Fri, 13 Feb 2015 18:35:20 +0000 (10:35 -0800)
committerTim Kilbourn <tkilbourn@google.com>
Tue, 31 Mar 2015 00:21:29 +0000 (17:21 -0700)
InputHub monitors paths for device changes and input events.
InputDeviceManager creates InputDevices and routes input events to
them. InputDevices currently just log these events during development.
InputHost represents a wrapper around the HAL interface.

Change-Id: Ic47d574498eb07bcdcd17812a648539fdf1c69d6

17 files changed:
include/hardware/input.h
modules/input/evdev/Android.mk
modules/input/evdev/EvdevModule.cpp
modules/input/evdev/InputDevice.cpp [new file with mode: 0644]
modules/input/evdev/InputDevice.h [new file with mode: 0644]
modules/input/evdev/InputDeviceManager.cpp [new file with mode: 0644]
modules/input/evdev/InputDeviceManager.h [new file with mode: 0644]
modules/input/evdev/InputHost.cpp [new file with mode: 0644]
modules/input/evdev/InputHost.h [new file with mode: 0644]
modules/input/evdev/InputHub.cpp [new file with mode: 0644]
modules/input/evdev/InputHub.h [new file with mode: 0644]
tests/input/Android.mk [new file with mode: 0644]
tests/input/evdev/Android.mk [new file with mode: 0644]
tests/input/evdev/InputDevice_test.cpp [new file with mode: 0644]
tests/input/evdev/InputHub_test.cpp [new file with mode: 0644]
tests/input/evdev/TestHelpers.cpp [new file with mode: 0644]
tests/input/evdev/TestHelpers.h [new file with mode: 0644]

index dad1bc0..d2f2f79 100644 (file)
@@ -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) {
index ad05af3..d3c49e7 100644 (file)
 
 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
 
index f56842a..e9c8222 100644 (file)
 #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 (file)
index 0000000..c0b59d7
--- /dev/null
@@ -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 (file)
index 0000000..3aa16cc
--- /dev/null
@@ -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 (file)
index 0000000..ceddd90
--- /dev/null
@@ -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 (file)
index 0000000..b652155
--- /dev/null
@@ -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 (file)
index 0000000..0903f47
--- /dev/null
@@ -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 (file)
index 0000000..129443e
--- /dev/null
@@ -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 (file)
index 0000000..e72ac2e
--- /dev/null
@@ -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 (file)
index 0000000..bec327a
--- /dev/null
@@ -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 (file)
index 0000000..3011b2e
--- /dev/null
@@ -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 (file)
index 0000000..167cbc2
--- /dev/null
@@ -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 (file)
index 0000000..a96d664
--- /dev/null
@@ -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 (file)
index 0000000..f2c8edf
--- /dev/null
@@ -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 (file)
index 0000000..63b579e
--- /dev/null
@@ -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 (file)
index 0000000..461db04
--- /dev/null
@@ -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_