fix: Trigger AAP connection when classic Bluetooth connects

initialConnect() only ran on profile changes, so if the L2CAP connection failed at service start (device not yet connected), it was never retried. Now also triggers on connectedDevices changes.

Also fix reconnect BLE address comparison: was comparing BLE RPA with bonded BR/EDR address (never matches), now uses profile address.
This commit is contained in:
darken
2026-03-31 19:17:09 +02:00
committed by Matthias Urhahn
parent 71638c13ab
commit dc2e318374
2 changed files with 38 additions and 3 deletions
@@ -12,6 +12,7 @@ import eu.darken.capod.pods.core.apple.aap.AapPodState
import eu.darken.capod.profiles.core.DeviceProfilesRepo
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -33,7 +34,10 @@ class AapAutoConnect @Inject constructor(
reconnectOnDisconnect(),
)
private fun initialConnect(): Flow<Unit> = profilesRepo.profiles
private fun initialConnect(): Flow<Unit> = combine(
profilesRepo.profiles,
bluetoothManager.connectedDevices,
) { profiles, _ -> profiles }
.map { profiles ->
val bondedDevices = bluetoothManager.bondedDevices().first()
@@ -88,7 +92,7 @@ class AapAutoConnect @Inject constructor(
// Check if still visible in BLE
val bleDevices = blePodMonitor.devices.first()
if (bleDevices.none { it.address == address }) {
if (bleDevices.none { it.meta?.profile?.address == address }) {
log(TAG) { "AAP reconnect: $address no longer visible in BLE, stopping" }
break
}
@@ -39,6 +39,7 @@ class AapAutoConnectTest : BaseTest() {
private lateinit var blePodMonitor: BlePodMonitor
private lateinit var profilesFlow: MutableStateFlow<List<DeviceProfile>>
private lateinit var connectedDevicesFlow: MutableStateFlow<List<BluetoothDevice2>>
private lateinit var disconnectEventsFlow: MutableSharedFlow<String>
private lateinit var allStatesFlow: MutableStateFlow<Map<String, AapPodState>>
@@ -55,6 +56,7 @@ class AapAutoConnectTest : BaseTest() {
@BeforeEach
fun setup() {
profilesFlow = MutableStateFlow(emptyList())
connectedDevicesFlow = MutableStateFlow(emptyList())
disconnectEventsFlow = MutableSharedFlow(extraBufferCapacity = 16)
allStatesFlow = MutableStateFlow(emptyMap())
@@ -69,11 +71,18 @@ class AapAutoConnectTest : BaseTest() {
bluetoothManager = mockk {
every { bondedDevices() } returns flowOf(setOf(testBondedDevice))
every { connectedDevices } returns connectedDevicesFlow
}
val bleMeta = object : BlePodSnapshot.Meta {
override val profile = testProfile
}
blePodMonitor = mockk {
every { devices } returns flowOf(
listOf(mockk<BlePodSnapshot>(relaxed = true) { every { address } returns testAddress })
listOf(mockk<BlePodSnapshot>(relaxed = true) {
every { address } returns "5A:3B:1C:2D:4E:6F"
every { meta } returns bleMeta
})
)
}
}
@@ -148,6 +157,28 @@ class AapAutoConnectTest : BaseTest() {
job.cancel()
}
@Test
fun `connects when classic Bluetooth connects after service start`() = runTest(testDispatcher) {
val autoConnect = createAutoConnect()
// Profiles already set, but no classic BT connection yet
profilesFlow.value = listOf(testProfile)
val job = launch { autoConnect.monitor().toList() }
advanceUntilIdle()
// Initial connect fires but L2CAP may fail (or succeed — either way, verify connect is called)
coVerify(exactly = 1) { aapManager.connect(testAddress, any(), PodModel.AIRPODS_PRO3) }
// Simulate classic BT connecting later
connectedDevicesFlow.value = listOf(testBondedDevice)
advanceUntilIdle()
// Should attempt connect again
coVerify(exactly = 2) { aapManager.connect(testAddress, any(), PodModel.AIRPODS_PRO3) }
job.cancel()
}
@Test
fun `does not skip disconnected devices`() = runTest(testDispatcher) {
allStatesFlow.value = mapOf(