Merge "Add support to hid commandline tool for hid device output."

This commit is contained in:
TreeHugger Robot
2020-09-03 20:49:40 +00:00
committed by Android (Google) Code Review
3 changed files with 64 additions and 30 deletions

View File

@@ -27,9 +27,9 @@
#include <cstring>
#include <memory>
#include <android/log.h>
#include <android/looper.h>
#include <jni.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
@@ -37,10 +37,8 @@
#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__)
// Log debug messages about the output.
static constexpr bool DEBUG_OUTPUT = false;
namespace android {
namespace uhid {
@@ -61,7 +59,7 @@ static int handleLooperEvents(int /* fd */, int events, void* data) {
static void checkAndClearException(JNIEnv* env, const char* methodName) {
if (env->ExceptionCheck()) {
LOGE("An exception was thrown by callback '%s'.", methodName);
ALOGE("An exception was thrown by callback '%s'.", methodName);
env->ExceptionClear();
}
}
@@ -115,9 +113,9 @@ void DeviceCallback::onDeviceGetReport(uint32_t requestId, uint8_t reportId) {
checkAndClearException(env, "onDeviceGetReport");
}
void DeviceCallback::onDeviceOutput(const std::vector<uint8_t>& data) {
void DeviceCallback::onDeviceOutput(uint8_t rType, const std::vector<uint8_t>& data) {
JNIEnv* env = getJNIEnv();
env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput,
env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput, rType,
toJbyteArray(env, data).get());
checkAndClearException(env, "onDeviceOutput");
}
@@ -133,13 +131,13 @@ std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid,
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);
ALOGE("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));
ALOGE("Failed to open uhid: %s", strerror(errno));
return nullptr;
}
@@ -159,14 +157,14 @@ std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid,
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));
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) {
LOGE("uhid node failed to start: %s", strerror(errno));
ALOGE("uhid node failed to start: %s", strerror(errno));
return nullptr;
}
// using 'new' to access non-public constructor
@@ -177,7 +175,7 @@ Device::Device(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCa
: 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");
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, handleLooperEvents,
@@ -189,7 +187,7 @@ Device::~Device() {
if (looper != NULL) {
ALooper_removeFd(looper, mFd);
} else {
LOGE("Could not remove fd, ALooper_forThread() returned NULL!");
ALOGE("Could not remove fd, ALooper_forThread() returned NULL!");
}
struct uhid_event ev = {};
ev.type = UHID_DESTROY;
@@ -200,13 +198,13 @@ Device::~Device() {
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));
ALOGE("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());
ALOGE("Received invalid report of size %zu, skipping", report.size());
return;
}
@@ -230,14 +228,14 @@ void Device::sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>&
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);
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) {
LOGE("Failed to read from uhid node: %s", strerror(errno));
ALOGE("Failed to read from uhid node: %s", strerror(errno));
mDeviceCallback->onDeviceError();
return 0;
}
@@ -254,23 +252,28 @@ int Device::handleEvents(int events) {
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);
ALOGE("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());
if (DEBUG_OUTPUT) {
ALOGD("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);
if (DEBUG_OUTPUT) {
ALOGD("UHID_OUTPUT rtype=%" PRIu8 " data=%s", output.rtype, toString(data).c_str());
}
mDeviceCallback->onDeviceOutput(output.rtype, data);
break;
}
default: {
LOGI("Unhandled event type: %" PRIu32, ev.type);
ALOGI("Unhandled event type: %" PRIu32, ev.type);
break;
}
}
@@ -318,7 +321,7 @@ static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr, jbyteArray ra
if (d) {
d->sendReport(report);
} else {
LOGE("Could not send report, Device* is null!");
ALOGE("Could not send report, Device* is null!");
}
}
@@ -329,7 +332,7 @@ static void sendGetFeatureReportReply(JNIEnv* env, jclass /* clazz */, jlong ptr
std::vector<uint8_t> report = getData(env, rawReport);
d->sendGetFeatureReportReply(id, report);
} else {
LOGE("Could not send get feature report reply, Device* is null!");
ALOGE("Could not send get feature report reply, Device* is null!");
}
}
@@ -354,7 +357,7 @@ static JNINativeMethod sMethods[] = {
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'");
ALOGE("Unable to find class 'DeviceCallback'");
return JNI_ERR;
}
uhid::gDeviceCallbackClassInfo.onDeviceOpen =
@@ -362,12 +365,12 @@ int register_com_android_commands_hid_Device(JNIEnv* env) {
uhid::gDeviceCallbackClassInfo.onDeviceGetReport =
env->GetMethodID(clazz, "onDeviceGetReport", "(II)V");
uhid::gDeviceCallbackClassInfo.onDeviceOutput =
env->GetMethodID(clazz, "onDeviceOutput", "([B)V");
env->GetMethodID(clazz, "onDeviceOutput", "(B[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");
ALOGE("Unable to obtain onDeviceOpen or onDeviceError methods");
return JNI_ERR;
}

View File

@@ -31,7 +31,7 @@ public:
void onDeviceOpen();
void onDeviceGetReport(uint32_t requestId, uint8_t reportId);
void onDeviceOutput(const std::vector<uint8_t>& data);
void onDeviceOutput(uint8_t rType, const std::vector<uint8_t>& data);
void onDeviceError();
private:

View File

@@ -26,6 +26,12 @@ import android.util.SparseArray;
import com.android.internal.os.SomeArgs;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
@@ -38,12 +44,16 @@ public class Device {
private static final int MSG_SEND_GET_FEATURE_REPORT_REPLY = 3;
private static final int MSG_CLOSE_DEVICE = 4;
// Sync with linux uhid_event_type::UHID_OUTPUT
private static final byte UHID_EVENT_TYPE_UHID_OUTPUT = 6;
private final int mId;
private final HandlerThread mThread;
private final DeviceHandler mHandler;
// mFeatureReports is limited to 256 entries, because the report number is 8-bit
private final SparseArray<byte[]> mFeatureReports;
private final Map<ByteBuffer, byte[]> mOutputs;
private final OutputStream mOutputStream;
private long mTimeToSend;
private final Object mCond = new Object();
@@ -66,6 +76,7 @@ public class Device {
mHandler = new DeviceHandler(mThread.getLooper());
mFeatureReports = featureReports;
mOutputs = outputs;
mOutputStream = System.out;
SomeArgs args = SomeArgs.obtain();
args.argi1 = id;
args.argi2 = vid;
@@ -188,7 +199,27 @@ public class Device {
}
// native callback
public void onDeviceOutput(byte[] data) {
public void onDeviceOutput(byte rtype, byte[] data) {
JSONObject json = new JSONObject();
try {
json.put("eventId", UHID_EVENT_TYPE_UHID_OUTPUT);
json.put("deviceId", mId);
json.put("reportType", rtype);
JSONArray dataArray = new JSONArray();
for (int i = 0; i < data.length; i++) {
dataArray.put(data[i] & 0xFF);
}
json.put("reportData", dataArray);
} catch (JSONException e) {
throw new RuntimeException("Could not create JSON object ", e);
}
try {
mOutputStream.write(json.toString().getBytes());
mOutputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (mOutputs == null) {
Log.e(TAG, "Received OUTPUT request, but 'outputs' section is not found");
return;