examples: tcl-55s470g says tv.remote.service carries the remote mic, and the docs find every list that disables it (#1441)

The TCL 55S470G list (G10, STT2.230203.001 AS50, the build of #1441 and #1547) still disables com.google.android.tv.remote.service by choice, but its note now says the package is also the remote mic's bridge instead of "the physical remote is unaffected", and the remote-mic bullet in docs/TROUBLESHOOTING.md adds the five lists that disable it since and gives the grep that prints the current set. Not tried on a TV.

Refs #1441, #1547

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Mazin KanugaandClaude Opus 5.5 committed 2026-10-25 12:04:19 -04:00
1 parent 67dc5bee16
commit 614c8fb270
3 files changed
+153 -11

No files matched your search

+12 -10
View File
@@ -401,16 +401,18 @@ Things that bit us or contributors, so they don't bite you. Commands use `tools/
com.google.android.katniss | grep enabled` and the same for `tv.remote.service` (`enabled=3` is
disabled), then `logcat -d | grep AudioRecord` right after pressing the button: a session that
opens and closes with no recognition is the missing bridge, not a broken Assistant. The AFMU0
list keeps both now; the lists that still disable `com.google.android.tv.remote.service` are
`rca-ai-pont`, `sharp-aquos-tve21f`, `sony-bravia-4k-gb-us`, `sony-xbr-65x900f`,
`sony-xbr-x900e`, `tcl-32s5k`, `tcl-40s5400a`, `tcl-55s470g`, `tcl-beyondtv2-android-11`,
`tcl-g09-google-tv`, `xgimi-horizon-s-pro`, `xgimi-mogo-3-pro` and `xiaomi-mi-box-s`. Those TVs
haven't been tested for this, but any of them whose remote has a microphone may show the same
symptom. The G10 list has since been tested from the other side: on a 43T6C (G10, Realtek, build
`STT2.230203.001`) a mic press with `com.google.android.tv.remote.service` disabled logs `Unable to
start service Intent { act=...AtvSearchAssistantService ... } U=0: not found` — that service lives in
the package — and re-enabling only it, with every other line applied, brought the button back, so
`tcl-g10-google-tv` keeps it now.
list keeps both now; the lists that still disable `com.google.android.tv.remote.service` include
`liberton-ltv-24h01at`, `panavox-google-tv`, `rca-ai-pont`, `sharp-aquos-tve21f`,
`sony-bravia-4k-gb-us`, `sony-xbr-65x900f`, `sony-xbr-x900e`, `tcl-32s5k`, `tcl-40s5400a`,
`tcl-55s470g`, `tcl-65ep640`, `tcl-beyondtv2-android-11`, `tcl-g09-google-tv`,
`xgimi-horizon-s-pro`, `xgimi-mogo-3-pro`, `xgimi-mogo-4`, `xiaomi-mi-box-3` and
`xiaomi-mi-box-s`, and `grep -l '^com.google.android.tv.remote.service' examples/*/debloat.txt`
prints the current set. Those TVs haven't been tested for this, but any of them whose remote has a
microphone may show the same symptom. The G10 list has since been tested from the other side: on a
43T6C (G10, Realtek, build `STT2.230203.001`) a mic press with `com.google.android.tv.remote.service`
disabled logs `Unable to start service Intent { act=...AtvSearchAssistantService ... } U=0: not found`
— that service lives in the package — and re-enabling only it, with every other line applied, brought
the button back, so `tcl-g10-google-tv` keeps it now.
- **Casting from a phone fails with "Untrusted device" after setupwraith is disabled**, seen on
Chromecast with Google TV HD (`boreal`) on Google TV 14 build UTTC.250917.004, the build the
`examples/google-chromecast-gtv-4k/` list was made on. YouTube on the phone says "Untrusted device.
+1 -1
View File
@@ -57,7 +57,7 @@ com.google.android.apps.mediashell # Chromecast built-in
com.google.android.youtube.tvmusic
com.google.android.videos # Google TV app (purchases/rentals)
com.tcl.smartalexa # Alexa
com.google.android.tv.remote.service # Google TV phone-app remote (the physical remote is unaffected)
com.google.android.tv.remote.service # Google TV phone-app remote, and the remote mic's bridge on this build (#1441): comment out with katniss to keep voice
# Needed for the custom launcher. Disable only AFTER launcher/ is installed (see README Troubleshooting).
com.google.android.tungsten.setupwraith
+140
View File
@@ -0,0 +1,140 @@
"""The lists that disable the remote mic's bridge say so, and a reader can find all of them (#1441).
`com.google.android.tv.remote.service` carries the physical remote's microphone, not only the phone-app
remote. On a TCL 43T6C (G10, Realtek, Google TV 12, firmware `STT2.230203.001`) a mic press starts
`AtvSearchAssistantService` inside it. With the package disabled the button did nothing, and re-enabling
only that package brought it back (#1441). A TCL 55Q651G with the same product and AS50 build had the same
dead mic until the package was re-enabled (#1547). tests/test_g10_mic_bridge.py pins the G10 list, which
keeps the package now.
Two places still pointed the other way:
- examples/tcl-55s470g/debloat.txt is on that same G10 build (AS50). It disables the package by choice,
together with voice, which is fine, but its note said "the physical remote is unaffected". An owner who
comments out katniss to get voice back is told this line does not matter, and the mic stays dead. The
line stays active so the owner's choice stands; only its note changes.
- The troubleshooting bullet named the folders that still disable the package as if the set were
complete, and five lists added after it disable it too. Making every new list edit the bullet would break
CONTRIBUTING.md's "Change nothing outside your folder", so the bullet also gives the grep that always
finds the current set. These tests run that grep's pattern over the lists instead of trusting it.
The list and the bullet are read with the helpers in tests/test_g10_mic_bridge.py, by identifier: the
bullet's prose can be rewritten without touching this file.
Run from the repository root: python3 -m unittest discover -s tests -v
"""
import re
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "tests"))
import test_g10_mic_bridge as mic # noqa: E402 (a module, so its tests do not run twice)
PACKAGE = mic.PACKAGE
DOC = ROOT / "docs" / "TROUBLESHOOTING.md"
# The command the bullet hands out. Its pattern is read as a Python regex, which is the same as grep's
# basic regex for a pattern made of `^`, `.` and plain characters.
GREP = re.compile(r"`grep -l '([^'`]+)' examples/\*/debloat\.txt`")
# A note that says what the package does for the physical remote's mic.
MIC_NOTE = re.compile(r"\bmic\b|AtvSearchAssistantService|#1441")
def lists():
return sorted((ROOT / "examples").glob("*/debloat.txt"))
def read(path):
return path.read_text(encoding="utf-8")
def header(text):
"""The comment block above a list's first package line, where the model and build are named."""
lines = []
for line in text.splitlines():
if line.split("#", 1)[0].strip():
break
lines.append(line)
return "\n".join(lines)
def on_g10_stt2(text):
head = header(text)
return bool(re.search(r"\bdevice G10\b", head)) and "STT2.230203.001" in head
def disablers():
"""The folders whose list disables the package on an active line."""
return {path.parent.name for path in lists() if PACKAGE in mic.active_packages(read(path))}
def bullet(doc):
"""The bullet from its list of folders to its end, with the line wraps joined."""
start = doc.index(mic.ANCHOR)
tail = doc[start:]
end = tail.find("\n- ")
return " ".join(tail[: end if end >= 0 else len(tail)].split())
def grep_finds(pattern):
"""What `grep -l '<pattern>' examples/*/debloat.txt` prints, as folder names."""
rx = re.compile(pattern)
return {path.parent.name for path in lists() if any(rx.search(line) for line in read(path).splitlines())}
class G10ListsSayTheLineCarriesTheMic(unittest.TestCase):
def test_the_g10_build_is_read_from_the_headers(self):
found = {path.parent.name for path in lists() if on_g10_stt2(read(path))}
self.assertLessEqual({"tcl-g10-google-tv", "tcl-55s470g"}, found, f"read {sorted(found)}")
def test_an_active_line_on_that_build_names_the_mic(self):
for path in lists():
text = read(path)
if not on_g10_stt2(text):
continue
lines = text.splitlines()
# A comment line that names the package (a header note, as in panavox-google-tv) counts too.
elsewhere = [line for line in lines if line.lstrip().startswith("#") and PACKAGE in line]
for number, line in enumerate(lines, 1):
code, _, note = line.partition("#")
if code.split()[:1] != [PACKAGE]:
continue
with self.subTest(example=path.parent.name, line=number):
self.assertNotIn(
"unaffected",
note,
"on this build the package carries the remote's mic (#1441), so the note should not "
"say the physical remote is unaffected",
)
self.assertTrue(
any(MIC_NOTE.search(n) for n in [note] + elsewhere),
"the note should say the line also carries the remote's mic, so an owner who wants "
"voice back comments it out with katniss",
)
class TheBulletLeadsToEveryListThatDisablesIt(unittest.TestCase):
def setUp(self):
self.doc = read(DOC)
self.text = bullet(self.doc)
self.command = GREP.search(self.text)
def test_every_list_that_disables_it_is_named_or_found_by_the_grep(self):
missing = disablers() - set(mic.disabler_list(self.doc))
if self.command:
missing -= grep_finds(self.command.group(1))
self.assertEqual(
[],
sorted(missing),
"these lists disable the package but the bullet neither names them nor gives a grep that finds them",
)
def test_the_grep_finds_exactly_the_lists_that_disable_it(self):
if not self.command:
self.skipTest("the bullet gives no grep command")
self.assertEqual(sorted(disablers()), sorted(grep_finds(self.command.group(1))))
if __name__ == "__main__":
unittest.main()