-
Notifications
You must be signed in to change notification settings - Fork 2
PoC - Try handle random failures of EnableNotificationAsync [android] #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: default
Are you sure you want to change the base?
Changes from all commits
549b392
6849267
06916f4
686b136
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |||||||||||||||
| using Android.OS; | ||||||||||||||||
| using Android.Runtime; | ||||||||||||||||
| using Java.Util; | ||||||||||||||||
| using BrickController2.Helpers; | ||||||||||||||||
| using BrickController2.PlatformServices.BluetoothLE; | ||||||||||||||||
|
|
||||||||||||||||
| namespace BrickController2.Droid.PlatformServices.BluetoothLE | ||||||||||||||||
|
|
@@ -18,7 +19,7 @@ public class BluetoothLEDevice : BluetoothGattCallback, IBluetoothLEDevice | |||||||||||||||
|
|
||||||||||||||||
| private readonly Context _context; | ||||||||||||||||
| private readonly BluetoothAdapter _bluetoothAdapter; | ||||||||||||||||
| private readonly object _lock = new object(); | ||||||||||||||||
| private readonly AsyncLock _lock = new(); | ||||||||||||||||
|
|
||||||||||||||||
| private BluetoothDevice? _bluetoothDevice = null; | ||||||||||||||||
| private BluetoothGatt? _bluetoothGatt = null; | ||||||||||||||||
|
|
@@ -49,14 +50,14 @@ public BluetoothLEDevice(Context context, BluetoothAdapter bluetoothAdapter, str | |||||||||||||||
| { | ||||||||||||||||
| using (token.Register(() => | ||||||||||||||||
| { | ||||||||||||||||
| lock (_lock) | ||||||||||||||||
| using (_lock.Lock()) | ||||||||||||||||
| { | ||||||||||||||||
| Disconnect(); | ||||||||||||||||
| _connectCompletionSource?.TrySetResult(null); | ||||||||||||||||
| } | ||||||||||||||||
| })) | ||||||||||||||||
| { | ||||||||||||||||
| lock (_lock) | ||||||||||||||||
| using (_lock.Lock(token)) | ||||||||||||||||
| { | ||||||||||||||||
| if (State != BluetoothLEDeviceState.Disconnected) | ||||||||||||||||
| { | ||||||||||||||||
|
|
@@ -77,11 +78,11 @@ public BluetoothLEDevice(Context context, BluetoothAdapter bluetoothAdapter, str | |||||||||||||||
|
|
||||||||||||||||
| if (Build.VERSION.SdkInt >= BuildVersionCodes.M) | ||||||||||||||||
| { | ||||||||||||||||
| _bluetoothGatt = _bluetoothDevice.ConnectGatt(_context, autoConnect, this, BluetoothTransports.Le); | ||||||||||||||||
| _bluetoothGatt = _bluetoothDevice.ConnectGatt(_context, autoConnect: false, this, BluetoothTransports.Le); | ||||||||||||||||
| } | ||||||||||||||||
| else | ||||||||||||||||
| { | ||||||||||||||||
| _bluetoothGatt = _bluetoothDevice.ConnectGatt(_context, autoConnect, this); | ||||||||||||||||
| _bluetoothGatt = _bluetoothDevice.ConnectGatt(_context, autoConnect: false, this); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| if (_bluetoothGatt is null) | ||||||||||||||||
|
|
@@ -97,7 +98,7 @@ public BluetoothLEDevice(Context context, BluetoothAdapter bluetoothAdapter, str | |||||||||||||||
|
|
||||||||||||||||
| var result = await _connectCompletionSource.Task; | ||||||||||||||||
|
|
||||||||||||||||
| lock (_lock) | ||||||||||||||||
| using (_lock.Lock(token)) | ||||||||||||||||
| { | ||||||||||||||||
| _connectCompletionSource = null; | ||||||||||||||||
| return result; | ||||||||||||||||
|
|
@@ -125,27 +126,25 @@ internal void Disconnect() | |||||||||||||||
| State = BluetoothLEDeviceState.Disconnected; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| public Task DisconnectAsync() | ||||||||||||||||
| public async Task DisconnectAsync() | ||||||||||||||||
| { | ||||||||||||||||
| lock (_lock) | ||||||||||||||||
| using (await _lock.LockAsync()) | ||||||||||||||||
| { | ||||||||||||||||
| Disconnect(); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| return Task.CompletedTask; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| public async Task<bool> EnableNotificationAsync(IGattCharacteristic characteristic, CancellationToken token) | ||||||||||||||||
| { | ||||||||||||||||
| using (token.Register(() => | ||||||||||||||||
| { | ||||||||||||||||
| lock (_lock) | ||||||||||||||||
| using (_lock.Lock()) | ||||||||||||||||
| { | ||||||||||||||||
| _descriptorWriteCompletionSource?.TrySetResult(false); | ||||||||||||||||
| } | ||||||||||||||||
| })) | ||||||||||||||||
|
|
||||||||||||||||
| lock (_lock) | ||||||||||||||||
| using (await _lock.LockAsync(token)) | ||||||||||||||||
| { | ||||||||||||||||
| if (_bluetoothGatt == null || State != BluetoothLEDeviceState.Connected) | ||||||||||||||||
| { | ||||||||||||||||
|
|
@@ -158,33 +157,54 @@ public async Task<bool> EnableNotificationAsync(IGattCharacteristic characterist | |||||||||||||||
| return false; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // wait slightly for local internal state to settle | ||||||||||||||||
| await Task.Delay(400, token); | ||||||||||||||||
|
|
||||||||||||||||
| var descriptor = nativeCharacteristic.GetDescriptor(ClientCharacteristicConfigurationUUID); | ||||||||||||||||
| if (descriptor == null) | ||||||||||||||||
| { | ||||||||||||||||
| return false; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| #pragma warning disable CA1422 // Validate platform compatibility | ||||||||||||||||
| if (!(descriptor?.SetValue(BluetoothGattDescriptor.EnableNotificationValue!.ToArray()) ?? false)) | ||||||||||||||||
| _descriptorWriteCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); | ||||||||||||||||
|
|
||||||||||||||||
| var valueToSet = BluetoothGattDescriptor.EnableNotificationValue!.ToArray(); | ||||||||||||||||
| bool writeInitiated = false; | ||||||||||||||||
|
|
||||||||||||||||
| if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu) // API 33+ | ||||||||||||||||
| { | ||||||||||||||||
| return false; | ||||||||||||||||
| // New API: Pass value and write type explicitly | ||||||||||||||||
| #pragma warning disable CA1416 // Validate platform compatibility | ||||||||||||||||
| var resultCode = _bluetoothGatt.WriteDescriptor(descriptor, valueToSet); | ||||||||||||||||
| #pragma warning restore CA1416 // Validate platform compatibility | ||||||||||||||||
| writeInitiated = resultCode == 0; | ||||||||||||||||
| } | ||||||||||||||||
| else | ||||||||||||||||
| { | ||||||||||||||||
| #pragma warning disable CA1422 // Validate platform compatibility | ||||||||||||||||
| // Old API: Set local value, then write | ||||||||||||||||
| if (!descriptor.SetValue(valueToSet)) | ||||||||||||||||
| { | ||||||||||||||||
| _descriptorWriteCompletionSource = null; | ||||||||||||||||
| return false; | ||||||||||||||||
| } | ||||||||||||||||
| writeInitiated = _bluetoothGatt.WriteDescriptor(descriptor); | ||||||||||||||||
| #pragma warning restore CA1422 // Validate platform compatibility | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| _descriptorWriteCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); | ||||||||||||||||
|
|
||||||||||||||||
| #pragma warning disable CA1422 // Validate platform compatibility | ||||||||||||||||
| if (!(_bluetoothGatt?.WriteDescriptor(descriptor) ?? false)) | ||||||||||||||||
| if (!writeInitiated) | ||||||||||||||||
| { | ||||||||||||||||
| _descriptorWriteCompletionSource = null; | ||||||||||||||||
| return false; | ||||||||||||||||
| } | ||||||||||||||||
| #pragma warning restore CA1422 // Validate platform compatibility | ||||||||||||||||
| return false; // BLE stack was busy or request failed immediately | ||||||||||||||||
|
||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| var result = await _descriptorWriteCompletionSource.Task.ConfigureAwait(false); | ||||||||||||||||
| // restrict wait time to avoid hanging till target device disconnects | ||||||||||||||||
|
||||||||||||||||
| // restrict wait time to avoid hanging till target device disconnects | |
| // Use a bounded wait so we do not hang indefinitely if the BLE stack or device stops responding. | |
| // 10 seconds is chosen as a conservative upper bound: in practice, GATT descriptor writes complete | |
| // within milliseconds to a few seconds, even on slow or congested links. If the operation takes | |
| // longer than 10 seconds, WaitAsync will throw a TimeoutException (or an OperationCanceledException | |
| // if the provided cancellation token is triggered), and the error is propagated to the caller instead | |
| // of blocking until the device eventually disconnects. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 400ms delay lacks justification for this specific value. Consider documenting why 400ms was chosen (e.g., based on Android BLE stack timing requirements, testing results, or hardware constraints) to help future maintainers understand whether this value should be adjusted.