From dc2e318374eea9e7778efc3b404ab7c6f9a1a694 Mon Sep 17 00:00:00 2001 From: darken Date: Tue, 31 Mar 2026 15:16:54 +0200 Subject: [PATCH] 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. --- .../capod/reaction/core/aap/AapAutoConnect.kt | 8 +++-- .../reaction/core/aap/AapAutoConnectTest.kt | 33 ++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/darken/capod/reaction/core/aap/AapAutoConnect.kt b/app/src/main/java/eu/darken/capod/reaction/core/aap/AapAutoConnect.kt index 2089a6c9..e206d453 100644 --- a/app/src/main/java/eu/darken/capod/reaction/core/aap/AapAutoConnect.kt +++ b/app/src/main/java/eu/darken/capod/reaction/core/aap/AapAutoConnect.kt @@ -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 = profilesRepo.profiles + private fun initialConnect(): Flow = 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 } diff --git a/app/src/test/java/eu/darken/capod/reaction/core/aap/AapAutoConnectTest.kt b/app/src/test/java/eu/darken/capod/reaction/core/aap/AapAutoConnectTest.kt index 31ff379f..6fc1a0f9 100644 --- a/app/src/test/java/eu/darken/capod/reaction/core/aap/AapAutoConnectTest.kt +++ b/app/src/test/java/eu/darken/capod/reaction/core/aap/AapAutoConnectTest.kt @@ -39,6 +39,7 @@ class AapAutoConnectTest : BaseTest() { private lateinit var blePodMonitor: BlePodMonitor private lateinit var profilesFlow: MutableStateFlow> + private lateinit var connectedDevicesFlow: MutableStateFlow> private lateinit var disconnectEventsFlow: MutableSharedFlow private lateinit var allStatesFlow: MutableStateFlow> @@ -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(relaxed = true) { every { address } returns testAddress }) + listOf(mockk(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(