@@ -15,6 +15,7 @@ import android.bluetooth.BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
1515import android.bluetooth.BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
1616import android.bluetooth.BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
1717import android.bluetooth.BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
18+ import com.juul.kable.AndroidPeripheral.Bond
1819import com.juul.kable.AndroidPeripheral.Priority
1920import com.juul.kable.AndroidPeripheral.Type
2021import com.juul.kable.State.Disconnected
@@ -36,10 +37,14 @@ import com.juul.kable.logs.detail
3637import kotlinx.coroutines.CoroutineScope
3738import kotlinx.coroutines.flow.Flow
3839import kotlinx.coroutines.flow.MutableStateFlow
40+ import kotlinx.coroutines.flow.SharingStarted
41+ import kotlinx.coroutines.flow.StateFlow
3942import kotlinx.coroutines.flow.asStateFlow
4043import kotlinx.coroutines.flow.filter
44+ import kotlinx.coroutines.flow.first
4145import kotlinx.coroutines.flow.launchIn
4246import kotlinx.coroutines.flow.onEach
47+ import kotlinx.coroutines.flow.stateIn
4348import kotlin.coroutines.cancellation.CancellationException
4449import kotlin.time.Duration
4550
@@ -67,6 +72,13 @@ internal class BluetoothDeviceAndroidPeripheral(
6772 }
6873 disconnect()
6974 }
75+
76+ onBondState { state ->
77+ logger.debug {
78+ message = " Bond state"
79+ detail(" state" , state.toString())
80+ }
81+ }
7082 }
7183
7284 private val connectAction = sharedRepeatableAction(::establishConnection)
@@ -100,6 +112,9 @@ internal class BluetoothDeviceAndroidPeripheral(
100112 override val name: String?
101113 get() = bluetoothDevice.name
102114
115+ override val bondState: StateFlow <Bond > = bondStateFor(bluetoothDevice)
116+ .stateIn(this , SharingStarted .Eagerly , Bond (bluetoothDevice.bondState))
117+
103118 private suspend fun establishConnection (scope : CoroutineScope ): CoroutineScope {
104119 checkBluetoothIsSupported()
105120 checkBluetoothIsOn()
@@ -123,6 +138,10 @@ internal class BluetoothDeviceAndroidPeripheral(
123138 disconnectTimeout,
124139 )
125140
141+ if (bondState.value == Bond .Bonding ) {
142+ logger.debug { message = " Awaiting bond state" }
143+ awaitNotBonding()
144+ }
126145 suspendUntil<State .Connecting .Services >()
127146 discoverServices()
128147 configureCharacteristicObservations()
@@ -197,8 +216,22 @@ internal class BluetoothDeviceAndroidPeripheral(
197216 }
198217
199218 val platformCharacteristic = servicesOrThrow().obtain(characteristic, writeType.properties)
200- connectionOrThrow().execute<OnCharacteristicWrite > {
201- writeCharacteristicOrThrow(platformCharacteristic, data, writeType.intValue)
219+ try {
220+ connectionOrThrow().execute<OnCharacteristicWrite > {
221+ writeCharacteristicOrThrow(platformCharacteristic, data, writeType.intValue)
222+ }
223+ } catch (e: BondRequiredException ) {
224+ logInsufficientAuthentication(e)
225+ awaitNotBonding()
226+ logger.debug {
227+ message = " Retrying write"
228+ detail(characteristic)
229+ detail(writeType)
230+ detail(data, Operation .Write )
231+ }
232+ connectionOrThrow().execute<OnCharacteristicWrite > {
233+ writeCharacteristicOrThrow(platformCharacteristic, data, writeType.intValue)
234+ }
202235 }
203236 }
204237
@@ -211,8 +244,20 @@ internal class BluetoothDeviceAndroidPeripheral(
211244 }
212245
213246 val platformCharacteristic = servicesOrThrow().obtain(characteristic, Read )
214- return connectionOrThrow().execute<OnCharacteristicRead > {
215- readCharacteristicOrThrow(platformCharacteristic)
247+ return try {
248+ connectionOrThrow().execute<OnCharacteristicRead > {
249+ readCharacteristicOrThrow(platformCharacteristic)
250+ }
251+ } catch (e: BondRequiredException ) {
252+ logInsufficientAuthentication(e)
253+ awaitNotBonding()
254+ logger.debug {
255+ message = " Retrying read"
256+ detail(characteristic)
257+ }
258+ connectionOrThrow().execute<OnCharacteristicRead > {
259+ readCharacteristicOrThrow(platformCharacteristic)
260+ }
216261 }.value!!
217262 }
218263
@@ -233,8 +278,21 @@ internal class BluetoothDeviceAndroidPeripheral(
233278 detail(data, Operation .Write )
234279 }
235280
236- connectionOrThrow().execute<OnDescriptorWrite > {
237- writeDescriptorOrThrow(platformDescriptor, data)
281+ try {
282+ connectionOrThrow().execute<OnDescriptorWrite > {
283+ writeDescriptorOrThrow(platformDescriptor, data)
284+ }
285+ } catch (e: BondRequiredException ) {
286+ logInsufficientAuthentication(e)
287+ awaitNotBonding()
288+ logger.debug {
289+ message = " Retrying write"
290+ detail(platformDescriptor)
291+ detail(data, Operation .Write )
292+ }
293+ connectionOrThrow().execute<OnDescriptorWrite > {
294+ writeDescriptorOrThrow(platformDescriptor, data)
295+ }
238296 }
239297 }
240298
@@ -247,8 +305,20 @@ internal class BluetoothDeviceAndroidPeripheral(
247305 }
248306
249307 val platformDescriptor = servicesOrThrow().obtain(descriptor)
250- return connectionOrThrow().execute<OnDescriptorRead > {
251- readDescriptorOrThrow(platformDescriptor)
308+ return try {
309+ connectionOrThrow().execute<OnDescriptorRead > {
310+ readDescriptorOrThrow(platformDescriptor)
311+ }
312+ } catch (e: BondRequiredException ) {
313+ logInsufficientAuthentication(e)
314+ awaitNotBonding()
315+ logger.debug {
316+ message = " Retrying read"
317+ detail(descriptor)
318+ }
319+ connectionOrThrow().execute<OnDescriptorRead > {
320+ readDescriptorOrThrow(platformDescriptor)
321+ }
252322 }.value!!
253323 }
254324
@@ -339,13 +409,28 @@ internal class BluetoothDeviceAndroidPeripheral(
339409 }
340410 }
341411
412+ private suspend fun awaitNotBonding (): Bond = bondState.first { it != Bond .Bonding }
413+
414+ private fun logInsufficientAuthentication (exception : BondRequiredException ) {
415+ logger.warn {
416+ message = " Insufficient authentication"
417+ detail(exception.status)
418+ }
419+ }
420+
342421 private fun onBluetoothDisabled (action : suspend (bluetoothState: Int ) -> Unit ) {
343422 bluetoothState
344423 .filter { state -> state == STATE_TURNING_OFF || state == STATE_OFF }
345424 .onEach(action)
346425 .launchIn(this )
347426 }
348427
428+ private fun onBondState (action : (bondState: Bond ) -> Unit ) {
429+ bondState
430+ .onEach(action)
431+ .launchIn(this )
432+ }
433+
349434 override fun toString (): String = " Peripheral(bluetoothDevice=$bluetoothDevice )"
350435}
351436
0 commit comments