Fix Menu popup, DPAD room-list navigation, and cleartext leftover

- Menu key: WebView refuses to forward Android "system" keycodes
  (KEYCODE_MENU among them) to page JS, confirmed via diagnostic
  logging (downConsumed=false even though the key reaches
  dispatchKeyEvent). Relay it as KEYCODE_X instead - room.js on the
  website side treats 'x' as an alias for the ContextMenu trigger.
- DPAD center/up/down/media keys are now only remapped inside a room
  (shouldIntercept() gates on isRoomList()) - the room-list page uses
  plain ArrowUp/ArrowDown/Enter and never moved focus when those keys
  arrived pre-remapped as Space/P/N.
- Removed a leftover cleartextTrafficPermitted="true" left over from
  local LAN debugging against a docker instance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHgWjmEJXsJEi1EVAoTp4K
This commit is contained in:
GGB
2026-09-17 19:55:12 +02:00
co-authored by Claude Sonnet 5
parent 5a8f593b98
commit 3412357526
2 changed files with 50 additions and 45 deletions
@@ -18,11 +18,27 @@ import android.webkit.WebViewClient
* den einmaligen Autoplay-Ton-Klick eine vertrauenswuerdige Nutzereingabe,
* die ein simuliertes KeyboardEvent nicht liefert.
*
* KEYCODE_MENU wird bewusst NICHT abgefangen: das Oeffnen des TV-Menue-
* Popups wurde bereits gegen die echte Website in einem normalen Browser
* auf echter Fire-TV-Hardware bestaetigt, ohne dass diese App beteiligt
* war - unveraendertes Durchreichen ist die naechste Annaeherung an genau
* dieses schon erprobte Verhalten.
* KEYCODE_MENU wird NICHT als KEYCODE_MENU ins WebView weitergereicht,
* sondern als KEYCODE_X. Grund (per Diagnose-Log bestaetigt): Androids
* WebView.dispatchKeyEvent() liefert "System"-Keycodes (KEYCODE_MENU,
* HOME, BACK, CALL, VOLUME_*, ...) grundsaetzlich nicht an den Seiteninhalt
* aus - sendKey(KEYCODE_MENU) kam zwar in dispatchKeyEvent() an, aber
* webView.dispatchKeyEvent() meldete downConsumed=false/upConsumed=false
* und JS bekam weder keydown- noch contextmenu-Events. Ein gewoehnlicher
* Buchstaben-Keycode wie KEYCODE_P (fuer DPAD_UP) wird dagegen anstandslos
* durchgereicht (downConsumed=true). Die Website (room.js) behandelt 'x'
* deshalb als Alias fuer die 'ContextMenu'-Taste, ausschliesslich fuer
* diesen App-Anwendungsfall.
*
* DPAD Center/Up/Down (und die entsprechenden Medientasten) werden NUR
* innerhalb eines Raums remapped (Space/P/N - siehe Design-Spec). Auf der
* Raumliste (/tv) haben sie dort keine Bedeutung: die Raumliste hat ihr
* eigenes Tastaturschema (ArrowUp/ArrowDown/Enter, siehe tv-rooms.js auf
* der Website) und muss die echten, unveraenderten Tasten bekommen, sonst
* bewegt sich der Fokus zwischen den Raum-Kacheln nie (genau der Fehler,
* der beim ersten Hardware-Test auftrat: die Raumliste laed, aber keine
* Taste tut etwas, weil DPAD_DOWN/CENTER als 'n'/Space statt als
* ArrowDown/Enter ankamen).
*/
class MainActivity : Activity() {
@@ -94,48 +110,37 @@ class MainActivity : Activity() {
sendKey(keyCode, shift = true)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.action != KeyEvent.ACTION_DOWN) {
return when (event.keyCode) {
/** Ob dispatchKeyEvent diese Taste ueberhaupt anfasst - fuer ACTION_DOWN
* und ACTION_UP gleich ausgewertet, damit nie nur die eine Haelfte eines
* Tastendrucks abgefangen wird (sonst haengt Chromiums interner
* Tastenstatus fest, weil ein Keydown ohne passendes Keyup ankaeme). */
private fun shouldIntercept(keyCode: Int): Boolean = when (keyCode) {
KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.KEYCODE_MENU -> true
KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_MEDIA_NEXT,
KeyEvent.KEYCODE_BACK -> true // ACTION_UP derselben Tasten verschlucken, wir haben schon auf ACTION_DOWN reagiert
else -> super.dispatchKeyEvent(event)
KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_MEDIA_NEXT -> !isRoomList()
else -> false
}
}
return when (event.keyCode) {
KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
sendKey(KeyEvent.KEYCODE_SPACE)
true
}
KeyEvent.KEYCODE_DPAD_LEFT -> {
handleSeek(KeyEvent.KEYCODE_DPAD_LEFT, event.repeatCount > 0)
true
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
handleSeek(KeyEvent.KEYCODE_DPAD_RIGHT, event.repeatCount > 0)
true
}
KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
sendKey(KeyEvent.KEYCODE_P)
true
}
KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT -> {
sendKey(KeyEvent.KEYCODE_N)
true
}
KeyEvent.KEYCODE_BACK -> {
when {
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (!shouldIntercept(event.keyCode)) return super.dispatchKeyEvent(event)
if (event.action != KeyEvent.ACTION_DOWN) return true // ACTION_UP der abgefangenen Taste verschlucken, ACTION_DOWN hat schon reagiert
when (event.keyCode) {
KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> sendKey(KeyEvent.KEYCODE_SPACE)
KeyEvent.KEYCODE_DPAD_LEFT -> handleSeek(KeyEvent.KEYCODE_DPAD_LEFT, event.repeatCount > 0)
KeyEvent.KEYCODE_DPAD_RIGHT -> handleSeek(KeyEvent.KEYCODE_DPAD_RIGHT, event.repeatCount > 0)
KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS -> sendKey(KeyEvent.KEYCODE_P)
KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT -> sendKey(KeyEvent.KEYCODE_N)
KeyEvent.KEYCODE_MENU -> sendKey(KeyEvent.KEYCODE_X)
KeyEvent.KEYCODE_BACK -> when {
isRoomList() -> { /* verschluckt, keine weitere Aktion */ }
isFullscreen() -> sendKey(KeyEvent.KEYCODE_ESCAPE)
else -> webView.loadUrl(BuildConfig.WEBSITE_URL)
}
true
}
else -> super.dispatchKeyEvent(event)
}
return true
}
companion object {
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>