feat: leanback webview kiosk activity

This commit is contained in:
Arnie via Claude
2026-06-12 13:21:16 +02:00
parent af5d4aa76a
commit 6ca64ea2ff
4 changed files with 117 additions and 0 deletions
+30
View File
@@ -1,3 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<!-- TV-only app: leanback required, touchscreen not -->
<uses-feature
android:name="android.software.leanback"
android:required="true" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<application
android:allowBackup="false"
android:banner="@drawable/banner"
android:icon="@drawable/icon"
android:label="Grafana Kiosk"
android:usesCleartextTraffic="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="landscape"
android:configChanges="keyboard|keyboardHidden|navigation|screenSize|density">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,87 @@
package cz.c3c.webviewkiosk
import android.annotation.SuppressLint
import android.app.Activity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.WindowManager
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
class MainActivity : Activity() {
companion object {
const val DASHBOARD_URL =
"https://grafana.c3c.cz/public-dashboards/381fe3e71e164eb99dd0b10e246a36e2"
}
private lateinit var webView: WebView
private val handler = Handler(Looper.getMainLooper())
private val backoff = BackoffPolicy(initialDelayMs = 2_000, maxDelayMs = 60_000)
private var lastLoadFailed = false
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
webView = WebView(this)
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
cacheMode = WebSettings.LOAD_DEFAULT
mediaPlaybackRequiresUserGesture = false
}
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
// onPageFinished also fires after a failed load (error page),
// so only treat a clean finish as recovery.
if (!lastLoadFailed) backoff.reset()
lastLoadFailed = false
}
override fun onReceivedError(
view: WebView,
request: WebResourceRequest,
error: WebResourceError,
) {
// Subresource failures (one panel's query, a font) must not
// tear down the whole page; only main-frame failures retry.
if (!request.isForMainFrame) return
lastLoadFailed = true
handler.postDelayed(
{ view.loadUrl(DASHBOARD_URL) },
backoff.nextDelayMs(),
)
}
}
setContentView(webView)
hideSystemUi()
webView.loadUrl(DASHBOARD_URL)
}
override fun onResume() {
super.onResume()
hideSystemUi()
}
private fun hideSystemUi() {
@Suppress("DEPRECATION") // WindowInsetsController needs API 30; minSdk is 26
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
}
override fun onDestroy() {
handler.removeCallbacksAndMessages(null)
webView.destroy()
super.onDestroy()
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B