diff --git a/core/src/main/java/device/CoroutinesDevice.kt b/core/src/main/java/device/CoroutinesDevice.kt index db48cd7..4ad80a0 100644 --- a/core/src/main/java/device/CoroutinesDevice.kt +++ b/core/src/main/java/device/CoroutinesDevice.kt @@ -5,16 +5,23 @@ package com.juul.able.device import android.bluetooth.BluetoothDevice -import android.bluetooth.BluetoothGatt.STATE_CONNECTED +import android.bluetooth.BluetoothGatt.GATT_SUCCESS +import android.bluetooth.BluetoothProfile.STATE_CONNECTED +import android.bluetooth.BluetoothProfile.STATE_DISCONNECTED import android.content.Context import android.os.RemoteException import com.juul.able.Able import com.juul.able.device.ConnectGattResult.Failure import com.juul.able.device.ConnectGattResult.Success +import com.juul.able.gatt.ConnectionLost import com.juul.able.gatt.CoroutinesGatt import com.juul.able.gatt.GattCallback -import com.juul.able.gatt.suspendUntilConnectionState +import com.juul.able.gatt.GattConnection +import com.juul.able.gatt.GattErrorStatus +import com.juul.able.gatt.asGattConnectionStateString import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.newSingleThreadContext private const val DISPATCHER_NAME = "Gatt" @@ -33,7 +40,7 @@ internal class CoroutinesDevice( return try { val gatt = CoroutinesGatt(bluetoothGatt, dispatcher, callback) - gatt.suspendUntilConnectionState(STATE_CONNECTED) + gatt.suspendUntilConnected() Success(gatt) } catch (cancellation: CancellationException) { Able.info { "connectGatt() canceled for $this" } @@ -48,5 +55,19 @@ internal class CoroutinesDevice( } } + private suspend fun GattConnection.suspendUntilConnected() { + Able.debug { "Suspending until device $device is connected" } + onConnectionStateChange + .onEach { event -> + Able.verbose { "← Device $device received $event while waiting for connection" } + if (event.status != GATT_SUCCESS) throw GattErrorStatus(event) + if (event.newState == STATE_DISCONNECTED) throw ConnectionLost() + } + .first { (_, newState) -> newState == STATE_CONNECTED } + .also { (_, newState) -> + Able.info { "Device $device reached ${newState.asGattConnectionStateString()}" } + } + } + override fun toString(): String = "CoroutinesDevice(device=$device)" } diff --git a/core/src/main/java/gatt/CoroutinesGatt.kt b/core/src/main/java/gatt/CoroutinesGatt.kt index e0bcfcf..7618468 100644 --- a/core/src/main/java/gatt/CoroutinesGatt.kt +++ b/core/src/main/java/gatt/CoroutinesGatt.kt @@ -5,6 +5,7 @@ package com.juul.able.gatt import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGatt.GATT_SUCCESS import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.BluetoothGattDescriptor import android.bluetooth.BluetoothGattService @@ -17,6 +18,8 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.ExecutorCoroutineDispatcher import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext @@ -42,7 +45,7 @@ class CoroutinesGatt internal constructor( try { Able.info { "Disconnecting $this" } bluetoothGatt.disconnect() - suspendUntilConnectionState(STATE_DISCONNECTED) + suspendUntilDisconnected() } finally { callback.close(bluetoothGatt) } @@ -128,5 +131,24 @@ class CoroutinesGatt internal constructor( ) } + private suspend fun suspendUntilDisconnected() { + Able.debug { "Suspending until device ${bluetoothGatt.device} is disconnected" } + onConnectionStateChange + .onEach { event -> + Able.verbose { + val device = bluetoothGatt.device + "← Device $device received $event while waiting for disconnection" + } + if (event.status != GATT_SUCCESS) throw GattErrorStatus(event) + } + .first { (_, newState) -> newState == STATE_DISCONNECTED } + .also { (_, newState) -> + Able.info { + val state = newState.asGattConnectionStateString() + "Device ${bluetoothGatt.device} reached $state" + } + } + } + override fun toString(): String = "CoroutinesGatt(device=${bluetoothGatt.device})" } diff --git a/core/src/main/java/gatt/GattConnection.kt b/core/src/main/java/gatt/GattConnection.kt index a63fb4e..e8381ed 100644 --- a/core/src/main/java/gatt/GattConnection.kt +++ b/core/src/main/java/gatt/GattConnection.kt @@ -8,13 +8,9 @@ package com.juul.able.gatt import android.bluetooth.BluetoothGatt.GATT_SUCCESS import android.bluetooth.BluetoothProfile -import android.bluetooth.BluetoothProfile.STATE_DISCONNECTED -import com.juul.able.Able import java.io.IOException import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.onEach /** * Represents the possible GATT connection statuses as defined in the Android source code. @@ -57,17 +53,3 @@ class ConnectionLost internal constructor( message: String? = null, cause: Throwable? = null ) : IOException(message, cause) - -internal suspend fun GattConnection.suspendUntilConnectionState(state: GattConnectionState) { - Able.debug { "Suspending until ${state.asGattConnectionStateString()}" } - onConnectionStateChange - .onEach { event -> - Able.verbose { "← Received $event while waiting for ${state.asGattConnectionStateString()}" } - if (event.status != GATT_SUCCESS) throw GattErrorStatus(event) - if (event.newState == STATE_DISCONNECTED && state != STATE_DISCONNECTED) throw ConnectionLost() - } - .first { (_, newState) -> newState == state } - .also { (_, newState) -> - Able.info { "Reached ${newState.asGattConnectionStateString()}" } - } -}