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?)
+    }
 }