From c02e126bc634b7f0331ac22cfacc700aa1f82ea1 Mon Sep 17 00:00:00 2001 From: subochev Date: Thu, 1 Oct 2026 14:10:34 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BE=D1=87=D0=BA=D0=B8:=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D0=B3=D0=B0=D1=81=D0=B8=D1=82=D1=8C=20=D1=8D=D0=BA=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=20=D0=BF=D1=80=D0=B8=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B?= =?UTF-8?q?=D1=82=D0=B8=D0=B8=20=D0=BF=D0=BE=D1=81=D1=82=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=BD=D0=BD=D0=B5=D0=B9=20WS-=D1=81=D0=B5=D1=81=D1=81=D0=B8?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Экран залипал в «Нет связи», хотя телефон был подключён и команды доходили. Причина: currentConn — одна ссылка на все сессии, и ЛЮБОЙ onDisconnected переводил экран в NoConnection. Лишние сессии создаёт в т.ч. mDNS-фоллбек телефона (probeWebSocket делает полный WS-апгрейд для проверки кандидата и сразу рвёт соединение). Теперь очки ведут набор живых сессий и гасят экран только когда закрылась последняя. --- .../pw/binom/rayneovm/glasses/VmService.kt | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/app-glasses-vm/src/main/kotlin/pw/binom/rayneovm/glasses/VmService.kt b/app-glasses-vm/src/main/kotlin/pw/binom/rayneovm/glasses/VmService.kt index 70237a9..da34fd2 100644 --- a/app-glasses-vm/src/main/kotlin/pw/binom/rayneovm/glasses/VmService.kt +++ b/app-glasses-vm/src/main/kotlin/pw/binom/rayneovm/glasses/VmService.kt @@ -174,6 +174,16 @@ class VmService : LifecycleService() { */ private val currentConn = java.util.concurrent.atomic.AtomicReference(null) + /** + * Все живые WS-сессии. Связь с телефоном считаем живой, пока жива ХОТЬ ОДНА. + * + * Нельзя вешать `NoConnection` на закрытие конкретной сессии: помимо телефона сюда + * прилетают короткоживущие сессии (mDNS-фоллбек телефона делает полный WS-апгрейд + * для проверки кандидата и сразу рвёт соединение). Из-за этого экран очков залипал + * в «Нет связи», хотя телефон был подключён и команды доходили. + */ + private val liveConns = java.util.concurrent.ConcurrentHashMap.newKeySet() + /** Ресивер `ACTION_SCREEN_ON/OFF`: сообщаем телефону реальное состояние экрана очков. */ private var screenReceiver: BroadcastReceiver? = null @@ -186,7 +196,8 @@ class VmService : LifecycleService() { handleIncoming(connId, json) }, onConnected = { connId -> - Log.i(TAG, "Phone connected: $connId") + liveConns.add(connId) + Log.i(TAG, "Phone connected: $connId (всего сессий: ${liveConns.size})") currentConn.set(connId) // Переподключение: снять залипшее «Нет связи с телефоном». controller.onPhoneConnected() @@ -196,11 +207,15 @@ class VmService : LifecycleService() { lifecycleScope.launch { sendScreenState(connId) } }, onDisconnected = { connId -> - Log.i(TAG, "Phone disconnected: $connId") - if (currentConn.compareAndSet(connId, null)) { + liveConns.remove(connId) + Log.i(TAG, "Session closed: $connId (осталось: ${liveConns.size})") + // «Push»-сессию переводим на любую живую, если ушла именно она. + if (currentConn.get() == connId) { + currentConn.set(liveConns.firstOrNull()) + } + // Экран гасим ТОЛЬКО когда не осталось ни одной сессии. + if (liveConns.isEmpty()) { controller.setState(GlassesScreenState.NoConnection) - } else { - Log.i(TAG, "Ignoring disconnect of stale session $connId") } }, )