The android/ directory contains 7 Kotlin files + 3 *.gradle.kts files,
all banned by the Hyperpolymath language policy:
android/settings.gradle.kts android/build.gradle.kts android/app/build.gradle.kts android/app/src/main/java/ai/neurophone/MainActivity.kt android/app/src/main/java/ai/neurophone/NeurophoneService.kt android/app/src/main/java/ai/neurophone/BootReceiver.kt android/app/src/main/java/ai/neurophone/NativeLib.kt android/app/src/main/java/ai/neurophone/widget/NeurophoneAppWidget.kt android/app/src/main/java/ai/neurophone/widget/NeurophoneWidgetActions.kt android/app/src/main/java/ai/neurophone/widget/NeurophoneWidgetConfigureActivity.kt
This keeps two estate CI gates permanently red:
-
Check for Banned Languages(workflowlanguage-policy.yml, stepCheck for Java/Kotlin files) -
governance / Language / package anti-pattern policy
Both are sourced from .github/workflows/language-policy.yml:87-94 which
hard-fails on any .kt / .kts / *.java match.
-
Migrate the Android UI + service to Tauri 2.0 (Rust backend + AffineScript/web UI).
-
Both CI checks turn green.
-
Behaviour preserved: sensor capture, foreground presence, widgets, boot-start.
-
Sub-PRs are small and reviewable; nothing bulk-deleted until owner approves this RFC.
| Kotlin component | LoC | Current role | Replacement |
|---|---|---|---|
|
377 |
UI shell (status / input / response), sensor capture in-activity, permissions,
intent handling (MAIN, SEND, VIEW |
Tauri 2 activity ( |
|
162 |
|
Rust foreground service via thin Java entry-point. Service class
|
|
25 |
|
Generated Java |
|
93 |
JNI surface declarations + sensor-type-string→int mapping. |
DELETE entirely. All JNI exports already live (as stubs) in
|
|
152 |
|
Generated Java |
|
47 |
Lightweight |
Generated Java |
|
70 |
Widget config UI (one checkbox: local-only mode), saved to prefs. |
Generated Java |
|
~100 |
Project + app module build config. |
Replaced by Tauri-generated |
Android instantiates the following by class name at platform boundaries. They cannot be implemented in pure Rust because Android’s class loader demands a class implementing the listed Android framework superclass:
-
Activity(main + widget-configure) -
Service -
BroadcastReceiver(boot + widget actions + widget provider)
Three resolution paths exist; this RFC proposes (A) and explicitly rejects (B) and ©. Owner picks before sub-PR work begins.
-
cargo tauri android initgenerates per-platform Java glue insidesrc-tauri/gen/android/(analogous to hownode_modules/is generated but not authored). -
These files are not Kotlin, they are minimal Java each delegating immediately to a single JNI function in Rust.
-
Add
gen/android/to theCheck for Java/Kotlin filesstep’s exemption list — same exemption pattern asnode_modules/for theCheck for ReScript filesstep. -
The
governance / Language / package anti-pattern policycheck is estate-wide (hyperpolymath/standards); adding the exemption needs a one-line change in the reusable workflow caller. Pre-coordination required. -
Outcome: zero hand-written Kotlin/Java in the repo’s source tree; CI passes; behaviour preserved.
-
Replace home-screen widget with a quick-settings tile + persistent notification. Both can be declared in
AndroidManifest.xmland driven from a service whose only Java shim is auto-generated. -
Pro: smaller JVM surface.
-
Con: regresses a shipped, user-visible feature; the OS integration doc (
docs/OS_INTEGRATION.adoc) lists the home-screen widget as the primary integration point.
Each is a small, reviewable PR. Numbers are PR ordering, not commit count.
-
#1 (THIS PR — DRAFT, no code changes) — RFC for owner review. No Kotlin deleted, no Tauri scaffolding generated.
-
#2 — CI exemption for generated mobile shims. Update
.github/workflows/language-policy.ymlstepCheck for Java/Kotlin filesto exemptsrc-tauri/gen/android/. Coordinated withhyperpolymath/standardsfor the reusable governance check. Lands before any Tauri scaffolding so CI doesn’t go redder en route. -
#3 — Tauri scaffolding (no behaviour). Run
cargo tauri android init; commitsrc-tauri/+ generatedgen/android/. No deletion of the existingandroid/Gradle project; both exist side by side so we can A/B sanity-check. -
#4 — Port
NativeLibto Rust. Fill outcrates/neurophone-android/src/lib.rswith the 10 JNI exports the current KotlinNativeLibdeclares. Wire to existing crates (neurophone-core,sensors,lsm,esn,bridge,llm,claude-client). Tests: JNI roundtrip via a Rust test that simulatesJNIEnv. -
#5 — Port
NeurophoneServiceto Rust. Java shim undergen/android/delegatesonCreate / onStartCommand / onDestroy / onSensorChangedto Rust. Salience window, wake-lock plumbing, widget tick — all Rust. -
#6 — Port
BootReceiverto Rust. Same shim pattern, single fn. -
#7 — Port widget triple (provider / actions / configure) to Rust. Java shims under
gen/android/are minimal; widget render logic (selecting strings, salience-pct, whatRemoteViewssetters to call) is decided in Rust and pushed back to the shim via a single data-bearing JNI fn. -
8 — AffineScript UI (replaces
activity_main.xmlview binding). Tauri loads the AffineScript-compiled UI in webview;[tauri::command]wiresstart/stop/query/get_contextto the Rust core. -
#9 — Delete legacy
android/tree. Both banned-language checks now green. Behaviour parity verified by running APK on hardware + Termux CLI smoke test.
PRs #2 and #3 are sequenced and blocking. PRs #4 through #7 can land in parallel after #3. PR #8 needs #4 done. PR #9 is gated on all prior.
-
Tauri vs Dioxus. Policy lists both. RFC recommends Tauri 2 for reasons: (a)
gen/android/shim pattern is documented and tested, (b) AffineScript→ESM→webview is the cleanest UI fit, (c) Dioxus mobile on Android (cargo-mobile2) is newer and has thinner foreground-service precedent. Confirm or override. -
CI exemption scope. OK to exempt
src-tauri/gen/android/from the Java/Kotlin check? This is the linchpin — without this exemption the migration cannot land cleanly because Android demands JVM bytecode at three system boundaries (Service / BroadcastReceiver / AppWidgetProvider). -
Estate governance check. Who owns the
governance / Language / package anti-pattern policycallable inhyperpolymath/standards? Cross-repo PR needed before #2. -
Widget config sacrifice. Acceptable to drop the widget-configure activity (Path B partial) in exchange for a smaller JVM surface? Saves one shim.
-
Behaviour-parity verification. Hardware test plan: Oppo Reno 13 (current target per
app/build.gradle.kts:21)? Or use Android emulator + Termux on a generic device? -
SPDX header policy clash. Existing Kotlin + AndroidManifest.xml use
PMPL-1.0-or-later; in-repohooks/validate-spdx.shmatches PMPL; but the local.git/hooks/pre-commitstill expectsMPL-2.0. This RFC uses MPL-2.0 to land cleanly under the active local hook, but new sub-PRs will need a resolved policy. Recommend updating.git/hooks/pre-committo recognize neurophone as a PMPL repo and re-stamping the existing PMPL files if MPL is canonical, or the reverse if PMPL is canonical.
-
Check for Banned Languagesjob — green onmain. -
governance / Language / package anti-pattern policy— green onmain. -
android/directory — removed. -
APK builds via
cargo tauri android build. -
Foreground service + boot-restart + widget + sensor capture all work on hardware.
-
Termux CLI smoke test passes (
neurophonecommand — unchanged by this migration, but verified not regressed).
-
No changes to the seven core Rust crates (
lsm,esn,bridge,sensors,llm,claude-client,neurophone-core) outside what JNI wiring strictly requires. -
No changes to the Termux CLI path.
-
No new dependencies beyond
tauri,tauri-build, and theandroid-activity/ndkecosystem crates Tauri already pulls in.