/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "UinputCommandDevice" #include "com_android_commands_uinput_Device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace uinput { using src::com::android::commands::uinput::InputAbsInfo; static constexpr const char* UINPUT_PATH = "/dev/uinput"; static struct { jmethodID onDeviceConfigure; jmethodID onDeviceVibrating; jmethodID onDeviceError; } gDeviceCallbackClassInfo; static void checkAndClearException(JNIEnv* env, const char* methodName) { if (env->ExceptionCheck()) { ALOGE("An exception was thrown by callback '%s'.", methodName); env->ExceptionClear(); } } DeviceCallback::DeviceCallback(JNIEnv* env, jobject callback) : mCallbackObject(env->NewGlobalRef(callback)) { env->GetJavaVM(&mJavaVM); } DeviceCallback::~DeviceCallback() { JNIEnv* env = getJNIEnv(); env->DeleteGlobalRef(mCallbackObject); } void DeviceCallback::onDeviceError() { JNIEnv* env = getJNIEnv(); env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceError); checkAndClearException(env, "onDeviceError"); } void DeviceCallback::onDeviceConfigure(int handle) { JNIEnv* env = getJNIEnv(); env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceConfigure, handle); checkAndClearException(env, "onDeviceConfigure"); } void DeviceCallback::onDeviceVibrating(int value) { JNIEnv* env = getJNIEnv(); env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceVibrating, value); checkAndClearException(env, "onDeviceVibrating"); } JNIEnv* DeviceCallback::getJNIEnv() { JNIEnv* env; mJavaVM->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); return env; } std::unique_ptr UinputDevice::open(int32_t id, const char* name, int32_t vendorId, int32_t productId, int32_t versionId, uint16_t bus, uint32_t ffEffectsMax, const char* port, std::unique_ptr callback) { android::base::unique_fd fd(::open(UINPUT_PATH, O_RDWR | O_NONBLOCK | O_CLOEXEC)); if (!fd.ok()) { ALOGE("Failed to open uinput: %s", strerror(errno)); return nullptr; } int32_t version; ::ioctl(fd, UI_GET_VERSION, &version); if (version < 5) { ALOGE("Kernel version %d older than 5 is not supported", version); return nullptr; } struct uinput_setup setupDescriptor; memset(&setupDescriptor, 0, sizeof(setupDescriptor)); strlcpy(setupDescriptor.name, name, UINPUT_MAX_NAME_SIZE); setupDescriptor.id.version = 1; setupDescriptor.id.bustype = bus; setupDescriptor.id.vendor = vendorId; setupDescriptor.id.product = productId; setupDescriptor.id.version = versionId; setupDescriptor.ff_effects_max = ffEffectsMax; // Request device configuration. callback->onDeviceConfigure(fd.get()); // register the input device if (::ioctl(fd, UI_DEV_SETUP, &setupDescriptor)) { ALOGE("UI_DEV_SETUP ioctl failed on fd %d: %s.", fd.get(), strerror(errno)); return nullptr; } // set the physical port. ::ioctl(fd, UI_SET_PHYS, port); if (::ioctl(fd, UI_DEV_CREATE) != 0) { ALOGE("Unable to create uinput device: %s.", strerror(errno)); return nullptr; } // using 'new' to access non-public constructor return std::unique_ptr(new UinputDevice(id, std::move(fd), std::move(callback))); } UinputDevice::UinputDevice(int32_t id, android::base::unique_fd fd, std::unique_ptr callback) : mId(id), mFd(std::move(fd)), mDeviceCallback(std::move(callback)) { ALooper* aLooper = ALooper_forThread(); if (aLooper == nullptr) { ALOGE("Could not get ALooper, ALooper_forThread returned NULL"); aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); } ALooper_addFd( aLooper, mFd, 0, ALOOPER_EVENT_INPUT, [](int, int events, void* data) { UinputDevice* d = reinterpret_cast(data); return d->handleEvents(events); }, reinterpret_cast(this)); ALOGI("uinput device %d created: version = %d, fd = %d", mId, UINPUT_VERSION, mFd.get()); } UinputDevice::~UinputDevice() { ::ioctl(mFd, UI_DEV_DESTROY); } void UinputDevice::injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code, int32_t value) { struct input_event event = {}; event.type = type; event.code = code; event.value = value; event.time.tv_sec = timestamp.count() / 1'000'000; event.time.tv_usec = timestamp.count() % 1'000'000; if (::write(mFd, &event, sizeof(input_event)) < 0) { ALOGE("Could not write event %" PRIu16 " %" PRIu16 " with value %" PRId32 " : %s", type, code, value, strerror(errno)); } } int UinputDevice::handleEvents(int events) { if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { ALOGE("uinput node was closed or an error occurred. events=0x%x", events); mDeviceCallback->onDeviceError(); return 0; } struct input_event ev; ssize_t ret = ::read(mFd, &ev, sizeof(ev)); if (ret < 0) { ALOGE("Failed to read from uinput node: %s", strerror(errno)); mDeviceCallback->onDeviceError(); return 0; } switch (ev.type) { case EV_UINPUT: { if (ev.code == UI_FF_UPLOAD) { struct uinput_ff_upload ff_upload; ff_upload.request_id = ev.value; ::ioctl(mFd, UI_BEGIN_FF_UPLOAD, &ff_upload); ff_upload.retval = 0; ::ioctl(mFd, UI_END_FF_UPLOAD, &ff_upload); } else if (ev.code == UI_FF_ERASE) { struct uinput_ff_erase ff_erase; ff_erase.request_id = ev.value; ::ioctl(mFd, UI_BEGIN_FF_ERASE, &ff_erase); ff_erase.retval = 0; ::ioctl(mFd, UI_END_FF_ERASE, &ff_erase); } break; } case EV_FF: { ALOGI("EV_FF effect = %d value = %d", ev.code, ev.value); mDeviceCallback->onDeviceVibrating(ev.value); break; } default: { ALOGI("Unhandled event type: %" PRIu32, ev.type); break; } } return 1; } } // namespace uinput std::vector toVector(JNIEnv* env, jintArray javaArray) { std::vector data; if (javaArray == nullptr) { return data; } ScopedIntArrayRO scopedArray(env, javaArray); size_t size = scopedArray.size(); data.reserve(size); for (size_t i = 0; i < size; i++) { data.push_back(static_cast(scopedArray[i])); } return data; } static jlong openUinputDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vendorId, jint productId, jint versionId, jint bus, jint ffEffectsMax, jstring rawPort, jobject callback) { ScopedUtfChars name(env, rawName); if (name.c_str() == nullptr) { return 0; } ScopedUtfChars port(env, rawPort); std::unique_ptr cb = std::make_unique(env, callback); std::unique_ptr d = uinput::UinputDevice::open(id, name.c_str(), vendorId, productId, versionId, bus, ffEffectsMax, port.c_str(), std::move(cb)); return reinterpret_cast(d.release()); } static void closeUinputDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { uinput::UinputDevice* d = reinterpret_cast(ptr); if (d != nullptr) { delete d; } } static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jlong timestampMicros, jint type, jint code, jint value) { uinput::UinputDevice* d = reinterpret_cast(ptr); if (d != nullptr) { d->injectEvent(std::chrono::microseconds(timestampMicros), static_cast(type), static_cast(code), static_cast(value)); } else { ALOGE("Could not inject event, Device* is null!"); } } static void configure(JNIEnv* env, jclass /* clazz */, jint handle, jint code, jintArray rawConfigs) { std::vector configs = toVector(env, rawConfigs); // Configure uinput device, with user specified code and value. for (auto& config : configs) { if (::ioctl(static_cast(handle), _IOW(UINPUT_IOCTL_BASE, code, int), config) < 0) { ALOGE("Error configuring device (ioctl %d, value 0x%x): %s", code, config, strerror(errno)); } } } static void setAbsInfo(JNIEnv* env, jclass /* clazz */, jint handle, jint axisCode, jobject infoObj) { Parcel* parcel = parcelForJavaObject(env, infoObj); uinput::InputAbsInfo info; info.readFromParcel(parcel); struct uinput_abs_setup absSetup; absSetup.code = axisCode; absSetup.absinfo.maximum = info.maximum; absSetup.absinfo.minimum = info.minimum; absSetup.absinfo.value = info.value; absSetup.absinfo.fuzz = info.fuzz; absSetup.absinfo.flat = info.flat; absSetup.absinfo.resolution = info.resolution; ::ioctl(static_cast(handle), UI_ABS_SETUP, &absSetup); } static jint getEvdevEventTypeByLabel(JNIEnv* env, jclass /* clazz */, jstring rawLabel) { ScopedUtfChars label(env, rawLabel); return InputEventLookup::getLinuxEvdevEventTypeByLabel(label.c_str()).value_or(-1); } static jint getEvdevEventCodeByLabel(JNIEnv* env, jclass /* clazz */, jint type, jstring rawLabel) { ScopedUtfChars label(env, rawLabel); return InputEventLookup::getLinuxEvdevEventCodeByLabel(type, label.c_str()).value_or(-1); } static jint getEvdevInputPropByLabel(JNIEnv* env, jclass /* clazz */, jstring rawLabel) { ScopedUtfChars label(env, rawLabel); return InputEventLookup::getLinuxEvdevInputPropByLabel(label.c_str()).value_or(-1); } static JNINativeMethod sMethods[] = { {"nativeOpenUinputDevice", "(Ljava/lang/String;IIIIIILjava/lang/String;" "Lcom/android/commands/uinput/Device$DeviceCallback;)J", reinterpret_cast(openUinputDevice)}, {"nativeInjectEvent", "(JJIII)V", reinterpret_cast(injectEvent)}, {"nativeConfigure", "(II[I)V", reinterpret_cast(configure)}, {"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast(setAbsInfo)}, {"nativeCloseUinputDevice", "(J)V", reinterpret_cast(closeUinputDevice)}, {"nativeGetEvdevEventTypeByLabel", "(Ljava/lang/String;)I", reinterpret_cast(getEvdevEventTypeByLabel)}, {"nativeGetEvdevEventCodeByLabel", "(ILjava/lang/String;)I", reinterpret_cast(getEvdevEventCodeByLabel)}, {"nativeGetEvdevInputPropByLabel", "(Ljava/lang/String;)I", reinterpret_cast(getEvdevInputPropByLabel)}, }; int register_com_android_commands_uinput_Device(JNIEnv* env) { jclass clazz = env->FindClass("com/android/commands/uinput/Device$DeviceCallback"); if (clazz == nullptr) { ALOGE("Unable to find class 'DeviceCallback'"); return JNI_ERR; } uinput::gDeviceCallbackClassInfo.onDeviceConfigure = env->GetMethodID(clazz, "onDeviceConfigure", "(I)V"); uinput::gDeviceCallbackClassInfo.onDeviceVibrating = env->GetMethodID(clazz, "onDeviceVibrating", "(I)V"); uinput::gDeviceCallbackClassInfo.onDeviceError = env->GetMethodID(clazz, "onDeviceError", "()V"); if (uinput::gDeviceCallbackClassInfo.onDeviceConfigure == nullptr || uinput::gDeviceCallbackClassInfo.onDeviceError == nullptr || uinput::gDeviceCallbackClassInfo.onDeviceVibrating == nullptr) { ALOGE("Unable to obtain onDeviceConfigure or onDeviceError or onDeviceVibrating methods"); return JNI_ERR; } return jniRegisterNativeMethods(env, "com/android/commands/uinput/Device", sMethods, NELEM(sMethods)); } } // namespace android jint JNI_OnLoad(JavaVM* jvm, void*) { JNIEnv* env = nullptr; if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6)) { return JNI_ERR; } if (android::register_com_android_commands_uinput_Device(env) < 0) { return JNI_ERR; } return JNI_VERSION_1_6; }