Add tests for BluetoothLe#scan
Bug: 293405117
Test: ./gradlew bluetooth:bluetooth-testing:check
Change-Id: I0313a430d00bbdb43ab1990501a2d54d06adbd7e
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt
index 1dd57c9..189873a 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt
@@ -16,17 +16,24 @@
package androidx.bluetooth.testing
+import android.bluetooth.le.ScanResult
+import android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES
import android.content.Context
import androidx.bluetooth.BluetoothLe
import androidx.bluetooth.ScanFilter
-import junit.framework.TestCase.fail
-import kotlinx.coroutines.TimeoutCancellationException
+import java.util.concurrent.atomic.AtomicReference
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collectIndexed
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.withTimeout
+import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
+import org.robolectric.Shadows.shadowOf
+import org.robolectric.shadows.ShadowBluetoothDevice
+import org.robolectric.shadows.ShadowBluetoothLeScanner
@RunWith(RobolectricTestRunner::class)
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@@ -38,17 +45,47 @@
}
@Test
- fun scan() = runTest {
- try {
- withTimeout(TIMEOUT_MS) {
- bluetoothLe.scan(listOf(ScanFilter())).collect {
- // Should not find any device
- fail()
+ fun scanTest() = runTest {
+ val scanResults = listOf(
+ createScanResult("00:00:00:00:00:01"),
+ createScanResult("00:00:00:00:00:02"),
+ createScanResult("00:00:00:00:00:03"),
+ )
+
+ val scannerRef = AtomicReference<ShadowBluetoothLeScanner>(null)
+ bluetoothLe.onStartScanListener = BluetoothLe.OnStartScanListener { scanner ->
+ val shadowScanner = shadowOf(scanner)
+ scannerRef.set(shadowScanner)
+
+ // Check if the scan is started
+ Assert.assertEquals(1, shadowScanner.activeScans.size)
+
+ shadowScanner.scanCallbacks.forEach { callback ->
+ scanResults.forEach { res ->
+ callback.onScanResult(CALLBACK_TYPE_ALL_MATCHES, res)
}
}
- fail()
- } catch (e: TimeoutCancellationException) {
- // expected
}
+
+ launch {
+ bluetoothLe.scan(listOf(ScanFilter())).collectIndexed { index, value ->
+ Assert.assertEquals(scanResults[index].device.address, value.deviceAddress.address)
+ if (index == scanResults.size - 1) {
+ this.cancel()
+ }
+ }
+ }.join()
+
+ // Check if the scan is stopped
+ Assert.assertEquals(0, scannerRef.get().activeScans.size)
+ }
+
+ @Suppress("DEPRECATION")
+ private fun createScanResult(
+ address: String,
+ rssi: Int = 0,
+ timestampNanos: Long = 0
+ ): ScanResult {
+ return ScanResult(ShadowBluetoothDevice.newInstance(address), null, rssi, timestampNanos)
}
}
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
index cc95ef8..ec05554 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
@@ -20,6 +20,7 @@
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
+import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult as FwkScanResult
import android.bluetooth.le.ScanSettings
@@ -55,6 +56,11 @@
val client = GattClient(context)
private val server = GattServer(context)
+ @VisibleForTesting
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+ @set:RestrictTo(RestrictTo.Scope.LIBRARY)
+ var onStartScanListener: OnStartScanListener? = null
+
/**
* Returns a _cold_ [Flow] to start Bluetooth LE Advertising. When the flow is successfully collected,
* the operation status [AdvertiseResult] will be delivered via the
@@ -149,6 +155,7 @@
val fwkFilters = filters.map { it.fwkScanFilter }
val scanSettings = ScanSettings.Builder().build()
bleScanner?.startScan(fwkFilters, scanSettings, callback)
+ onStartScanListener?.onStartScan(bleScanner)
awaitClose {
bleScanner?.stopScan(callback)
@@ -322,4 +329,10 @@
fun updateServices(services: List<GattService>) {
server.updateServices(services)
}
+
+ @VisibleForTesting
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun interface OnStartScanListener {
+ fun onStartScan(scanner: BluetoothLeScanner?)
+ }
}