blob: 1e200c52a2075fdc8343f8e220efd1ca98f63c43 [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 <inttypes.h>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <memory>
#include <android/log.h>
#include <android/looper.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
#include <android-base/stringprintf.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
namespace android {
namespace uhid {
static const char* UHID_PATH = "/dev/uhid";
static struct {
jmethodID onDeviceOpen;
jmethodID onDeviceGetReport;
jmethodID onDeviceOutput;
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()) {
LOGE("An exception was thrown by callback '%s'.", methodName);
env->ExceptionClear();
}
}
static ScopedLocalRef<jbyteArray> toJbyteArray(JNIEnv* env, const std::vector<uint8_t>& vector) {
ScopedLocalRef<jbyteArray> array(env, env->NewByteArray(vector.size()));
if (array.get() == nullptr) {
jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
return array;
}
static_assert(sizeof(char) == sizeof(uint8_t));
env->SetByteArrayRegion(array.get(), 0, vector.size(),
reinterpret_cast<const signed char*>(vector.data()));
return array;
}
static std::string toString(const std::vector<uint8_t>& data) {
std::string s = "";
for (uint8_t b : data) {
s += android::base::StringPrintf("%x ", b);
}
return s;
}
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::onDeviceOpen() {
JNIEnv* env = getJNIEnv();
env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOpen);
checkAndClearException(env, "onDeviceOpen");
}
void DeviceCallback::onDeviceGetReport(uint32_t requestId, uint8_t reportId) {
JNIEnv* env = getJNIEnv();
env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceGetReport,
requestId, reportId);
checkAndClearException(env, "onDeviceGetReport");
}
void DeviceCallback::onDeviceOutput(const std::vector<uint8_t>& data) {
JNIEnv* env = getJNIEnv();
env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput,
toJbyteArray(env, data).get());
checkAndClearException(env, "onDeviceOutput");
}
JNIEnv* DeviceCallback::getJNIEnv() {
JNIEnv* env;
mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
return env;
}
std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
uint16_t bus, const std::vector<uint8_t>& descriptor,
std::unique_ptr<DeviceCallback> callback) {
size_t size = descriptor.size();
if (size > HID_MAX_DESCRIPTOR_SIZE) {
LOGE("Received invalid hid report with descriptor size %zu, skipping", size);
return nullptr;
}
android::base::unique_fd fd(::open(UHID_PATH, O_RDWR | O_CLOEXEC));
if (!fd.ok()) {
LOGE("Failed to open uhid: %s", strerror(errno));
return nullptr;
}
struct uhid_event ev = {};
ev.type = UHID_CREATE2;
strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name));
std::string uniq = android::base::StringPrintf("Id: %d", id);
strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq.c_str(), sizeof(ev.u.create2.uniq));
memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0]));
ev.u.create2.rd_size = size;
ev.u.create2.bus = bus;
ev.u.create2.vendor = vid;
ev.u.create2.product = pid;
ev.u.create2.version = 0;
ev.u.create2.country = 0;
errno = 0;
ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev)));
if (ret < 0 || ret != sizeof(ev)) {
LOGE("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) {
LOGE("uhid node failed to start: %s", strerror(errno));
return nullptr;
}
// using 'new' to access non-public constructor
return std::unique_ptr<Device>(new Device(id, std::move(fd), std::move(callback)));
}
Device::Device(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCallback> callback)
: mId(id), mFd(std::move(fd)), mDeviceCallback(std::move(callback)) {
ALooper* aLooper = ALooper_forThread();
if (aLooper == NULL) {
LOGE("Could not get ALooper, ALooper_forThread returned NULL");
aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
}
ALooper_addFd(aLooper, mFd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents,
reinterpret_cast<void*>(this));
}
Device::~Device() {
ALooper* looper = ALooper_forThread();
if (looper != NULL) {
ALooper_removeFd(looper, mFd);
} else {
LOGE("Could not remove fd, ALooper_forThread() returned NULL!");
}
struct uhid_event ev = {};
ev.type = UHID_DESTROY;
TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
}
// Send event over the fd.
static void writeEvent(int fd, struct uhid_event& ev, const char* messageType) {
ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev)));
if (ret < 0 || ret != sizeof(ev)) {
LOGE("Failed to send uhid_event %s: %s", messageType, strerror(errno));
}
}
void Device::sendReport(const std::vector<uint8_t>& report) const {
if (report.size() > UHID_DATA_MAX) {
LOGE("Received invalid report of size %zu, skipping", report.size());
return;
}
struct uhid_event ev = {};
ev.type = UHID_INPUT2;
ev.u.input2.size = report.size();
memcpy(&ev.u.input2.data, report.data(), report.size() * sizeof(ev.u.input2.data[0]));
writeEvent(mFd, ev, "UHID_INPUT2");
}
void Device::sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& report) const {
struct uhid_event ev = {};
ev.type = UHID_GET_REPORT_REPLY;
ev.u.get_report_reply.id = id;
ev.u.get_report_reply.err = report.size() == 0 ? EIO : 0;
ev.u.get_report_reply.size = report.size();
memcpy(&ev.u.get_report_reply.data, report.data(),
report.size() * sizeof(ev.u.get_report_reply.data[0]));
writeEvent(mFd, ev, "UHID_GET_REPORT_REPLY");
}
int Device::handleEvents(int events) {
if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
LOGE("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) {
LOGE("Failed to read from uhid node: %s", strerror(errno));
mDeviceCallback->onDeviceError();
return 0;
}
switch (ev.type) {
case UHID_OPEN: {
mDeviceCallback->onDeviceOpen();
break;
}
case UHID_GET_REPORT: {
mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum);
break;
}
case UHID_SET_REPORT: {
const struct uhid_set_report_req& set_report = ev.u.set_report;
if (set_report.size > UHID_DATA_MAX) {
LOGE("SET_REPORT contains too much data: size = %" PRIu16, set_report.size);
return 0;
}
std::vector<uint8_t> data(set_report.data, set_report.data + set_report.size);
LOGI("Received SET_REPORT: id=%" PRIu32 " rnum=%" PRIu8 " data=%s", set_report.id,
set_report.rnum, toString(data).c_str());
break;
}
case UHID_OUTPUT: {
struct uhid_output_req& output = ev.u.output;
std::vector<uint8_t> data(output.data, output.data + output.size);
mDeviceCallback->onDeviceOutput(data);
break;
}
default: {
LOGI("Unhandled event type: %" PRIu32, ev.type);
break;
}
}
return 1;
}
} // namespace uhid
std::vector<uint8_t> getData(JNIEnv* env, jbyteArray javaArray) {
std::vector<uint8_t> data;
if (javaArray == nullptr) {
return data;
}
ScopedByteArrayRO scopedArray(env, javaArray);
size_t size = scopedArray.size();
data.reserve(size);
for (size_t i = 0; i < size; i++) {
data.push_back(static_cast<uint8_t>(scopedArray[i]));
}
return data;
}
static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid,
jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) {
ScopedUtfChars name(env, rawName);
if (name.c_str() == nullptr) {
return 0;
}
std::vector<uint8_t> desc = getData(env, rawDescriptor);
std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback));
std::unique_ptr<uhid::Device> d =
uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, bus, desc,
std::move(cb));
return reinterpret_cast<jlong>(d.release());
}
static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr, jbyteArray rawReport) {
std::vector<uint8_t> report = getData(env, rawReport);
uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr);
if (d) {
d->sendReport(report);
} else {
LOGE("Could not send report, Device* is null!");
}
}
static void sendGetFeatureReportReply(JNIEnv* env, jclass /* clazz */, jlong ptr, jint id,
jbyteArray rawReport) {
uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr);
if (d) {
std::vector<uint8_t> report = getData(env, rawReport);
d->sendGetFeatureReportReply(id, report);
} else {
LOGE("Could not send get feature report reply, Device* is null!");
}
}
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;IIII[B"
"Lcom/android/commands/hid/Device$DeviceCallback;)J",
reinterpret_cast<void*>(openDevice)},
{"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)},
{"nativeSendGetFeatureReportReply", "(JI[B)V",
reinterpret_cast<void*>(sendGetFeatureReportReply)},
{"nativeCloseDevice", "(J)V", reinterpret_cast<void*>(closeDevice)},
};
int register_com_android_commands_hid_Device(JNIEnv* env) {
jclass clazz = env->FindClass("com/android/commands/hid/Device$DeviceCallback");
if (clazz == NULL) {
LOGE("Unable to find class 'DeviceCallback'");
return JNI_ERR;
}
uhid::gDeviceCallbackClassInfo.onDeviceOpen =
env->GetMethodID(clazz, "onDeviceOpen", "()V");
uhid::gDeviceCallbackClassInfo.onDeviceGetReport =
env->GetMethodID(clazz, "onDeviceGetReport", "(II)V");
uhid::gDeviceCallbackClassInfo.onDeviceOutput =
env->GetMethodID(clazz, "onDeviceOutput", "([B)V");
uhid::gDeviceCallbackClassInfo.onDeviceError =
env->GetMethodID(clazz, "onDeviceError", "()V");
if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL ||
uhid::gDeviceCallbackClassInfo.onDeviceError == NULL) {
LOGE("Unable to obtain onDeviceOpen or onDeviceError methods");
return JNI_ERR;
}
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;
}