Skip to content

Commit 2fd195f

Browse files
authored
Merge pull request #209 from NordicSemiconductor/improvement/disconnection
Improvement related to disconnection
2 parents 7d91393 + ab281d7 commit 2fd195f

File tree

3 files changed

+55
-23
lines changed

3 files changed

+55
-23
lines changed

client-android/src/main/java/no/nordicsemi/kotlin/ble/client/android/internal/NativeExecutor.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ internal class NativeExecutor(
211211

212212
override fun disconnect(): Boolean {
213213
gatt?.let { gatt ->
214+
gattCallback.disconnectRequest = true
214215
gatt.disconnect()
215216
return true
216217
}
@@ -220,6 +221,7 @@ internal class NativeExecutor(
220221
override fun close() {
221222
gatt?.let { gatt ->
222223
this.gatt = null
224+
gattCallback.disconnectRequest = false
223225
try {
224226
gatt.disconnect()
225227
} catch (_: Exception) {

client-android/src/main/java/no/nordicsemi/kotlin/ble/client/android/internal/NativeGattCallback.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,27 @@ internal class NativeGattCallback: BluetoothGattCallback() {
6060
private val _events: MutableSharedFlow<GattEvent> = MutableSharedFlow(extraBufferCapacity = 64)
6161
val events: SharedFlow<GattEvent> = _events.asSharedFlow()
6262

63+
/**
64+
* Older Android versions don't return status=8 (GATT_CONN_TIMEOUT) when the connection
65+
* drops due to a link loss. By checking whether it was the user who requested disconnection
66+
* we can improve the status.
67+
*/
68+
var disconnectRequest = false
69+
6370
// Handling connection state updates
6471

6572
// TODO Remove all debug logs
6673

6774
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
6875
logger.debug("onConnectionStateChange: status=$status, newState=$newState")
69-
_events.tryEmit(ConnectionStateChanged(newState.toConnectionState(status)))
76+
// Pixel 4 with Android 12 does returns status 0 when link is lost to a device.
77+
// Newer versions (Pixel 7 with Android 16) report status 8 (timeout) in the same case.
78+
val betterStatus = if (
79+
newState == BluetoothGatt.STATE_DISCONNECTED &&
80+
status == BluetoothGatt.GATT_SUCCESS &&
81+
!disconnectRequest
82+
) 0x08 /* GATT_CONN_TIMEOUT */ else status
83+
_events.tryEmit(ConnectionStateChanged(newState.toConnectionState(betterStatus)))
7084
}
7185

7286
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {

client-core/src/main/java/no/nordicsemi/kotlin/ble/client/Peripheral.kt

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ abstract class Peripheral<ID: Any, EX: Peripheral.Executor<ID>>(
9595
*
9696
* This is not-null when the device is connected or was connected using auto connect,
9797
* that is when any GATT event for the device, including connection state change, is expected.
98+
*
99+
* It's set to `null` when the peripheral is closed.
98100
*/
99101
private var gattEventCollector: Job? = null
100102

@@ -200,7 +202,9 @@ abstract class Peripheral<ID: Any, EX: Peripheral.Executor<ID>>(
200202
* Returns `true` if the peripheral is disconnected of getting disconnected.
201203
*/
202204
val isDisconnected: Boolean
203-
get() = state.value is ConnectionState.Disconnected || state.value is ConnectionState.Disconnecting
205+
get() = state.value is ConnectionState.Disconnected ||
206+
state.value is ConnectionState.Disconnecting ||
207+
state.value is ConnectionState.Closed
204208

205209
/**
206210
* Waits until the given condition is met.
@@ -266,6 +270,7 @@ abstract class Peripheral<ID: Any, EX: Peripheral.Executor<ID>>(
266270
impl.close()
267271
_state.update { ConnectionState.Closed }
268272
}
273+
gattEventCollector = null
269274
}
270275

271276
/**
@@ -476,36 +481,47 @@ abstract class Peripheral<ID: Any, EX: Peripheral.Executor<ID>>(
476481
* @throws SecurityException If BLUETOOTH_CONNECT permission is denied.
477482
*/
478483
suspend fun disconnect() {
479-
// Check if the peripheral isn't already disconnected or has a pending disconnection.
480-
state.value.let { currentState ->
481-
if (currentState is ConnectionState.Disconnected) {
482-
// Make sure the AutoConnect also gets cancelled if the peripheral is currently disconnected.
483-
close()
484+
// Depending on the state...
485+
when (state.value) {
486+
is ConnectionState.Closed -> {
487+
// Do nothing when already closed.
484488
return
485489
}
486-
if (currentState is ConnectionState.Disconnecting) {
487-
waitUntil { it is ConnectionState.Disconnected }
490+
is ConnectionState.Disconnected -> {
491+
// Make sure auto-connection is closed.
492+
close()
488493
return
489494
}
495+
is ConnectionState.Disconnecting -> {
496+
// Skip..
497+
}
498+
is ConnectionState.Connecting -> {
499+
// Cancel the connection attempt.
500+
logger.trace("Cancelling connection to {}", this)
501+
_state.update { ConnectionState.Disconnecting }
502+
impl.disconnect()
503+
}
504+
is ConnectionState.Connected -> {
505+
// Disconnect from the peripheral.
506+
logger.trace("Disconnecting from {}", this)
507+
_state.update { ConnectionState.Disconnecting }
508+
impl.disconnect()
509+
}
490510
}
491511

492-
// Disconnect from the peripheral.
493-
logger.trace("Disconnecting from {}", this)
494-
_state.update { ConnectionState.Disconnecting }
495-
if (impl.disconnect()) {
496-
try {
497-
waitUntil(500.milliseconds) { it is ConnectionState.Disconnected }
498-
} catch (e: TimeoutCancellationException) {
499-
logger.warn("Disconnection takes longer than expected, closing")
512+
// If the peripheral is disconnecting, wait until it is disconnected and close.
513+
try {
514+
if (!impl.isClosed) {
515+
waitUntil(500.milliseconds) { it.isDisconnected }
500516
}
501-
} else {
502-
// Update the state if disconnect() returned false.
503-
_state.update {
504-
ConnectionState.Disconnected(ConnectionState.Disconnected.Reason.Success)
517+
} catch (e: TimeoutCancellationException) {
518+
if (!isDisconnected) {
519+
logger.warn("Disconnection takes longer than expected, closing")
505520
}
521+
} finally {
522+
close()
523+
logger.info("Disconnected from {}", this)
506524
}
507-
close()
508-
logger.info("Disconnected from {}", this)
509525
}
510526

511527
// Other

0 commit comments

Comments
 (0)