mirror of
https://github.com/Gathin23/cleanyourtv.git
synced 2026-10-27 19:54:45 +00:00
docs: the key-helper volume trade on a second set, a Xiaomi MiTV-MOSR4 (#944)
This commit is contained in:
1 parent
22dc48fe21
commit
8d42904320
5 files changed
+197
-2
No files matched your search
@@ -607,7 +607,10 @@ alone, `InputKeyService` alone and both together all broke it, with `settings pu
|
||||
enabled_accessibility_services ''` plus `accessibility_enabled 0` restoring it. The dumps show
|
||||
`capabilities=8` (`CAPABILITY_CAN_FILTER_KEY_EVENTS`), so the platform routes every key through a filter
|
||||
path that drops the repeats, before an app sees them; the launcher's own helpers return `false` for keys
|
||||
they do not map, so they are not consuming the volume key. Check the state first, do not rewrite it blind:
|
||||
they do not map, so they are not consuming the volume key. The same was reported on a Xiaomi MiTV-MOSR4
|
||||
(`irobot`, Android 11, `RTM5.220609.003.1265`) with the launcher's own helpers, both and `InputKeyService`
|
||||
alone (#944), where the empty list was not tried because it also unhooks the buttons the helpers were
|
||||
installed for. Check the state first, do not rewrite it blind:
|
||||
|
||||
```sh
|
||||
tools/tv.py 'settings get secure enabled_accessibility_services'
|
||||
|
||||
@@ -78,6 +78,30 @@ home, and the [G09 example list](../../examples/tcl-g09-google-tv/debloat.txt) d
|
||||
after the cleanup only a helper can answer them. That is where the trade comes from: on this firmware
|
||||
the buttons and the volume ramp cannot both be had, because both need the same filter capability.
|
||||
|
||||
## The same trade on a Xiaomi MediaTek set
|
||||
|
||||
A second owner reported it on [#944](https://github.com/Gathin23/cleanyourtv/issues/944): Xiaomi
|
||||
MiTV-MOSR4 (`irobot`, MediaTek), Google TV / Android 11 (SDK 30), firmware `RTM5.220609.003.1265`.
|
||||
Holding volume up with `InputKeyService` and `HomeKeyService` bound moves it one step, and
|
||||
`InputKeyService` alone is still one step; `dumpsys accessibility` lists the bound service with
|
||||
`capabilities=8`. Same symptom, same capability, second vendor and second Android version.
|
||||
|
||||
Two things differ from the bisect above, and both are worth keeping. The empty-list control was not run:
|
||||
on this set clearing the list unhooks the buttons the helpers were installed for - with launcherx
|
||||
disabled, its Input, Settings, YouTube and Netflix buttons reach only
|
||||
`com.xiaomi.android.tvsetup.partnercustomizer/.GlobalKeyReceiver`, which starts launcherx activities and
|
||||
crashes (the [MOSR4 note](xiaomi-mitv-mosr4.md) has that part, and its install line passes both helpers).
|
||||
So "the ramp returns with no service bound" is still measured on the G09 alone. And the build string in
|
||||
the issue is not the one that note carries (`RTM5.220609.003/1585`, written on another unit of the same
|
||||
model), so treat the two as separate units until both fingerprints are read: the model, the firmware
|
||||
family and the behaviour are what they agree on.
|
||||
|
||||
That is still enough to unhook the mechanism from "this firmware": the volume keys take the early
|
||||
question from the window policy on Android 11 and 12, on two vendors, whenever a bound service holds
|
||||
`CAPABILITY_CAN_FILTER_KEY_EVENTS`. The trade and the choices below are the same on both sets. The
|
||||
reporter's own suggestion - a helper that synthesizes the repeat with `adjustSuggestedStreamVolume` while
|
||||
a volume key is held - is launcher code, and an off-by-default switch at best; it is not in this note.
|
||||
|
||||
## Choices
|
||||
|
||||
- **Keep the repeat and skip the helpers.** Do not pass `INPUT_KEY_SERVICE=1` or `HOME_KEY_SERVICE=1`
|
||||
|
||||
@@ -55,6 +55,14 @@ the real remote (no `GLOBAL_BUTTON` lines, no crashes), using an equivalent key
|
||||
stand-alone launcher; not yet with `com.gathin.tvhome`. The Netflix button went to this receiver,
|
||||
not to overseaservice. The mic button keeps working.
|
||||
|
||||
Both helpers cost the remote's live key repeat on this set: holding volume up or down moves one step
|
||||
per press, with `InputKeyService` alone as well as with both, and `dumpsys accessibility` shows the bound
|
||||
service at `capabilities=8` (`CAPABILITY_CAN_FILTER_KEY_EVENTS`) - [#944](https://github.com/Gathin23/cleanyourtv/issues/944).
|
||||
The bisect, the ways back to either behaviour and the way the repeat is lost are in
|
||||
[the G09 note](tcl-55p71b-g09-key-repeat.md). The empty-list control was not tried here: clearing the
|
||||
list unhooks the buttons this section installs the helpers for, so "the ramp returns with nothing bound"
|
||||
is that note's measurement, not this set's.
|
||||
|
||||
## Other
|
||||
|
||||
- `com.mitv.tvhome.mitvplus` is re-enabled at every boot.
|
||||
|
||||
+1
-1
@@ -222,7 +222,7 @@ on that firmware, grouped and commented. These are starting points: comment out
|
||||
| `xiaomi-mitv-mftr0/` | Xiaomi MiTV-MFTR0 (freeguy, MediaTek mt9653), China firmware OS2.0.23.0.RFTMATV (HyperOS TV 2) | Android 11 (HyperOS TV 2, CN build) | Second CN set from the ASTR0 sessions, one HyperOS generation earlier (Android 11): same ad screensaver carrier (`com.xiaomi.mitv.hyper.screensaver`) and the same `com.ts.launcher` allowlist trampoline for third-party Home with PatchWall disabled, both carried over and verified; no WPS/overseaservice/adservices here; keep `com.xiaomi.mitv.smartpanel` (Menu-button panel); ADB needs the on-TV RSA confirmation once and closes on every reboot; [debloat and Home notes](../docs/devices/xiaomi-mitv-mftr0-debloat-home.md), [inputs/settings notes](../docs/devices/xiaomi-mitv-mftr0-freeguy.txt) |
|
||||
| `xiaomi-mitv-moeu0/` | Xiaomi TV S Mini LED 75 2025 (MiTV-MOEU0, ladybird, MediaTek) | Google TV 14 | Mi TV+ banners and MiChannel rows, Mi analytics, TV Manager cleaner, Play auto-installs; setupwraith off (its Wi-Fi network belonged to the system here); remote's Settings 176, YouTube 190, Apps 284 and Xiaomi TV+ 192 noted |
|
||||
| `xiaomi-mitv-moor2/` | Xiaomi MiTV-MOOR2 (venom, MediaTek) | Google TV 11 | Mi ad row and PatchWall, Mi analytics, MiLink, store demo; setup wizard off so a third-party launcher can own Home |
|
||||
| `xiaomi-mitv-mosr4/` | Xiaomi MiTV-MOSR4 (irobot, MediaTek) | Google TV 11 | Mi ad rows and PatchWall, Mi analytics, TV Manager, Miracast sink, OTA off by choice; setup wizard off so a third-party launcher can own Home; `overseaservice` hosts the Netflix key and must stay; second unit: setupwraith before launcherx, remote buttons need both key services and `config.json`; [notes](../docs/devices/xiaomi-mitv-mosr4.md) |
|
||||
| `xiaomi-mitv-mosr4/` | Xiaomi MiTV-MOSR4 (irobot, MediaTek) | Google TV 11 | Mi ad rows and PatchWall, Mi analytics, TV Manager, Miracast sink, OTA off by choice; setup wizard off so a third-party launcher can own Home; `overseaservice` hosts the Netflix key and must stay; second unit: setupwraith before launcherx, remote buttons need both key services and `config.json`, and those services cost the volume's live repeat; [notes](../docs/devices/xiaomi-mitv-mosr4.md) |
|
||||
| `xiaomi-mitv-mssp0/` | Xiaomi MiTV-MSSP0 / Xiaomi Mi TV 4A 32 (amelie), firmware PI.5926 | Android TV 9 | Xiaomi telemetry and promo helpers, Google recommendation feeds; Assistant, Gallery and media browser off by owner choice; Clock replaces Backdrop; TV inputs, Gboard, Bluetooth and firmware update settings kept; 11 packages, applied October 19 and checked again October 21 |
|
||||
| `xiaomi-mitv-mssp1/` | Xiaomi MiTV-MSSP1 (nino), firmware PI.5925 | Android TV 9 | Eight Xiaomi telemetry/content and Google recommendation packages; replacement Home reboot-checked; updater, Assistant, Chromecast, USB media and TV input services kept enabled |
|
||||
| `xiaomi-mitv-mssp3/` | Xiaomi MiTV-MSSP3 (machuca, MediaTek) | Android TV 11 | PatchWall, Mi analytics, Netflix observer; Chromecast, screensavers, unused streaming apps off by choice |
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
"""Pins the second data point for the key-helper/auto-repeat trade (#944).
|
||||
|
||||
docs/devices/tcl-55p71b-g09-key-repeat.md was written on one TCL build, and its own Limits said the
|
||||
claim belongs to that build: whether other firmwares do the same was open. #944 answers part of that
|
||||
with a Xiaomi MiTV-MOSR4 (Google TV 11, MediaTek) where the launcher's own helpers cost the same repeat,
|
||||
which is worth recording next to the bisect - but it is a weaker report than the first one: the owner did
|
||||
not run the empty-list control (clearing the list unhooks the buttons he installed the helpers for), and
|
||||
the build string he gives is not the one docs/devices/xiaomi-mitv-mosr4.md carries. These tests keep the
|
||||
section from growing into a claim the report does not support, and keep the two notes and the two
|
||||
entry points pointing at each other.
|
||||
"""
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
G09 = ROOT / "docs" / "devices" / "tcl-55p71b-g09-key-repeat.md"
|
||||
MOSR4 = ROOT / "docs" / "devices" / "xiaomi-mitv-mosr4.md"
|
||||
TROUBLE = ROOT / "docs" / "TROUBLESHOOTING.md"
|
||||
INDEX = ROOT / "examples" / "README.md"
|
||||
JAVA = ROOT / "launcher" / "app" / "src" / "main" / "java"
|
||||
|
||||
HEADING = "## The same trade on a Xiaomi MediaTek set"
|
||||
BUILD_IN_ISSUE = "RTM5.220609.003.1265"
|
||||
BUILD_IN_NOTE = "RTM5.220609.003/1585"
|
||||
RECEIVER = "com.xiaomi.android.tvsetup.partnercustomizer/.GlobalKeyReceiver"
|
||||
|
||||
|
||||
def flat(path):
|
||||
return re.sub(r"\s+", " ", path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def section_of(text, heading):
|
||||
"""The flattened body under `heading`, up to the next `##` heading; None when it is not there."""
|
||||
flat_text = re.sub(r"\s+", " ", text)
|
||||
if heading not in flat_text:
|
||||
return None
|
||||
rest = flat_text.split(heading, 1)[1]
|
||||
following = re.search(r"\s##\s", rest)
|
||||
return rest if following is None else rest[:following.start()]
|
||||
|
||||
|
||||
def section(path):
|
||||
return section_of(path.read_text(encoding="utf-8"), HEADING)
|
||||
|
||||
|
||||
def links(path):
|
||||
text = path.read_text(encoding="utf-8")
|
||||
return [str((path.parent / target).resolve()) for target in re.findall(r"\]\(([^)\s]+)\)", text)
|
||||
if not target.startswith(("http", "#"))]
|
||||
|
||||
|
||||
class SecondUnitTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
body = section(G09)
|
||||
self.assertIsNotNone(body, f"{HEADING} is gone from the G09 note")
|
||||
self.section = body or ""
|
||||
|
||||
def test_says_which_set_and_which_build(self):
|
||||
for needle in ("#944", "MiTV-MOSR4", "`irobot`", BUILD_IN_ISSUE, "Android 11", "MediaTek"):
|
||||
with self.subTest(needle=needle):
|
||||
self.assertIn(needle, self.section)
|
||||
|
||||
def test_keeps_both_rows_of_the_bisect(self):
|
||||
self.assertIn("with `InputKeyService` and `HomeKeyService` bound moves it one step", self.section)
|
||||
self.assertIn("`InputKeyService` alone is still one step", self.section)
|
||||
|
||||
def test_names_the_missing_control_instead_of_borrowing_it(self):
|
||||
self.assertIn("empty-list control was not run", self.section)
|
||||
self.assertIn("still measured on the G09 alone", self.section,
|
||||
"the control was not repeated on this set, so it must not be reported as if it was")
|
||||
|
||||
def test_names_the_capability_and_how_it_was_read(self):
|
||||
for needle in ("capabilities=8", "CAPABILITY_CAN_FILTER_KEY_EVENTS", "dumpsys accessibility"):
|
||||
with self.subTest(needle=needle):
|
||||
self.assertIn(needle, self.section)
|
||||
|
||||
def test_the_two_build_strings_are_kept_apart(self):
|
||||
self.assertIn(BUILD_IN_NOTE, self.section,
|
||||
"the MOSR4 note's own build has to be named, or the two units read as one")
|
||||
self.assertIn("separate units", self.section)
|
||||
self.assertIn(BUILD_IN_NOTE, flat(MOSR4), "the note this section defers to no longer carries that build")
|
||||
|
||||
def test_the_receiver_claim_matches_the_mosr4_note(self):
|
||||
self.assertIn(RECEIVER, self.section)
|
||||
note = flat(MOSR4)
|
||||
self.assertIn(RECEIVER, note, "the receiver is the MOSR4 note's finding, not something to invent here")
|
||||
for button in ("Input", "Settings", "YouTube", "Netflix"):
|
||||
with self.subTest(button=button):
|
||||
self.assertIn(button, self.section)
|
||||
|
||||
def test_no_fix_is_promised_and_the_suggestion_is_left_to_the_launcher(self):
|
||||
self.assertIn("adjustSuggestedStreamVolume", self.section)
|
||||
self.assertIn("is not in this note", self.section)
|
||||
self.assertNotIn("solves", self.section.lower(),
|
||||
"the suggestion is untested here; it does not belong in a note as an answer")
|
||||
if JAVA.is_dir():
|
||||
sources = "\n".join(p.read_text(encoding="utf-8") for p in JAVA.rglob("*.java"))
|
||||
self.assertNotIn("adjustSuggestedStreamVolume", sources,
|
||||
"the launcher now synthesises the repeat: the note's wording has to change with it")
|
||||
self.assertIn("does the same is untested", flat(G09),
|
||||
"the G09 Limits sentence this section defers to was reworded away")
|
||||
|
||||
|
||||
class EntryPointsTest(unittest.TestCase):
|
||||
def test_the_mosr4_note_points_back(self):
|
||||
text = flat(MOSR4)
|
||||
self.assertIn("#944", text)
|
||||
self.assertIn("capabilities=8", text)
|
||||
self.assertIn(str(G09.resolve()), links(MOSR4))
|
||||
self.assertIn("unhooks the buttons", text, "the reason the control was skipped belongs there too")
|
||||
|
||||
def test_troubleshooting_mentions_the_second_set(self):
|
||||
text = flat(TROUBLE)
|
||||
heading = "## A key helper is bound and a held remote button stopped repeating"
|
||||
self.assertEqual(text.count(heading), 1)
|
||||
body = text[text.index(heading):]
|
||||
body = body[:body.find(" ## ")] if " ## " in body else body
|
||||
self.assertIn("MiTV-MOSR4", body)
|
||||
self.assertIn(BUILD_IN_ISSUE, body)
|
||||
self.assertIn("#944", body)
|
||||
self.assertIn("empty list was not tried", body)
|
||||
|
||||
def test_the_index_row_warns_before_the_helpers_are_installed(self):
|
||||
rows = [line for line in INDEX.read_text(encoding="utf-8").split("\n")
|
||||
if line.startswith("| `xiaomi-mitv-mosr4/`")]
|
||||
self.assertEqual(len(rows), 1)
|
||||
self.assertIn("cost the volume's live repeat", rows[0])
|
||||
self.assertIn(str(MOSR4.resolve()), links(INDEX))
|
||||
|
||||
def test_links_resolve(self):
|
||||
for page in (G09, MOSR4):
|
||||
for target in links(page):
|
||||
with self.subTest(page=page.name, target=target):
|
||||
self.assertTrue(Path(target).exists(), "relative link target does not exist")
|
||||
|
||||
|
||||
class SectionReaderTest(unittest.TestCase):
|
||||
def test_missing_heading_is_none(self):
|
||||
self.assertIsNone(section_of("# A page\n\nSome text.\n", HEADING))
|
||||
|
||||
def test_stops_at_the_next_heading(self):
|
||||
text = f"# Page\n\n{HEADING}\n\nBody.\n\n## Choices\n\nLater.\n"
|
||||
self.assertIn("Body.", section_of(text, HEADING))
|
||||
self.assertNotIn("Later.", section_of(text, HEADING))
|
||||
|
||||
def test_a_deeper_heading_does_not_end_it(self):
|
||||
text = f"# Page\n\n{HEADING}\n\nBody.\n\n### Detail\n\nStill here.\n\n## Choices\n\nGone.\n"
|
||||
body = section_of(text, HEADING)
|
||||
self.assertIn("Still here.", body)
|
||||
self.assertNotIn("Gone.", body)
|
||||
|
||||
def test_last_section_runs_to_the_end(self):
|
||||
text = f"# Page\n\n{HEADING}\n\nBody.\n"
|
||||
self.assertIn("Body.", section_of(text, HEADING))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user