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(