diff options
author | Tim Kilbourn <tkilbourn@google.com> | 2015-02-13 10:35:20 -0800 |
---|---|---|
committer | Tim Kilbourn <tkilbourn@google.com> | 2015-03-30 17:21:29 -0700 |
commit | 73475a4eb2cebf06f965c58e015d06c333e71e61 (patch) | |
tree | ee7d5d7a4673867738f0725a278f54a0f7aaf261 /modules | |
parent | 3fba7bebe84b6e2ae515dda142dc3b37071b4dcd (diff) | |
download | hardware_libhardware-73475a4eb2cebf06f965c58e015d06c333e71e61.zip hardware_libhardware-73475a4eb2cebf06f965c58e015d06c333e71e61.tar.gz hardware_libhardware-73475a4eb2cebf06f965c58e015d06c333e71e61.tar.bz2 |
Input event hub for evdev input HAL module.
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
Diffstat (limited to 'modules')
-rw-r--r-- | modules/input/evdev/Android.mk | 30 | ||||
-rw-r--r-- | modules/input/evdev/EvdevModule.cpp | 80 | ||||
-rw-r--r-- | modules/input/evdev/InputDevice.cpp | 102 | ||||
-rw-r--r-- | modules/input/evdev/InputDevice.h | 60 | ||||
-rw-r--r-- | modules/input/evdev/InputDeviceManager.cpp | 50 | ||||
-rw-r--r-- | modules/input/evdev/InputDeviceManager.h | 53 | ||||
-rw-r--r-- | modules/input/evdev/InputHost.cpp | 78 | ||||
-rw-r--r-- | modules/input/evdev/InputHost.h | 133 | ||||
-rw-r--r-- | modules/input/evdev/InputHub.cpp | 802 | ||||
-rw-r--r-- | modules/input/evdev/InputHub.h | 204 |
10 files changed, 1585 insertions, 7 deletions
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_ |