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:
James Mackay committed 2026-10-12 10:54:38 +01:00
1 parent f29f62ddae
commit 7b32de789a
16 files changed
+973 -176

No files matched your search

+27 -4
View File
@@ -57,7 +57,13 @@ class TestSourceList:
def test_it_falls_back_to_the_curated_directory(self, modules):
player, _served = _player(modules) # no map configured
assert player.source_list == ["TiviMate", "Wholphin", "Netflix", "YouTube"]
# t66: the curated table grew to cover the apps the report listed, and the
# dropdown is the same table (so it is asserted against the table itself,
# not a copy that would drift)
from nsh.const import DEFAULT_APP_DIRECTORY
assert player.source_list == list(DEFAULT_APP_DIRECTORY.values())
assert "Netflix" in player.source_list # a name the user recognises
def test_a_blank_name_never_reaches_the_dropdown(self, modules):
"""t49: the map is hand-editable, so 'com.x=' must not offer an empty entry.
@@ -464,8 +470,25 @@ class TestCaseInsensitiveNames:
assert player.source == " TiviMate " # this surface shows it as typed
assert player.source not in player.source_list
def test_a_blank_map_value_falls_through_to_the_raw_package(self, modules):
"""Blanks are not displayed as nothing — the chain continues past them."""
def test_a_blank_map_value_falls_through_the_chain(self, modules):
"""Blanks are not displayed as nothing — the chain continues past them (t66).
Past a blank map value comes the device label, then the curated table, then
the raw id: `ar.tvplayer.tv` is curated, so the dropdown row is its NAME.
The raw-id tail of the same chain is pinned in the test below.
"""
player, _served = _player(modules, app_map={TIVIMATE: ""}, foreground=TIVIMATE)
assert player.source == TIVIMATE
assert player.source == "TiviMate"
def test_a_package_nothing_names_stays_raw(self, modules):
"""The last tier: no map value, no device label, no curated row → the id.
It is shown ONCE (the id itself), never as a `pkg=pkg` pair.
"""
player, _served = _player(
modules, app_map={"com.example.unknown": ""}, foreground="com.example.unknown"
)
assert player.source == "com.example.unknown"
assert player.source_list == [] # a name is the ticker's raw id