fix: flow VERSION=2 for new entries; optional ENTER gains the ADB keyevent-66 fallback; CI installs androidtvremote2 for the real-library tests (F4, F5, F6)
This commit is contained in:
1 parent
8462ad494c
commit
5f8c3290cc
6 files changed
+50
-6
No files matched your search
@@ -2,7 +2,9 @@
|
|||||||
#
|
#
|
||||||
# The test suite is stdlib-only (parsers, intents, running-apps) and runs the
|
# The test suite is stdlib-only (parsers, intents, running-apps) and runs the
|
||||||
# vendored ha_adb_common helpers without installing Home Assistant — see
|
# vendored ha_adb_common helpers without installing Home Assistant — see
|
||||||
# tests/conftest.py. Real firmware captures live in tests/fixtures
|
# tests/conftest.py. androidtvremote2 is installed so the real-library
|
||||||
|
# volume/TYPED-dict tests execute (tests/test_protocol_transport.py). Real
|
||||||
|
# firmware captures live in tests/fixtures
|
||||||
# (how to capture more: docs/RESEARCH_SHIELD.md §8).
|
# (how to capture more: docs/RESEARCH_SHIELD.md §8).
|
||||||
|
|
||||||
name: Test
|
name: Test
|
||||||
@@ -19,7 +21,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install test tooling
|
- name: Install test tooling
|
||||||
run: python3 -m pip install --quiet pytest pytest-asyncio ruff
|
run: python3 -m pip install --quiet pytest pytest-asyncio ruff androidtvremote2
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: ruff check custom_components tests
|
run: ruff check custom_components tests
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ def _build_selectors():
|
|||||||
class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Nvidia Shield."""
|
"""Handle a config flow for Nvidia Shield."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 2
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._user_data = {}
|
self._user_data = {}
|
||||||
|
|||||||
@@ -451,6 +451,12 @@ class ShieldConnectionManager:
|
|||||||
return False
|
return False
|
||||||
return await self._poller.send_digit_keyevent(digit)
|
return await self._poller.send_digit_keyevent(digit)
|
||||||
|
|
||||||
|
async def send_enter_key(self) -> bool:
|
||||||
|
"""ENTER via the ADB keyevent path (KEYCODE_ENTER = 66, §7 fallback)."""
|
||||||
|
if not self.available:
|
||||||
|
return False
|
||||||
|
return await self._poller.send_enter_keyevent()
|
||||||
|
|
||||||
# ── Lifecycle ──
|
# ── Lifecycle ──
|
||||||
|
|
||||||
async def async_start(self) -> None:
|
async def async_start(self) -> None:
|
||||||
|
|||||||
@@ -174,6 +174,14 @@ class AdbClientBase:
|
|||||||
_LOGGER.debug("ADB digit keyevent %d (%s)", 7 + int(digit), digit)
|
_LOGGER.debug("ADB digit keyevent %d (%s)", 7 + int(digit), digit)
|
||||||
return result is not None
|
return result is not None
|
||||||
|
|
||||||
|
async def send_enter_keyevent(self) -> bool:
|
||||||
|
"""Send ENTER via `input keyevent` (KEYCODE_ENTER = 66) — the ENTER
|
||||||
|
fallback for the §7 channel sequence."""
|
||||||
|
result = await self.shell("input keyevent 66")
|
||||||
|
if result is not None:
|
||||||
|
_LOGGER.debug("ADB ENTER keyevent (66)")
|
||||||
|
return result is not None
|
||||||
|
|
||||||
async def bring_to_foreground(self, package: str) -> bool:
|
async def bring_to_foreground(self, package: str) -> bool:
|
||||||
"""Bring an app to the foreground using monkey command."""
|
"""Bring an app to the foreground using monkey command."""
|
||||||
result = await self.shell(
|
result = await self.shell(
|
||||||
|
|||||||
@@ -834,9 +834,15 @@ class NvidiaShieldMediaPlayer(MediaPlayerEntity):
|
|||||||
media={"media_content_id": digit, "media_content_type": "channel"},
|
media={"media_content_id": digit, "media_content_type": "channel"},
|
||||||
)
|
)
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
if send_enter:
|
if (
|
||||||
_LOGGER.debug("play_media(channel): optional ENTER key sent")
|
send_enter
|
||||||
await self._manager.send_protocol_key("KEYCODE_ENTER")
|
and self._manager is not None
|
||||||
|
and not await self._manager.send_protocol_key("KEYCODE_ENTER")
|
||||||
|
):
|
||||||
|
# ENTER fallback chain: protocol key → ADB keyevent 66.
|
||||||
|
# (No legacy-entity tier: the external androidtv_remote has
|
||||||
|
# no ENTER service to delegate to.)
|
||||||
|
await self._manager.send_enter_key()
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ class FakeAdbPoller:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.media_calls: list[str] = []
|
self.media_calls: list[str] = []
|
||||||
self.digits: list[str] = []
|
self.digits: list[str] = []
|
||||||
|
self.enter_calls: list[bool] = []
|
||||||
self.results: dict[str, bool] = {}
|
self.results: dict[str, bool] = {}
|
||||||
self.digit_result = True
|
self.digit_result = True
|
||||||
|
|
||||||
@@ -70,6 +71,10 @@ class FakeAdbPoller:
|
|||||||
self.digits.append(digit)
|
self.digits.append(digit)
|
||||||
return self.digit_result
|
return self.digit_result
|
||||||
|
|
||||||
|
async def send_enter_keyevent(self) -> bool:
|
||||||
|
self.enter_calls.append(True)
|
||||||
|
return True
|
||||||
|
|
||||||
async def bring_to_foreground(self, package: str) -> bool:
|
async def bring_to_foreground(self, package: str) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -214,6 +219,23 @@ class TestChannelDigits:
|
|||||||
assert player._manager._poller.digits == ["5"] # ADB digit keyevent served
|
assert player._manager._poller.digits == ["5"] # ADB digit keyevent served
|
||||||
assert served == []
|
assert served == []
|
||||||
|
|
||||||
|
async def test_optional_enter_falls_back_to_adb_keyevent(self, modules):
|
||||||
|
player, served, _i = make_player(modules)
|
||||||
|
protocol = player._manager._protocol
|
||||||
|
poller = player._manager._poller
|
||||||
|
protocol.fail_keys.add("KEYCODE_ENTER")
|
||||||
|
protocol.key_calls.clear()
|
||||||
|
|
||||||
|
player._entry.options["channel_send_enter"] = True # §7 optional ENTER on
|
||||||
|
|
||||||
|
await player._channel_entry_sequence(["4", "1"]) # digits fine, ENTER fails
|
||||||
|
|
||||||
|
# digits served by protocol; ENTER retried over ADB keyevent 66
|
||||||
|
assert ("KEYCODE_4", "SHORT") in protocol.key_calls
|
||||||
|
assert poller.digits == [] # digits were fine over the protocol
|
||||||
|
assert poller.enter_calls == [True]
|
||||||
|
assert served == []
|
||||||
|
|
||||||
async def test_legacy_fallback_when_both_transports_down(self, modules):
|
async def test_legacy_fallback_when_both_transports_down(self, modules):
|
||||||
player, served, _intents = make_player(modules)
|
player, served, _intents = make_player(modules)
|
||||||
protocol = player._manager._protocol
|
protocol = player._manager._protocol
|
||||||
|
|||||||
Reference in new issue
Block a user