Add support for new Steam Controller on Android (#15044)
This commit is contained in:
@@ -19,9 +19,13 @@ import android.os.*;
|
|||||||
|
|
||||||
import java.lang.Runnable;
|
import java.lang.Runnable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
|
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
|
||||||
|
|
||||||
private static final String TAG = "hidapi";
|
private static final String TAG = "hidapi";
|
||||||
@@ -37,6 +41,11 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
private LinkedList<GattOperation> mOperations;
|
private LinkedList<GattOperation> mOperations;
|
||||||
GattOperation mCurrentOperation = null;
|
GattOperation mCurrentOperation = null;
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
|
private int mProductId = -1;
|
||||||
|
|
||||||
|
private static final int D0G_BLE2_PID = 0x1106;
|
||||||
|
private static final int TRITON_BLE_PID = 0x1303;
|
||||||
|
|
||||||
|
|
||||||
private static final int TRANSPORT_AUTO = 0;
|
private static final int TRANSPORT_AUTO = 0;
|
||||||
private static final int TRANSPORT_BREDR = 1;
|
private static final int TRANSPORT_BREDR = 1;
|
||||||
@@ -45,10 +54,13 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
|
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
|
||||||
|
|
||||||
static final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
|
static final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
|
||||||
static final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
|
static final UUID inputCharacteristicD0G = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
|
||||||
|
static final UUID inputCharacteristicTriton = UUID.fromString("100F6C7A-1735-4313-B402-38567131E5F3");
|
||||||
static final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
|
static final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
|
||||||
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
|
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
|
||||||
|
|
||||||
|
private HashMap<Integer, BluetoothGattCharacteristic> mOutputReportChars = new HashMap<Integer, BluetoothGattCharacteristic>();
|
||||||
|
|
||||||
static class GattOperation {
|
static class GattOperation {
|
||||||
private enum Operation {
|
private enum Operation {
|
||||||
CHR_READ,
|
CHR_READ,
|
||||||
@@ -314,8 +326,38 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
|
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
|
||||||
|
|
||||||
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
|
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
|
||||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
boolean bShouldStartNotifications = false;
|
||||||
Log.v(TAG, "Found input characteristic");
|
|
||||||
|
if (chr.getUuid().equals(inputCharacteristicTriton)) {
|
||||||
|
Log.v(TAG, "Found Triton input characteristic");
|
||||||
|
mProductId = TRITON_BLE_PID;
|
||||||
|
bShouldStartNotifications = true;
|
||||||
|
} else if (chr.getUuid().equals(inputCharacteristicD0G)) {
|
||||||
|
Log.v(TAG, "Found D0G input characteristic");
|
||||||
|
mProductId = D0G_BLE2_PID;
|
||||||
|
bShouldStartNotifications = true;
|
||||||
|
} else {
|
||||||
|
Pattern reportPattern = Pattern.compile("100F6C([0-9A-Z]{2})", Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher matcher = reportPattern.matcher(chr.getUuid().toString());
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
try {
|
||||||
|
int reportId = Integer.parseInt(matcher.group(1), 16);
|
||||||
|
|
||||||
|
reportId -= 0x35;
|
||||||
|
if (reportId >= 0x80) {
|
||||||
|
// This is a Triton output report characteristic that we need to care about.
|
||||||
|
Log.v(TAG, "Found Triton output report 0x" + Integer.toString(reportId, 16));
|
||||||
|
mOutputReportChars.put(reportId, chr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NumberFormatException nfe) {
|
||||||
|
Log.w(TAG, "Could not parse report characteristic " + chr.getUuid().toString() + ": " + nfe.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bShouldStartNotifications) {
|
||||||
// Start notifications
|
// Start notifications
|
||||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||||
if (cccd != null) {
|
if (cccd != null) {
|
||||||
@@ -448,8 +490,16 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
mIsConnected = false;
|
mIsConnected = false;
|
||||||
gatt.disconnect();
|
gatt.disconnect();
|
||||||
mGatt = connectGatt(false);
|
mGatt = connectGatt(false);
|
||||||
|
} else {
|
||||||
|
if (getProductId() == TRITON_BLE_PID) {
|
||||||
|
// Android will not properly play well with Data Length Extensions without manually requesting a large MTU,
|
||||||
|
// and Triton controllers require DLE support.
|
||||||
|
//
|
||||||
|
// 517 is basically a "magic number" as far as Android's bluetooth code is concerned, so do not change
|
||||||
|
// this value. It is functionally "please enable data length extensions" on some Android builds.
|
||||||
|
mGatt.requestMtu(517);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
probeService(this);
|
probeService(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -487,7 +537,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
// Enable this for verbose logging of controller input reports
|
// Enable this for verbose logging of controller input reports
|
||||||
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
|
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
|
||||||
|
|
||||||
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
|
if (characteristic.getUuid().equals(getInputCharacteristic()) && !mFrozen) {
|
||||||
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,15 +552,23 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
||||||
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
||||||
|
|
||||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
if (chr.getUuid().equals(getInputCharacteristic())) {
|
||||||
boolean hasWrittenInputDescriptor = true;
|
boolean hasWrittenInputDescriptor = true;
|
||||||
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
||||||
if (reportChr != null) {
|
if (reportChr != null) {
|
||||||
|
if (getProductId() == TRITON_BLE_PID) {
|
||||||
|
// For Triton we just mark things registered.
|
||||||
|
Log.v(TAG, "Registering Triton Steam Controller with ID: " + getId());
|
||||||
|
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0, true);
|
||||||
|
setRegistered();
|
||||||
|
} else {
|
||||||
|
// For the original controller, we need to manually enter Valve mode.
|
||||||
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
||||||
reportChr.setValue(enterValveMode);
|
reportChr.setValue(enterValveMode);
|
||||||
gatt.writeCharacteristic(reportChr);
|
gatt.writeCharacteristic(reportChr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
finishCurrentGattOperation();
|
finishCurrentGattOperation();
|
||||||
}
|
}
|
||||||
@@ -548,9 +606,28 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getProductId() {
|
public int getProductId() {
|
||||||
// We don't have an easy way to query from the Bluetooth device, but we know what it is
|
if (mProductId > 0) {
|
||||||
final int D0G_BLE2_PID = 0x1106;
|
// We've already set a product ID.
|
||||||
return D0G_BLE2_PID;
|
return mProductId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mDevice.getName().startsWith("Steam Ctrl")) {
|
||||||
|
// We're a newer Triton device
|
||||||
|
mProductId = TRITON_BLE_PID;
|
||||||
|
} else {
|
||||||
|
// We're an OG Steam Controller
|
||||||
|
mProductId = D0G_BLE2_PID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mProductId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID getInputCharacteristic() {
|
||||||
|
if (getProductId() == TRITON_BLE_PID) {
|
||||||
|
return inputCharacteristicTriton;
|
||||||
|
} else {
|
||||||
|
return inputCharacteristicD0G;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -601,10 +678,29 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
writeCharacteristic(reportCharacteristic, actual_report);
|
writeCharacteristic(reportCharacteristic, actual_report);
|
||||||
return report.length;
|
return report.length;
|
||||||
} else {
|
} else {
|
||||||
|
// If we're an original-recipe Steam Controller we just write to the characteristic directly.
|
||||||
|
if (getProductId() == D0G_BLE2_PID) {
|
||||||
//Log.v(TAG, "writeOutputReport " + HexDump.dumpHexString(report));
|
//Log.v(TAG, "writeOutputReport " + HexDump.dumpHexString(report));
|
||||||
writeCharacteristic(reportCharacteristic, report);
|
writeCharacteristic(reportCharacteristic, report);
|
||||||
return report.length;
|
return report.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're a Triton, we need to find the correct report characteristic.
|
||||||
|
if (report.length > 0) {
|
||||||
|
int reportId = report[0];
|
||||||
|
BluetoothGattCharacteristic targetedReportCharacteristic = mOutputReportChars.get(reportId);
|
||||||
|
if (targetedReportCharacteristic != null) {
|
||||||
|
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
|
||||||
|
//Log.v(TAG, "writeOutputReport 0x" + Integer.toString(reportId, 16) + " " + HexDump.dumpHexString(report));
|
||||||
|
writeCharacteristic(targetedReportCharacteristic.getUuid(), actual_report);
|
||||||
|
return report.length;
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Got report write request for unknown report type 0x" + Integer.toString(reportId, 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -528,7 +528,13 @@ public class HIDDeviceManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
// Steam Controllers will always support Bluetooth Low Energy
|
||||||
|
if ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match on the name either the original Steam Controller or the new second-generation one advertise with.
|
||||||
|
return bluetoothDevice.getName().equals("SteamController") || bluetoothDevice.getName().startsWith("Steam Ctrl");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void close() {
|
private void close() {
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ extern "C" {
|
|||||||
typedef uint32_t uint32;
|
typedef uint32_t uint32;
|
||||||
typedef uint64_t uint64;
|
typedef uint64_t uint64;
|
||||||
|
|
||||||
|
#define D0G_BLE2_PID 0x1106
|
||||||
|
#define TRITON_BLE_PID 0x1303
|
||||||
|
|
||||||
|
|
||||||
struct hid_device_
|
struct hid_device_
|
||||||
{
|
{
|
||||||
@@ -438,8 +441,7 @@ public:
|
|||||||
|
|
||||||
// The Bluetooth Steam Controller needs special handling
|
// The Bluetooth Steam Controller needs special handling
|
||||||
const int VALVE_USB_VID = 0x28DE;
|
const int VALVE_USB_VID = 0x28DE;
|
||||||
const int D0G_BLE2_PID = 0x1106;
|
if ( pInfo->vendor_id == VALVE_USB_VID && ( pInfo->product_id == D0G_BLE2_PID || pInfo->product_id == TRITON_BLE_PID ) )
|
||||||
if ( pInfo->vendor_id == VALVE_USB_VID && pInfo->product_id == D0G_BLE2_PID )
|
|
||||||
{
|
{
|
||||||
m_bIsBLESteamController = true;
|
m_bIsBLESteamController = true;
|
||||||
}
|
}
|
||||||
@@ -603,8 +605,15 @@ public:
|
|||||||
const hid_buffer &buffer = m_vecData.front();
|
const hid_buffer &buffer = m_vecData.front();
|
||||||
size_t nDataLen = buffer.size() > length ? length : buffer.size();
|
size_t nDataLen = buffer.size() > length ? length : buffer.size();
|
||||||
if ( m_bIsBLESteamController )
|
if ( m_bIsBLESteamController )
|
||||||
|
{
|
||||||
|
if ( m_pInfo->product_id == TRITON_BLE_PID )
|
||||||
|
{
|
||||||
|
data[0] = 0x45;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
data[0] = 0x03;
|
data[0] = 0x03;
|
||||||
|
}
|
||||||
SDL_memcpy( data + 1, buffer.data(), nDataLen );
|
SDL_memcpy( data + 1, buffer.data(), nDataLen );
|
||||||
++nDataLen;
|
++nDataLen;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user