Skip to content

Commit 66d28b8

Browse files
authored
Merge pull request #241 from NordicSemiconductor/feature/mock
Client mock implementation (Part 1)
2 parents 2a79098 + f549264 commit 66d28b8

File tree

66 files changed

+4788
-1129
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+4788
-1129
lines changed

advertiser-android-mock/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,9 @@ dependencies {
5656
dokka {
5757
dokkaSourceSets.named("main") {
5858
includes.from("Module.md")
59+
perPackageOption {
60+
matchingRegex.set("no.nordicsemi.kotlin.ble.advertiser.android.mock.internal")
61+
suppress.set(true)
62+
}
5963
}
6064
}

advertiser-android/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,9 @@ dependencies {
6464
dokka {
6565
dokkaSourceSets.named("main") {
6666
includes.from("Module.md")
67+
perPackageOption {
68+
matchingRegex.set("no.nordicsemi.kotlin.ble.advertiser.android.internal")
69+
suppress.set(true)
70+
}
6771
}
6872
}

client-android-mock/src/main/java/no/nordicsemi/kotlin/ble/client/android/mock/CentralManagerFactory.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import no.nordicsemi.kotlin.ble.android.mock.MockEnvironment
3939
import no.nordicsemi.kotlin.ble.client.android.CentralManager
4040
import no.nordicsemi.kotlin.ble.client.android.mock.internal.MockCentralManagerImpl
4141
import no.nordicsemi.kotlin.ble.client.mock.SimulationProvider
42+
import no.nordicsemi.kotlin.ble.client.mock.internal.MockBluetoothLeAdvertiser
4243

4344
/**
4445
* Creates a mock implementation of a [CentralManager] that can emulate scanning and connecting

client-android-mock/src/main/java/no/nordicsemi/kotlin/ble/client/android/mock/internal/MockCentralManagerImpl.kt

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ open class MockCentralManagerImpl(
8383

8484
// Simulation methods
8585
private var peripheralSpecs = mutableListOf<PeripheralSpec<String>>()
86-
private val mockAdvertiser = MockBluetoothLeAdvertiser<String>(scope, environment)
86+
private val mockAdvertiser = MockBluetoothLeAdvertiser<String>(scope)
8787

8888
override fun simulatePowerOn() = simulateStateChange(Manager.State.POWERED_ON)
8989

@@ -93,14 +93,31 @@ open class MockCentralManagerImpl(
9393
require(peripheralSpecs.isEmpty()) {
9494
"Peripherals have already been added to the simulation"
9595
}
96+
// Validate the MAC addresses.
9697
peripherals.forEach {
97-
// Validate the MAC address.
98-
require(it.identifier.matches(Regex("([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})"))) {
99-
"Invalid MAC address: ${it.identifier}"
98+
require(checkBluetoothAddress(it.identifier)) {
99+
"${it.identifier} + is not a valid Bluetooth address"
100100
}
101101
}
102+
// Add known peripherals to the managed list. They will be available for connection without scanning.
103+
// This list includes bonded devices as well.
104+
peripherals
105+
.filter { it.isKnown }
106+
.forEach {
107+
managedPeripherals.put(
108+
key = it.identifier,
109+
value = Peripheral(
110+
scope = scope,
111+
impl = MockExecutor(
112+
peripheralSpec = it,
113+
name = it.name,
114+
environment = environment,
115+
advertisements = mockAdvertiser.events,
116+
)
117+
)
118+
)
119+
}
102120
peripheralSpecs.addAll(peripherals)
103-
managedPeripherals
104121
mockAdvertiser.simulateAdvertising(peripherals)
105122
}
106123

@@ -131,8 +148,6 @@ open class MockCentralManagerImpl(
131148
}
132149

133150
// Implementation
134-
private val _advertisingEvents = MutableSharedFlow<ScanResult>()
135-
136151
private val _state = MutableStateFlow(
137152
when {
138153
!environment.isBluetoothSupported -> Manager.State.UNSUPPORTED
@@ -160,7 +175,9 @@ open class MockCentralManagerImpl(
160175
ensureOpen()
161176

162177
return ids.map { id ->
163-
require(checkBluetoothAddress(id)) { "$id + is not a valid Bluetooth address" }
178+
require(checkBluetoothAddress(id)) {
179+
"$id + is not a valid Bluetooth address"
180+
}
164181
peripheral(id) {
165182
Peripheral(
166183
scope = scope,
@@ -172,7 +189,9 @@ open class MockCentralManagerImpl(
172189
identifier = id,
173190
proximity = Proximity.OUT_OF_RANGE
174191
),
175-
name = null
192+
name = null,
193+
environment = environment,
194+
advertisements = mockAdvertiser.events,
176195
)
177196
)
178197
}
@@ -195,19 +214,25 @@ open class MockCentralManagerImpl(
195214
// but were not scanned yet.
196215
val managedBondedPeripherals = managedPeripherals.values
197216
.filter { it.hasBondInformation }
198-
val otherBondedPeripherals = peripheralSpecs
199-
.filter { it.isBonded }
200-
.filter { it.identifier !in managedPeripherals.keys }
201-
.map { peripheralSpec ->
202-
peripheral(peripheralSpec.identifier) {
203-
Peripheral(
204-
scope = scope,
205-
impl = MockExecutor(peripheralSpec, peripheralSpec.name)
206-
)
207-
}
208-
}
217+
// val otherBondedPeripherals = peripheralSpecs
218+
// .filter { it.isBonded }
219+
// .filter { it.identifier !in managedPeripherals.keys }
220+
// .map { peripheralSpec ->
221+
// peripheral(peripheralSpec.identifier) {
222+
// Peripheral(
223+
// scope = scope,
224+
// impl = MockExecutor(
225+
// peripheralSpec = peripheralSpec,
226+
// name = peripheralSpec.name,
227+
// advertisements = mockAdvertiser.events,
228+
// )
229+
// )
230+
// }
231+
// }
209232
// TODO any order?
210-
return managedBondedPeripherals + otherBondedPeripherals
233+
// TODO NOTE all known (including bonded) peripherals were added to managed already in simulatePeripherals
234+
// TODO a peripheral may change MAC address, how about that?
235+
return managedBondedPeripherals // + otherBondedPeripherals
211236
}
212237

213238
override fun scan(
@@ -235,15 +260,32 @@ open class MockCentralManagerImpl(
235260
val reportResult = environment.scanner().getOrThrow()
236261

237262
// Emit all scan results until the timeout.
238-
withTimeoutOrNull<Nothing>(timeout) {
263+
withTimeoutOrNull(timeout) {
264+
// Keep IDs of all scanned peripherals to this scan.
265+
// This is used for handing passive scan.
266+
val cache = mutableSetOf<String>()
267+
239268
mockAdvertiser.events.collect { result ->
240269
// Some (most?) Android devices do not report scan error using `onScanFailed`
241270
// callback, but instead don't return any results.
242-
// Re
243271
if (!reportResult) {
244272
return@collect
245273
}
246274

275+
// Some phones send only one Scan Request to connectable devices per scan.
276+
// Such devices are only reported once. Non-connectable devices, which only
277+
// send Advertising Data, are reported continuously.
278+
// TODO Modify to support passive scan on Android 16+
279+
if (environment.issueOnlyOneActiveScan) {
280+
if (result.isConnectable) {
281+
if (cache.contains(result.peripheralSpec.identifier)) {
282+
return@collect
283+
} else {
284+
cache.add(result.peripheralSpec.identifier)
285+
}
286+
}
287+
}
288+
247289
// Starting from Android 6 Location permission and Location service are required
248290
// to scan for BLE devices. Since Android 12, apps can set a `neverForLocation`
249291
// flag to claim that they won't estimate user's location from scan results.
@@ -283,7 +325,12 @@ open class MockCentralManagerImpl(
283325
peripheral(peripheralSpec.identifier) {
284326
Peripheral(
285327
scope = scope,
286-
impl = MockExecutor(peripheralSpec, name)
328+
impl = MockExecutor(
329+
peripheralSpec = peripheralSpec,
330+
name = name,
331+
environment = environment,
332+
advertisements = mockAdvertiser.events,
333+
)
287334
)
288335
}
289336
}

0 commit comments

Comments
 (0)