| /* |
| * 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 <linux/uhid.h> |
| |
| #include <fcntl.h> |
| #include <cstdio> |
| #include <cstring> |
| #include <memory> |
| #include <unistd.h> |
| |
| #include <android_runtime/AndroidRuntime.h> |
| #include <android_runtime/Log.h> |
| #include <android_os_MessageQueue.h> |
| #include <core_jni_helpers.h> |
| #include <jni.h> |
| #include <JNIHelp.h> |
| #include <ScopedPrimitiveArray.h> |
| #include <ScopedUtfChars.h> |
| #include <utils/Log.h> |
| #include <utils/Looper.h> |
| #include <utils/StrongPointer.h> |
| |
| 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<Device*>(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<uint8_t[]> descriptor, size_t descriptorSize, |
| std::unique_ptr<DeviceCallback> callback, sp<Looper> 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<DeviceCallback> callback, sp<Looper> looper) : |
| mId(id), mFd(fd), mDeviceCallback(std::move(callback)), mLooper(looper) { |
| looper->addFd(fd, 0, Looper::EVENT_INPUT, handleLooperEvents, reinterpret_cast<void*>(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<uint8_t[]> getData(JNIEnv* env, jbyteArray javaArray, size_t& outSize) { |
| ScopedByteArrayRO scopedArray(env, javaArray); |
| outSize = scopedArray.size(); |
| std::unique_ptr<uint8_t[]> data(new uint8_t[outSize]); |
| for (size_t i = 0; i < outSize; i++) { |
| data[i] = static_cast<uint8_t>(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<uint8_t[]> desc = getData(env, rawDescriptor, size); |
| |
| std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback)); |
| sp<Looper> looper = android_os_MessageQueue_getMessageQueue(env, queue)->getLooper(); |
| |
| uhid::Device* d = uhid::Device::open( |
| id, reinterpret_cast<const char*>(name.c_str()), vid, pid, |
| std::move(desc), size, std::move(cb), std::move(looper)); |
| return reinterpret_cast<jlong>(d); |
| } |
| |
| static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr,jbyteArray rawReport) { |
| size_t size; |
| std::unique_ptr<uint8_t[]> report = getData(env, rawReport, size); |
| uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr); |
| if (d) { |
| d->sendReport(report.get(), size); |
| } |
| } |
| |
| static void closeDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { |
| uhid::Device* d = reinterpret_cast<uhid::Device*>(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<void*>(openDevice) }, |
| { "nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport) }, |
| { "nativeCloseDevice", "(J)V", reinterpret_cast<void*>(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<void**>(&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; |
| } |