I'm attempting to connect to a Bluetooth device on Android. I'm receiving status 133 in my onClientConnectionState
handler. I don't always get this error - sometimes it connects fine. I've not been able to put a finger on what triggers the problem. I've even had it immediately after restarting the device and my repro app.
I'm aware of several questions and suggested solutions to this problem, including (from here, here, and here):
But I am doing all that. What's more, my device is a Nexus 5 (running Lollipop) which according to some shouldn't even need the BT interactions to be on the UI thread.
I have put together the simplest possible repro. It's in C# but the Java equivalent should be obvious:
[Activity(Label = "BluetoothGatt133ErrorRepro", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
{
protected override void OnCreate(Android.OS.Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
var button = FindViewById<Button>(Resource.Id.button);
button.Click += this.OnClick;
}
private async void OnClick(object sender, EventArgs e)
{
Action<string> log = message => Console.WriteLine($"***** #{Environment.CurrentManagedThreadId} {message}");
log("Beginning");
var bluetoothManager = (BluetoothManager)Application.Context.GetSystemService(Context.BluetoothService);
var adapter = bluetoothManager.Adapter;
var scanner = adapter.BluetoothLeScanner;
var callback = new Callback();
var filters = new List<ScanFilter>();
var settings = new ScanSettings.Builder()
.SetScanMode(global::Android.Bluetooth.LE.ScanMode.LowLatency)
.Build();
log("Starting scan");
scanner.StartScan(filters, settings, callback);
var result = await callback.Result;
log($"Got device: {result.Device.Name}");
var remoteDevice = adapter.GetRemoteDevice(result.Device.Address);
var gattCallback = new GattCallback(log);
log("Connecting GATT");
var gatt = remoteDevice.ConnectGatt(Application.Context, true, gattCallback);
gatt.Connect();
await gattCallback.Result;
log("Disconnecting GATT");
gatt.Close();
gatt.Dispose();
}
private sealed class Callback : ScanCallback
{
private readonly TaskCompletionSource<ScanResult> result;
public Callback()
{
this.result = new TaskCompletionSource<ScanResult>();
}
public Task<ScanResult> Result => this.result.Task;
public override void OnBatchScanResults(IList<ScanResult> results)
{
foreach (var result in results)
{
this.HandleResult(result);
}
}
public override void OnScanResult(ScanCallbackType callbackType, ScanResult result)
{
this.HandleResult(result);
}
public override void OnScanFailed(ScanFailure errorCode)
{
this.result.TrySetException(new InvalidOperationException($"Failed with error code {errorCode}."));
}
private void HandleResult(ScanResult result)
{
if (result.Device.Name.Contains("elided"))
{
this.result.TrySetResult(result);
}
}
}
private sealed class GattCallback : BluetoothGattCallback
{
private readonly Action<string> log;
private readonly TaskCompletionSource<bool> result;
public GattCallback(Action<string> log)
{
this.log = log;
this.result = new TaskCompletionSource<bool>();
}
public Task<bool> Result => this.result.Task;
public override void OnConnectionStateChange(BluetoothGatt gatt, GattStatus status, ProfileState newState)
{
this.log($"Connection state changed to {newState} with status {status}.");
this.result.TrySetResult(true);
}
}
}
And here's the output from running this (I've left in the output from Android's BluetoothGatt source too):
***** #1 Beginning
***** #1 Starting scan
07-01 11:53:21.458 D/BluetoothLeScanner(10377): onClientRegistered() - status=0 clientIf=5
***** #1 Got device: elided
***** #1 Connecting GATT
07-01 11:53:22.833 D/BluetoothGatt(10377): connect() - device: 00:00:DE:AD:BE:EF, auto: true
07-01 11:53:22.833 D/BluetoothGatt(10377): registerApp()
07-01 11:53:22.833 D/BluetoothGatt(10377): registerApp() - UUID=fa5bce8a-416d-47fe-9a8a-e44156f7e865
07-01 11:53:22.834 D/BluetoothGatt(10377): onClientRegistered() - status=0 clientIf=6
07-01 11:53:24.622 D/BluetoothGatt(10377): onClientConnectionState() - status=133 clientIf=6 device=00:00:DE:AD:BE:EF
***** #4 Connection state changed to Disconnected with status 133.
***** #1 Disconnecting GATT
07-01 11:53:24.707 D/BluetoothGatt(10377): close()
07-01 11:53:24.707 D/BluetoothGatt(10377): unregisterApp() - mClientIf=6
As you can see, all my interaction with the Bluetooth stack is occurring on the main thread (#1). But despite that, I'm receiving status 133 in my onClientConnectionState
handler.
My manifest has these permissions:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
I'm compiling with latest Marshmallow tooling, and am targeting Marshmallow with a minimum target of 4.0.3 (API level 15).
What might be causing this?
(Note: You may already be doing this, but I'm not well versed in C#)
In my experience, it hasn't really been that you just interact with the BLE device on the main thread, it's just that you don't flood the device with too many requests at once.
I used to get this issue when working with BLE on Android (and read similar comments about using the main thread), and it was because I was posting too many requests (reads/writes, notification/indication registrations, etc) to the remote Gatt device before receiving callbacks for previous operations in the BluetoothGattCallback object. I set up my own managed gatt operation queue (a thread that blocks until a callback for that operation is received in the GattCallback, or the initial read/write operation returns false, then handles the next queued operation or retries with a backoff multiplier), and since I've not run into this issue. From what I can tell, Android doesn't do a good job of "queueing" operations, so the "isBusy" boolean bites you unknowingly (Look at the BlueoothGattCharacteristic write method if this doesn't jump out at you). I've also noticed that you don't want to do much work in your callback object, but delegate the callback to another thread or broadcast out the result (so you don't block the Binder thread). Normally I just copy the byte payload, and pass it off to another HandlerThread to parse.
Also, yeah, disconnecting and closing is pretty important for sure. I generally use a Service to handle BLE interactions and call both of these before the Service's onDestroy finishes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With