diff --git a/doc/chat_call_arch.md b/doc/chat_call_arch.md index fc0ae347..32c36958 100644 --- a/doc/chat_call_arch.md +++ b/doc/chat_call_arch.md @@ -13,6 +13,31 @@ DM-группа, маршрутизация через `etcp_router` идент Полный цикл: проверка онлайна (BGP) → INVITE → звонок → ACCEPT/DECLINE → аудиопоток через роутер → HANGUP. CM инициируется вызывающим при старте, вызываемым при принятии вызова. +### Входящие вызовы и телефонная занятость + +Android хранит пять независимых настроек: показывать статус звонка, предупреждать вызывающего, +отклонять при телефонной занятости, отклонять все обычные звонки, отклонять все приглашения в рацию. +Они передаются маской `CALL_POLICY_*` через `[chat] incoming_call_policy` при старте, +а при изменениях — вместе с живым состоянием телефона через JNI в uasync. +Первые три настройки используют `READ_PHONE_STATE`; запреты всех входящих разрешения не требуют. +Наблюдатель учитывает все активные SIM, набор номера, удержание и входящий телефонный вызов. +Без разрешения или без телефонной службы состояние неизвестно, а не свободно. + +Приоритет: запрет типа вызова → автоматический отказ при телефонной занятости → предупреждение с выбором. +Отказ при занятости возвращает `CALL_REASON_PHONE_BUSY`/`RADIO_CALL_PHONE_BUSY`. +Предупреждение переводит попытку в `WAIT_DECISION`: у вызываемого нет события INCOMING, рингтона и регистрации в Telecom. +Вызывающий видит «Абонент разговаривает по телефону», «Всё равно позвонить» и «Завершить». +Продолжение подтверждается повторяемой командой внутри той же попытки; освобождение телефона само не запускает звонок. +Повторные пакеты не создают повторных входящих событий. При изменении политики перед продолжением запрет проверяется снова. +Предел всей попытки — 60с от начала, включая выбор; продолжение deadline не меняет. +У рации первый ответ и подтверждение продолжения ограничены 2с; сообщение о занятости является ответом, но не RINGING. + +Рация, уже включённая у адресата в указанном канале, возвращает `RADIO_CALL_ALREADY_ACTIVE`: +вызывающий получает уведомление, входящий вызов не показывается, его собственная рация не включается. +Этот ответ описывает существующую подписку и не создаёт нового входящего вызова даже при включённом запрете приглашений. +Принятие обычного приглашения в рацию по-прежнему сначала включает местную подписку, затем отправляет ACCEPT. +Android обычного звонка показывает своё уведомление после `onShowIncomingCallUi` и обрабатывает отказ Telecom. + ## 2. Разбиение файлов (отдельный каталог `src/call/`) | Файл | Язык | Сборки | Назначение | @@ -34,7 +59,7 @@ Autotools-сборка демона остаётся C-only (`.cpp` не ком `ROUTER_SVC_PAYLOAD_OFF=26`, затем subcmd и тело команды. ``` -CALL_INVITE 0x01 {call_id:8, group_id:8, tracks:[{type:AUDIO, codec:OPUS, sr:4, ch:1, bitrate:4}]} +CALL_INVITE 0x01 {call_id:8, group_id:8, num_tracks:1, force:1, expires_us:8, tracks:[{type:AUDIO, codec:OPUS, sr:4, ch:1, bitrate:4}]} CALL_RINGING 0x02 {call_id:8} CALL_ACCEPT 0x03 {call_id:8} CALL_DECLINE 0x04 {call_id:8, reason:1} @@ -42,6 +67,7 @@ CALL_BUSY 0x05 {call_id:8} CALL_CANCEL 0x06 {call_id:8} CALL_HANGUP 0x07 {call_id:8, reason:1} CALL_END_ACK 0x08 {call_id:8} +CALL_PHONE_BUSY 0x09 {call_id:8} CALL_MEDIA 0x10 {call_id:8, track_id:1, seq:2, ts_ms:4, capture_pts_us:8, capture_epoch:8, codec_delay_samples:4, payload} ``` @@ -72,7 +98,7 @@ NO_TRAFFIC / RING_TIMEOUT / CONNECT_FAIL`. держит маршрут живым; - **watchdog 2с у вызываемого** — нет кодограммы 2с → вызов считается сорванным (отмена потерялась), сессия закрывается локально (`CALL_REASON_CANCEL`); -- ring-timeout 45с (вызывающий, нет ответа → CANCEL); +- ring-timeout 60с от начала, включая ожидание решения вызывающего (нет ответа → CANCEL); - `CONN_EVENT_DOWN/TIMEOUT` CM не завершают звонок: роутер использует доступные пути; - **NO_TRAFFIC 20с** — нет входящих медиа-кадров → авто HANGUP.