docs(call): record validated Bluetooth lifecycle fix and regressions

This commit is contained in:
evgeny committed 2026-10-26 20:22:27 +01:00
1 parent 2e8885548b
commit b84a6ab3b9
2 files changed
+139 -1

No files matched your search

+126 -1
View File
@@ -25,7 +25,11 @@ desktop-профиль25/25 PASS; подтверждён дефект Android mu
Новая регрессия5/5, voice/desktop30/30, Android timing/trace14/14,
assembleDebug для двух ABI и check203/203 PASS,proxy9/9,burst1/1,load8/8,168с.
Диагностика/план — раздел13, внедрение/актуальная проверка — раздел14.
База проверки: f79b9261, чистое рабочее дерево после предыдущего push stream.
Продолжена проверка Android playback/router. После отдельного согласования
плана исправлено владение Bluetooth proxy при позднем callback, disconnect
и restart; постоянные JVM-регрессии15/15, прежний профиль26/26,
voice30/30, сборки и check204/204 PASS. Подробности — раздел15.
Исходная база аудита: f79b9261, чистое дерево после предыдущего push stream.
1. Подтверждённый дефект: call_start не проверяет обычную занятость
@@ -1036,3 +1040,124 @@ IPv6/lo включены только в отдельном sudo unshare --net;
запускались. Реальная акустика/переключение mute на устройстве требуют отдельного
контроля; непосредственное последействие lossy Opus-декодера этим тестом не измеряется.
Новые коммиты локальные, push в этом шаге не выполнялся.
15. Android I/O: владение Bluetooth profile proxy и проверка исправления
База этого продолжения — опубликованный fa76447f, после rebase Android mute.
Прочитаны CallAudioEngine/CallAudioOutput, playback clock/buffer/recovery,
CallAudioRouter, владельцы в CallController/MediaAudioController и native
android_call_output_jni.c. Это продолжение платформенной проверки голоса;
видео и оставшиеся core/platform контракты целиком не закрыты.
Подтверждённый дефект и SDK-контракт
Прежний router принимал onServiceConnected без проверки started/поколения.
stop до первого callback оставлял принятый Android proxy открытым;
disconnect обнулял btHeadset/btProfileProxyBound, хотя владение сохранялось;
при stop/start старый callback мог подменить proxy нового lifecycle.
При последующем connected новый proxy перезаписывал ссылку на старый.
Сверены локальные исходники установленного Android SDK36:
BluetoothAdapter.java:884–931,3412–3491,3495–3539,3690–3740;
BluetoothHeadset.java:498–512 в /home/user/android/sources/android-36/android/bluetooth/.
getProfileProxy регистрирует proxy до доставки callback через main Handler.
Отключение сервиса/Bluetooth сохраняет регистрацию до closeProfileProxy;
закрытие идемпотентно, getConnectedDevices без сервиса возвращает пустой список.
MediaAudioController действительно вызывает router.start из I/O executor,
поэтому одного сравнения поколения вне общей критической секции недостаточно.
Сначала без изменений проекта проверены два штатных контроля PASS и три
ожидаемых FAIL старого router: late, disconnected, pending_restart.
Временная модель в /tmp/call-router-audit управляет порядком API callbacks;
читает приватное состояние только для диагностики, не подставляет его.
Кандидат /tmp/call-router-audit/CallAudioRouterCandidate.kt:9/9 PASS,
включая64 конкурентных callback/stop; после cleanup нет открытых proxy.
Компиляция кандидата с настоящими типами SDK36 PASS; прежний Android
timing/trace/buffer/recovery профиль26/26 PASS. Проект/документы/чек-лист
на этапе обсуждения плана не менялись, новых коммитов не было.
/tmp/call-router-plan-candidate.diff, /tmp/call-router-plan-{build,results,sdk-build,validation}.log;
исходные логи /tmp/call-router-audit-*.log.
Согласованный план и внедрение
По запросу пользователя план сначала изложен в чате и проверен на временном
кандидате; после следующего «ok» внедрён в production CallAudioRouter:
1) btHeadset означает владение; btProfileAdapter хранит создавший adapter.
Удалён дублирующий флаг btProfileProxyBound. started/proxy имеют volatile
для читателей вне монитора; adapter/поколение используются под монитором.
2) start, уже синхронизированный stop и оба BT callbacks используют один
существующий монитор router. Новых потоков и объектов lock нет.
3) Запрос захватывает поколение/adapter. Текущий connected принимает proxy;
поздний connected закрывает его через захваченный adapter, не меняя owner.
4) Текущий disconnected сохраняет ссылки и обновляет доступность устройств;
старый disconnected игнорируется. Reconnect того же proxy разрешён.
5) stop инвалидирует поколение до раннего return, отсоединяет ссылки до
Android close и закрывает также proxy отключённого сервиса. Повтор stop
не закрывает тот же owner повторно. Пока pending-запрос не дал proxy,
публичный API не позволяет закрыть его: очистка происходит при connected.
Логи CallAudioRouter содержат generation/current, proxy, accepted,
connected/retained/owned closed/stale closed/ignored. Отказ запроса и API
exceptions имеют WARN/ERROR; успешный media hot path не получил новых логов.
Сигнатуры публичного router, wire, JNI, DSP и алгоритмы playback не менялись.
Постоянная регрессия и отрицательный контроль
Добавлен отдельный Kotlin/JVM Gradle-модуль tools/chatgui-android/router-tests.
Он компилирует реальный CallAudioRouter.kt приложения с минимальными моделями
Android API. Поля router не подменяются и не читаются через reflection;
критерии — публичная доступность/route, callbacks, API close и реестр proxy
на стороне fixture. Модель не создаёт системных Bluetooth/Binder-ресурсов.
JUnit15/15 PASS, failures0/errors0/skipped0:
- connected/stop, два штатных restart, duplicate start и повтор stop;
- late connected после stop, disconnect/stop, pending stop/start;
- reconnect одного proxy, старый disconnected после принятия нового proxy;
-64 конкурентных callback/stop с join и проверкой ownership после каждого;
- I/O start, задержанный внутри getProfileProxy барьером, одновременные
callback/stop: все потоки завершаются, proxy/device callback освобождены;
- rejected request, exception, отсутствие adapter, отзыв permission после acquire.
Все положительные cleanup оставляют0 proxy и0 зарегистрированных device callbacks;
неожиданных ERROR нет. Два WARN намеренно проверяют rejected/throw request.
Обычная задача :app:testDebugUnitTest зависит от :router-tests:test только
через зависимость задач. JVM fixtures не включены в classpath приложения/APK.
JUnit используется той же версии4.13.2, Kotlin1.9.20; новых внешних библиотек нет.
Параметры routerSourceDir/routerTestsBuildDir позволяют независимый старый
исходник/выход сборки в /tmp без замены production файлов или положительных результатов.
С router из fa76447f: штатный connectedStop PASS, late/disconnected/pendingRestart
дают ровно три ожидаемых FAIL из четырёх тестов, Gradle exit1.
Каждый FAIL показывает оставшееся владение; @After очищает только объекты
fixture. Это проверка чувствительности к исходному дефекту, не сбой исправления.
/tmp/call-bt-fix-negative.log и /tmp/call-bt-fix-negative-build/test-results/test/.
Положительный и отрицательный вывод новых сценариев прочитан полностью.
Сборка и общий профиль
./build.sh --no-gui -j4 PASS. Android Gradle одним запуском выполняет
:router-tests:test, :app:testDebugUnitTest для Timing8/Trace6/PlaybackBuffer10/
OutputRecovery2 и assembleDebug:15+26 PASS, BUILD SUCCESSFUL13с.
APK содержит arm64-v8a/x86_64 libutun.so, fixture-маркеров в DEX нет.
Известное предупреждение AGP8.2 о compileSdk36 и четыре unused-параметра
моделей API не являются ошибками сборки. APK не устанавливался.
После уточнения только комментария снимка устройств финальный assembleDebug
также PASS,1с; поведение router после положительных тестов не менялось.
/tmp/call-bt-fix-{native-build,android-build,android-final-build,gradle}.log;
JUnit XML в router-tests/build/test-results/test и app/build/test-results/testDebugUnitTest.
CMake configure/build и voice/desktop30/30 PASS,20,41с. Включены прежние
тесты mute/TX/часов/очередей/восстановления. Эти тесты не выполняют Bluetooth.
/tmp/call-bt-fix-voice-{configure,build,tests,details}.log.
Полный ./check.sh:204 passed,0 failed,0 skipped,exit0,173с;
proxy9/9,burst1/1 (8388608 байт),load8/8 (134217728 байт), обе стороны rc0.
JVM-регрессии15 и CMake voice30 в число C-only204 не входят.
/tmp/call-bt-fix-{full-check,check-runner}.log и {check,netns}.sh.
Весь общий вывод прочитан. IPv6/lo включались только в отдельном network
namespace; check работал от user. Host all/default/lo disable_ipv6=1 сохранены.
Задача закрыта, doc/chat_call_arch.md обновлён: BT ownership, AAudio20мс/
AudioTrack40мс, reporter resize и переоткрытие выхода playback worker,
сохранение незаписанного хвоста и база общего счётчика без обещания replay
прежней принятой устройством очереди. Runtime playback этим исправлением не менялся.
Общий аудит остаётся частичным:call0[V]/11[~]/10[ ],desktop8[~],архитектура[~],видео[ ].
Настоящие Binder callbacks, акустика/BT на телефоне, новый sanitizer и
Windows/FreeBSD runtime не проверялись. Коммиты локальные, push не выполнялся.
+13
View File
@@ -33,6 +33,19 @@
Подробности: [dm_audit_findings.txt](dm_audit_findings.txt),раздел9; /tmp/dm-utf8-fix-*.log.
Исправление закрыто; полный аудит DM остаётся частичным, аппаратные GUI/Android не проверялись.
[+] **Android CallAudioRouter: владение Bluetooth proxy при stop/restart** — исправлено 26.10.2026.
Поколение запроса и общий монитор start/stop/BT callbacks исключают принятие proxy старого lifecycle.
Disconnect сохраняет owner до close; stop отсоединяет ссылки и закрывает через создавший adapter,
поздний connected закрывает свой proxy. Диагностика содержит proxy/generation/accepted и ошибки API.
Отдельный JVM-модуль компилирует production router с управляемой моделью Android API:15/15 PASS,
включая64 конкурентных callback/stop и I/O start с задержанным callback; после cleanup0 proxy/callbacks.
Прежний исходник:штатный контроль PASS,три ownership-сценария ожидаемо FAIL.
Android clock/trace/buffer/recovery26/26,voice/desktop30/30,нативная сборка и assembleDebug для двух ABI PASS.
check.sh204/204 PASS,0 failed/0 skipped;proxy9/9,burst1/1,load8/8,exit0,173с.
Fixtures не входят в APK;Bluetooth/Binder/звук на устройстве не проверялись. Общий аудит call остаётся частичным.
Подробности: [call_audit_findings.txt](call_audit_findings.txt),раздел15;
/tmp/call-bt-fix-{gradle,negative,voice-tests,full-check}.log.
[+] **Android call mute: настоящий PCM и флаг mute передаются через JNI до core** — исправлено 26.10.2026.
Kotlin передаёт исходный buf и snapshot muted на кадр; NativeLib/JNI/C bridge вызывают feed_pcm_muted.
AEC/AGC продолжают получать микрофон, mute зануляет PCM непосредственно перед Opus; wire/DSP/desktop не менялись.