t66: app names on a device that exposes none
The reported Shield names no app: `pm dump com.netflix.ninja` is ~2241 lines of
dumpsys and grep for `application-label` over the whole capture finds nothing.
The probe shipped `| head -300`, which cut the dump BEFORE the package section,
so "no label" was a truncation artefact; the sweep then named every app after
its own package id, and the apps editor pre-filled the `pkg=pkg` row the user
read as a bug.
* the probe greps ON THE DEVICE for the three shapes Android prints
(`application-label`, `nonLocalizedLabel`, `labelRes`); a quoted value is a
name, `null` and a resource id are not. The dump is never truncated.
* after APP_LABEL_PROBE_LIMIT consecutive "ran, no label" answers the device
is taken at its word: the sweep stops spending a shell per package
(APP_LABEL_TTL expiry re-checks, and a package whose probe FAILED still owes
its retry — t57 UNIT 0 survives, driven by the app on screen).
* ONE precedence chain in `ha_adb_common/app_label.resolve_package_name`, used
by the media player, the Current App sensor, the discovery table, the source
list and the apps editor: user map > device label > curated map > package id
(the id shown ONCE, `name` empty when nothing names the app).
* the curated table covers the user's 21 apps (community-maintained, not
device truth) and the Configure row says when the device named nothing.
* the real device shape is a test fixture (trimmed, and marked as such in the
file: the raw capture is not in the repo) pinning that no label is
extracted, that the probe count is bounded, and that `Netflix` surfaces.
Also drops the dead `ATTR_MEDIA_DAG_NOW_PLAYING` re-exports left by the t65
flattening and updates the RESEARCH_SHIELD now-playing note to the flat names.
No real Shield was available: the fetch/burst behaviour is exercised against the
documented Android output and the trimmed reproduction of the reported capture.
This commit is contained in:
1 parent
f29f62ddae
commit
7b32de789a
16 files changed
+973
-176
No files matched your search
@@ -875,7 +875,9 @@ class TestPrivacyOrdering:
|
||||
player._last_channel = "101"
|
||||
np = player._get_app_now_playing()
|
||||
|
||||
assert np["title"] == TIVIMATE_PACKAGE # app identity, not the channel
|
||||
# t66: the identity tier now resolves through the shared chain, so the app
|
||||
# reads by its name — still the APP, never the channel or the media title.
|
||||
assert np["title"] == "TiviMate"
|
||||
assert np["title"] != "101"
|
||||
|
||||
async def test_channel_number_never_leaks_in_privacy_mode_with_last_known(self, modules):
|
||||
@@ -893,7 +895,8 @@ class TestPrivacyOrdering:
|
||||
np = player._get_app_now_playing()
|
||||
|
||||
assert np["state"] == "playing" # playback truth survives privacy
|
||||
assert np["title"] == TIVIMATE_PACKAGE # but identity stays the app
|
||||
assert np["title"] == "TiviMate" # but identity stays the app
|
||||
assert "Sky News" not in np.values() # the media title never leaks
|
||||
|
||||
|
||||
class TestVolumeAndPowerLadder:
|
||||
@@ -1725,23 +1728,26 @@ class TestCurrentAppNameEndToEndT55:
|
||||
return player, manager, sensor, poller
|
||||
|
||||
async def test_known_app_resolves_by_fetching_its_label(self, modules):
|
||||
# t66: a package NOT in the curated table, so the fall-through under test
|
||||
# is really the raw id and not a curated name.
|
||||
player, manager, sensor, poller = self._wire(
|
||||
modules, "com.netflix.ninja", {"com.netflix.ninja": "application-label: 'Netflix'"}
|
||||
modules,
|
||||
"com.example.labelled",
|
||||
{"com.example.labelled": "application-label: 'Labelled App'"},
|
||||
)
|
||||
|
||||
# first render: cache miss → the sensor falls through to the package
|
||||
assert sensor.native_value == "com.netflix.ninja"
|
||||
assert manager.app_label_requests == {"com.netflix.ninja"}
|
||||
assert sensor.native_value == "com.example.labelled"
|
||||
assert manager.app_label_requests == {"com.example.labelled"}
|
||||
|
||||
# the watchdog drains the fetch for the CURRENT package
|
||||
await manager._watchdog_cycle(5.0)
|
||||
await asyncio.sleep(0)
|
||||
assert poller.dumps and "com.netflix.ninja" in poller.dumps[0]
|
||||
assert poller.dumps and "com.example.labelled" in poller.dumps[0]
|
||||
|
||||
# next render: the resolved device name, in the sensor AND the player
|
||||
assert sensor.native_value == "Netflix"
|
||||
assert sensor.extra_state_attributes["app_name"] == "Netflix"
|
||||
assert player.app_name == "Netflix"
|
||||
assert sensor.native_value == "Labelled App"
|
||||
assert player.app_name == "Labelled App"
|
||||
|
||||
async def test_mapped_app_never_needs_the_device_label(self, modules):
|
||||
player, manager, sensor, poller = self._wire(
|
||||
@@ -1818,7 +1824,9 @@ class TestCurrentAppNameEndToEndT55:
|
||||
here — the pre-fix tree fails this at the second cycle.
|
||||
"""
|
||||
player, manager, sensor, poller = self._wire(
|
||||
modules, "ar.tvplayer.tv", {"ar.tvplayer.tv": "application-label: 'TiviMate'"}
|
||||
modules,
|
||||
"com.example.labelled",
|
||||
{"com.example.labelled": "application-label: 'Labelled App'"},
|
||||
)
|
||||
|
||||
async def _blank(_cmd):
|
||||
@@ -1827,16 +1835,16 @@ class TestCurrentAppNameEndToEndT55:
|
||||
|
||||
# cycle 1: the transport delivered nothing → a FAILED fetch
|
||||
poller.shell = _blank
|
||||
assert sensor.native_value == "ar.tvplayer.tv"
|
||||
assert sensor.native_value == "com.example.labelled"
|
||||
await manager._watchdog_cycle(5.0)
|
||||
await asyncio.sleep(0)
|
||||
|
||||
assert "ar.tvplayer.tv" not in manager._app_labels # nothing stamped
|
||||
assert sensor.native_value == "ar.tvplayer.tv"
|
||||
assert "com.example.labelled" not in manager._app_labels # nothing stamped
|
||||
assert sensor.native_value == "com.example.labelled"
|
||||
|
||||
# the next render re-enqueues, and the retry is what recovers the label
|
||||
assert manager.app_label("ar.tvplayer.tv") is None
|
||||
assert manager.app_label_requests == {"ar.tvplayer.tv"}
|
||||
assert manager.app_label("com.example.labelled") is None
|
||||
assert manager.app_label_requests == {"com.example.labelled"}
|
||||
dump_count = len([d for d in poller.dumps if "pm dump" in d])
|
||||
|
||||
real_shell = _LabelPoller.shell.__get__(poller, _LabelPoller)
|
||||
@@ -1846,9 +1854,9 @@ class TestCurrentAppNameEndToEndT55:
|
||||
|
||||
# a later GOOD dump recovers it — the pre-fix cache made this impossible
|
||||
assert len([d for d in poller.dumps if "pm dump" in d]) == dump_count + 1
|
||||
assert sensor.native_value == "TiviMate"
|
||||
assert sensor.extra_state_attributes["app_name"] == "TiviMate"
|
||||
assert player.app_name == "TiviMate"
|
||||
assert sensor.native_value == "Labelled App"
|
||||
assert sensor.extra_state_attributes["app_name"] == "Labelled App"
|
||||
assert player.app_name == "Labelled App"
|
||||
|
||||
@pytest.mark.parametrize("blank", [None, ""], ids=["none", "empty"])
|
||||
async def test_the_player_recovers_the_label_after_a_failed_fetch(
|
||||
@@ -1861,7 +1869,9 @@ class TestCurrentAppNameEndToEndT55:
|
||||
stuck on the package id — so both are pinned.
|
||||
"""
|
||||
player, manager, sensor, poller = self._wire(
|
||||
modules, "com.netflix.ninja", {"com.netflix.ninja": "application-label: 'Netflix'"}
|
||||
modules,
|
||||
"com.example.labelled",
|
||||
{"com.example.labelled": "application-label: 'Labelled App'"},
|
||||
)
|
||||
|
||||
async def _blank(_cmd):
|
||||
@@ -1869,19 +1879,19 @@ class TestCurrentAppNameEndToEndT55:
|
||||
return blank
|
||||
|
||||
poller.shell = _blank
|
||||
assert player.app_name == "com.netflix.ninja"
|
||||
assert player.app_name == "com.example.labelled"
|
||||
await manager._watchdog_cycle(5.0)
|
||||
await asyncio.sleep(0)
|
||||
assert "com.netflix.ninja" not in manager._app_labels
|
||||
assert player.app_name == "com.netflix.ninja"
|
||||
assert "com.example.labelled" not in manager._app_labels
|
||||
assert player.app_name == "com.example.labelled"
|
||||
|
||||
assert manager.app_label("com.netflix.ninja") is None # re-enqueued
|
||||
assert manager.app_label("com.example.labelled") is None # re-enqueued
|
||||
poller.shell = _LabelPoller.shell.__get__(poller, _LabelPoller)
|
||||
await manager._watchdog_cycle(5.0)
|
||||
await asyncio.sleep(0)
|
||||
|
||||
assert player.app_name == "Netflix"
|
||||
assert sensor.native_value == "Netflix"
|
||||
assert player.app_name == "Labelled App"
|
||||
assert sensor.native_value == "Labelled App"
|
||||
|
||||
async def test_a_genuinely_label_less_package_is_still_negatively_cached(
|
||||
self, modules
|
||||
@@ -1910,16 +1920,16 @@ class TestCurrentAppNameEndToEndT55:
|
||||
async def test_the_fetch_targets_the_app_on_screen_not_the_previous_one(self, modules):
|
||||
player, manager, sensor, poller = self._wire(
|
||||
modules,
|
||||
"ar.tvplayer.tv",
|
||||
"com.example.one",
|
||||
{
|
||||
"ar.tvplayer.tv": "application-label: 'TiviMate'",
|
||||
"com.google.android.youtube.tv": "application-label: 'YouTube'",
|
||||
"com.example.one": "application-label: 'One TV'",
|
||||
"com.example.two": "application-label: 'Two TV'",
|
||||
},
|
||||
)
|
||||
|
||||
assert sensor.native_value == "ar.tvplayer.tv" # miss for the 1st app
|
||||
manager._on_current_app("com.google.android.youtube.tv")
|
||||
assert sensor.native_value == "com.google.android.youtube.tv"
|
||||
assert sensor.native_value == "com.example.one" # miss for the 1st app
|
||||
manager._on_current_app("com.example.two")
|
||||
assert sensor.native_value == "com.example.two"
|
||||
|
||||
# both packages are queued; the watchdog drains ONE per cycle
|
||||
await manager._watchdog_cycle(5.0)
|
||||
@@ -1929,9 +1939,9 @@ class TestCurrentAppNameEndToEndT55:
|
||||
|
||||
# every drained fetch names a package, and the app that is CURRENT is
|
||||
# the one the sensor now shows the label of
|
||||
assert "ar.tvplayer.tv" in poller.dumps[0]
|
||||
assert "com.google.android.youtube.tv" in poller.dumps[1]
|
||||
assert sensor.native_value == "YouTube"
|
||||
assert "com.example.one" in poller.dumps[0]
|
||||
assert "com.example.two" in poller.dumps[1]
|
||||
assert sensor.native_value == "Two TV"
|
||||
|
||||
|
||||
class TestAvailabilityLadder:
|
||||
|
||||
Reference in new issue
Block a user