From 918462a966b38aa525787a73c5e61c67af9bb774 Mon Sep 17 00:00:00 2001 From: Timothy <6560631+TimoPtr@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:05:01 +0200 Subject: [PATCH] Map DPAD_DOWN to TAB in HAWebViewActivity --- .../android/util/compose/webview/HAWebView.kt | 22 +++++++++++++++++++ .../android/webview/WebViewActivity.kt | 11 ---------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/util/compose/webview/HAWebView.kt b/app/src/main/kotlin/io/homeassistant/companion/android/util/compose/webview/HAWebView.kt index bcd0d257419..09d8c132704 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/util/compose/webview/HAWebView.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/util/compose/webview/HAWebView.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.res.Configuration import android.graphics.Color import android.os.Build +import android.view.KeyEvent import android.webkit.WebSettings import android.webkit.WebView import android.widget.FrameLayout @@ -38,6 +39,7 @@ const val BLANK_URL = "about:blank" * - Night mode support * - Custom user agent * - Transparent background + * - DPAD_DOWN to TAB remapping for Android TV navigation (via `OnKeyListener`) * * This composable provides a convenient way to embed a WebView within a Jetpack Compose UI. * Further customization of the WebView instance is possible through the [configure] lambda. @@ -53,6 +55,8 @@ const val BLANK_URL = "about:blank" * @param modifier The modifier to be applied to this WebView. * @param nightModeTheme current [NightModeTheme] * @param configure A lambda that allows for customization of the WebView instance. + * Note: calling `setOnKeyListener` in this lambda will override the default + * DPAD_DOWN to TAB remapping. * @param factory A lambda that creates the WebView instance. If this returns null, a new * WebView will be created with the current context. This is useful for providing * a pre-configured WebView instance. @@ -90,6 +94,7 @@ fun HAWebView( FrameLayout.LayoutParams.MATCH_PARENT, ) defaultSettings() + remapDpadDownToTab() configure(this) } } catch (t: Throwable) { @@ -123,6 +128,23 @@ fun HAWebView( } } +/** + * Remaps DPAD_DOWN key events to TAB for Android TV remote control navigation. + * + * Android TV remotes send DPAD_DOWN events which don't navigate the web frontend + * properly. Remapping to TAB allows basic element-to-element navigation in the WebView. + */ +private fun WebView.remapDpadDownToTab() { + setOnKeyListener { view, keyCode, event -> + if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && event.action == KeyEvent.ACTION_DOWN) { + view.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB)) + true + } else { + false + } + } +} + fun WebView.settings(configureDsl: WebSettings.() -> Unit) { try { settings.configureDsl() diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt b/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt index 900706926b4..a2608b8abfe 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt @@ -20,7 +20,6 @@ import android.text.method.HideReturnsTransformationMethod import android.text.method.PasswordTransformationMethod import android.util.DisplayMetrics import android.util.Rational -import android.view.KeyEvent import android.view.MotionEvent import android.view.View import android.view.WindowManager @@ -2163,16 +2162,6 @@ class WebViewActivity : evaluateJavascript(eventCode, null) } - override fun dispatchKeyEvent(event: KeyEvent): Boolean { - // Workaround to sideload on Android TV and use a remote for basic navigation in WebView - if (event.keyCode == KeyEvent.KEYCODE_DPAD_DOWN && event.action == KeyEvent.ACTION_DOWN) { - dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB)) - return true - } - - return super.dispatchKeyEvent(event) - } - private fun setWebViewZoom() = lifecycleScope.launch { // Set base zoom level (percentage must be scaled to device density/percentage) webView.setInitialScale((resources.displayMetrics.density * presenter.getPageZoomLevel()).toInt())