Ignore ended Telecom callbacks and contain route listener failures
This commit is contained in:
1 parent
f031e510b8
commit
8a9e421ea7
1 file changed
+35
-15
@@ -17,9 +17,8 @@ import java.util.concurrent.CopyOnWriteArrayList
|
||||
* Self-managed Telecom Connection для P2P-звонка uTun.
|
||||
*
|
||||
* Сам звонок (сигналинг + аудио) полностью ведёт приложение; Connection лишь
|
||||
* регистрирует вызов в Telecom, чтобы система считала его настоящим и корректно
|
||||
* маршрутизировала аудио VOICE_COMMUNICATION на разговорный динамик (иначе на
|
||||
* Android 14+ вызов без Connection не получает earpiece-маршрут).
|
||||
* регистрирует вызов в Telecom для системного управления VoIP аудио. Фактический
|
||||
* маршрут подтверждают callbacks CallEndpoint (API 34+) или CallAudioState; успешный запрос не подменяет подтверждение.
|
||||
*
|
||||
* Владелец — TelecomCallManager (создаётся в UtunConnectionService, хранится в
|
||||
* реестре по callId). Ответ/отбой/разрыв приходят сюда из системного ввода
|
||||
@@ -46,10 +45,11 @@ class UtunConnection(
|
||||
private var requestedEndpoint: android.os.ParcelUuid? = null
|
||||
private var requestSerial = 0L
|
||||
private var requestedLegacyRoute = 0
|
||||
private var ended = false
|
||||
|
||||
init {
|
||||
setConnectionProperties(PROPERTY_SELF_MANAGED)
|
||||
setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD or CAPABILITY_MUTE)
|
||||
setConnectionCapabilities(CAPABILITY_MUTE) // Hold не реализован приложением и не рекламируется Telecom.
|
||||
setAudioModeIsVoip(true)
|
||||
setAddress(Uri.fromParts("node", "0x%016x".format(peerNodeId), null), TelecomManager.PRESENTATION_UNKNOWN)
|
||||
if (displayName.isNotEmpty()) setCallerDisplayName(displayName, TelecomManager.PRESENTATION_UNKNOWN)
|
||||
@@ -58,20 +58,26 @@ class UtunConnection(
|
||||
"created callId=0x%016x peer=0x%016x name=%s".format(callId, peerNodeId, displayName))
|
||||
}
|
||||
|
||||
override fun onAnswer() = TelecomCallManager.notifyAnswer(callId)
|
||||
override fun onAnswer() = systemInput("answer") { TelecomCallManager.notifyAnswer(callId) }
|
||||
|
||||
override fun onReject() = TelecomCallManager.notifyReject(callId)
|
||||
override fun onReject() = systemInput("reject") { TelecomCallManager.notifyReject(callId) }
|
||||
|
||||
override fun onDisconnect() = TelecomCallManager.notifyDisconnect(callId)
|
||||
override fun onDisconnect() = systemInput("disconnect") { TelecomCallManager.notifyDisconnect(callId) }
|
||||
|
||||
override fun onAbort() = TelecomCallManager.notifyAbort(callId)
|
||||
override fun onShowIncomingCallUi() = TelecomCallManager.notifyIncomingUiAllowed(callId)
|
||||
override fun onAbort() = systemInput("abort") { TelecomCallManager.notifyAbort(callId) }
|
||||
override fun onShowIncomingCallUi() = systemInput("incoming UI") { TelecomCallManager.notifyIncomingUiAllowed(callId) }
|
||||
|
||||
private fun systemInput(event: String, action: () -> Unit) {
|
||||
if (ended) { LogManager.addLog("WARN", "UtunConn", "late system input ignored id=$callId event=$event"); return }
|
||||
action()
|
||||
}
|
||||
|
||||
override fun onStateChanged(state: Int) {
|
||||
LogManager.addLog("DEBUG", "UtunConn", "state -> %s".format(Connection.stateToString(state)))
|
||||
}
|
||||
|
||||
override fun onCallAudioStateChanged(audioState: CallAudioState?) {
|
||||
if (ended) return
|
||||
LogManager.addLog("DEBUG", "UtunConn",
|
||||
"audioState route=%s muted=%s".format(audioState?.route ?: -1, audioState?.isMuted ?: false))
|
||||
if (Build.VERSION.SDK_INT < 34 && audioState != null) {
|
||||
@@ -83,12 +89,13 @@ class UtunConnection(
|
||||
}
|
||||
if (route != null) confirmRoute(route)
|
||||
else LogManager.addLog("WARN", "UtunConn", "unrecognized audio route=${audioState.route}")
|
||||
onEndpointsChanged?.invoke()
|
||||
notifyEndpointsChanged()
|
||||
wantedRoute?.let { routeTo(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCallEndpointChanged(endpoint: CallEndpoint) {
|
||||
if (ended) return
|
||||
val route = when (endpoint.endpointType) {
|
||||
CallEndpoint.TYPE_EARPIECE -> AudioRoute.EARPIECE
|
||||
CallEndpoint.TYPE_SPEAKER -> AudioRoute.SPEAKER
|
||||
@@ -105,20 +112,29 @@ class UtunConnection(
|
||||
wantedRoute = null; requestedLegacyRoute = 0
|
||||
}
|
||||
LogManager.addLog("INFO", "UtunConn", "route confirmed id=$callId route=$route pending=$wantedRoute")
|
||||
onRouteChanged?.invoke(route)
|
||||
try { onRouteChanged?.invoke(route) } catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "UtunConn", "route confirmation callback failed id=$callId route=$route: $e")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAvailableCallEndpointsChanged(availableEndpoints: MutableList<CallEndpoint>) {
|
||||
if (ended) return
|
||||
val changed = this.availableEndpoints.map { it.identifier to it.endpointType }.toSet() !=
|
||||
availableEndpoints.map { it.identifier to it.endpointType }.toSet()
|
||||
this.availableEndpoints.clear()
|
||||
this.availableEndpoints.addAll(availableEndpoints)
|
||||
if (changed) LogManager.addLog("INFO", "UtunConn",
|
||||
"endpoints id=$callId types=${availableEndpoints.map { it.endpointType }} headset=$headsetAvailable confirmed=$confirmedRoute")
|
||||
onEndpointsChanged?.invoke()
|
||||
notifyEndpointsChanged()
|
||||
wantedRoute?.let { routeTo(it) }
|
||||
}
|
||||
|
||||
private fun notifyEndpointsChanged() {
|
||||
try { onEndpointsChanged?.invoke() } catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "UtunConn", "endpoint list callback failed id=$callId: $e")
|
||||
}
|
||||
}
|
||||
|
||||
/** Перевести вызов в звонящее состояние (входящий). */
|
||||
fun markRinging() = setRinging()
|
||||
|
||||
@@ -127,6 +143,7 @@ class UtunConnection(
|
||||
|
||||
/** Завершение с причиной (коды приложения: 0 — отбой, 2 — отклонено, 7 — ошибка). */
|
||||
fun markDisconnected(reason: Int) {
|
||||
ended = true; availableEndpoints.clear()
|
||||
wantedRoute = null; requestedEndpoint = null; requestedLegacyRoute = 0; ++requestSerial
|
||||
onRouteChanged = null; onEndpointsChanged = null
|
||||
val code = when (reason) {
|
||||
@@ -140,6 +157,7 @@ class UtunConnection(
|
||||
|
||||
/** Маршрутизация аудио через Telecom (CallEndpoint API на Android 14+, иначе setAudioRoute). */
|
||||
fun routeTo(route: AudioRoute) {
|
||||
if (ended) { LogManager.addLog("WARN", "UtunConn", "route after disconnect ignored id=$callId route=$route"); return }
|
||||
wantedRoute = route
|
||||
if (confirmedRoute == route && requestedEndpoint == null && requestedLegacyRoute == 0) { wantedRoute = null; return }
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
@@ -158,14 +176,16 @@ class UtunConnection(
|
||||
val executor = ContextCompat.getMainExecutor(ChatApplication.instance)
|
||||
requestCallEndpointChange(ep, executor, object : OutcomeReceiver<Void, CallEndpointException> {
|
||||
override fun onResult(result: Void?) {
|
||||
LogManager.addLog("INFO", "UtunConn", "endpoint change ok -> %d".format(ep.endpointType))
|
||||
if (requestSerial != serial || ended) { LogManager.addLog("DEBUG", "UtunConn", "stale endpoint result id=$callId serial=$serial"); return }
|
||||
LogManager.addLog("INFO", "UtunConn", "endpoint request completed id=$callId type=${ep.endpointType} confirmed=$confirmedRoute")
|
||||
if (requestSerial == serial) {
|
||||
requestedEndpoint = null
|
||||
wantedRoute?.takeIf { it != route }?.let { routeTo(it) }
|
||||
}
|
||||
}
|
||||
override fun onError(error: CallEndpointException) {
|
||||
LogManager.addLog("WARN", "UtunConn", "endpoint change failed: %s".format(error.message))
|
||||
if (requestSerial != serial || ended) { LogManager.addLog("DEBUG", "UtunConn", "stale endpoint error id=$callId serial=$serial"); return }
|
||||
LogManager.addLog("WARN", "UtunConn", "endpoint request failed id=$callId type=${ep.endpointType}: $error")
|
||||
if (requestSerial == serial) {
|
||||
requestedEndpoint = null
|
||||
val next = wantedRoute?.takeIf { it != route }
|
||||
@@ -176,7 +196,7 @@ class UtunConnection(
|
||||
})
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
requestedEndpoint = null; wantedRoute = null
|
||||
if (requestSerial == serial) { requestedEndpoint = null; wantedRoute = null; ++requestSerial }
|
||||
LogManager.addLog("WARN", "UtunConn", "requestCallEndpointChange threw: %s".format(e.message))
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user