diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/RadioCallDialog.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/RadioCallDialog.kt index d90df74c..2d68c275 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/RadioCallDialog.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/RadioCallDialog.kt @@ -26,7 +26,10 @@ import com.utun.chat.data.RadioCallPhase import com.utun.chat.data.RadioCallState import com.utun.chat.ui.components.PortableRadioIcon -/** Как входящий звонок, рисуется внутри Activity, в том числе поверх keyguard. */ +/** Полноэкранная карточка снимка RadioCallState внутри Activity; режим keyguard задаёт сама Activity. + * INCOMING предлагает accept/decline, WAIT_DECISION — продолжить/завершить. Во время PREPARING кнопок нет: + * Activity возвращает фактический результат включения рации через activated. ENDED показывает причину + * до удаления карточки контроллером. Компонент не владеет звуком, сигналингом или deadline вызова. */ @Composable fun RadioCallDialog(state: RadioCallState) { Box(Modifier.fillMaxSize().background(Color(0xCC000000)), contentAlignment = Alignment.Center) { diff --git a/tools/chatgui-android/doc/ARCHITECTURE.md b/tools/chatgui-android/doc/ARCHITECTURE.md index 4f389ea5..49ded8ca 100644 --- a/tools/chatgui-android/doc/ARCHITECTURE.md +++ b/tools/chatgui-android/doc/ARCHITECTURE.md @@ -235,6 +235,43 @@ feed/pull workers и маршрутом Android Telecom; DSP и сетевой Вызовы lifecycle DSP выполняются после остановки его внешних feed/pull callers. Контракты PCM/PTS: `src/call/call_audio.h`, `src/radio/radio_audio.h`, `jni_bridge/android_jni_bridge.h`. +`RadioCallController` на main управляет адресным приглашением участника в обычную +рацию канала. Это отдельный сигналинг `src/radio/radio_call.h`, без отдельного +аудиозвонка. START резервирует busy до первого события ядра: быстрое повторное +нажатие не создаёт второй запрос, а CoreActivity удерживает активность также +на этом промежутке. Отказ очереди освобождает резерв, reset отменяет pending. + +Событие 65 содержит ровно 27 little-endian байтов: id/group/peer по 8, затем +phase/reason/incoming по 1. ID ненулевые; Long хранит все биты uint64, строка +канала — десятичный uint64. Проверяются диапазоны и сочетания роли/фазы/причины, +а у своей попытки — вся тройка и incoming. Посторонние/устаревшие события не +меняют живую попытку и не публикуют результат от старой. При недоступных именах +repository UI использует ID, READY по-прежнему отправляется. + +Входящий сначала публикует оповещение и отправляет READY. Ответ из уведомления +ждёт onStart Activity, разрешение микрофона перепроверяется; callbacks старой +попытки не принимают новую. Accept публикует PREPARING/activation, Activity +включает обычную рацию и возвращает activated(id, success). Только тогда входящий +отправляет ACCEPT либо FAIL и ждёт FINISHED. У исходящего ACCEPTED запрашивает +локальную активацию; её успешное завершение сразу снимает карточку. Поздние +RINGING/WAIT_DECISION/повторный FINISHED не откатывают исходящий PREPARING. +CONTINUE разрешён только своей WAIT_DECISION-попытке и сохраняет id. + +Отказ/исключение JNI логируется с action/id/group/peer. Неотправленный READY, +ACCEPT/FAIL или CONTINUE завершает локальную попытку; DECLINE/CANCEL очищает UI +независимо от очереди. Блокирующая захват трансляция отправляет FAIL и закрывает +оповещение. Ошибка CoreActivity не прерывает сигналинг и независимую очистку: +CoreActivity сохраняет намерение для следующего sync. Deadline приглашения +остаётся в ядре; приложение владеет только отменяемой задержкой 2,5 с удаления +ENDED-карточки. Новая попытка/reset отменяет её, callback проверяет сам снимок. + +Завершение приглашения не выключает обычную рацию. StateFlow повторяет текущие +state/activation для пересозданной Activity; история завершённых вызовов не +восстанавливается. RadioCallDialog отображает этот снимок и передаёт действия +контроллеру, не управляя deadline/аудио. Результат видимой Activity показывается +Toast, в фоне — отдельным уведомлением 1004, без замены уведомлений сервисов. +Отказ уведомления/локализации не мешает удалению карточки. + `RadioPttHolds` на main хранит по одному ручному удержанию BUTTON/OVERLAY/VOLUME. Повтор press того же источника идемпотентен; несколько volume-клавиш агрегирует Activity и отпускает общий VOLUME после последней клавиши. Release возвращает @@ -512,6 +549,13 @@ native-эффектом, отказ feed/PTT/границы, отсутстви после запроса новой группы. Проверяются освобождение подписок/encoder/track и сохранение нового PTT. Android/JNI в этих сценариях моделируются на границах; DSP, звуковое оборудование и сетевой эфир не запускаются. +RadioCallLifecycleTest использует настоящие RadioCallController, IncomingCallManager, +CoreActivity и гудки. Управляемые Android/JNI/repository-границы проверяют все +команды сигналинга, pending START, валидацию событий/полной identity, активацию, +старые callbacks/таймеры, PHONE_BUSY/CONTINUE, uint64 ID, отказы активности и +публикации результатов. IncomingCallLifecycleTest также использует настоящий +рационный контроллер при доставке DECLINE. Радиоэфир и C-сигналинг в JVM не +запускаются; native-протокол проверяется отдельным test_radio. На Linux `:app:testDebugUnitTest` также запускает 25 fault-сценариев настоящего JNI logger через `:app:testJniLog`; окружение и границы модели: `tests/jni_log/README.md`. Проверки native bridge/standby доступны в `tests/Makefile.am`;