summaryrefslogtreecommitdiff
path: root/cmds/hid
diff options
context:
space:
mode:
authorSiarhei Vishniakou <svv@google.com>2019-12-06 22:43:28 -0800
committerSiarhei Vishniakou <svv@google.com>2019-12-10 13:47:08 -0800
commitf6081119aa48d1d54ec5bd2d0e2ce336af99f97b (patch)
treebfc0144d6cee118fee3cc92aa3619d62986fd2b8 /cmds/hid
parentc490e7acdeaf682275cb687684e82ac984ab4a61 (diff)
Add UHID_OUTPUT handling to hid command
Some joysticks may send UHID_OUTPUT requests during probe. They expect to receive a report in response. Provide a facility to specify the expected outputs from the driver, and a way to respond to each of these requests. Typically, this would be something like "driver: please use full report mode", "joystick: ACK". If the ACK is not received by the driver, the probe would fail, and input device would never get registered. Bug: 135136477 Test: atest NintendoSwitchProTest Change-Id: Ic2c7a73d3d4bf759a1a104324687cd01646f256e
Diffstat (limited to 'cmds/hid')
-rw-r--r--cmds/hid/jni/com_android_commands_hid_Device.cpp33
-rw-r--r--cmds/hid/jni/com_android_commands_hid_Device.h1
-rw-r--r--cmds/hid/src/com/android/commands/hid/Device.java37
-rw-r--r--cmds/hid/src/com/android/commands/hid/Event.java75
-rw-r--r--cmds/hid/src/com/android/commands/hid/Hid.java9
5 files changed, 134 insertions, 21 deletions
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index f3871d74320b..f56dd6e4968e 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -27,12 +27,13 @@
#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/looper.h>
-#include <android/log.h>
#include <android-base/stringprintf.h>
@@ -49,6 +50,7 @@ static const char* UHID_PATH = "/dev/uhid";
static struct {
jmethodID onDeviceOpen;
jmethodID onDeviceGetReport;
+ jmethodID onDeviceOutput;
jmethodID onDeviceError;
} gDeviceCallbackClassInfo;
@@ -64,6 +66,18 @@ static void checkAndClearException(JNIEnv* env, const char* methodName) {
}
}
+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) {
@@ -101,6 +115,13 @@ void DeviceCallback::onDeviceGetReport(uint32_t requestId, uint8_t 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);
@@ -240,6 +261,12 @@ int Device::handleEvents(int events) {
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;
@@ -332,6 +359,8 @@ int register_com_android_commands_hid_Device(JNIEnv* env) {
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 ||
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index b0471eda44c6..93ea881cfe28 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -31,6 +31,7 @@ public:
void onDeviceOpen();
void onDeviceGetReport(uint32_t requestId, uint8_t reportId);
+ void onDeviceOutput(const std::vector<uint8_t>& data);
void onDeviceError();
private:
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index 616d411ef7bb..874604ceb5e4 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -20,13 +20,16 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
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;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Map;
+
public class Device {
private static final String TAG = "HidDevice";
@@ -40,6 +43,7 @@ public class Device {
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 long mTimeToSend;
private final Object mCond = new Object();
@@ -55,12 +59,13 @@ public class Device {
private static native void nativeCloseDevice(long ptr);
public Device(int id, String name, int vid, int pid, byte[] descriptor,
- byte[] report, SparseArray<byte[]> featureReports) {
+ byte[] report, SparseArray<byte[]> featureReports, Map<ByteBuffer, byte[]> outputs) {
mId = id;
mThread = new HandlerThread("HidDeviceHandler");
mThread.start();
mHandler = new DeviceHandler(mThread.getLooper());
mFeatureReports = featureReports;
+ mOutputs = outputs;
SomeArgs args = SomeArgs.obtain();
args.argi1 = id;
args.argi2 = vid;
@@ -160,6 +165,11 @@ public class Device {
}
public void onDeviceGetReport(int requestId, int reportId) {
+ if (mFeatureReports == null) {
+ Log.e(TAG, "Received GET_REPORT request for reportId=" + reportId
+ + ", but 'feature_reports' section is not found");
+ return;
+ }
byte[] report = mFeatureReports.get(reportId);
if (report == null) {
@@ -176,6 +186,29 @@ public class Device {
mHandler.sendMessageAtTime(msg, mTimeToSend);
}
+ // native callback
+ public void onDeviceOutput(byte[] data) {
+ if (mOutputs == null) {
+ Log.e(TAG, "Received OUTPUT request, but 'outputs' section is not found");
+ return;
+ }
+ byte[] response = mOutputs.get(ByteBuffer.wrap(data));
+ if (response == null) {
+ Log.i(TAG,
+ "Requested response for output " + Arrays.toString(data) + " is not found");
+ return;
+ }
+
+ Message msg;
+ msg = mHandler.obtainMessage(MSG_SEND_REPORT, response);
+
+ // 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_OUTPUT requests during probe, and expect a response right away.
+ 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);
diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java
index 746e37289076..62587a70f10d 100644
--- a/cmds/hid/src/com/android/commands/hid/Event.java
+++ b/cmds/hid/src/com/android/commands/hid/Event.java
@@ -21,10 +21,13 @@ import android.util.JsonToken;
import android.util.Log;
import android.util.SparseArray;
-import java.io.InputStreamReader;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
public class Event {
private static final String TAG = "HidEvent";
@@ -41,6 +44,7 @@ public class Event {
private int mPid;
private byte[] mReport;
private SparseArray<byte[]> mFeatureReports;
+ private Map<ByteBuffer, byte[]> mOutputs;
private int mDuration;
public int getId() {
@@ -75,6 +79,10 @@ public class Event {
return mFeatureReports;
}
+ public Map<ByteBuffer, byte[]> getOutputs() {
+ return mOutputs;
+ }
+
public int getDuration() {
return mDuration;
}
@@ -88,6 +96,7 @@ public class Event {
+ ", pid=" + mPid
+ ", report=" + Arrays.toString(mReport)
+ ", feature_reports=" + mFeatureReports.toString()
+ + ", outputs=" + mOutputs.toString()
+ ", duration=" + mDuration
+ "}";
}
@@ -123,6 +132,10 @@ public class Event {
mEvent.mFeatureReports = reports;
}
+ public void setOutputs(Map<ByteBuffer, byte[]> outputs) {
+ mEvent.mOutputs = outputs;
+ }
+
public void setVid(int vid) {
mEvent.mVid = vid;
}
@@ -199,6 +212,9 @@ public class Event {
case "feature_reports":
eb.setFeatureReports(readFeatureReports());
break;
+ case "outputs":
+ eb.setOutputs(readOutputs());
+ break;
case "duration":
eb.setDuration(readInt());
break;
@@ -250,7 +266,7 @@ public class Event {
private SparseArray<byte[]> readFeatureReports()
throws IllegalStateException, IOException {
- SparseArray<byte[]> featureReports = new SparseArray();
+ SparseArray<byte[]> featureReports = new SparseArray<>();
try {
mReader.beginArray();
while (mReader.hasNext()) {
@@ -276,17 +292,60 @@ public class Event {
}
}
mReader.endObject();
- if (data != null)
+ if (data != null) {
featureReports.put(id, data);
+ }
}
mReader.endArray();
- } catch (IllegalStateException|NumberFormatException e) {
+ } catch (IllegalStateException | NumberFormatException e) {
+ consumeRemainingElements();
+ mReader.endArray();
+ throw new IllegalStateException("Encountered malformed data.", e);
+ }
+ return featureReports;
+ }
+
+ private Map<ByteBuffer, byte[]> readOutputs()
+ throws IllegalStateException, IOException {
+ Map<ByteBuffer, byte[]> outputs = new HashMap<>();
+
+ try {
+ mReader.beginArray();
+ while (mReader.hasNext()) {
+ byte[] output = null;
+ byte[] response = null;
+ mReader.beginObject();
+ while (mReader.hasNext()) {
+ String name = mReader.nextName();
+ switch (name) {
+ case "description":
+ // Description is only used to keep track of the output responses
+ mReader.nextString();
+ break;
+ case "output":
+ output = readData();
+ break;
+ case "response":
+ response = readData();
+ break;
+ default:
+ consumeRemainingElements();
+ mReader.endObject();
+ throw new IllegalStateException("Invalid key in outputs: " + name);
+ }
+ }
+ mReader.endObject();
+ if (output != null) {
+ outputs.put(ByteBuffer.wrap(output), response);
+ }
+ }
+ mReader.endArray();
+ } catch (IllegalStateException | NumberFormatException e) {
consumeRemainingElements();
mReader.endArray();
throw new IllegalStateException("Encountered malformed data.", e);
- } finally {
- return featureReports;
}
+ return outputs;
}
private void consumeRemainingElements() throws IOException {
@@ -296,10 +355,6 @@ public class Event {
}
}
- private static void error(String msg) {
- error(msg, null);
- }
-
private static void error(String msg, Exception e) {
System.out.println(msg);
Log.e(TAG, msg);
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index 54ac1b0733ff..0ee2cc45932f 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -16,22 +16,17 @@
package com.android.commands.hid;
-import android.util.JsonReader;
-import android.util.JsonToken;
import android.util.Log;
import android.util.SparseArray;
import libcore.io.IoUtils;
-import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.IOException;
import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
public class Hid {
private static final String TAG = "HID";
@@ -119,7 +114,7 @@ public class Hid {
}
int id = e.getId();
Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(),
- e.getDescriptor(), e.getReport(), e.getFeatureReports());
+ e.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs());
mDevices.append(id, d);
}