mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-06-01 06:44:27 +08:00
Handle the Amazon Fire TV's weird Bluetooth behavior
This commit is contained in:
committed by
Sam Lantinga
parent
fd8682ccc9
commit
418960bb4e
@@ -37,6 +37,8 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
private boolean mIsConnected = false;
|
private boolean mIsConnected = false;
|
||||||
private boolean mIsChromebook = false;
|
private boolean mIsChromebook = false;
|
||||||
private boolean mIsReconnecting = false;
|
private boolean mIsReconnecting = false;
|
||||||
|
private boolean mHasEnabledNotifications = false;
|
||||||
|
private boolean mHasSeenInputUpdate = false;
|
||||||
private boolean mFrozen = false;
|
private boolean mFrozen = false;
|
||||||
private LinkedList<GattOperation> mOperations;
|
private LinkedList<GattOperation> mOperations;
|
||||||
GattOperation mCurrentOperation = null;
|
GattOperation mCurrentOperation = null;
|
||||||
@@ -73,6 +75,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
byte[] mValue;
|
byte[] mValue;
|
||||||
BluetoothGatt mGatt;
|
BluetoothGatt mGatt;
|
||||||
boolean mResult = true;
|
boolean mResult = true;
|
||||||
|
int mDelayMs = 0;
|
||||||
|
|
||||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
|
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
|
||||||
mGatt = gatt;
|
mGatt = gatt;
|
||||||
@@ -80,6 +83,13 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
mUuid = uuid;
|
mUuid = uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, int delayMs) {
|
||||||
|
mGatt = gatt;
|
||||||
|
mOp = operation;
|
||||||
|
mUuid = uuid;
|
||||||
|
mDelayMs = delayMs;
|
||||||
|
}
|
||||||
|
|
||||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
|
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
|
||||||
mGatt = gatt;
|
mGatt = gatt;
|
||||||
mOp = operation;
|
mOp = operation;
|
||||||
@@ -87,6 +97,14 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
mValue = value;
|
mValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value, int delayMs) {
|
||||||
|
mGatt = gatt;
|
||||||
|
mOp = operation;
|
||||||
|
mUuid = uuid;
|
||||||
|
mValue = value;
|
||||||
|
mDelayMs = delayMs;
|
||||||
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
// This is executed in main thread
|
// This is executed in main thread
|
||||||
BluetoothGattCharacteristic chr;
|
BluetoothGattCharacteristic chr;
|
||||||
@@ -148,6 +166,8 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
return mResult;
|
return mResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDelayMs() { return mDelayMs; }
|
||||||
|
|
||||||
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
|
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
|
||||||
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
||||||
if (valveService == null)
|
if (valveService == null)
|
||||||
@@ -166,6 +186,10 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
|
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
|
||||||
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
|
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid, int delayMs) {
|
||||||
|
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid, delayMs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
|
HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
|
||||||
@@ -178,6 +202,8 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
mHandler = new Handler(Looper.getMainLooper());
|
mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
mGatt = connectGatt();
|
mGatt = connectGatt();
|
||||||
|
mHasEnabledNotifications = false;
|
||||||
|
mHasSeenInputUpdate = false;
|
||||||
// final HIDDeviceBLESteamController finalThis = this;
|
// final HIDDeviceBLESteamController finalThis = this;
|
||||||
// mHandler.postDelayed(new Runnable() {
|
// mHandler.postDelayed(new Runnable() {
|
||||||
// @Override
|
// @Override
|
||||||
@@ -414,8 +440,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
mCurrentOperation = mOperations.removeFirst();
|
mCurrentOperation = mOperations.removeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run in main thread
|
Runnable gattOperationRunnable = new Runnable() {
|
||||||
mHandler.post(new Runnable() {
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
synchronized (mOperations) {
|
synchronized (mOperations) {
|
||||||
@@ -428,7 +453,17 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
// now wait for the GATT callback and when it comes, finish this operation
|
// now wait for the GATT callback and when it comes, finish this operation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (mCurrentOperation.getDelayMs() == 0) {
|
||||||
|
// Run in main thread
|
||||||
|
mHandler.post(gattOperationRunnable);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If we have a delay on this operation, wait before we post it.
|
||||||
|
mHandler.postDelayed(gattOperationRunnable, mCurrentOperation.getDelayMs());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void queueGattOperation(GattOperation op) {
|
private void queueGattOperation(GattOperation op) {
|
||||||
@@ -439,8 +474,39 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void enableNotification(UUID chrUuid) {
|
private void enableNotification(UUID chrUuid) {
|
||||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
|
// Add a 500ms delay to notification write for Amazon Fire TV devices, as otherwise if we do this too quickly after connecting
|
||||||
|
// it will return success and then silently drop the operation on the floor.
|
||||||
|
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid, 500);
|
||||||
queueGattOperation(op);
|
queueGattOperation(op);
|
||||||
|
|
||||||
|
// Amazon Fire devices can also silently timeout on writeDescriptor, so
|
||||||
|
// set up a little delayed check that will attempt to write a second time.
|
||||||
|
//
|
||||||
|
// While this only seems to be needed on Amazon Fire TV devices at present, it
|
||||||
|
// doesn't hurt to have a retry on other devices as well.
|
||||||
|
//
|
||||||
|
final HIDDeviceBLESteamController finalThis = this;
|
||||||
|
final UUID finalUuid = chrUuid;
|
||||||
|
mHandler.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!finalThis.mHasEnabledNotifications) {
|
||||||
|
|
||||||
|
if (finalThis.mHasSeenInputUpdate) {
|
||||||
|
// Amazon Five devices may have enabled notifications on the input characteristic and not given us a callback. If we've seen
|
||||||
|
// input reports, though, somewhat by definition notifications are enabled.
|
||||||
|
Log.w(TAG, "WriteDescriptor has never returned, but we've seen input reports. Moving on with controller initialization.");
|
||||||
|
finalThis.mHasEnabledNotifications = true;
|
||||||
|
finalThis.enableValveMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give one more try.
|
||||||
|
GattOperation retry = HIDDeviceBLESteamController.GattOperation.enableNotification(finalThis.mGatt, finalUuid, 500);
|
||||||
|
finalThis.queueGattOperation(retry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeCharacteristic(UUID uuid, byte[] value) {
|
void writeCharacteristic(UUID uuid, byte[] value) {
|
||||||
@@ -538,6 +604,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
//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(getInputCharacteristic()) && !mFrozen) {
|
if (characteristic.getUuid().equals(getInputCharacteristic()) && !mFrozen) {
|
||||||
|
mHasSeenInputUpdate = true;
|
||||||
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -547,14 +614,13 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
//Log.v(TAG, "onDescriptorRead status=" + status);
|
//Log.v(TAG, "onDescriptorRead status=" + status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void enableValveMode()
|
||||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
{
|
||||||
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
||||||
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
if (valveService == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (chr.getUuid().equals(getInputCharacteristic())) {
|
BluetoothGattCharacteristic reportChr = valveService.getCharacteristic(reportCharacteristic);
|
||||||
boolean hasWrittenInputDescriptor = true;
|
|
||||||
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
|
||||||
if (reportChr != null) {
|
if (reportChr != null) {
|
||||||
if (getProductId() == TRITON_BLE_PID) {
|
if (getProductId() == TRITON_BLE_PID) {
|
||||||
// For Triton we just mark things registered.
|
// For Triton we just mark things registered.
|
||||||
@@ -565,11 +631,21 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
|||||||
// For the original controller, we need to manually enter Valve mode.
|
// 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);
|
mGatt.writeCharacteristic(reportChr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||||
|
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
||||||
|
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
||||||
|
|
||||||
|
if (chr.getUuid().equals(getInputCharacteristic())) {
|
||||||
|
mHasEnabledNotifications = true;
|
||||||
|
enableValveMode();
|
||||||
|
}
|
||||||
|
|
||||||
finishCurrentGattOperation();
|
finishCurrentGattOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user