diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..003a1db --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,25 @@ +name: build +on: + push: + branches: [main] + tags: ['*'] +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true +jobs: + build: + runs-on: ubuntu-latest + container: + image: docksee/nixos-gitea:${{ vars.NIX_IMAGE_VERSION }} + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Unit tests + run: nix develop --command gradle --no-daemon :app:testReleaseUnitTest + - name: Build release APK + run: nix develop --command gradle --no-daemon :app:assembleRelease + - uses: actions/upload-artifact@v3 + with: + name: android-webview-kiosk-apk + path: app/build/outputs/apk/release/app-release.apk diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a574190 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,52 @@ +# android-webview-kiosk + +Android TV app: fullscreen WebView showing one hardcoded Grafana dashboard. +Runs on the living-room Sony Bravia KD-65XE9305 (Android 8.0, API 26; +Android System WebView updates independently via Play Store — currently v138). +Launched remotely through the Bravia REST IP-control API by Control4 +(driver lives in a separate project). See README.md for the API sequence. + +## Lab project — with deviations + +This is a lab project (`lab` skill conventions apply) EXCEPT: + +- No container image, no helm chart, no /livez//readyz — artifact is an APK. +- No `nix build`: gradle needs network for Maven deps. The flake is a dev + shell only. All builds run as `nix develop --command gradle ...`. +- Agents cannot run nix/gradle/adb here — ask the user to run commands + and report output. + +## NixOS aapt2 workaround + +AGP cannot run the Maven-downloaded aapt2 on NixOS (dynamically linked +generic Linux binary). The nix shellHook rewrites the +`android.aapt2FromMavenOverride` line in `gradle.properties` to point at +the SDK's nix-patched aapt2 on every `nix develop`. The sentinel value +`__NIX_SHELLHOOK_SETS_THIS__` is what's tracked in git; the shellHook +replaces it before any gradle command runs. + +## Commands (user-run, from repo root) + +- Tests: `nix develop --command gradle --no-daemon :app:testReleaseUnitTest` +- Release APK: `nix develop --command gradle --no-daemon :app:assembleRelease` + → `app/build/outputs/apk/release/app-release.apk` +- Sideload: `adb connect :5555 && adb install -r ` + (bump `versionCode` in app/build.gradle.kts first for upgrades) + +## Key facts + +- Package/applicationId: `cz.c3c.webviewkiosk`; repo name keeps the + `android-` prefix, the package can't (hyphens illegal). +- Dashboard URL hardcoded in `MainActivity.DASHBOARD_URL` — changing it + means rebuild + sideload. Intentional: keeps Control4 integration to a + single parameterless launch call. +- `signing/release.keystore` is committed on purpose (private repo, + LAN-only kiosk) so every build is upgrade-compatible. Don't reuse the key. +- minSdk 26 is a hard floor — the TV never gets newer Android. +- `LEANBACK_LAUNCHER` category + `android:banner` are what make the app + visible to the Bravia API's `getApplicationList`/`setActiveApp`. Don't + remove either. +- App URI (from `getApplicationList`): + `com.sony.dtv.cz.c3c.webviewkiosk.cz.c3c.webviewkiosk.MainActivity` +- `setActiveApp` alone wakes the TV from standby — no separate + `setPowerStatus` call needed. Control4 driver can skip the wake step. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab7af5a --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# android-webview-kiosk + +Fullscreen WebView kiosk for Android TV. Shows the house Grafana dashboard: + + +Target device: Sony Bravia KD-65XE9305 (Android 8.0). Built for remote +launch via the Bravia REST IP-control API, driven by Control4. + +## Build + + nix develop --command gradle --no-daemon :app:assembleRelease + +APK: `app/build/outputs/apk/release/app-release.apk` + +## Sideload + +1. TV one-time: Settings → About → press *Build* 7× → Developer options → + enable ADB debugging. +2. `adb connect :5555` +3. `adb install -r app/build/outputs/apk/release/app-release.apk` + +Upgrades: bump `versionCode` in `app/build.gradle.kts`, rebuild, reinstall +with `-r`. Same committed keystore = no uninstall needed. + +## Remote launch (Sony Bravia IP control) + +TV prerequisite: IP control auth = "Normal and Pre-Shared Key", Remote +start enabled (wake from deep standby). + +App URI (from `getApplicationList`): +`com.sony.dtv.cz.c3c.webviewkiosk.cz.c3c.webviewkiosk.MainActivity` + +**`setActiveApp` alone wakes the TV from standby** — no separate +`setPowerStatus` call is needed. The Control4 driver can use a single call: + + curl -s -X POST http://$TV_IP/sony/appControl \ + -H "X-Auth-PSK: $PSK" -H 'Content-Type: application/json' \ + -d '{"method":"setActiveApp","id":601,"version":"1.0","params":[{"uri":"com.sony.dtv.cz.c3c.webviewkiosk.cz.c3c.webviewkiosk.MainActivity"}]}' + +If the TV is already on, `setActiveApp` brings the kiosk to the foreground. +If in standby, it wakes and launches directly. + +For reference, the two-step sequence (if needed for other integrations): + + # 1. wake (optional — setActiveApp does this implicitly) + curl -s -X POST http://$TV_IP/sony/system \ + -H "X-Auth-PSK: $PSK" -H 'Content-Type: application/json' \ + -d '{"method":"setPowerStatus","id":55,"version":"1.0","params":[{"status":true}]}' + + # 2. launch (~6 s from quick standby, up to ~30 s from deep eco standby) + curl -s -X POST http://$TV_IP/sony/appControl \ + -H "X-Auth-PSK: $PSK" -H 'Content-Type: application/json' \ + -d '{"method":"setActiveApp","id":601,"version":"1.0","params":[{"uri":"com.sony.dtv.cz.c3c.webviewkiosk.cz.c3c.webviewkiosk.MainActivity"}]}' + +Control4: a DriverWorks driver issuing `setActiveApp` is a separate project.