From b71f992a4573a07e540d5ee5ac29ad1505ebe8f1 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 02:24:25 +0100 Subject: [PATCH] Retain the media teardown barrier while Telecom cleanup is pending --- .../java/com/utun/chat/data/CallController.kt | 2 +- .../main/java/com/utun/chat/data/CallService.kt | 4 ++-- .../com/utun/chat/data/TelecomCallManager.kt | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt index 1fb74153..34bcf36d 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallController.kt @@ -43,7 +43,7 @@ object CallController : TelecomCallManager.Listener { /** Подготовка, разговор и неподтверждённый cleanup сервиса запрещают запуск чужого media. * Predicate читается также media workers; CallService публикует retained-владельца через volatile. */ - fun blocksMedia(): Boolean = CallService.cleanupPending() || mediaPreparation?.isActive == true || _state.value?.let { + fun blocksMedia(): Boolean = CallService.cleanupPending() || TelecomCallManager.cleanupPending() || mediaPreparation?.isActive == true || _state.value?.let { it.preparing || it.phase == CallPhase.OUTGOING || it.phase == CallPhase.RINGING || it.phase == CallPhase.WAIT_DECISION || it.phase == CallPhase.ACTIVE } == true diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallService.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallService.kt index 619f4f87..4db89d22 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallService.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallService.kt @@ -52,8 +52,8 @@ class CallService : Service() { /** На main повторить незавершённую очистку. Ошибка запрещает заменять владельца или останавливать ядро. */ fun requireReleased() { checkMain() - val previous = pendingCleanup ?: return - previous.shutdown() + pendingCleanup?.shutdown() + TelecomCallManager.requireReleased() check(pendingCleanup == null) { val message = "Call service cleanup still pending id=${previous.callId}" LogManager.addLog("ERROR", "CallService", message) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/TelecomCallManager.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/TelecomCallManager.kt index aab9abc8..b366c77c 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/TelecomCallManager.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/TelecomCallManager.kt @@ -50,6 +50,7 @@ object TelecomCallManager { private val disconnected = mutableSetOf() private var cleanupQueued = false private var cleanupDelay = 250L + @Volatile private var retiring = false private val cleanupRetry = Runnable { cleanupQueued = false; cleanupConnections() } @Volatile private var phoneAccountHandle: PhoneAccountHandle? = null @@ -84,6 +85,17 @@ object TelecomCallManager { fun isReady(): Boolean = ready + /** Не передавать глобальную аудиополитику другому media/core, пока SDK удерживает старый Connection. */ + fun cleanupPending(): Boolean = retiring + + fun requireReleased() { + checkMain(); cleanupConnections() + check(!retiring) { + LogManager.addLog("ERROR", TAG, "Telecom cleanup still pending connections=${closing.size}") + "Telecom connection cleanup pending" + } + } + /** Создать исходящий вызов в Telecom. Возвращает false, если Telecom недоступен. */ fun placeCall(callId: Long, peerNodeId: Long, displayName: String): Boolean { val tm = telecomManager() ?: return false @@ -202,6 +214,7 @@ object TelecomCallManager { private fun retire(callId: Long, conn: UtunConnection, reason: Int) { closing.putIfAbsent(conn, Closing(callId, reason, conn in disconnected)) + retiring = true reasons.remove(conn); disconnected.remove(conn); cleanupConnections() } @@ -214,7 +227,8 @@ object TelecomCallManager { LogManager.addLog("ERROR", TAG, "connection destroy failed id=${owner.id}; retained: $e") } } - if (closing.isEmpty()) { mainHandler.removeCallbacks(cleanupRetry); cleanupQueued = false; cleanupDelay = 250; return } + retiring = closing.isNotEmpty() + if (!retiring) { mainHandler.removeCallbacks(cleanupRetry); cleanupQueued = false; cleanupDelay = 250; return } if (!cleanupQueued) { cleanupQueued = mainHandler.postDelayed(cleanupRetry, cleanupDelay) if (!cleanupQueued) LogManager.addLog("ERROR", TAG, "connection cleanup retry rejected count=${closing.size}; owners retained")