/* * 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 "HidCommandDevice" #include "com_android_commands_hid_Device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace uhid { static const char* UHID_PATH = "/dev/uhid"; static const size_t UHID_MAX_NAME_LENGTH = 128; static struct { jmethodID onDeviceOpen; jmethodID onDeviceError; } gDeviceCallbackClassInfo; static int handleLooperEvents(int /* fd */, int events, void* data) { Device* d = reinterpret_cast(data); return d->handleEvents(events); } static void checkAndClearException(JNIEnv* env, const char* methodName) { if (env->ExceptionCheck()) { ALOGE("An exception was thrown by callback '%s'.", methodName); LOGE_EX(env); env->ExceptionClear(); } } DeviceCallback::DeviceCallback(JNIEnv* env, jobject callback) : mCallbackObject(env->NewGlobalRef(callback)) { } DeviceCallback::~DeviceCallback() { JNIEnv* env = AndroidRuntime::getJNIEnv(); env->DeleteGlobalRef(mCallbackObject); } void DeviceCallback::onDeviceError() { JNIEnv* env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceError); checkAndClearException(env, "onDeviceError"); } void DeviceCallback::onDeviceOpen() { JNIEnv* env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOpen); checkAndClearException(env, "onDeviceOpen"); } Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, std::unique_ptr descriptor, size_t descriptorSize, std::unique_ptr callback, sp looper) { int fd = ::open(UHID_PATH, O_RDWR | O_CLOEXEC); if (fd < 0) { ALOGE("Failed to open uhid: %s", strerror(errno)); return nullptr; } struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_CREATE; strncpy((char*)ev.u.create.name, name, UHID_MAX_NAME_LENGTH); ev.u.create.rd_data = descriptor.get(); ev.u.create.rd_size = descriptorSize; ev.u.create.bus = BUS_BLUETOOTH; ev.u.create.vendor = vid; ev.u.create.product = pid; ev.u.create.version = 0; ev.u.create.country = 0; errno = 0; ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev))); if (ret < 0 || ret != sizeof(ev)) { ::close(fd); ALOGE("Failed to create uhid node: %s", strerror(errno)); return nullptr; } // Wait for the device to actually be created. ret = TEMP_FAILURE_RETRY(::read(fd, &ev, sizeof(ev))); if (ret < 0 || ev.type != UHID_START) { ::close(fd); ALOGE("uhid node failed to start: %s", strerror(errno)); return nullptr; } return new Device(id, fd, std::move(callback), looper); } Device::Device(int32_t id, int fd, std::unique_ptr callback, sp looper) : mId(id), mFd(fd), mDeviceCallback(std::move(callback)), mLooper(looper) { looper->addFd(fd, 0, Looper::EVENT_INPUT, handleLooperEvents, reinterpret_cast(this)); } Device::~Device() { mLooper->removeFd(mFd); struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_DESTROY; TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev))); ::close(mFd); mFd = -1; } void Device::sendReport(uint8_t* report, size_t reportSize) { struct uhid_event ev; memset(&ev, 0, sizeof(ev)); ev.type = UHID_INPUT; ev.u.input.size = reportSize; memcpy(&ev.u.input.data, report, reportSize); ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev))); if (ret < 0 || ret != sizeof(ev)) { ALOGE("Failed to send hid event: %s", strerror(errno)); } } int Device::handleEvents(int events) { if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) { ALOGE("uhid node was closed or an error occurred. events=0x%x", events); mDeviceCallback->onDeviceError(); return 0; } struct uhid_event ev; ssize_t ret = TEMP_FAILURE_RETRY(::read(mFd, &ev, sizeof(ev))); if (ret < 0) { ALOGE("Failed to read from uhid node: %s", strerror(errno)); mDeviceCallback->onDeviceError(); return 0; } if (ev.type == UHID_OPEN) { mDeviceCallback->onDeviceOpen(); } return 1; } } // namespace uhid std::unique_ptr getData(JNIEnv* env, jbyteArray javaArray, size_t& outSize) { ScopedByteArrayRO scopedArray(env, javaArray); outSize = scopedArray.size(); std::unique_ptr data(new uint8_t[outSize]); for (size_t i = 0; i < outSize; i++) { data[i] = static_cast(scopedArray[i]); } return data; } static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid, jint pid, jbyteArray rawDescriptor, jobject queue, jobject callback) { ScopedUtfChars name(env, rawName); if (name.c_str() == nullptr) { return 0; } size_t size; std::unique_ptr desc = getData(env, rawDescriptor, size); std::unique_ptr cb(new uhid::DeviceCallback(env, callback)); sp looper = android_os_MessageQueue_getMessageQueue(env, queue)->getLooper(); uhid::Device* d = uhid::Device::open( id, reinterpret_cast(name.c_str()), vid, pid, std::move(desc), size, std::move(cb), std::move(looper)); return reinterpret_cast(d); } static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr,jbyteArray rawReport) { size_t size; std::unique_ptr report = getData(env, rawReport, size); uhid::Device* d = reinterpret_cast(ptr); if (d) { d->sendReport(report.get(), size); } } static void closeDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { uhid::Device* d = reinterpret_cast(ptr); if (d) { delete d; } } static JNINativeMethod sMethods[] = { { "nativeOpenDevice", "(Ljava/lang/String;III[BLandroid/os/MessageQueue;" "Lcom/android/commands/hid/Device$DeviceCallback;)J", reinterpret_cast(openDevice) }, { "nativeSendReport", "(J[B)V", reinterpret_cast(sendReport) }, { "nativeCloseDevice", "(J)V", reinterpret_cast(closeDevice) }, }; int register_com_android_commands_hid_Device(JNIEnv* env) { jclass clazz = FindClassOrDie(env, "com/android/commands/hid/Device$DeviceCallback"); uhid::gDeviceCallbackClassInfo.onDeviceOpen = GetMethodIDOrDie(env, clazz, "onDeviceOpen", "()V"); uhid::gDeviceCallbackClassInfo.onDeviceError= GetMethodIDOrDie(env, clazz, "onDeviceError", "()V"); return jniRegisterNativeMethods(env, "com/android/commands/hid/Device", sMethods, NELEM(sMethods)); } } // namespace android jint JNI_OnLoad(JavaVM* jvm, void*) { JNIEnv *env = NULL; if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6)) { return JNI_ERR; } if (android::register_com_android_commands_hid_Device(env) < 0 ){ return JNI_ERR; } return JNI_VERSION_1_6; }