diff --git a/custom_components/nvidia_shield/connection.py b/custom_components/nvidia_shield/connection.py index 763ddaa..f1d2f99 100644 --- a/custom_components/nvidia_shield/connection.py +++ b/custom_components/nvidia_shield/connection.py @@ -619,22 +619,27 @@ class ShieldConnectionManager: from adb_shell.adb_device_async import AdbDeviceTcpAsync from adb_shell.auth.sign_pythonrsa import PythonRSASigner + config = poller.config # public accessor (t27 F-NEW3) + async def _lines(): - key_path = getattr(poller, "_key_path", None) or getattr( - poller, "key_path", None - ) - host = getattr(poller, "_host", None) or getattr(poller, "host", None) - port = getattr(poller, "_port", None) or getattr(poller, "port", None) loop = asyncio.get_running_loop() signer = await loop.run_in_executor( - None, PythonRSASigner.FromRSAKeyPath, key_path + None, PythonRSASigner.FromRSAKeyPath, config["key_path"] ) - device = AdbDeviceTcpAsync(host, port, default_transport_timeout_s=10.0) + device = AdbDeviceTcpAsync(config["host"], config["port"]) connected = await device.connect(rsa_keys=[signer], auth_timeout_s=10.0) if not connected: raise RuntimeError("dedicated logcat connection failed") + # F-NEW1 (HIGH): quiet logcat is NORMAL on a bored device — the + # TCP transport timeout must NOT be the idle detector or every + # ≥10 s gap becomes a TcpTimeoutException streak that ends in + # the session auto-disable in the field. transport_timeout_s=None + # makes bulk_read block indefinitely; real loss (dead socket) is + # the ladder's job (reconnect with backoff), and async_stop is + # the cleanup path. async for line in device.streaming_shell( - "logcat -v brief ActivityTaskManager:*" + "logcat -v brief ActivityTaskManager:*", + transport_timeout_s=None, ): yield line @@ -660,11 +665,23 @@ class ShieldConnectionManager: async def _logcat_pass(self) -> None: """One full consumption of the logcat line-stream (P5-3). - Returns when the stream ends (the dedicated connection dropped); - errors propagate to the caller's ladder. + Yields lines are buffered 3-deep (t27 F-NEW2) before parsing so a + START record whose cmp= continuation spans stream lines is not + missed. Returns when the stream ends (the dedicated connection + dropped); errors propagate to the caller's ladder. """ + buffer: list[str] = [] async for line in self._open_logcat_stream(): - package = foreground_from_logcat([str(line)]) + buffer.append(str(line)) + if len(buffer) < 3: # small wrap window (t27 F-NEW2) + continue + package = foreground_from_logcat(buffer) + if package: + self._on_logcat_app_start(package) + buffer = buffer[-2:] # keep the tail for a pending continuation + # flush the tail buffer once the stream ends + if buffer: + package = foreground_from_logcat(buffer) if package: self._on_logcat_app_start(package) raise RuntimeError("stream ended (device closed)") diff --git a/custom_components/nvidia_shield/ha_adb_common/adb_client.py b/custom_components/nvidia_shield/ha_adb_common/adb_client.py index 12d9547..a52c489 100644 --- a/custom_components/nvidia_shield/ha_adb_common/adb_client.py +++ b/custom_components/nvidia_shield/ha_adb_common/adb_client.py @@ -80,6 +80,20 @@ class AdbClientBase: def connected(self) -> bool: return self._connected + @property + def config(self) -> dict: + """Public connection config: {host, port, key_path} (t27 F-NEW3). + + Replaces the private-attr reach-ins for secondary streams — callers + (the dedicated logcat stream) share the signer material without + touching the poller's socket or its private attributes. + """ + return { + "host": self._host, + "port": self._port, + "key_path": self._key_path, + } + @property def last_info(self) -> MediaSessionInfo | None: return self._last_info