Add support for UHID_GET_REPORT requests
Updated to support UHID_GET_REPORT requests mainly for CTS use.
All feature reports are assumed to be static and should be specified
along with the register json.
For example, in sony_dualshock4_register.json, add the following.
"feature_reports": [
{
"id": 5,
"data": [0x05, 0x1e, 0x00, 0x05, 0x00, 0xe2, 0xff, 0xf2, 0x22, 0xbe, 0x22, 0x8d, 0x22, 0x4f,
0xdd, 0x4d, 0xdd, 0x39, 0xdd, 0x1c, 0x02, 0x1c, 0x02, 0xe3, 0x1f, 0x8b, 0xdf, 0x8c, 0x1e,
0xb4, 0xde, 0x30, 0x20, 0x71, 0xe0, 0x10, 0x00, 0xca, 0xfc, 0x64, 0x4d]
}
]
Test: Make sure build has hid-sony driver that's backported from >4.10.
Run Dualshock4 CTS
> atest cts/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4TestCase.java
Change-Id: Ib0d534d47c6aa6fcc11af68b913c3db1cfa72752
This commit is contained in:
@@ -45,6 +45,7 @@ static const char* UHID_PATH = "/dev/uhid";
|
||||
|
||||
static struct {
|
||||
jmethodID onDeviceOpen;
|
||||
jmethodID onDeviceGetReport;
|
||||
jmethodID onDeviceError;
|
||||
} gDeviceCallbackClassInfo;
|
||||
|
||||
@@ -82,6 +83,13 @@ void DeviceCallback::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");
|
||||
}
|
||||
|
||||
JNIEnv* DeviceCallback::getJNIEnv() {
|
||||
JNIEnv* env;
|
||||
mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
|
||||
@@ -103,8 +111,7 @@ Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct uhid_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
struct uhid_event ev = {};
|
||||
ev.type = UHID_CREATE2;
|
||||
strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name));
|
||||
memcpy(&ev.u.create2.rd_data, descriptor.data(),
|
||||
@@ -152,8 +159,7 @@ Device::~Device() {
|
||||
} else {
|
||||
LOGE("Could not remove fd, ALooper_forThread() returned NULL!");
|
||||
}
|
||||
struct uhid_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
struct uhid_event ev = {};
|
||||
ev.type = UHID_DESTROY;
|
||||
TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
|
||||
::close(mFd);
|
||||
@@ -166,8 +172,7 @@ void Device::sendReport(const std::vector<uint8_t>& report) const {
|
||||
return;
|
||||
}
|
||||
|
||||
struct uhid_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
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]));
|
||||
@@ -177,6 +182,20 @@ void Device::sendReport(const std::vector<uint8_t>& report) const {
|
||||
}
|
||||
}
|
||||
|
||||
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]));
|
||||
ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
|
||||
if (ret < 0 || ret != sizeof(ev)) {
|
||||
LOGE("Failed to send hid event (UHID_GET_REPORT_REPLY): %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -193,6 +212,11 @@ int Device::handleEvents(int events) {
|
||||
|
||||
if (ev.type == UHID_OPEN) {
|
||||
mDeviceCallback->onDeviceOpen();
|
||||
} else if (ev.type == UHID_GET_REPORT) {
|
||||
mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum);
|
||||
} else if (ev.type == UHID_SET_REPORT) {
|
||||
LOGE("UHID_SET_REPORT is currently not supported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
@@ -201,9 +225,13 @@ int Device::handleEvents(int events) {
|
||||
} // 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();
|
||||
std::vector<uint8_t> data;
|
||||
data.reserve(size);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
data.push_back(static_cast<uint8_t>(scopedArray[i]));
|
||||
@@ -237,6 +265,17 @@ static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr, jbyteArray ra
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -250,6 +289,8 @@ static JNINativeMethod sMethods[] = {
|
||||
"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) },
|
||||
};
|
||||
|
||||
@@ -261,6 +302,8 @@ int register_com_android_commands_hid_Device(JNIEnv* env) {
|
||||
}
|
||||
uhid::gDeviceCallbackClassInfo.onDeviceOpen =
|
||||
env->GetMethodID(clazz, "onDeviceOpen", "()V");
|
||||
uhid::gDeviceCallbackClassInfo.onDeviceGetReport =
|
||||
env->GetMethodID(clazz, "onDeviceGetReport", "(II)V");
|
||||
uhid::gDeviceCallbackClassInfo.onDeviceError =
|
||||
env->GetMethodID(clazz, "onDeviceError", "()V");
|
||||
if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL ||
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
~DeviceCallback();
|
||||
|
||||
void onDeviceOpen();
|
||||
void onDeviceGetReport(uint32_t requestId, uint8_t reportId);
|
||||
void onDeviceError();
|
||||
|
||||
private:
|
||||
@@ -45,6 +46,7 @@ public:
|
||||
~Device();
|
||||
|
||||
void sendReport(const std::vector<uint8_t>& report) const;
|
||||
void sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& report) const;
|
||||
void close();
|
||||
|
||||
int handleEvents(int events);
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.Message;
|
||||
import android.os.MessageQueue;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.os.SomeArgs;
|
||||
|
||||
@@ -31,11 +32,14 @@ public class Device {
|
||||
|
||||
private static final int MSG_OPEN_DEVICE = 1;
|
||||
private static final int MSG_SEND_REPORT = 2;
|
||||
private static final int MSG_CLOSE_DEVICE = 3;
|
||||
private static final int MSG_SEND_GET_FEATURE_REPORT_REPLY = 3;
|
||||
private static final int MSG_CLOSE_DEVICE = 4;
|
||||
|
||||
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 long mTimeToSend;
|
||||
|
||||
private final Object mCond = new Object();
|
||||
@@ -47,13 +51,16 @@ public class Device {
|
||||
private static native long nativeOpenDevice(String name, int id, int vid, int pid,
|
||||
byte[] descriptor, DeviceCallback callback);
|
||||
private static native void nativeSendReport(long ptr, byte[] data);
|
||||
private static native void nativeSendGetFeatureReportReply(long ptr, int id, byte[] data);
|
||||
private static native void nativeCloseDevice(long ptr);
|
||||
|
||||
public Device(int id, String name, int vid, int pid, byte[] descriptor, byte[] report) {
|
||||
public Device(int id, String name, int vid, int pid, byte[] descriptor,
|
||||
byte[] report, SparseArray<byte[]> featureReports) {
|
||||
mId = id;
|
||||
mThread = new HandlerThread("HidDeviceHandler");
|
||||
mThread.start();
|
||||
mHandler = new DeviceHandler(mThread.getLooper());
|
||||
mFeatureReports = featureReports;
|
||||
SomeArgs args = SomeArgs.obtain();
|
||||
args.argi1 = id;
|
||||
args.argi2 = vid;
|
||||
@@ -113,6 +120,13 @@ public class Device {
|
||||
Log.e(TAG, "Tried to send report to closed device.");
|
||||
}
|
||||
break;
|
||||
case MSG_SEND_GET_FEATURE_REPORT_REPLY:
|
||||
if (mPtr != 0) {
|
||||
nativeSendGetFeatureReportReply(mPtr, msg.arg1, (byte[]) msg.obj);
|
||||
} else {
|
||||
Log.e(TAG, "Tried to send feature report reply to closed device.");
|
||||
}
|
||||
break;
|
||||
case MSG_CLOSE_DEVICE:
|
||||
if (mPtr != 0) {
|
||||
nativeCloseDevice(mPtr);
|
||||
@@ -145,6 +159,23 @@ public class Device {
|
||||
mHandler.resumeEvents();
|
||||
}
|
||||
|
||||
public void onDeviceGetReport(int requestId, int reportId) {
|
||||
byte[] report = mFeatureReports.get(reportId);
|
||||
|
||||
if (report == null) {
|
||||
Log.e(TAG, "Requested feature report " + reportId + " is not specified");
|
||||
}
|
||||
|
||||
Message msg;
|
||||
msg = mHandler.obtainMessage(MSG_SEND_GET_FEATURE_REPORT_REPLY, requestId, 0, report);
|
||||
|
||||
// Message is set to asynchronous so it won't be blocked by synchronization
|
||||
// barrier during UHID_OPEN. This is necessary for drivers that do
|
||||
// UHID_GET_REPORT requests during probe.
|
||||
msg.setAsynchronous(true);
|
||||
mHandler.sendMessageAtTime(msg, mTimeToSend);
|
||||
}
|
||||
|
||||
public void onDeviceError() {
|
||||
Log.e(TAG, "Device error occurred, closing /dev/uhid");
|
||||
Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.commands.hid;
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonToken;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
@@ -39,6 +40,7 @@ public class Event {
|
||||
private int mVid;
|
||||
private int mPid;
|
||||
private byte[] mReport;
|
||||
private SparseArray<byte[]> mFeatureReports;
|
||||
private int mDuration;
|
||||
|
||||
public int getId() {
|
||||
@@ -69,6 +71,10 @@ public class Event {
|
||||
return mReport;
|
||||
}
|
||||
|
||||
public SparseArray<byte[]> getFeatureReports() {
|
||||
return mFeatureReports;
|
||||
}
|
||||
|
||||
public int getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
@@ -81,6 +87,7 @@ public class Event {
|
||||
+ ", vid=" + mVid
|
||||
+ ", pid=" + mPid
|
||||
+ ", report=" + Arrays.toString(mReport)
|
||||
+ ", feature_reports=" + mFeatureReports.toString()
|
||||
+ ", duration=" + mDuration
|
||||
+ "}";
|
||||
}
|
||||
@@ -112,6 +119,10 @@ public class Event {
|
||||
mEvent.mReport = report;
|
||||
}
|
||||
|
||||
public void setFeatureReports(SparseArray<byte[]> reports) {
|
||||
mEvent.mFeatureReports = reports;
|
||||
}
|
||||
|
||||
public void setVid(int vid) {
|
||||
mEvent.mVid = vid;
|
||||
}
|
||||
@@ -185,6 +196,9 @@ public class Event {
|
||||
case "report":
|
||||
eb.setReport(readData());
|
||||
break;
|
||||
case "feature_reports":
|
||||
eb.setFeatureReports(readFeatureReports());
|
||||
break;
|
||||
case "duration":
|
||||
eb.setDuration(readInt());
|
||||
break;
|
||||
@@ -234,6 +248,47 @@ public class Event {
|
||||
return Integer.decode(val);
|
||||
}
|
||||
|
||||
private SparseArray<byte[]> readFeatureReports()
|
||||
throws IllegalStateException, IOException {
|
||||
SparseArray<byte[]> featureReports = new SparseArray();
|
||||
try {
|
||||
mReader.beginArray();
|
||||
while (mReader.hasNext()) {
|
||||
// If "id" is not specified, it defaults to 0, which means
|
||||
// report does not contain report ID (based on HID specs).
|
||||
int id = 0;
|
||||
byte[] data = null;
|
||||
mReader.beginObject();
|
||||
while (mReader.hasNext()) {
|
||||
String name = mReader.nextName();
|
||||
switch (name) {
|
||||
case "id":
|
||||
id = readInt();
|
||||
break;
|
||||
case "data":
|
||||
data = readData();
|
||||
break;
|
||||
default:
|
||||
consumeRemainingElements();
|
||||
mReader.endObject();
|
||||
throw new IllegalStateException("Invalid key in feature report: "
|
||||
+ name);
|
||||
}
|
||||
}
|
||||
mReader.endObject();
|
||||
if (data != null)
|
||||
featureReports.put(id, data);
|
||||
}
|
||||
mReader.endArray();
|
||||
} catch (IllegalStateException|NumberFormatException e) {
|
||||
consumeRemainingElements();
|
||||
mReader.endArray();
|
||||
throw new IllegalStateException("Encountered malformed data.", e);
|
||||
} finally {
|
||||
return featureReports;
|
||||
}
|
||||
}
|
||||
|
||||
private void consumeRemainingElements() throws IOException {
|
||||
while (mReader.hasNext()) {
|
||||
mReader.skipValue();
|
||||
|
||||
@@ -119,7 +119,7 @@ public class Hid {
|
||||
}
|
||||
int id = e.getId();
|
||||
Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(),
|
||||
e.getDescriptor(), e.getReport());
|
||||
e.getDescriptor(), e.getReport(), e.getFeatureReports());
|
||||
mDevices.append(id, d);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user