Verify call preparation waits for voice cleanup and document process barriers

This commit is contained in:
evgeny committed 2026-10-29 13:35:19 +01:00
1 parent 41ea2afaaa
commit 3f2cc2abb3
2 files changed
+30

No files matched your search

@@ -45,6 +45,7 @@ class CallServiceLifecycleTest {
AppLanguage.failText = false; AppLanguage.language = "en"; NotificationCompat.failBuild = false
Handler.rejectImmediate = false; Handler.rejectDelayed = false; PendingIntent.reset()
MediaAudioController.beforeIdle = {}
AudioRecorderManager.stopped = java.util.concurrent.CompletableFuture.completedFuture(Unit)
TelecomCallManager.pending = false
CallVideoEngine.failCreate = false; CallVideoEngine.created.clear()
CallController.reset(); RadioAudioEngine.instance().activeGroup.value = null; RadioAudioEngine.instance().restored.clear()
@@ -53,6 +54,7 @@ class CallServiceLifecycleTest {
}
@After fun release() {
AudioRecorderManager.stopped.complete(Unit)
gates.forEach { it.countDown() }; MediaAudioController.beforeIdle = {}
TelecomCallManager.pending = false
NativeLib.failActivityRequest = false
@@ -486,4 +488,16 @@ class CallServiceLifecycleTest {
assertEquals(CallPhase.ENDED, state?.phase); assertEquals(7, state?.reason)
diagnostic("service request")
}
@Test fun voiceCleanupBlocksSignallingAndRemainsABarrierAfterCallCancellation() {
val voice = java.util.concurrent.CompletableFuture<Unit>()
AudioRecorderManager.stopped = voice
request(); dispatcher.scheduler.runCurrent()
assertTrue(state?.preparing == true)
assertFalse(NativeLib.callCommands.any { it.first == "start" })
CallController.reset()
assertTrue(CallController.blocksMedia()); assertFalse(voice.isCancelled)
voice.complete(Unit)
await("voice release after cancelled call") { !CallController.blocksMedia() }
assertFalse(NativeLib.callCommands.any { it.first == "start" })
}
}
+16
View File
@@ -692,10 +692,26 @@ true/false — ответ ОС, null — ошибка чтения сервис
Android HeadlessService — foreground service с уведомлением и общим native-ядром,
а не Linux control-loop. Он запускает NetworkChangeMonitor и NetworkActivityMonitor.
Foreground подтверждается до запуска native; отказ завершает ожидание startup ошибкой.
Уведомления и независимые части onDestroy логируют ошибки отдельно. CoreShutdown
переживает Service и повторяет отказ stop с backoff 250 мс..30 с; новый init ждёт успешный stop.
Переход UI в фон передаёт client_activity в uasync. Standby чередует ACTIVE/SLEEP;
сам event loop продолжает работать. Ожидания SLEEP будятся после перехода в ACTIVE,
сетевой activity может разбудить после min_sleep. Контракт отмены: `libutun_lite/standby.h`.
AppEventHandler передаёт каждый native event одним main-заданием и независимо вызывает
обработчики владельцев. Сбой одного обработчика не отменяет остальные и доставку ViewModel.
SharedFlow не повторяет старые команды/ACK новой ViewModel; начальный снимок читается
при DB_READY или проверке уже готового ядра. Emit при заполнении буфера ждёт на main coroutine,
без блокировки native-потока. Отказ main-поста и ошибка обработки конкретного события логируются.
AudioRecorderManager сохраняется в process registry с начала захвата либо playback до release ACK.
ViewModel.onCleared запрашивает асинхронную очистку, рабочий поток переживает UI.
Native cancel и Android release повторяются независимо; неудачный AudioTrack/decoder close
остаётся у playback worker также после completion callback. Capture cleanup не держит playback lock.
NativeLib запрещает новый voice capture во время core teardown/звонка; stop ядра и подготовка
звонка ждут освобождения этих владельцев до доступа к native recorder/звонковому I/O.
DEBUG_* фильтруются общими уровнями/категориями. Bridge переводит их в logcat и,
при включённой подписке, Kotlin LogManager. Debug hook также пишет в UDP-буфер:
16KiB threshold либо timer 200мс, неблокирующая отправка без повторов. ERROR вызывает flush.