Skip to content

Commit 151e54d

Browse files
authored
Allow for configuration of transport and PHY on Android (#90)
1 parent 27c6469 commit 151e54d

3 files changed

Lines changed: 121 additions & 11 deletions

File tree

core/api/core.api

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,21 @@ public final class com/juul/kable/Peripheral$DefaultImpls {
173173
}
174174

175175
public final class com/juul/kable/PeripheralKt {
176-
public static final fun peripheral (Lkotlinx/coroutines/CoroutineScope;Landroid/bluetooth/BluetoothDevice;)Lcom/juul/kable/Peripheral;
176+
public static final fun peripheral (Lkotlinx/coroutines/CoroutineScope;Landroid/bluetooth/BluetoothDevice;Lcom/juul/kable/Transport;Lcom/juul/kable/Phy;)Lcom/juul/kable/Peripheral;
177177
public static final fun peripheral (Lkotlinx/coroutines/CoroutineScope;Landroid/bluetooth/BluetoothDevice;Lcom/juul/kable/WriteNotificationDescriptor;)Lcom/juul/kable/Peripheral;
178178
public static final fun peripheral (Lkotlinx/coroutines/CoroutineScope;Lcom/juul/kable/Advertisement;)Lcom/juul/kable/Peripheral;
179+
public static final fun peripheral (Lkotlinx/coroutines/CoroutineScope;Lcom/juul/kable/Advertisement;Lcom/juul/kable/Transport;Lcom/juul/kable/Phy;)Lcom/juul/kable/Peripheral;
179180
public static final fun peripheral (Lkotlinx/coroutines/CoroutineScope;Lcom/juul/kable/Advertisement;Lcom/juul/kable/WriteNotificationDescriptor;)Lcom/juul/kable/Peripheral;
181+
public static synthetic fun peripheral$default (Lkotlinx/coroutines/CoroutineScope;Landroid/bluetooth/BluetoothDevice;Lcom/juul/kable/Transport;Lcom/juul/kable/Phy;ILjava/lang/Object;)Lcom/juul/kable/Peripheral;
182+
public static synthetic fun peripheral$default (Lkotlinx/coroutines/CoroutineScope;Lcom/juul/kable/Advertisement;Lcom/juul/kable/Transport;Lcom/juul/kable/Phy;ILjava/lang/Object;)Lcom/juul/kable/Peripheral;
183+
}
184+
185+
public final class com/juul/kable/Phy : java/lang/Enum {
186+
public static final field Le1M Lcom/juul/kable/Phy;
187+
public static final field Le2M Lcom/juul/kable/Phy;
188+
public static final field LeCoded Lcom/juul/kable/Phy;
189+
public static fun valueOf (Ljava/lang/String;)Lcom/juul/kable/Phy;
190+
public static fun values ()[Lcom/juul/kable/Phy;
180191
}
181192

182193
public final class com/juul/kable/ScanFailedException : java/lang/IllegalStateException {
@@ -265,6 +276,14 @@ public final class com/juul/kable/State$Disconnecting : com/juul/kable/State {
265276
public static final field INSTANCE Lcom/juul/kable/State$Disconnecting;
266277
}
267278

279+
public final class com/juul/kable/Transport : java/lang/Enum {
280+
public static final field Auto Lcom/juul/kable/Transport;
281+
public static final field BrEdr Lcom/juul/kable/Transport;
282+
public static final field Le Lcom/juul/kable/Transport;
283+
public static fun valueOf (Ljava/lang/String;)Lcom/juul/kable/Transport;
284+
public static fun values ()[Lcom/juul/kable/Transport;
285+
}
286+
268287
public final class com/juul/kable/WriteNotificationDescriptor : java/lang/Enum {
269288
public static final field Always Lcom/juul/kable/WriteNotificationDescriptor;
270289
public static final field Auto Lcom/juul/kable/WriteNotificationDescriptor;

core/src/androidMain/kotlin/BluetoothDevice.kt

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ package com.juul.kable
33
import android.annotation.TargetApi
44
import android.bluetooth.BluetoothDevice
55
import android.bluetooth.BluetoothDevice.PHY_LE_1M_MASK
6+
import android.bluetooth.BluetoothDevice.PHY_LE_2M_MASK
7+
import android.bluetooth.BluetoothDevice.PHY_LE_CODED_MASK
68
import android.bluetooth.BluetoothDevice.TRANSPORT_AUTO
9+
import android.bluetooth.BluetoothDevice.TRANSPORT_BREDR
10+
import android.bluetooth.BluetoothDevice.TRANSPORT_LE
711
import android.content.Context
812
import android.os.Build
913
import android.os.Handler
@@ -13,25 +17,39 @@ import kotlinx.coroutines.android.asCoroutineDispatcher
1317
import kotlinx.coroutines.flow.MutableStateFlow
1418
import kotlinx.coroutines.newSingleThreadContext
1519

20+
/**
21+
* @param transport is only used on API level >= 23.
22+
* @param phy is only used on API level >= 26.
23+
*/
1624
internal fun BluetoothDevice.connect(
1725
context: Context,
26+
transport: Transport,
27+
phy: Phy,
1828
state: MutableStateFlow<State>,
1929
invokeOnClose: () -> Unit,
2030
): Connection? =
2131
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
22-
connectApi26(context, state, invokeOnClose)
32+
connectApi26(context, transport, phy, state, invokeOnClose)
2333
} else {
24-
connectApi21(context, state, invokeOnClose)
34+
connectApi21(context, transport, state, invokeOnClose)
2535
}
2636

37+
/**
38+
* @param transport is only used on API level >= 23.
39+
*/
2740
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
2841
private fun BluetoothDevice.connectApi21(
2942
context: Context,
43+
transport: Transport,
3044
state: MutableStateFlow<State>,
3145
invokeOnClose: () -> Unit,
3246
): Connection? {
3347
val callback = Callback(state)
34-
val bluetoothGatt = connectGatt(context, false, callback) ?: return null
48+
val bluetoothGatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
49+
connectGatt(context, false, callback, transport.intValue)
50+
} else {
51+
connectGatt(context, false, callback)
52+
} ?: return null
3553

3654
// Explicitly set Connecting state so when Peripheral is suspending until Connected, it doesn't incorrectly see
3755
// Disconnected before the connection request has kicked off the Connecting state (via Callback).
@@ -52,6 +70,8 @@ private fun BluetoothDevice.connectApi21(
5270
@TargetApi(Build.VERSION_CODES.O)
5371
private fun BluetoothDevice.connectApi26(
5472
context: Context,
73+
transport: Transport,
74+
phy: Phy,
5575
state: MutableStateFlow<State>,
5676
invokeOnClose: () -> Unit,
5777
): Connection? {
@@ -61,12 +81,9 @@ private fun BluetoothDevice.connectApi26(
6181
val dispatcher = handler.asCoroutineDispatcher()
6282
val callback = Callback(state)
6383

64-
// todo: Have `transport` and `phy` be configurable.
65-
val transport = TRANSPORT_AUTO
66-
val phy = PHY_LE_1M_MASK
67-
6884
val bluetoothGatt =
69-
connectGatt(context, false, callback, transport, phy, handler) ?: return null
85+
connectGatt(context, false, callback, transport.intValue, phy.intValue, handler)
86+
?: return null
7087

7188
// Explicitly set Connecting state so when Peripheral is suspending until Connected, it doesn't incorrectly see
7289
// Disconnected before the connection request has kicked off the Connecting state (via Callback).
@@ -87,5 +104,21 @@ private fun BluetoothDevice.connectApi26(
87104
}
88105
}
89106

107+
private val Transport.intValue: Int
108+
@TargetApi(Build.VERSION_CODES.M)
109+
get() = when (this) {
110+
Transport.Auto -> TRANSPORT_AUTO
111+
Transport.BrEdr -> TRANSPORT_BREDR
112+
Transport.Le -> TRANSPORT_LE
113+
}
114+
115+
private val Phy.intValue: Int
116+
@TargetApi(Build.VERSION_CODES.O)
117+
get() = when (this) {
118+
Phy.Le1M -> PHY_LE_1M_MASK
119+
Phy.Le2M -> PHY_LE_2M_MASK
120+
Phy.LeCoded -> PHY_LE_CODED_MASK
121+
}
122+
90123
private val BluetoothDevice.threadName: String
91124
get() = "Gatt@$this"

core/src/androidMain/kotlin/Peripheral.kt

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,67 @@ import kotlin.coroutines.CoroutineContext
4242

4343
private val clientCharacteristicConfigUuid = uuidFrom(CLIENT_CHARACTERISTIC_CONFIG_UUID)
4444

45+
/** Preferred transport for GATT connections to remote dual-mode devices. */
46+
public enum class Transport {
47+
48+
/** No preference of physical transport for GATT connections to remote dual-mode devices. */
49+
Auto,
50+
51+
/** Prefer BR/EDR transport for GATT connections to remote dual-mode devices. */
52+
BrEdr,
53+
54+
/** Prefer LE transport for GATT connections to remote dual-mode devices. */
55+
Le,
56+
}
57+
58+
/** Preferred Physical Layer (PHY) for connections to remote LE devices. */
59+
public enum class Phy {
60+
61+
/** Bluetooth LE 1M PHY. */
62+
Le1M,
63+
64+
/**
65+
* Bluetooth LE 2M PHY.
66+
*
67+
* Per [Exploring Bluetooth 5 – Going the Distance](https://www.bluetooth.com/blog/exploring-bluetooth-5-going-the-distance/#mcetoc_1d7vdh6b25):
68+
* "The new LE 2M PHY allows the physical layer to operate at 2 Ms/s and thus enables higher data rates than LE 1M
69+
* and Bluetooth 4."
70+
*/
71+
Le2M,
72+
73+
/**
74+
* Bluetooth LE Coded PHY.
75+
*
76+
* Per [Exploring Bluetooth 5 – Going the Distance](https://www.bluetooth.com/blog/exploring-bluetooth-5-going-the-distance/#mcetoc_1d7vdh6b26):
77+
* "The LE Coded PHY allows range to be quadrupled (approximately), compared to Bluetooth® 4 and this has been
78+
* accomplished without increasing the transmission power required."
79+
*/
80+
LeCoded,
81+
}
82+
4583
public actual fun CoroutineScope.peripheral(
4684
advertisement: Advertisement,
4785
): Peripheral = peripheral(advertisement.bluetoothDevice)
4886

87+
/**
88+
* @param transport preferred transport for GATT connections to remote dual-mode devices.
89+
* @param phy preferred PHY for connections to remote LE device.
90+
*/
91+
public fun CoroutineScope.peripheral(
92+
advertisement: Advertisement,
93+
transport: Transport = Transport.Le,
94+
phy: Phy = Phy.Le1M,
95+
): Peripheral = peripheral(advertisement.bluetoothDevice, transport, phy)
96+
97+
/**
98+
* @param transport preferred transport for GATT connections to remote dual-mode devices.
99+
* @param phy preferred PHY for connections to remote LE device.
100+
*/
49101
public fun CoroutineScope.peripheral(
50102
bluetoothDevice: BluetoothDevice,
51-
): Peripheral = AndroidPeripheral(coroutineContext, bluetoothDevice)
103+
transport: Transport = Transport.Le,
104+
phy: Phy = Phy.Le1M,
105+
): Peripheral = AndroidPeripheral(coroutineContext, bluetoothDevice, transport, phy)
52106

53107
@Deprecated(
54108
message = "'writeObserveDescriptor' parameter is no longer used and is handled automatically by 'observe' function. 'writeObserveDescriptor' argument will be removed in a future release.",
@@ -68,11 +122,13 @@ public fun CoroutineScope.peripheral(
68122
public fun CoroutineScope.peripheral(
69123
bluetoothDevice: BluetoothDevice,
70124
writeObserveDescriptor: WriteNotificationDescriptor,
71-
): Peripheral = AndroidPeripheral(coroutineContext, bluetoothDevice)
125+
): Peripheral = AndroidPeripheral(coroutineContext, bluetoothDevice, Transport.Le, Phy.Le1M)
72126

73127
public class AndroidPeripheral internal constructor(
74128
parentCoroutineContext: CoroutineContext,
75129
private val bluetoothDevice: BluetoothDevice,
130+
private val transport: Transport,
131+
private val phy: Phy,
76132
) : Peripheral {
77133

78134
private val job = SupervisorJob(parentCoroutineContext[Job]).apply {
@@ -108,6 +164,8 @@ public class AndroidPeripheral internal constructor(
108164
private fun establishConnection(): Connection =
109165
bluetoothDevice.connect(
110166
applicationContext,
167+
transport,
168+
phy,
111169
_state,
112170
invokeOnClose = { connectJob.value = null }
113171
) ?: throw ConnectionRejectedException()

0 commit comments

Comments
 (0)