8.8 KiB
TASK-cancel-listen: отмена слушания двойным тапом
Симптом (с железа, 24.08)
Пользователь в чате: сказал фразу → ассистент ответил. Далее один тап (чтобы снова начать слушать) → очки начали слушать (микрофон открыт), пользователь молчит. Теперь хочет отменить слушание — по замыслу это двойной тап — но отмена НЕ происходит: либо ничего не меняется, либо микрофон снова открывается, кружок записи гаснет/возвращается непонятно. Состояние «слушаю» залипает.
Причина (расследование — opencode)
Ключевой дефект: на стороне очков ветка SttCancel реагирует на reason некорректно.
- «Пустой клик» (тап без голоса) и «двойной клик-отмена» на телефоне приходят на очки ОДИНАКОВО
(
SttCancelсreason="user"), и очки на любой делаютresumeSttListening()→ микрофон снова открыт, из режима не выходим. - После закрытия микрофона (
sendPhrase()/cancelDictation(),MainActivity.kt~279-321) флагlisteningостаётсяtrue, и вAssistantGestures.kt(~34-39)listeningпобеждает все остальные ветки → никакого двойного тапа в ANSWER/THINKING не видно, «выход из режима» недоступен.
Целевое поведение (договорно)
- Один тап (в режиме общения, не слушаю) → начать слушать (микрофон открыт,
listening=true, красный кружок). Без изменений. - Пустой клик без голоса (тап, но человек ничего не сказал) → телефон шлёт
StopStt(cancel=false)с пустым текстом → очки возвращаются в «слушаю» (как сейчас,resumeSttListening) — это желаемое поведение, НЕ трогать. - Двойной тап в состоянии «слушаю» → отмена диктовки: микрофон закрыт,
listening=false,thinking=false, остаёмся в режиме общения (показываем последний ответ), НЕ выходим из режима целиком. И НЕ открываем микрофон заново. Это новое поведение — сейчас оно не работает. - Таймаут тишины 30с (
reason="timeout") → выход ИЗ режима общения (exitAssistantMode), как сейчас. НЕ трогать. - После любой посылки/отмены микрофон должен быть закрыт,
listeningсбрасывать наfalse.
Механика правки (семантика протокола)
SttCancel.reason — свободная строка, значения:
"user"— пустой клик без голоса → очки возвращаются в «слушаю» (resume). (без изменений)"timeout"— тишина 30с → очки ВЫХОДЯТ из режима (exitAssistantMode). (без изменений)"cancel"— НОВОЕ: явная отмена диктовки двойным тапом → очки закрывают микрофон,listening=false,thinking=false, остаются в режиме (показывают последний ответ), НЕ открывают микрофон заново.
Точки правки (подтверди точные строки/логику при чтении)
- lib-core
.../core/protocol/Messages.kt—SttCancelуже несётreason: String = "user". Ввести константу-компаньонSttCancel.REASON_CANCEL = "cancel"(и REASON_USER/REASON_TIMEOUT для читабельности). Протокол меняется на ОБЕИХ сторонах — после правки переустанавливать И app-glasses, И app-phone (рассинхрон версий вернёт старый баг). - app-phone
.../phone/GlassesServer.kt(~206) — обработчикStopStt:StopStt(cancel=true)→ бродкаститьSttCancel(reason=REASON_CANCEL)(ранее шло "user").StopStt(cancel=false)с пустымfull→SttCancel(reason=REASON_USER)(возврат в «слушаю»).StopStt(cancel=false)с непустым текстом →SttDone(...)(без изменений).
- app-glasses
.../glasses/.../HostConnection.kt(~362-377) — веткаSttCancel:reason == REASON_CANCEL→ закрыть микрофон +listening=false+thinking=false, НЕresumeSttListening(), НЕ выход из режима — остаться в режиме со следующим ответом.reason == REASON_USER→ как сейчас (thinking=false+resumeSttListening()).reason == REASON_TIMEOUT→exitAssistantMode()(как сейчас).
- app-glasses
.../glasses/MainActivity.kt(~279-321) —sendPhrase()иcancelDictation(): после закрытия микрофона обязательноlistening=false. Ввести/использоватьcancelDictation()как точку «отмена слушания» (закрывает микрофон, сбрасываетlistening). - app-glasses
.../glasses/gestures/AssistantGestures.kt(~34-39, ~103-107) — проверить, что двойной тап в ветке LISTENING маршрутизируется вcancelDictation()(которое шлётStopStt(cancel=true)→ на телефонеStopStt(cancel=true)→SttCancel(REASON_CANCEL)). Убедиться, что приоритетlisteningне перебивает двойной тап и не «залипает».
Подтвердить при чтении (не догадываться)
- Реально ли
GestureDetectorWithSoundпри двойном тапе НЕ дёргает промежуточный single-Click (гонка таймингов). Если дёргает — двойной тап превращается в single+single и отмена не срабатывает. При необходимости поправить дискриминацию single/double. - Точные имена полей/функций в
Messages.ktи номера строк вHostConnection.kt/GlassesServer.kt— сверить с реальным кодом, не брать на веру из описания выше.
Ограничения
- НЕ менять поведение
reason="user"(пустой клик → возврат в «слушаю») иreason="timeout"(выход из режима) — это зафиксированные решения от 23.08. - Рабочая копия:
/root/WORK/view-mate. Не трогать абсолютные пути вне workspace. - Картинки/red-индикатор (
504e66f) и историю диалога (706cc10) не ломать. - Язык кода — только Kotlin.
Тесты
- JVM-юнит (lib-core + app-glasses gestures): новый
SttCancel(reason="cancel"):- очки НЕ вызывают
resumeSttListening(), НЕ делаютexitAssistantMode(),listening=false. reason="user"→resumeSttListening()(регресс).reason="timeout"→exitAssistantMode()(регресс).
- очки НЕ вызывают
- app-phone test (
GlassesServerTest):StopStt(cancel=true)→ бродкаститсяSttCancel("cancel");StopStt(cancel=false)пустой →SttCancel("user"); непустой →SttDone. - Гейт:
./gradlew :lib-core:jvmTest :app-glasses:testDebugUnitTest :app-phone:testDebugUnitTest. - Полная сборка:
./gradlew :app-phone:assembleDebug :app-glasses:assembleDebug :lib-core:jvmTest.
Примечание по приёмке на железе
DEBUG-крюк: adb -s 7687f645 shell 'am broadcast -a pw.binom.viewmate.glasses.DEBUG_GESTURE --es type double_click'
проверяет, как машина жествов реагирует на двойной тап в состоянии «слушаю». Проверка на очках
после установки — за Hermes-ом (не агента).