Close GATT when the job is finished
Add a test for ensuring it is closed even
if an exception was thrown inside the block.
Test: ./gradlew bluetooth:bluetooth-testing:check
Change-Id: I742bab84bb9e263de52d03a47a6c292772338e93
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
index 32729d4..09b12f3 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
@@ -35,7 +35,8 @@
import java.util.UUID
import java.util.concurrent.atomic.AtomicInteger
import junit.framework.TestCase.fail
-import kotlinx.coroutines.CompletableDeferred
+import kotlin.coroutines.cancellation.CancellationException
+import kotlin.test.assertFailsWith
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.take
@@ -43,6 +44,7 @@
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Assert
+import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -109,32 +111,49 @@
@Test
fun connectGatt() = runTest {
val device = createDevice("00:11:22:33:44:55")
- val closed = CompletableDeferred<Unit>()
acceptConnect()
bluetoothLe.connectGatt(device) {
+ assertTrue(clientAdapter.shadowBluetoothGatt.isConnected)
+
Assert.assertEquals(sampleServices.size, services.size)
sampleServices.forEachIndexed { index, service ->
Assert.assertEquals(service.uuid, services[index].uuid)
}
- closed.complete(Unit)
}
- assertTrue(closed.isCompleted)
+ assertTrue(clientAdapter.shadowBluetoothGatt.isClosed)
+ assertFalse(clientAdapter.shadowBluetoothGatt.isConnected)
+ }
+
+ @Test
+ fun connectGatt_throwException_closeGatt() = runTest {
+ val device = createDevice("00:11:22:33:44:55")
+
+ acceptConnect()
+
+ assertFailsWith<RuntimeException> {
+ bluetoothLe.connectGatt(device) {
+ assertTrue(clientAdapter.shadowBluetoothGatt.isConnected)
+ throw RuntimeException()
+ }
+ }
+
+ assertTrue(clientAdapter.shadowBluetoothGatt.isClosed)
+ assertFalse(clientAdapter.shadowBluetoothGatt.isConnected)
}
@Test
fun connectFail() = runTest {
val device = createDevice("00:11:22:33:44:55")
rejectConnect()
- assertTrue(runCatching { bluetoothLe.connectGatt(device) { } }.isFailure)
+ assertFailsWith<CancellationException> { bluetoothLe.connectGatt(device) { } }
}
@Test
fun readCharacteristic() = runTest {
val testValue = 48
- val closed = CompletableDeferred<Unit>()
val device = createDevice("00:11:22:33:44:55")
acceptConnect()
@@ -158,9 +177,9 @@
readCharacteristic(
services[0].getCharacteristic(readCharUuid)!!
).getOrNull()?.toInt())
- closed.complete(Unit)
}
- assertTrue(closed.isCompleted)
+ assertTrue(clientAdapter.shadowBluetoothGatt.isClosed)
+ assertFalse(clientAdapter.shadowBluetoothGatt.isConnected)
}
@Test
@@ -188,7 +207,6 @@
fun writeCharacteristic() = runTest {
val initialValue = 48
val valueToWrite = 96
- val closed = CompletableDeferred<Unit>()
val device = createDevice("00:11:22:33:44:55")
val currentValue = AtomicInteger(initialValue)
@@ -229,9 +247,9 @@
valueToWrite.toByteArray())
Assert.assertEquals(valueToWrite,
readCharacteristic(characteristic).getOrNull()?.toInt())
- closed.complete(Unit)
}
- assertTrue(closed.isCompleted)
+ assertTrue(clientAdapter.shadowBluetoothGatt.isClosed)
+ assertFalse(clientAdapter.shadowBluetoothGatt.isConnected)
}
@Test
@@ -260,7 +278,6 @@
fun subscribeToCharacteristic() = runTest {
val initialValue = 48
val valueToNotify = 96
- val closed = CompletableDeferred<Unit>()
val device = createDevice("00:11:22:33:44:55")
val currentValue = AtomicInteger(initialValue)
@@ -304,9 +321,9 @@
subscribeToCharacteristic(characteristic).first().toInt())
Assert.assertEquals(valueToNotify,
readCharacteristic(characteristic).getOrNull()?.toInt())
- closed.complete(Unit)
}
- assertTrue(closed.isCompleted)
+ assertTrue(clientAdapter.shadowBluetoothGatt.isClosed)
+ assertFalse(clientAdapter.shadowBluetoothGatt.isConnected)
}
@Test
@@ -369,33 +386,31 @@
private fun acceptConnect() {
clientAdapter.onConnectListener =
StubClientFrameworkAdapter.OnConnectListener { device, _ ->
- shadowOf(device).simulateGattConnectionChange(
- BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_CONNECTED
- )
- true
- }
+ clientAdapter.shadowBluetoothGatt.notifyConnection(device.address)
+ true
+ }
clientAdapter.onRequestMtuListener =
StubClientFrameworkAdapter.OnRequestMtuListener { mtu ->
- clientAdapter.callback?.onMtuChanged(clientAdapter.bluetoothGatt, mtu,
- BluetoothGatt.GATT_SUCCESS)
- }
+ clientAdapter.callback?.onMtuChanged(clientAdapter.bluetoothGatt, mtu,
+ BluetoothGatt.GATT_SUCCESS)
+ }
clientAdapter.onDiscoverServicesListener =
StubClientFrameworkAdapter.OnDiscoverServicesListener {
- clientAdapter.gattServices = sampleServices
- clientAdapter.callback?.onServicesDiscovered(clientAdapter.bluetoothGatt,
- BluetoothGatt.GATT_SUCCESS)
- }
+ clientAdapter.gattServices = sampleServices
+ clientAdapter.callback?.onServicesDiscovered(clientAdapter.bluetoothGatt,
+ BluetoothGatt.GATT_SUCCESS)
+ }
}
private fun rejectConnect() {
clientAdapter.onConnectListener =
StubClientFrameworkAdapter.OnConnectListener { device, _ ->
- shadowOf(device).simulateGattConnectionChange(
- BluetoothGatt.GATT_FAILURE, BluetoothGatt.STATE_DISCONNECTED
- )
- false
+ shadowOf(device).simulateGattConnectionChange(
+ BluetoothGatt.GATT_FAILURE, BluetoothGatt.STATE_DISCONNECTED
+ )
+ false
}
}
@@ -478,6 +493,10 @@
?.onSetCharacteristicNotification(characteristic, enable)
}
+ override fun closeGatt() {
+ baseAdapter.closeGatt()
+ }
+
fun interface OnConnectListener {
fun onConnect(device: FwkDevice, callback: BluetoothGattCallback): Boolean
}
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
index 01456ac..bbd339a 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
@@ -81,6 +81,8 @@
fun writeDescriptor(descriptor: FwkDescriptor, value: ByteArray)
fun setCharacteristicNotification(characteristic: FwkCharacteristic, enable: Boolean)
+
+ fun closeGatt()
}
@VisibleForTesting
@@ -373,6 +375,9 @@
}
}
}
+ coroutineContext.job.invokeOnCompletion {
+ fwkAdapter.closeGatt()
+ }
gattScope.block()
}
@@ -444,6 +449,12 @@
) {
bluetoothGatt?.setCharacteristicNotification(characteristic, enable)
}
+
+ @RequiresPermission(BLUETOOTH_CONNECT)
+ override fun closeGatt() {
+ bluetoothGatt?.close()
+ bluetoothGatt?.disconnect()
+ }
}
private open class FrameworkAdapterApi33 : FrameworkAdapterBase() {