fix(sensor): F2 — the Current App sensor shows the resolved app name (t40)
Reported from a live install: the sensor showed ``ar.tvplayer.tv`` while the media player showed "TiviMate" — the same foreground app, two identities. The sensor's ``native_value`` returned ``manager.foreground_app`` (the raw package push) or ``running_apps[0]["package"]`` as-is, while the media player resolved names through apps map → cached manifest label → androidtv entity tier. Both now call ONE function, ``resolve_app_name`` (media_player.py), so the two entities cannot disagree again; the sensor keeps the raw package as its ``app_id`` attribute and ``app_name`` carries the shown name. Per the contract: * apps map wins, then the cached manifest label, then the androidtv entity's ``app_name``, then the package text itself; * a bare or unknown package falls back to the package text, NOT None (it used to be possible for the value to vanish while the app was plainly running); * pinned by tests: map tier, label tier, entity tier, the ADB-recents source, the unknown-package fallback, and sensor == media player for the same inputs. Verified: 302 passed / 2 skipped, ruff clean.
This commit is contained in:
1 parent
be3abae3e7
commit
dd23d01a88
3 files changed
+177
-34
No files matched your search
@@ -1172,3 +1172,95 @@ class TestBrowseMediaDirectory:
|
||||
await player.async_play_media("app", selected.media_content_id)
|
||||
|
||||
assert protocol.launch_calls == [selected.media_content_id] # C-LAUNCH
|
||||
|
||||
|
||||
def make_app_sensor(modules, manager, entry, *, atv_state=None):
|
||||
"""A Current App sensor over the given manager/entry (t40)."""
|
||||
from types import SimpleNamespace
|
||||
|
||||
states = {"media_player.atv": atv_state} if atv_state is not None else {}
|
||||
hass = SimpleNamespace(states=SimpleNamespace(get=states.get))
|
||||
return modules["sensor"].NvidiaShieldCurrentAppSensor(hass, entry)
|
||||
|
||||
|
||||
class TestCurrentAppSensorFriendlyName:
|
||||
"""t40: the sensor shows the RESOLVED name; the raw package is an attribute.
|
||||
|
||||
It used to publish ``manager.foreground_app`` — the raw package id — so a
|
||||
live install showed "ar.tvplayer.tv" where the media player showed
|
||||
"TiviMate". Both now resolve through the shared ``resolve_app_name`` chain.
|
||||
"""
|
||||
|
||||
def _sensor(self, modules, player):
|
||||
# The sensor resolves the manager through entry.runtime_data — the same
|
||||
# wiring a real setup gives it.
|
||||
player._entry.runtime_data = player._manager
|
||||
return make_app_sensor(modules, player._manager, player._entry)
|
||||
|
||||
def test_mapped_package_shows_the_friendly_name(self, modules):
|
||||
player, _served, _intents = make_player(modules)
|
||||
player._entry.options["app_name_map"] = {"ar.tvplayer.tv": "TiviMate"}
|
||||
sensor = self._sensor(modules, player)
|
||||
|
||||
player._manager._on_current_app("ar.tvplayer.tv")
|
||||
|
||||
assert sensor.native_value == "TiviMate"
|
||||
assert sensor.extra_state_attributes["app_id"] == "ar.tvplayer.tv"
|
||||
assert sensor.extra_state_attributes["app_name"] == "TiviMate"
|
||||
|
||||
def test_cached_manifest_label_is_the_second_tier(self, modules):
|
||||
player, _served, _intents = make_player(modules)
|
||||
manager = player._manager
|
||||
manager._app_labels["com.netflix.ninja"] = ("Netflix", manager._clock())
|
||||
sensor = self._sensor(modules, player)
|
||||
|
||||
manager._on_current_app("com.netflix.ninja")
|
||||
|
||||
assert sensor.native_value == "Netflix"
|
||||
assert sensor.extra_state_attributes["app_id"] == "com.netflix.ninja"
|
||||
|
||||
def test_androidtv_entity_tier_serves_when_no_map_or_label(self, modules):
|
||||
from types import SimpleNamespace
|
||||
|
||||
player, _served, _intents = make_player(modules)
|
||||
player._entry.data["androidtv_entity"] = "media_player.atv"
|
||||
atv_state = SimpleNamespace(
|
||||
attributes={"app_id": "ar.tvplayer.tv", "app_name": "ATV Tivi"}
|
||||
)
|
||||
sensor = make_app_sensor(modules, player._manager, player._entry, atv_state=atv_state)
|
||||
|
||||
player._manager._on_current_app("ar.tvplayer.tv")
|
||||
|
||||
assert sensor.native_value == "ATV Tivi"
|
||||
|
||||
def test_recent_apps_fallback_resolves_through_the_label_tier(self, modules):
|
||||
player, _served, _intents = make_player(modules)
|
||||
manager = player._manager
|
||||
manager._app_labels["ar.tvplayer.tv"] = ("TiviMate", manager._clock())
|
||||
manager.store.set("running_apps", [{"package": "ar.tvplayer.tv"}])
|
||||
sensor = self._sensor(modules, player)
|
||||
|
||||
assert sensor.native_value == "TiviMate"
|
||||
|
||||
def test_unknown_package_falls_back_to_the_package_text(self, modules):
|
||||
player, _served, _intents = make_player(modules)
|
||||
sensor = self._sensor(modules, player)
|
||||
|
||||
player._manager._on_current_app("com.example.unknown")
|
||||
|
||||
assert sensor.native_value == "com.example.unknown" # not None
|
||||
assert sensor.extra_state_attributes == {
|
||||
"app_id": "com.example.unknown",
|
||||
"app_name": "com.example.unknown",
|
||||
}
|
||||
|
||||
def test_sensor_and_media_player_resolve_identically(self, modules):
|
||||
"""One chain, two entities — they cannot disagree about the app name."""
|
||||
player, _served, _intents = make_player(modules)
|
||||
player._entry.options["app_name_map"] = {"ar.tvplayer.tv": "TiviMate"}
|
||||
player._androidtv_entity = "" # standalone, like the unit harness
|
||||
sensor = self._sensor(modules, player)
|
||||
|
||||
player._manager._on_current_app("ar.tvplayer.tv")
|
||||
|
||||
assert sensor.native_value == player.app_name == "TiviMate"
|
||||
Reference in new issue
Block a user