feat(android): scaffold the native Android client (Rust-heavy JNI bridge)
apple / swift (push) Successful in 52s
ci / docs-site (push) Successful in 27s
android / android (push) Successful in 4m52s
ci / web (push) Successful in 26s
ci / bench (push) Successful in 1m33s
ci / rust (push) Successful in 6m56s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m54s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 4m29s
deb / build-publish (push) Successful in 6m46s
docker / deploy-docs (push) Successful in 22s
apple / swift (push) Successful in 52s
ci / docs-site (push) Successful in 27s
android / android (push) Successful in 4m52s
ci / web (push) Successful in 26s
ci / bench (push) Successful in 1m33s
ci / rust (push) Successful in 6m56s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m54s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 4m29s
deb / build-publish (push) Successful in 6m46s
docker / deploy-docs (push) Successful in 22s
Rust-heavy client model (like punktfunk-client-linux): a new cdylib crate crates/punktfunk-android links punktfunk-core and exposes the JNI seam; Kotlin (clients/android) owns only the Android-framework surface. Kotlin can't import the C header the way Swift can, so the bridge is written in Rust to reuse the Linux client's orchestration rather than re-port it. - crates/punktfunk-android: JNI bridge — abiVersion/coreVersion native-link proof + session connect/close handle; plane pumps stubbed for M4 stage 1. - clients/android: Gradle project — :app (Compose) + :kit (Android library with a cargo-ndk Exec task -> jniLibs). AGP 9.2 / Gradle 9.4.1 / Kotlin 2.3.21 / Compose BOM 2026.05.01 / compileSdk 37 / targetSdk 36 / minSdk 31, shipping arm64-v8a + x86_64. Phone + TV (leanback) installable. README rewritten. - .gitea/workflows/android.yml: CI mirroring apple.yml on a Linux runner. - punktfunk-core: switch rcgen to the ring backend so the whole quic tree is aws-lc-free (smaller client .so, cmake-free cross-compile; a win for all targets). Validated on this box: :app:assembleDebug -> APK with both ABIs; emulator first-light renders the bridge linked (core ABI v2) with logcat confirmation; clippy -D warnings + cargo fmt clean; core tests green on the ring backend. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import java.io.File
|
||||
import java.util.Properties
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
// AGP 9 built-in Kotlin compiles this module's Kotlin (NativeBridge) — no kotlin.android plugin.
|
||||
id("com.android.library")
|
||||
}
|
||||
|
||||
val ndkVer = "28.2.13676358" // r28 LTS — matches the SDK NDK installed for cargo-ndk
|
||||
|
||||
android {
|
||||
namespace = "io.unom.punktfunk.kit"
|
||||
compileSdk = 37 // Android 17 — align with :app (androidx.core 1.19.0 requires it)
|
||||
ndkVersion = ndkVer
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 31
|
||||
ndk { abiFilters += listOf("arm64-v8a", "x86_64") }
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
packaging { jniLibs { useLegacyPackaging = false } } // 16 KB-page friendly
|
||||
}
|
||||
|
||||
kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_21) } }
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// cargo-ndk: cross-compile crates/punktfunk-android into this module's jniLibs/<abi>/ so the
|
||||
// resulting libpunktfunk_android.so is packaged into the app (and any AAR this module produces).
|
||||
// NDK r28+ aligns to 16 KB pages by default — no extra linker flags. Prereqs (see clients/android
|
||||
// /README.md): `cargo install cargo-ndk` + `rustup target add aarch64-linux-android x86_64-linux-android`.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
val repoRoot = rootDir.parentFile.parentFile // clients/android -> clients -> repo root
|
||||
val cargoBin = "${System.getProperty("user.home")}/.cargo/bin"
|
||||
|
||||
// SDK location without depending on AGP's DSL (sdkDirectory isn't in AGP 9's library extension):
|
||||
// env first (set by Android Studio and by our CLI shell), then local.properties, then the default.
|
||||
fun androidSdkDir(): String {
|
||||
System.getenv("ANDROID_HOME")?.let { return it }
|
||||
System.getenv("ANDROID_SDK_ROOT")?.let { return it }
|
||||
val lp = rootProject.file("local.properties")
|
||||
if (lp.exists()) {
|
||||
val props = Properties()
|
||||
lp.inputStream().use { props.load(it) }
|
||||
props.getProperty("sdk.dir")?.let { return it }
|
||||
}
|
||||
return "${System.getProperty("user.home")}/Library/Android/sdk"
|
||||
}
|
||||
|
||||
fun registerCargoNdk(taskName: String, release: Boolean) =
|
||||
tasks.register<Exec>(taskName) {
|
||||
group = "rust"
|
||||
description = "cargo-ndk build of punktfunk-android (${if (release) "release" else "debug"})"
|
||||
workingDir = repoRoot
|
||||
val sdk = androidSdkDir()
|
||||
// A GUI Android Studio launch does not source the login shell, so make cargo + the NDK
|
||||
// discoverable explicitly (works the same from a bare CLI).
|
||||
environment("PATH", cargoBin + File.pathSeparator + System.getenv("PATH"))
|
||||
environment("ANDROID_HOME", sdk)
|
||||
environment("ANDROID_NDK_HOME", "$sdk/ndk/$ndkVer")
|
||||
val cmd = mutableListOf(
|
||||
"cargo", "ndk",
|
||||
"-t", "arm64-v8a", "-t", "x86_64",
|
||||
"-o", file("src/main/jniLibs").absolutePath,
|
||||
"build", "-p", "punktfunk-android",
|
||||
)
|
||||
if (release) cmd += "--release"
|
||||
commandLine(cmd)
|
||||
}
|
||||
|
||||
val cargoNdkDebug = registerCargoNdk("cargoNdkDebug", release = false)
|
||||
val cargoNdkRelease = registerCargoNdk("cargoNdkRelease", release = true)
|
||||
|
||||
afterEvaluate {
|
||||
tasks.named("preDebugBuild").configure { dependsOn(cargoNdkDebug) }
|
||||
tasks.named("preReleaseBuild").configure { dependsOn(cargoNdkRelease) }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Library module manifest. The namespace lives in build.gradle.kts (AGP 9). -->
|
||||
<manifest />
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.unom.punktfunk.kit
|
||||
|
||||
/**
|
||||
* The single JNI seam to `libpunktfunk_android.so` (the Rust-heavy client core).
|
||||
*
|
||||
* Symbols are implemented in `crates/punktfunk-android`. This object is intentionally thin —
|
||||
* all protocol logic lives in Rust (`punktfunk-core` + the connector); Kotlin only marshals.
|
||||
*/
|
||||
object NativeBridge {
|
||||
init {
|
||||
System.loadLibrary("punktfunk_android")
|
||||
}
|
||||
|
||||
/** punktfunk-core C-ABI version. A successful call proves the native library is linked. */
|
||||
external fun abiVersion(): Int
|
||||
|
||||
/** punktfunk-core crate version string. */
|
||||
external fun coreVersion(): String
|
||||
|
||||
/**
|
||||
* Connect to a host (trust-on-first-use, anonymous) and return an opaque session handle, or
|
||||
* `0` on failure. Pair the handle with exactly one [nativeClose].
|
||||
*
|
||||
* TODO(M4): pin/identity/pairing, plane pumps (video/audio/rumble/HID), input, mode
|
||||
* renegotiation — see `crates/punktfunk-android/src/session.rs`.
|
||||
*/
|
||||
external fun nativeConnect(host: String, port: Int, width: Int, height: Int, refreshHz: Int): Long
|
||||
|
||||
/** Tear down a session handle returned by [nativeConnect]. No-op on `0`. */
|
||||
external fun nativeClose(handle: Long)
|
||||
}
|
||||
Reference in New Issue
Block a user