fix: Prevent duplicate device cards for stranger AirPods with same model

This commit is contained in:
darken
2026-04-13 14:09:33 +02:00
committed by Matthias Urhahn
parent 7f2b6976ce
commit 70d9c047dc
3 changed files with 132 additions and 3 deletions
@@ -9,6 +9,7 @@ import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.aap.AapConnectionManager
import eu.darken.capod.pods.core.apple.aap.AapPodState
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesRepo
@@ -43,6 +44,17 @@ class DeviceMonitorTest : BaseTest() {
model = PodModel.AIRPODS_PRO2_USBC,
address = null,
)
private val keyedProfile = AppleDeviceProfile(
label = "Keyed AirPods",
model = PodModel.AIRPODS_PRO2_USBC,
address = testAddress,
identityKey = ByteArray(16) { 0x42 },
)
private val noKeyProfile = AppleDeviceProfile(
label = "No Key AirPods",
model = PodModel.AIRPODS_PRO2_USBC,
address = "11:22:33:44:55:66",
)
private val testCachedState = CachedDeviceState(
profileId = testProfile.id,
@@ -76,6 +88,22 @@ class DeviceMonitorTest : BaseTest() {
}
}
private fun mockAppleBlePod(
profile: DeviceProfile,
isIRKMatch: Boolean,
signalQuality: Float = 0.5f,
): ApplePods {
val appleMeta = ApplePods.AppleMeta(
isIRKMatch = isIRKMatch,
profile = profile as? AppleDeviceProfile,
)
return mockk(relaxed = true) {
every { meta } returns appleMeta
every { this@mockk.model } returns profile.model
every { this@mockk.signalQuality } returns signalQuality
}
}
private fun createMonitor(
ble: List<BlePodSnapshot> = emptyList(),
aap: Map<BluetoothAddress, AapPodState> = emptyMap(),
@@ -390,4 +418,69 @@ class DeviceMonitorTest : BaseTest() {
devices.count { it.profileId == null } shouldBe 1
devices.count { it.profileId == testProfile.id } shouldBe 1
}
@Test
fun `profile dedup - two live pods with same IRK-backed profileId collapsed to IRK winner`() =
runTest(testDispatcher) {
val irkPod = mockAppleBlePod(keyedProfile, isIRKMatch = true, signalQuality = 0.6f)
val strangerPod = mockAppleBlePod(keyedProfile, isIRKMatch = false, signalQuality = 0.8f)
val monitor = createMonitor(
ble = listOf(irkPod, strangerPod),
profiles = listOf(keyedProfile),
scope = backgroundScope,
)
val devices = monitor.devices.first()
devices.size shouldBe 1
devices.single().ble shouldBe irkPod
}
@Test
fun `profile dedup - two IRK-matched pods for same profile keeps higher signal quality`() =
runTest(testDispatcher) {
val weakPod = mockAppleBlePod(keyedProfile, isIRKMatch = true, signalQuality = 0.3f)
val strongPod = mockAppleBlePod(keyedProfile, isIRKMatch = true, signalQuality = 0.9f)
val monitor = createMonitor(
ble = listOf(weakPod, strongPod),
profiles = listOf(keyedProfile),
scope = backgroundScope,
)
val devices = monitor.devices.first()
devices.size shouldBe 1
devices.single().ble shouldBe strongPod
}
@Test
fun `profile dedup - two pods with same no-IRK profileId are NOT collapsed`() =
runTest(testDispatcher) {
val pod1 = mockAppleBlePod(noKeyProfile, isIRKMatch = false, signalQuality = 0.6f)
val pod2 = mockAppleBlePod(noKeyProfile, isIRKMatch = false, signalQuality = 0.8f)
val monitor = createMonitor(
ble = listOf(pod1, pod2),
profiles = listOf(noKeyProfile),
scope = backgroundScope,
)
val devices = monitor.devices.first()
devices.size shouldBe 2
}
@Test
fun `profile dedup - anonymous pods always pass through`() = runTest(testDispatcher) {
val anon1 = mockAnonymousBlePod(PodModel.AIRPODS_PRO2_USBC)
val anon2 = mockAnonymousBlePod(PodModel.AIRPODS_PRO2_USBC)
val monitor = createMonitor(
ble = listOf(anon1, anon2),
profiles = emptyList(),
scope = backgroundScope,
)
val devices = monitor.devices.first()
devices.size shouldBe 2
}
}