mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-16 11:16:12 -04:00
feat: Speed up AAP L2CAP reconnection
Only attempt AAP connections to classically-connected devices, eliminating futile retries for devices connected to other phones. - Filter initialConnect() to devices in connectedAddresses - Use mapLatest so stale retry loops cancel on state changes - Parallelize per-profile connection attempts - Add 5s connect timeout (best-effort) to cap retry cycles - Add classic BT check to reconnectOnDisconnect() - Keep MonitorService alive when AAP connections are active
This commit is contained in:
@@ -34,6 +34,7 @@ import eu.darken.capod.monitor.core.primaryDevice
|
||||
import eu.darken.capod.monitor.ui.MonitorNotifications
|
||||
import eu.darken.capod.profiles.core.DeviceProfile
|
||||
import eu.darken.capod.profiles.core.DeviceProfilesRepo
|
||||
import eu.darken.capod.pods.core.apple.aap.AapConnectionManager
|
||||
import eu.darken.capod.reaction.core.aap.AapAutoConnect
|
||||
import eu.darken.capod.reaction.core.aap.AapKeyPersister
|
||||
import eu.darken.capod.reaction.core.autoconnect.AutoConnect
|
||||
@@ -76,6 +77,7 @@ class MonitorService : Service() {
|
||||
@Inject lateinit var profilesRepo: DeviceProfilesRepo
|
||||
@Inject lateinit var aapAutoConnect: AapAutoConnect
|
||||
@Inject lateinit var aapKeyPersister: AapKeyPersister
|
||||
@Inject lateinit var aapConnectionManager: AapConnectionManager
|
||||
|
||||
private val monitorScope = MonitorCoroutineScope()
|
||||
private var monitoringJob: Job? = null
|
||||
@@ -215,8 +217,9 @@ class MonitorService : Service() {
|
||||
generalSettings.monitorMode.flow,
|
||||
profilesRepo.profiles,
|
||||
bluetoothManager.connectedDevices,
|
||||
) { monitorMode, profiles, connectedDevices ->
|
||||
listOf(monitorMode, profiles, connectedDevices)
|
||||
aapConnectionManager.allStates,
|
||||
) { monitorMode, profiles, connectedDevices, aapStates ->
|
||||
listOf(monitorMode, profiles, connectedDevices, aapStates)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,6 +233,9 @@ class MonitorService : Service() {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val devices = arguments[2] as Collection<BluetoothDevice2>
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val aapStates = arguments[3] as Map<*, *>
|
||||
|
||||
val connectedAddresses = devices.map { it.address }.toSet()
|
||||
val knownAddresses = profiles.mapNotNull { it.address }.toSet()
|
||||
log(TAG) { "Monitor mode: $monitorMode" }
|
||||
@@ -248,7 +254,7 @@ class MonitorService : Service() {
|
||||
log(TAG, WARN) { "Main device address not set, staying alive while any is connected" }
|
||||
}
|
||||
|
||||
knownAddresses.any { it in connectedAddresses } -> {
|
||||
knownAddresses.any { it in connectedAddresses } || aapStates.isNotEmpty() -> {
|
||||
log(TAG) { "A device is connected, aborting any timeout." }
|
||||
}
|
||||
|
||||
|
||||
@@ -12,15 +12,19 @@ import eu.darken.capod.pods.core.apple.aap.AapConnectionManager
|
||||
import eu.darken.capod.pods.core.apple.aap.AapPodState
|
||||
import eu.darken.capod.profiles.core.AppleDeviceProfile
|
||||
import eu.darken.capod.profiles.core.DeviceProfilesRepo
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -43,50 +47,73 @@ class AapAutoConnect @Inject constructor(
|
||||
private fun initialConnect(): Flow<Unit> = combine(
|
||||
profilesRepo.profiles,
|
||||
bluetoothManager.connectedDevices,
|
||||
) { profiles, _ -> profiles }
|
||||
.map { profiles ->
|
||||
) { profiles, connectedDevices ->
|
||||
val connectedAddresses = connectedDevices.map { it.address }.toSet()
|
||||
profiles.filter { it.address in connectedAddresses }
|
||||
}
|
||||
.mapLatest { profiles ->
|
||||
val bondedDevices = bluetoothManager.bondedDevices().first()
|
||||
|
||||
for (profile in profiles) {
|
||||
val address = profile.address ?: continue
|
||||
val bonded = bondedDevices.firstOrNull { it.address == address } ?: continue
|
||||
|
||||
val currentState = aapManager.allStates.value[address]
|
||||
if (currentState != null && currentState.connectionState != AapPodState.ConnectionState.DISCONNECTED) {
|
||||
log(TAG, VERBOSE) { "AAP already connected/connecting to $address" }
|
||||
continue
|
||||
}
|
||||
|
||||
log(TAG) { "AAP connecting to $address (${profile.label})" }
|
||||
try {
|
||||
aapManager.connect(address, bonded.internal, profile.model)
|
||||
log(TAG) { "AAP connected to $address" }
|
||||
} catch (e: Exception) {
|
||||
log(TAG, WARN) { "AAP initial connect failed for $address: ${e.message}" }
|
||||
|
||||
for ((attempt, delayMs) in RETRY_DELAYS.withIndex()) {
|
||||
delay(delayMs)
|
||||
|
||||
val retryState = aapManager.allStates.value[address]
|
||||
if (retryState != null && retryState.connectionState != AapPodState.ConnectionState.DISCONNECTED) {
|
||||
log(TAG) { "AAP initial retry: $address already reconnected, stopping" }
|
||||
break
|
||||
}
|
||||
|
||||
try {
|
||||
log(TAG) { "AAP initial retry ${attempt + 1} for $address after ${delayMs}ms" }
|
||||
aapManager.connect(address, bonded.internal, profile.model)
|
||||
log(TAG) { "AAP connected to $address on retry ${attempt + 1}" }
|
||||
break
|
||||
} catch (retryException: Exception) {
|
||||
log(TAG, WARN) { "AAP initial retry ${attempt + 1} failed for $address: ${retryException.message}" }
|
||||
}
|
||||
}
|
||||
coroutineScope {
|
||||
for (profile in profiles) {
|
||||
launch { connectWithRetries(profile, bondedDevices) }
|
||||
}
|
||||
}
|
||||
}
|
||||
.setupCommonEventHandlers(TAG) { "initialConnect" }
|
||||
|
||||
private suspend fun connectWithRetries(
|
||||
profile: eu.darken.capod.profiles.core.DeviceProfile,
|
||||
bondedDevices: Set<eu.darken.capod.common.bluetooth.BluetoothDevice2>,
|
||||
) {
|
||||
val address = profile.address ?: return
|
||||
val bonded = bondedDevices.firstOrNull { it.address == address } ?: return
|
||||
|
||||
val currentState = aapManager.allStates.value[address]
|
||||
if (currentState != null && currentState.connectionState != AapPodState.ConnectionState.DISCONNECTED) {
|
||||
log(TAG, VERBOSE) { "AAP already connected/connecting to $address" }
|
||||
return
|
||||
}
|
||||
|
||||
log(TAG) { "AAP connecting to $address (${profile.label})" }
|
||||
try {
|
||||
withTimeout(CONNECT_TIMEOUT) {
|
||||
aapManager.connect(address, bonded.internal, profile.model)
|
||||
}
|
||||
log(TAG) { "AAP connected to $address" }
|
||||
} catch (e: Exception) {
|
||||
log(TAG, WARN) { "AAP initial connect failed for $address: ${e.message}" }
|
||||
|
||||
for ((attempt, delayMs) in RETRY_DELAYS.withIndex()) {
|
||||
delay(delayMs)
|
||||
|
||||
// Bail out if classic BT disconnected
|
||||
val currentConnected = bluetoothManager.connectedDevices.first().map { it.address }.toSet()
|
||||
if (address !in currentConnected) {
|
||||
log(TAG) { "AAP initial retry: $address no longer classically connected, stopping" }
|
||||
break
|
||||
}
|
||||
|
||||
val retryState = aapManager.allStates.value[address]
|
||||
if (retryState != null && retryState.connectionState != AapPodState.ConnectionState.DISCONNECTED) {
|
||||
log(TAG) { "AAP initial retry: $address already reconnected, stopping" }
|
||||
break
|
||||
}
|
||||
|
||||
try {
|
||||
log(TAG) { "AAP initial retry ${attempt + 1} for $address after ${delayMs}ms" }
|
||||
withTimeout(CONNECT_TIMEOUT) {
|
||||
aapManager.connect(address, bonded.internal, profile.model)
|
||||
}
|
||||
log(TAG) { "AAP connected to $address on retry ${attempt + 1}" }
|
||||
break
|
||||
} catch (retryException: Exception) {
|
||||
log(TAG, WARN) { "AAP initial retry ${attempt + 1} failed for $address: ${retryException.message}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reconnectOnDisconnect(): Flow<Unit> = aapManager.disconnectEvents
|
||||
.onEach { address ->
|
||||
if (!activeReconnects.add(address)) {
|
||||
@@ -113,6 +140,13 @@ class AapAutoConnect @Inject constructor(
|
||||
break
|
||||
}
|
||||
|
||||
// Check if still classically connected
|
||||
val currentConnected = bluetoothManager.connectedDevices.first().map { it.address }.toSet()
|
||||
if (address !in currentConnected) {
|
||||
log(TAG) { "AAP reconnect: $address no longer classically connected, stopping" }
|
||||
break
|
||||
}
|
||||
|
||||
// Check if still visible in BLE
|
||||
val bleDevices = blePodMonitor.devices.first()
|
||||
if (bleDevices.none { it.meta?.profile?.address == address }) {
|
||||
@@ -129,7 +163,9 @@ class AapAutoConnect @Inject constructor(
|
||||
|
||||
try {
|
||||
log(TAG) { "AAP reconnect attempt ${attempt + 1} for $address in ${delayMs}ms" }
|
||||
aapManager.connect(address, bonded.internal, profile.model)
|
||||
withTimeout(CONNECT_TIMEOUT) {
|
||||
aapManager.connect(address, bonded.internal, profile.model)
|
||||
}
|
||||
log(TAG) { "AAP reconnected to $address" }
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
@@ -176,7 +212,7 @@ class AapAutoConnect @Inject constructor(
|
||||
profilesRepo.updateProfile(profile.copy(model = detectedModel))
|
||||
log(TAG) { "AAP model corrected for $address: ${profile.model} -> $detectedModel" }
|
||||
|
||||
// Reconnect explicitly — initialConnect may be blocked by retry loops for other devices
|
||||
// Reconnect explicitly
|
||||
val bonded = bluetoothManager.bondedDevices().first()
|
||||
.firstOrNull { it.address == address }
|
||||
if (bonded != null) {
|
||||
@@ -193,5 +229,6 @@ class AapAutoConnect @Inject constructor(
|
||||
companion object {
|
||||
private val TAG = logTag("Reaction", "AapAutoConnect")
|
||||
internal val RETRY_DELAYS = longArrayOf(3_000, 3_000, 3_000, 5_000, 5_000, 10_000, 10_000)
|
||||
private val CONNECT_TIMEOUT = 5.seconds
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user