blob: 1ea33ced7bbfadc086124361f3a1ab786db585f2 [file] [log] [blame]
/*
* 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;
}