feat(android): port NativeLib (Kotlin) JNI surface to Rust (#110)#130
Open
hyperpolymath wants to merge 1 commit into
Open
feat(android): port NativeLib (Kotlin) JNI surface to Rust (#110)#130hyperpolymath wants to merge 1 commit into
hyperpolymath wants to merge 1 commit into
Conversation
Implement the 11-method `ai.neurophone.NativeLib` JNI contract in crates/neurophone-android, replacing the `hello()` stub. Each `Java_ai_neurophone_NativeLib_*` export decodes JVM arguments and delegates to the safe pure-Rust workspace crates (neurophone-core, llm, claude-client, sensors). - Process-global, Mutex-guarded NativeRuntime singleton. - Sensor id map per contract (accel=1, mag=2, gyro=4, light=5, prox=8). - Local LLM via llm::MockBackend; cloud via claude_client (graceful "no API key" path); hybrid query via core orchestrator. - Crate is #![deny(unsafe_code)] with justified #[allow(unsafe_code)] only on the JNI ABI declarations (documented). - Pin jni to 0.21 (stable native-method API) for the android crate. - Revert incompatible rand 0.10 / rand_distr 0.6 bumps back to 0.9/0.5 to match ndarray-rand 0.16, unbreaking esn/lsm so the workspace builds (pre-existing regression masked by the pinned Cargo.lock). Kotlin bindings (NativeLib.kt/MainActivity.kt) left untouched; reconciled in the shim/legacy-delete PRs. TODO(#83). cargo build --workspace + cargo test --workspace green (32 test binaries, all pass; 7 new tests in neurophone-android). https://claude.ai/code/session_01Gu1JFCZHuBtBhAWPr4sMQw
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Sub-PR #4 of the Android Kotlin→Rust/Gossamer migration (epic #83, RFC #97, sub-issue #110). Implements the native (
neurophone_android) JNI surface, replacing thepub fn hello()stub with the full 11-methodai.neurophone.NativeLibcontract. The most independent step of the migration — no Service/widget/UI code is touched.Each
Java_ai_neurophone_NativeLib_*export decodes its JVM arguments and delegates to the existing pure-Rust workspace crates:neurophone-core(NeuroSymbolicSystem) — lifecycle, sensor processing, neural context, state, hybrid query router.llm(MockBackend) — on-device LLM stand-in (llama.cppswaps in later).claude-client(HybridInference/ClaudeClient) — cloud path, with a graceful "no API key" branch.sensors(SensorKind) — Android sensor-type id mapping.JNI contract implemented (class
ai.neurophone.NativeLib, libneurophone_android)init(String?)->bool,start()->bool,stop()->void,processSensor(int, float[], long, int)->bool,queryLocal(String)->String,queryClaude(String)->String,query(String, bool)->String,getNeuralContext()->String,getState()->String(JSON),reset()->void,isRunning()->bool.Sensor-type id map per spec: accelerometer=1, magnetometer=2, gyroscope=4, light=5, proximity=8, else unknown (rejected).
Files changed
crates/neurophone-android/src/lib.rs— full JNI implementation (was a stub). AMutex-guarded process-globalNativeRuntimesingleton; safecore_*functions hold all logic; thinunsafe extern "C"exports decode args and delegate. 7 unit tests.crates/neurophone-android/Cargo.toml— addclaude-clientandllmpath deps.Cargo.toml(workspace) — pinjni = "0.21"; revertrand/rand_distrto0.9/0.5(see Risks).Cargo.lock— refreshed (jni 0.21.1,rand 0.9.4).The Kotlin/Java bindings (
NativeLib.kt,MainActivity.kt) are intentionally left untouched — reconciled in the shim PRs / legacy-delete step. MarkedTODO(#83)in code.Unsafe-on-JNI-boundary justification
The crate is
#[deny(unsafe_code)]. The JVM resolves native methods by C symbol name (Java_<class>_<method>), which requires#[unsafe(no_mangle)]+unsafe extern "C"— there is no safe-Rust spelling of an exported C-ABI symbol, and thejnihandles are raw JVM-provided values. So:forbidto#[deny(unsafe_code)].#[allow(unsafe_code)] // JNI ABI: see module-level justification.core_*function and performs nounsafeoperations beyond the ABI declaration. Theunsafesurface is purely the entry-point signatures, confined to thejni_boundarymodule.What I verified
cargo build --workspace— green.cargo test --workspace— green: 32 test binaries, all pass, incl. 7 new tests here (sensor id map, config fallback, init/start/stop/reset lifecycle, sensor arity/type validation, local/cloud/hybrid query paths, JSON state shape, and pre-init safety).cargo clippy -p neurophone-android --all-targets— no warnings.nm -D libneurophone_android.so— all 11Java_ai_neurophone_NativeLib_*symbols exported.TODOs / risks
rand/rand_distrrevert (pre-existing breakage). Dependabot PRs chore(deps): bump rand from 0.9.3 to 0.10.1 #49/chore(deps): bump rand_distr from 0.5.1 to 0.6.0 #67 bumpedrand 0.9→0.10andrand_distr 0.5→0.6, butndarray-rand 0.16still requiresrand 0.9. This breaksesn/lsmthe moment the lockfile is refreshed; the committedCargo.lockpinnedrand 0.9.4, which masked the regression. I reverted to0.9/0.5(with an explanatoryNOTE(#83)inCargo.toml) so the workspace builds. Re-bump only alongside anndarray-randupgrade that supportsrand 0.10. Happy to split this into its own commit/PR if preferred.jnipinned to 0.21. The workspace previously declared0.22, whose native-method API was reworked aroundEnvUnowned::with_envand is still settling. 0.21'sJNIEnv-first-arg surface keeps the FFI boundary small and auditable. Onlyneurophone-androidconsumesjni, so the blast radius is nil. Revisit when 0.22's API stabilises.MockBackendfor local LLM is a deterministic stand-in; realllama.cppwiring is out of scope (tracked separately).queryClaudebuilds a short-lived current-thread tokio runtime per call and requires an API key from the environment; without one it returns a clear[claude-unavailable]string rather than failing the FFI call. Per-call runtime is fine for the current call pattern; revisit if it becomes hot.TODO(#83)).https://claude.ai/code/session_01Gu1JFCZHuBtBhAWPr4sMQw
Generated by Claude Code