315 lines
8.8 KiB
Java
315 lines
8.8 KiB
Java
|
// Patched HID device handling in relation to:
|
||
|
// https://github.com/libsdl-org/SDL/issues/3850
|
||
|
|
||
|
package org.libsdl.app;
|
||
|
|
||
|
import android.hardware.usb.*;
|
||
|
import android.os.Build;
|
||
|
import android.util.Log;
|
||
|
import java.util.Arrays;
|
||
|
|
||
|
class HIDDeviceUSB implements HIDDevice {
|
||
|
|
||
|
private static final String TAG = "hidapi";
|
||
|
|
||
|
protected HIDDeviceManager mManager;
|
||
|
protected UsbDevice mDevice;
|
||
|
protected int mInterface;
|
||
|
protected int mDeviceId;
|
||
|
protected UsbDeviceConnection mConnection;
|
||
|
protected UsbEndpoint mInputEndpoint;
|
||
|
protected UsbEndpoint mOutputEndpoint;
|
||
|
protected InputThread mInputThread;
|
||
|
protected boolean mRunning;
|
||
|
protected boolean mFrozen;
|
||
|
|
||
|
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_number) {
|
||
|
mManager = manager;
|
||
|
mDevice = usbDevice;
|
||
|
mInterface = interface_number;
|
||
|
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
|
||
|
mRunning = false;
|
||
|
}
|
||
|
|
||
|
public String getIdentifier() {
|
||
|
return String.format("%s/%x/%x", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getId() {
|
||
|
return mDeviceId;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getVendorId() {
|
||
|
return mDevice.getVendorId();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getProductId() {
|
||
|
return mDevice.getProductId();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String getSerialNumber() {
|
||
|
String result = null;
|
||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||
|
try {
|
||
|
result = mDevice.getSerialNumber();
|
||
|
} catch (SecurityException e) {
|
||
|
|
||
|
}
|
||
|
}
|
||
|
if (result == null) {
|
||
|
result = "";
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getVersion() {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String getManufacturerName() {
|
||
|
String result = null;
|
||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||
|
result = mDevice.getManufacturerName();
|
||
|
}
|
||
|
if (result == null) {
|
||
|
result = String.format("%x", getVendorId());
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String getProductName() {
|
||
|
String result = null;
|
||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||
|
result = mDevice.getProductName();
|
||
|
}
|
||
|
if (result == null) {
|
||
|
result = String.format("%x", getProductId());
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
public UsbDevice getDevice() {
|
||
|
return mDevice;
|
||
|
}
|
||
|
|
||
|
public String getDeviceName() {
|
||
|
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean open() {
|
||
|
mConnection = mManager.getUSBManager().openDevice(mDevice);
|
||
|
if (mConnection == null) {
|
||
|
Log.w(TAG, "Unable to open USB device " + getDeviceName());
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Force claim all interfaces
|
||
|
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
|
||
|
UsbInterface iface = mDevice.getInterface(i);
|
||
|
|
||
|
if (!mConnection.claimInterface(iface, true)) {
|
||
|
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Find the endpoints
|
||
|
UsbInterface iface = mDevice.getInterface(mInterface);
|
||
|
for (int j = 0; j < iface.getEndpointCount(); j++) {
|
||
|
UsbEndpoint endpt = iface.getEndpoint(j);
|
||
|
switch (endpt.getDirection()) {
|
||
|
case UsbConstants.USB_DIR_IN:
|
||
|
if (mInputEndpoint == null) {
|
||
|
mInputEndpoint = endpt;
|
||
|
}
|
||
|
break;
|
||
|
case UsbConstants.USB_DIR_OUT:
|
||
|
if (mOutputEndpoint == null) {
|
||
|
mOutputEndpoint = endpt;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Make sure the required endpoints were present
|
||
|
if (mInputEndpoint == null || mOutputEndpoint == null) {
|
||
|
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
|
||
|
close();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Start listening for input
|
||
|
mRunning = true;
|
||
|
mInputThread = new InputThread();
|
||
|
mInputThread.start();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int sendFeatureReport(byte[] report) {
|
||
|
int res = -1;
|
||
|
int offset = 0;
|
||
|
int length = report.length;
|
||
|
boolean skipped_report_id = false;
|
||
|
byte report_number = report[0];
|
||
|
|
||
|
if (report_number == 0x0) {
|
||
|
++offset;
|
||
|
--length;
|
||
|
skipped_report_id = true;
|
||
|
}
|
||
|
|
||
|
res = mConnection.controlTransfer(
|
||
|
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
|
||
|
0x09/*HID set_report*/,
|
||
|
(3/*HID feature*/ << 8) | report_number,
|
||
|
0,
|
||
|
report, offset, length,
|
||
|
1000/*timeout millis*/);
|
||
|
|
||
|
if (res < 0) {
|
||
|
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (skipped_report_id) {
|
||
|
++length;
|
||
|
}
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int sendOutputReport(byte[] report) {
|
||
|
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
|
||
|
if (r != report.length) {
|
||
|
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean getFeatureReport(byte[] report) {
|
||
|
int res = -1;
|
||
|
int offset = 0;
|
||
|
int length = report.length;
|
||
|
boolean skipped_report_id = false;
|
||
|
byte report_number = report[0];
|
||
|
|
||
|
if (report_number == 0x0) {
|
||
|
/* Offset the return buffer by 1, so that the report ID
|
||
|
will remain in byte 0. */
|
||
|
++offset;
|
||
|
--length;
|
||
|
skipped_report_id = true;
|
||
|
}
|
||
|
|
||
|
res = mConnection.controlTransfer(
|
||
|
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
|
||
|
0x01/*HID get_report*/,
|
||
|
(3/*HID feature*/ << 8) | report_number,
|
||
|
0,
|
||
|
report, offset, length,
|
||
|
1000/*timeout millis*/);
|
||
|
|
||
|
if (res < 0) {
|
||
|
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (skipped_report_id) {
|
||
|
++res;
|
||
|
++length;
|
||
|
}
|
||
|
|
||
|
byte[] data;
|
||
|
if (res == length) {
|
||
|
data = report;
|
||
|
} else {
|
||
|
data = Arrays.copyOfRange(report, 0, res);
|
||
|
}
|
||
|
mManager.HIDDeviceFeatureReport(mDeviceId, data);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void close() {
|
||
|
mRunning = false;
|
||
|
if (mInputThread != null) {
|
||
|
while (mInputThread.isAlive()) {
|
||
|
mInputThread.interrupt();
|
||
|
try {
|
||
|
mInputThread.join();
|
||
|
} catch (InterruptedException e) {
|
||
|
// Keep trying until we're done
|
||
|
}
|
||
|
}
|
||
|
mInputThread = null;
|
||
|
}
|
||
|
if (mConnection != null) {
|
||
|
for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
|
||
|
UsbInterface iface = mDevice.getInterface(i);
|
||
|
mConnection.releaseInterface(iface);
|
||
|
}
|
||
|
mConnection.close();
|
||
|
mConnection = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void shutdown() {
|
||
|
close();
|
||
|
mManager = null;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setFrozen(boolean frozen) {
|
||
|
mFrozen = frozen;
|
||
|
}
|
||
|
|
||
|
protected class InputThread extends Thread {
|
||
|
@Override
|
||
|
public void run() {
|
||
|
int packetSize = mInputEndpoint.getMaxPacketSize();
|
||
|
byte[] packet = new byte[packetSize];
|
||
|
while (mRunning) {
|
||
|
int r;
|
||
|
try
|
||
|
{
|
||
|
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
|
||
|
break;
|
||
|
}
|
||
|
if (r < 0) {
|
||
|
// Could be a timeout or an I/O error
|
||
|
}
|
||
|
if (r > 0) {
|
||
|
byte[] data;
|
||
|
if (r == packetSize) {
|
||
|
data = packet;
|
||
|
} else {
|
||
|
data = Arrays.copyOfRange(packet, 0, r);
|
||
|
}
|
||
|
|
||
|
if (!mFrozen) {
|
||
|
mManager.HIDDeviceInputReport(mDeviceId, data);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|