docs: an HDMI row opens any app that claims the channel MIME type (#805)

openInput() sends an implicit ACTION_VIEW on the passthrough channel URI and
only tries the theme's input_action when nothing claims it. On an RCA Android
TV 9 (RTK2851P) the firmware has no Live TV app for that URI and ES File
Explorer declares a VIEW filter for the same MIME type, so HDMI 1 opened the
file manager. Records the mechanism, the query-activities check, why
live_tv_package (setPackage on the Live TV row only) cannot filter it, and the
source_panel_service workaround. Pins the note in themes/README's input_action
row.
This commit is contained in:
xbsheng authored and xbsheng committed 2026-10-22 08:19:13 +08:00
1 parent 59332f4442
commit 483aaed6f8
3 files changed
+135 -1

No files matched your search

+20
View File
@@ -118,6 +118,26 @@ Things that bit us or contributors, so they don't bite you. Commands use `tools/
tools/tv.py 'pm enable --user 0 com.google.android.apps.mediashell'
```
Keep the package out of your list if you use Stremio on the TV, or cast from a phone.
- **Picking HDMI 1 in the Inputs menu opened an unrelated app**, seen on an RCA Android TV 9
(Toptech/Zeasn firmware `RTK2851P`, Realtek RTD2851P,
[#805](https://github.com/Gathin23/cleanyourtv/issues/805)). The launcher switches to a passthrough
input with an *implicit* `ACTION_VIEW` on the input's channel URI
(`vnd.android.cursor.item/channel`), and only sends the theme's `input_action` when nothing claims
that intent at all. "Nothing claims it" is the catch: that firmware has no Live TV app for the URI,
and ES File Explorer (`com.estrongs.android.pop/.app.transferstation.FileTransferStationActivity`)
declares a `VIEW` filter for the same MIME type, so HDMI 1 opened the file manager and
`input_action` was never reached. A set with no handler at least shows "Not available on this TV";
a set with one wrong handler opens that app. Ask which app would take a row before blaming the
launcher:
```sh
tools/tv.py 'cmd package query-activities --brief -a android.intent.action.VIEW -t vnd.android.cursor.item/channel'
```
`live_tv_package` does not help: it is applied as `setPackage` on the Live TV row only, and the
HDMI and AV rows carry no package. Until the launcher checks that the handler is a TV-input app,
hide the rows instead: a theme with `source_panel_service` set and `live_tv_package` empty makes
the Inputs pill open the TV's own source panel (what the reporter did), or disable the app that
answers. Report another one rather than reading it as a firmware quirk: the wrong-handler case needs
a source change, not a setting.
- **`adb exec-out screencap -p` gives a broken PNG** on some MediaTek sets (Xiaomi, and Amazon's
own Fire TV smart TVs): the binary prints `Init wrapper sys mutex successful` on stdout first.
Save on the TV and pull the file instead: `screencap -p /data/local/tmp/s.png`, or cut
+114
View File
@@ -0,0 +1,114 @@
"""The Inputs-menu hijack note (#805), and the theme key it explains.
Documentation only, so the tests pin what a reader has to be able to act on: the intent the launcher
sends for an HDMI row, the command that names the app which would take it, why `live_tv_package`
cannot help, and the one workaround that needs no rebuild. If the launcher ever restricts the row to
a TV-input package, the bullet has to lose its "until" clause - that is a doc change, not a test change.
Run from the repository root: python3 -m unittest discover -s tests -v
"""
import re
from pathlib import Path
import unittest
ROOT = Path(__file__).resolve().parents[1]
TROUBLE = ROOT / "docs" / "TROUBLESHOOTING.md"
THEMES = ROOT / "themes" / "README.md"
ISSUE = "https://github.com/Gathin23/cleanyourtv/issues/805"
START = "- **Picking HDMI 1 in the Inputs menu opened an unrelated app**"
CHANNEL = "vnd.android.cursor.item/channel"
LINK = re.compile(r"\]\(([^)\s]+)\)")
def bullet(text, start):
"""The markdown bullet that begins with `start`, up to the next top-level bullet."""
lines = text.split("\n")
first = next(i for i, line in enumerate(lines) if line.startswith(start))
out = [lines[first]]
for line in lines[first + 1:]:
if line.startswith("- **") or line.startswith("## "):
break
out.append(line)
return "\n".join(out)
def broken_links(path):
broken = []
for target in LINK.findall(path.read_text(encoding="utf-8")):
if target.startswith(("http://", "https://", "mailto:", "#")):
continue
local = target.split("#", 1)[0]
if local and not (path.parent / local).exists():
broken.append(target)
return broken
class InputIntentNoteTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.trouble_text = TROUBLE.read_text(encoding="utf-8")
cls.theme_text = THEMES.read_text(encoding="utf-8")
cls.note = bullet(cls.trouble_text, START)
def test_bullet_exists_once(self):
self.assertEqual(self.trouble_text.count(START), 1)
def test_bullet_links_the_issue(self):
self.assertIn(f"[#805]({ISSUE})", self.note)
def test_bullet_says_the_intent_is_implicit(self):
self.assertIn("implicit", self.note)
self.assertIn(CHANNEL, self.note)
self.assertIn("ACTION_VIEW", self.note)
def test_bullet_gives_the_query_command_verbatim(self):
# The flags are what make `query-activities` answer for the same intent the row sends; a
# reordered or abbreviated command is what a reader would then report as "no handler".
self.assertIn(
"tools/tv.py 'cmd package query-activities --brief -a android.intent.action.VIEW "
f"-t {CHANNEL}'",
self.note,
)
def test_bullet_names_the_app_that_took_the_row(self):
self.assertIn(
"com.estrongs.android.pop/.app.transferstation.FileTransferStationActivity", self.note
)
def test_bullet_explains_why_live_tv_package_does_not_help(self):
self.assertIn("`live_tv_package`", self.note)
self.assertIn("`setPackage`", self.note)
self.assertIn("Live TV row", self.note)
def test_bullet_contrasts_the_two_failures(self):
self.assertIn('"Not available on this TV"', self.note)
def test_bullet_gives_the_workaround_with_both_keys(self):
self.assertIn("`source_panel_service`", self.note)
self.assertIn("`live_tv_package` empty", self.note)
self.assertIn("source panel", self.note)
def test_bullet_does_not_promise_a_launcher_fix(self):
self.assertIn("needs\n a source change, not a setting", self.note.replace("\r", ""))
def test_theme_row_says_when_input_action_is_reached(self):
rows = [line for line in self.theme_text.split("\n")
if line.startswith("| `input_action` |")]
self.assertEqual(len(rows), 1, "the input_action row moved or was duplicated")
row = rows[0]
self.assertIn("nothing handles the standard `VIEW`", row)
self.assertIn("query-activities", row)
self.assertIn(f"[#805]({ISSUE})", row)
def test_theme_readme_still_lists_the_workaround_keys(self):
self.assertIn("| `source_panel_service`, `source_panel_action` |", self.theme_text)
self.assertIn("| `live_tv_package`, `live_tv_action` |", self.theme_text)
def test_no_broken_relative_links(self):
self.assertEqual(broken_links(TROUBLE), [])
self.assertEqual(broken_links(THEMES), [])
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -41,7 +41,7 @@ is ignored, and a file that doesn't parse is ignored as a whole, so a theme can
| `launch_actions` | list of `"package=action"` | Tiles that open a screen of the app instead of its start screen; an action that no longer resolves opens the app normally (MagentaTV's guide: `com.telekom.onetv.tv.pl=com.telekom.onetv.tv.pl.action.PROGRAM_GUIDE`) |
| `app_labels` | list of `"package=Name"` | A tile's name instead of the app's own (MagentaTV as `com.telekom.onetv.tv.pl=TV`) |
| `app_banners` | list of `"package=banner_name"` | A tile's artwork from a `banner_*` drawable bundled with the launcher (`banner_tv`: magenta with the Live TV glyph) |
| `input_action` | string | Action sent with an HDMI/AV row's channel URI when nothing handles the standard `VIEW` (XGIMI: `com.xgimi.action.hdmiPlayer`) |
| `input_action` | string | Action sent with an HDMI/AV row's channel URI when nothing handles the standard `VIEW` (XGIMI: `com.xgimi.action.hdmiPlayer`). Only a handler-less set gets here; any app that claims the channel MIME type takes the row instead, so check with `query-activities` ([#805](https://github.com/Gathin23/cleanyourtv/issues/805)) |
| `input_uri_scheme` | string | Scheme an HDMI/AV row's passthrough URI is sent with instead of `content://`, with no MIME type (YaOS: `live-tv`, which its source player answers) |
| `input_sources` | list, `config.xml` only | Vendor source numbers for the Inputs menu, `HW<n>=<number>` by the input id's last part; a mapped input opens by sending `input_action` with the number as the int extra `input`, and `HW<n>=-` leaves a port the set doesn't have out of the menu. Xiaomi/Redmi China firmware: [redmi-mitv-mftp0](../examples/redmi-mitv-mftp0/). Not read from a theme |
| `columns` | 1 to 8 | Tiles per row; tiles shrink to fit from 5 up |