diff options
author | Siarhei Vishniakou <svv@google.com> | 2019-12-06 22:43:28 -0800 |
---|---|---|
committer | Siarhei Vishniakou <svv@google.com> | 2019-12-10 13:47:08 -0800 |
commit | f6081119aa48d1d54ec5bd2d0e2ce336af99f97b (patch) | |
tree | bfc0144d6cee118fee3cc92aa3619d62986fd2b8 /cmds/hid | |
parent | c490e7acdeaf682275cb687684e82ac984ab4a61 (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.cpp | 33 | ||||
-rw-r--r-- | cmds/hid/jni/com_android_commands_hid_Device.h | 1 | ||||
-rw-r--r-- | cmds/hid/src/com/android/commands/hid/Device.java | 37 | ||||
-rw-r--r-- | cmds/hid/src/com/android/commands/hid/Event.java | 75 | ||||
-rw-r--r-- | cmds/hid/src/com/android/commands/hid/Hid.java | 9 |
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); } |