mirror of
https://github.com/Gathin23/cleanyourtv.git
synced 2026-10-27 19:54:45 +00:00
device note, example update and wiring test for key_filter
- docs/devices/tcl-55p71b-pro-g09.md: second G09 unit (AS23 firmware); records the all-or-nothing loss of remote key repeat under any bound key-filtering service, the unrecoverable volume tick, the remote's real keycodes, and the setupwraith Wi-Fi ownership handover step. - examples/tcl-g09-google-tv: README warns that INPUT_KEY_SERVICE=1 breaks long-press repeat on this build and points at key_filter; debloat.txt gains the packages the AS23 unit additionally confirmed (graymatrix.did, mirakulo, smartalexa, useragreement, initsetup) with the initsetup/channel-install caveat reconciled in the header. - tests/test_key_filter.py: asserts every hop of the switch (config.xml default, Config field + theme override, applyKeyFilter clearing the flag, both services calling it, docs mentioning it) so a rename cannot silently restore the broken repeat.
This commit is contained in:
1 parent
baa86b99b2
commit
3d382183b2
4 files changed
+183
-1
No files matched your search
@@ -0,0 +1,88 @@
|
||||
# TCL 55P71B Pro: second G09 unit, key filtering kills remote key repeat
|
||||
|
||||
Checked on a real TV in one session on 2026-10-22 (India, Shan7anu_5G Wi-Fi), after a factory reset,
|
||||
then re-verified the next day. Companion list: [`examples/tcl-g09-google-tv/`](../examples/tcl-g09-google-tv),
|
||||
whose packages and remote-button table matched this unit.
|
||||
|
||||
## Identify the firmware, not just the generic model name
|
||||
|
||||
| Field | Observed value |
|
||||
|---|---|
|
||||
| Retail model | `55P71B Pro`, supplied by the owner; not exposed by `ro.product.model` |
|
||||
| `ro.product.manufacturer` / `brand` | `TCL` |
|
||||
| `ro.product.model` | `Smart TV Pro` |
|
||||
| `ro.product.name` (product) | `G09_4K_GB` |
|
||||
| `ro.product.device` | `G09` |
|
||||
| `ro.hardware` / `ro.board.platform` | `amlogic` / `t5m` |
|
||||
| `ro.product.board` | `TC8000` |
|
||||
| Android / SDK | `12` / `31` |
|
||||
| `ro.build.display.id` / firmware | `STT2.230203.001.AS23 release-keys` |
|
||||
| Security patch | `2025-09-05` |
|
||||
| `ro.product.cpu.abilist` | `armeabi-v7a,armeabi` only (32-bit; no `arm64`, no `x86`) |
|
||||
| `ro.adb.secure` | `1` |
|
||||
|
||||
The Brazil unit in `examples/tcl-g09-google-tv/` reports the same product name on firmware
|
||||
`STT2.230203.001 AS24`, so this is the same firmware family one build earlier. `Smart TV Pro` and
|
||||
`G09` alone do not pin the retail model — several TCL ranges share them.
|
||||
|
||||
## Held-down volume does not repeat while any key-filtering service is bound
|
||||
|
||||
The finding this note exists for, and the reason for `key_filter` in `launcher/` (issue #763):
|
||||
|
||||
- `HomeKeyService` + `InputKeyService` enabled → holding volume up/down on the remote steps once per
|
||||
press.
|
||||
- Either one alone → still broken.
|
||||
- All accessibility services off (`enabled_accessibility_services` empty, `accessibility_enabled 0`)
|
||||
→ repeat works again.
|
||||
|
||||
On this build the `AccessibilityInputFilter` path drops key auto-repeat for any service that requests
|
||||
`FLAG_REQUEST_FILTER_KEY_EVENTS`; it is all-or-nothing. `RemoteKeys.applyKeyFilter` lets the services
|
||||
stay bound with the flag stripped (`"key_filter": false` in a theme), which is the configuration this
|
||||
unit ended on — the tradeoff is the remote buttons below stay dead, the user's pick.
|
||||
|
||||
## The volume tick is gone with the stock launcher
|
||||
|
||||
The beep the TV played on each volume press disappeared once launcherx was disabled, and nothing got it
|
||||
back: every debloated package was re-enabled in batches (`pm enable`), the TV rebooted, and no logcat
|
||||
line even attempted a sound on a press (`com.tcl.systemui.shared` still draws the volume bar).
|
||||
`STREAM_SYSTEM` reports `streamVolume:1` on this build regardless of `volume_system`, but that is a
|
||||
diagnostic dead end rather than the mechanism — the tick appears to have been produced by the stock
|
||||
home, not any single package.
|
||||
|
||||
## Remote buttons
|
||||
|
||||
| Button | Keycode | Scancode | Note |
|
||||
|---|---|---|---|
|
||||
| Gear (Settings) | 83 (`KEYCODE_NOTIFICATION`) | 561 | dead without a helper; stock home opened its panel |
|
||||
| Netflix | 4062 | 472 | IR; `com.tcl.ttvs`'s `KeyDownBehaviorReceiver` logs `keyDown:keyString=4062 type=IR` but launches nothing |
|
||||
| Prime Video | 4099 | 473 | same path (from the G09 example's table) |
|
||||
| YouTube | 4065 | 569 | same path |
|
||||
|
||||
Captured with `tools/tv.py 'logcat -d | grep handleKeyBeforeQueueing'` while pressing the real remote —
|
||||
injected keys never reach accessibility filtering. Volume keys arrive as `source=0x301, deviceId=6`,
|
||||
scanCode 115.
|
||||
|
||||
## Home handover
|
||||
|
||||
Same as the G09 example: `launcherx` owns Home (priority 2), `setupwraith/.RecoveryActivity` claims it
|
||||
the moment launcherx is disabled, and the saved Wi-Fi network belonged to setupwraith after the reset —
|
||||
`dumpsys wifi | grep -o 'cname=[^ ]*'` showed `cname=com.google.android.tungsten.setupwraith` until the
|
||||
network was forgotten and re-joined, after which it read `cname=com.android.tv.settings`. Only then was
|
||||
setupwraith disabled and `build-and-install.sh` re-run; its own recovery check caught the failed
|
||||
handover and put launcherx back the first time, exactly as designed.
|
||||
|
||||
## What was kept that the example disables
|
||||
|
||||
Owner's choice, verified working: `com.google.android.apps.mediashell` (Chromecast), `com.tcl.miracast`
|
||||
+ `com.tcl.magiconnectfree` (mirroring), `com.tcl.airplay2` + `com.amlogic.airplay` (AirPlay),
|
||||
`com.google.android.katniss` (Assistant), `com.tcl.hearaid`, `com.tcl.ttvs` (firmware updates; also owns
|
||||
`KeyDownBehaviorReceiver`), `com.google.android.tv.remote.service`, `com.google.android.feedback`.
|
||||
The example's own disabled set applied cleanly here too.
|
||||
|
||||
## Limits
|
||||
|
||||
- One unit, two days; the keep-list above is preference, not a recommendation either way.
|
||||
- The tick was not recovered; the mechanism is unknown, so do not assume the same packages are
|
||||
responsible on other G09 builds.
|
||||
- `key_filter` was verified on the launcher's own build from `main` plus the patch under #763;
|
||||
it does not exist in older builds — the manual alternative is disabling both services.
|
||||
@@ -75,6 +75,17 @@ Found with `logcat` while pressing each button: TCL logs every press as `handleK
|
||||
{ ... keyCode=..., scanCode=... }` from `WindowManager`. Keys injected over ADB never reach `InputKeyService`,
|
||||
so check with the real remote.
|
||||
|
||||
## `INPUT_KEY_SERVICE=1` and volume repeat
|
||||
|
||||
On a second unit (55P71B Pro, same `G09_4K_GB` product, firmware `STT2.230203.001.AS23`, India) any bound
|
||||
key-filtering accessibility service breaks key auto-repeat: long-pressing volume steps once per press,
|
||||
with either helper alone or both. It is all-or-nothing on this build — either the app buttons above work,
|
||||
or volume repeat does. `key_filter` in the launcher (`"key_filter": false` in `config.json`) lets the
|
||||
service stay bound with filtering off, which is the repeat-keeping half of that choice; see
|
||||
[docs/TROUBLESHOOTING.md](../../docs/TROUBLESHOOTING.md) and
|
||||
[the unit's note](../../docs/devices/tcl-55p71b-pro-g09.md). The same unit also lost the volume beep for
|
||||
good when launcherx was disabled — re-enabling every debloated package did not bring it back.
|
||||
|
||||
## Notes for this set
|
||||
|
||||
- The remote's Input button keeps working without Google TV's home: TCL draws that panel itself.
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#
|
||||
# Stock launcher: com.google.android.apps.tv.launcherx. Live TV app: com.tcl.tv (also owns HDMI playback).
|
||||
# Keep com.tcl.tvinput, com.tcl.suspension (Input/source menu), com.tcl.dashboard, com.tcl.autopair,
|
||||
# com.tcl.systemserver (mic hotkeys), com.tcl.providers.config and com.tcl.initsetup (channel install).
|
||||
# com.tcl.systemserver (mic hotkeys) and com.tcl.providers.config. com.tcl.initsetup is in the list
|
||||
# below (it also owns channel installation) - comment it out if you still need to scan channels.
|
||||
# Firmware updates come through Play Services (gms SystemUpdatePanoActivity); there is no TCL OTA package.
|
||||
|
||||
# TCL telemetry and analytics
|
||||
@@ -16,6 +17,8 @@ com.tcl.ttvs # TCL BI/awareness/identifier services
|
||||
com.tcl.logkit # log capture and upload
|
||||
com.tcl.interactive # TCL TIAS background service
|
||||
com.google.android.feedback
|
||||
com.graymatrix.did # device analytics (data app; on the AS23 unit)
|
||||
com.mirakulo.astrotv.service # analytics service (data app; on the AS23 unit)
|
||||
|
||||
# TCL content, ads and promos
|
||||
com.tcl.channelplus # TCL Live FAST channels, also removes them from Live TV
|
||||
@@ -28,6 +31,9 @@ com.tcl.smartlink.core # bundles a silent app installer
|
||||
|
||||
# Background services
|
||||
com.tcl.guard # TGuard: device-use tracking, memory cleaner
|
||||
com.tcl.smartalexa # Alexa client (AS23 unit; keep it if you use Alexa)
|
||||
com.tcl.useragreement # EULA/privacy screens (AS23 unit)
|
||||
com.tcl.initsetup # TCL first-run setup incl. SambaTV ACR hooks; re-enable before rescanning channels
|
||||
com.tcl.usercenter # TCL account sign-in
|
||||
com.tcl.exhibit # store demo
|
||||
com.tcl.esticker # store demo stickers
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
"""key_filter: the switch that keeps remote key repeat must reach both helpers and the docs.
|
||||
|
||||
On TCL Google TV 12 builds (observed on a 55P71B Pro, `G09_4K_GB`, STT2.230203.001.AS23) any bound
|
||||
accessibility service requesting FLAG_REQUEST_FILTER_KEY_EVENTS breaks key auto-repeat from the real
|
||||
remote: long-pressing volume steps once per press with either helper alone or both. `key_filter`
|
||||
defaults to true so existing installs are unchanged; set false, each helper strips the flag from its
|
||||
own AccessibilityServiceInfo at bind time via RemoteKeys.applyKeyFilter.
|
||||
|
||||
The flag only helps if every hop of it exists: config.xml defines it, Config reads it (and a theme can
|
||||
override it), both services call applyKeyFilter, applyKeyFilter actually clears the flag, and the
|
||||
theme/table docs tell a user the switch exists. A rename at any one of those points silently returns
|
||||
the broken behaviour, so each hop gets its own assertion.
|
||||
|
||||
Run from the repository root: python3 -m unittest discover -s tests -v
|
||||
"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def read(relative):
|
||||
"""Text of a repo file, or empty if it does not exist yet (each test then fails with its own message)."""
|
||||
path = ROOT / relative
|
||||
return path.read_text(encoding="utf-8") if path.is_file() else ""
|
||||
|
||||
|
||||
CONFIG_XML = read("launcher/app/src/main/res/values/config.xml")
|
||||
CONFIG_JAVA = read("launcher/app/src/main/java/com/gathin/tvhome/Config.java")
|
||||
REMOTE_KEYS = read("launcher/app/src/main/java/com/gathin/tvhome/RemoteKeys.java")
|
||||
HOME_KEY = read("launcher/app/src/main/java/com/gathin/tvhome/HomeKeyService.java")
|
||||
INPUT_KEY = read("launcher/app/src/main/java/com/gathin/tvhome/InputKeyService.java")
|
||||
THEMES_README = read("themes/README.md")
|
||||
TROUBLE = read("docs/TROUBLESHOOTING.md")
|
||||
|
||||
|
||||
class TestKeyFilterWiring(unittest.TestCase):
|
||||
|
||||
def test_config_xml_defines_key_filter_defaulting_true(self):
|
||||
self.assertIn('<bool name="key_filter">true</bool>', CONFIG_XML,
|
||||
"config.xml must define key_filter with default true so existing installs are unchanged")
|
||||
|
||||
def test_config_reads_resource_and_theme_override(self):
|
||||
self.assertIn('R.bool.key_filter', CONFIG_JAVA,
|
||||
"Config must read the key_filter resource")
|
||||
self.assertIn('"key_filter"', CONFIG_JAVA,
|
||||
"Config must accept a theme config.json \"key_filter\" override")
|
||||
self.assertRegex(CONFIG_JAVA, r'\bboolean keyFilter\b',
|
||||
"Config must carry a keyFilter field")
|
||||
self.assertIn('this.keyFilter = keyFilter', CONFIG_JAVA,
|
||||
"Config constructor must store keyFilter")
|
||||
|
||||
def test_apply_key_filter_clears_the_flag(self):
|
||||
self.assertIn('applyKeyFilter', REMOTE_KEYS)
|
||||
self.assertIn('FLAG_REQUEST_FILTER_KEY_EVENTS', REMOTE_KEYS,
|
||||
"applyKeyFilter must clear FLAG_REQUEST_FILTER_KEY_EVENTS when key_filter is false")
|
||||
self.assertIn('setServiceInfo', REMOTE_KEYS,
|
||||
"applyKeyFilter must apply the info back through setServiceInfo()")
|
||||
self.assertRegex(REMOTE_KEYS, r'if \(keyFilter\) return',
|
||||
"applyKeyFilter must be a no-op when key_filter is true")
|
||||
|
||||
def test_both_services_apply_it(self):
|
||||
for name, src in (("HomeKeyService", HOME_KEY), ("InputKeyService", INPUT_KEY)):
|
||||
self.assertIn('RemoteKeys.applyKeyFilter(this, config.keyFilter)', src,
|
||||
f"{name} must call RemoteKeys.applyKeyFilter with the config value")
|
||||
|
||||
def test_docs_mention_the_switch(self):
|
||||
self.assertIn('key_filter', THEMES_README,
|
||||
"themes/README.md's config.json table must list key_filter")
|
||||
self.assertIn('key_filter', TROUBLE,
|
||||
"docs/TROUBLESHOOTING.md must tell a user hitting broken repeat about key_filter")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user