Skip to content

Commit c0b7310

Browse files
Merge pull request #30 from OpenArchive/fix/jni-0.22-veilid-compat
fix(android): upgrade jni to 0.22.4 for veilid-core compatibility
2 parents ddbfccb + 6fc85b1 commit c0b7310

5 files changed

Lines changed: 138 additions & 180 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ log = "0.4"
5252
env_logger = "0.10"
5353

5454
[target.'cfg(target_os = "android")'.dependencies]
55-
jni = "0.21.1"
55+
jni = "0.22.4"
5656
tokio = { version = "^1.43", default-features = false, features = ["rt", "rt-multi-thread", "sync", "time", "macros"] }
5757
veilid-core = { git = "https://gitlab.com/veilid/veilid.git", tag = "v0.5.3" }
5858
blake3 = "1.8.2"

build-android.sh

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,26 @@
33
clear
44

55
# Auto-detect Android SDK if not provided
6-
if [ -z "$ANDROID_HOME" ] && [ -d "$HOME/Android/Sdk" ]; then
7-
export ANDROID_HOME="$HOME/Android/Sdk"
6+
if [ -z "$ANDROID_HOME" ]; then
7+
if [ -d "$HOME/Android/Sdk" ]; then
8+
export ANDROID_HOME="$HOME/Android/Sdk"
9+
elif [ -d "$HOME/Library/Android/sdk" ]; then
10+
export ANDROID_HOME="$HOME/Library/Android/sdk"
11+
fi
812
fi
913

1014
# Auto-detect Android NDK if not provided
11-
if [ -z "$ANDROID_NDK_HOME" ] && [ -d "$HOME/Android/Sdk/ndk" ]; then
12-
NDK_LATEST="$(ls -1 "$HOME/Android/Sdk/ndk" 2>/dev/null | sort -V | tail -n 1)"
13-
if [ -n "$NDK_LATEST" ] && [ -d "$HOME/Android/Sdk/ndk/$NDK_LATEST" ]; then
14-
export ANDROID_NDK_HOME="$HOME/Android/Sdk/ndk/$NDK_LATEST"
15+
if [ -z "$ANDROID_NDK_HOME" ] && [ -n "$ANDROID_HOME" ] && [ -d "$ANDROID_HOME/ndk" ]; then
16+
NDK_LATEST=""
17+
for ndk_dir in "$ANDROID_HOME/ndk"/*/; do
18+
[ -d "$ndk_dir" ] || continue
19+
candidate="$(basename "$ndk_dir")"
20+
if [ -z "$NDK_LATEST" ] || [ "$(printf '%s\n' "$NDK_LATEST" "$candidate" | sort -V | tail -n 1)" = "$candidate" ]; then
21+
NDK_LATEST="$candidate"
22+
fi
23+
done
24+
if [ -n "$NDK_LATEST" ] && [ -d "$ANDROID_HOME/ndk/$NDK_LATEST" ]; then
25+
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$NDK_LATEST"
1526
fi
1627
fi
1728

@@ -35,20 +46,18 @@ mkdir -p $JNI_DIR
3546
#
3647
# cargo update save-dweb-backend
3748

38-
# Add this target if we need to support older devices.
39-
# armv7-linux-androideabi
40-
#
49+
# arm64-v8a: modern 64-bit ARM devices (primary production ABI)
50+
# armv7-linux-androideabi: older 32-bit ARM devices (armeabi-v7a)
51+
# x86_64-linux-android: emulators and x86_64 devices (dev/CI)
4152
rustup target add \
4253
aarch64-linux-android \
54+
armv7-linux-androideabi \
4355
x86_64-linux-android
4456

4557
# Build the android libraries in the jniLibs directory
46-
#
47-
# Add this target if we need to support older devices.
48-
# armeabi-v7a
49-
#
5058
cargo ndk -o $JNI_DIR \
5159
--manifest-path ../Cargo.toml \
5260
-t arm64-v8a \
61+
-t armeabi-v7a \
5362
-t x86_64 \
54-
build --release
63+
build --release

src/android_bridge.rs

Lines changed: 91 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -6,187 +6,146 @@ use crate::server;
66
use crate::server::start;
77
use crate::{log_debug, log_error, log_info};
88
use jni::errors::Result as JniResult;
9+
use jni::errors::ThrowRuntimeExAndDefault;
10+
use jni::jni_sig;
11+
use jni::jni_str;
12+
use jni::objects::{JClass, JObject, JString, JValue};
913
use jni::sys::{jint, jstring};
10-
use jni::{
11-
objects::GlobalRef, objects::JClass, objects::JMethodID, objects::JObject, objects::JString,
12-
objects::JValue, objects::JValueGen, JNIEnv, JavaVM,
13-
};
14-
use lazy_static::lazy_static;
15-
use std::error::Error;
16-
use std::sync::{Arc, Mutex, Once};
17-
use std::thread;
14+
use jni::{Env, EnvUnowned};
1815
use std::time::Duration;
1916
use veilid_core::veilid_core_setup_android;
2017

21-
trait IntoJObject {
22-
fn into_jobject(&self) -> JObject;
23-
}
24-
25-
impl IntoJObject for GlobalRef {
26-
fn into_jobject(&self) -> JObject {
27-
unsafe { JObject::from_raw(self.as_raw()) }
28-
}
29-
}
30-
3118
#[no_mangle]
3219
#[allow(non_snake_case)]
3320
pub extern "system" fn Java_net_opendasharchive_openarchive_services_snowbird_SnowbirdBridge_initializeRustService(
34-
env: JNIEnv,
35-
class: JClass,
21+
mut env: EnvUnowned,
22+
_class: JClass,
3623
) {
37-
// match jni_globals::setup_android(env, class) {
38-
// Ok(_) => log_debug!(TAG, "Rust service initialized successfully"),
39-
// Err(e) => log_error!(TAG, "Failed to initialize Rust service: {:?}", e),
40-
// }
24+
env.with_env(|_env| -> JniResult<()> {
25+
// match jni_globals::setup_android(env, class) {
26+
// Ok(_) => log_debug!(TAG, "Rust service initialized successfully"),
27+
// Err(e) => log_error!(TAG, "Failed to initialize Rust service: {:?}", e),
28+
// }
4129

42-
log_info!(TAG, "SnowbirdBridge initialized");
30+
log_info!(TAG, "SnowbirdBridge initialized");
31+
Ok(())
32+
})
33+
.resolve::<ThrowRuntimeExAndDefault>();
4334
}
4435

4536
#[no_mangle]
4637
#[allow(non_snake_case)]
4738
pub extern "system" fn Java_net_opendasharchive_openarchive_services_snowbird_SnowbirdBridge_startServer(
48-
mut env: JNIEnv,
39+
mut env: EnvUnowned,
4940
clazz: JClass,
5041
context: JObject,
5142
backend_base_directory: JString,
5243
server_socket_path: JString,
5344
) -> jstring {
54-
let env_ptr = env.get_native_interface();
55-
5645
log_debug!(TAG, "Bridge: starting");
5746

58-
match setup_jni_environments(&mut env, context, clazz) {
59-
Ok(_) => {
47+
// Initialize JNI globals, smoke-test the Java callback, and read Java args while
48+
// EnvUnowned is still available. veilid_core_setup_android consumes env/context.
49+
let (backend_base_directory, server_socket_path, output) = env
50+
.with_env(|env| -> JniResult<(String, String, jstring)> {
51+
jni_globals::init_jni(env, clazz).map_err(|e| {
52+
jni::errors::Error::ParseFailed(format!("Failed to initialize JNI globals: {e}"))
53+
})?;
54+
jni_smoke_test(env)?;
55+
56+
let backend_base_directory = backend_base_directory.try_to_string(env)?;
57+
let server_socket_path = server_socket_path.try_to_string(env)?;
58+
let output = JString::from_str(
59+
env,
60+
format!("Server started on Unix socket: {server_socket_path}"),
61+
)?
62+
.into_raw();
63+
6064
log_debug!(TAG, "JNI stuff successful");
61-
}
62-
Err(e) => {
63-
log_error!(TAG, "Error doing JNI stuff: {:?}", e);
64-
}
65-
}
6665

67-
let backend_base_directory: String = env
68-
.get_string(&backend_base_directory)
69-
.expect("Couldn't get socket path string")
70-
.into();
66+
Ok((backend_base_directory, server_socket_path, output))
67+
})
68+
.resolve::<ThrowRuntimeExAndDefault>();
7169

72-
let server_socket_path: String = env
73-
.get_string(&server_socket_path)
74-
.expect("Couldn't get socket path string")
75-
.into();
70+
// resolve() throws to Java and returns null on failure; do not start Veilid or the server.
71+
if output.is_null() {
72+
return output;
73+
}
7674

77-
let backend_base_directory_clone = backend_base_directory.clone();
78-
let server_socket_path_clone = server_socket_path.clone();
75+
veilid_core_setup_android(env, context);
7976

8077
std::thread::spawn(move || {
8178
let runtime = tokio::runtime::Runtime::new().unwrap();
8279
runtime.block_on(async {
83-
start(&backend_base_directory_clone, &server_socket_path_clone)
80+
start(&backend_base_directory, &server_socket_path)
8481
.await
8582
.unwrap();
8683
});
8784
});
8885

8986
log_debug!(TAG, "Bridge startup complete.");
9087

91-
let output = env
92-
.new_string(format!(
93-
"Server started on Unix socket: {}",
94-
server_socket_path
95-
))
96-
.expect("Couldn't create java string!");
97-
98-
output.into_raw()
88+
output
9989
}
10090

10191
#[no_mangle]
10292
#[allow(non_snake_case)]
10393
pub extern "system" fn Java_net_opendasharchive_openarchive_services_snowbird_SnowbirdBridge_stopServer(
104-
mut env: JNIEnv,
94+
mut env: EnvUnowned,
10595
_clazz: JClass,
106-
ctx: JObject,
96+
_ctx: JObject,
10797
) -> jstring {
10898
log_debug!(TAG, "Bridge: stopping server");
10999

110-
// Create a runtime to handle async operations
111-
let runtime = tokio::runtime::Runtime::new().unwrap();
112-
113-
// Stop the backend server and clean up Veilid API
114-
let stop_result = runtime.block_on(async {
115-
// First stop the backend
116-
match server::stop().await {
117-
Ok(_) => {
118-
log_info!(TAG, "Backend stopped successfully");
119-
120-
// Get the backend to access Veilid API
121-
if let Ok(backend) = server::get_backend().await {
122-
// Shutdown Veilid API
123-
if let Some(veilid_api) = backend.get_veilid_api().await {
124-
veilid_api.shutdown().await;
125-
log_info!(TAG, "Veilid API shut down successfully");
100+
let stop_ok = env
101+
.with_env(|_env| -> JniResult<bool> {
102+
// Create a runtime to handle async operations
103+
let runtime = tokio::runtime::Runtime::new().unwrap();
104+
105+
// Stop the backend server and clean up Veilid API
106+
runtime.block_on(async {
107+
// First stop the backend
108+
match server::stop().await {
109+
Ok(_) => {
110+
log_info!(TAG, "Backend stopped successfully");
111+
112+
// Get the backend to access Veilid API
113+
if let Ok(backend) = server::get_backend().await {
114+
// Shutdown Veilid API
115+
if let Some(veilid_api) = backend.get_veilid_api().await {
116+
veilid_api.shutdown().await;
117+
log_info!(TAG, "Veilid API shut down successfully");
118+
}
119+
}
120+
121+
// Add a small delay to ensure tasks complete
122+
tokio::time::sleep(Duration::from_millis(500)).await;
123+
Ok(true)
124+
}
125+
Err(e) => {
126+
log_error!(TAG, "Error stopping server: {:?}", e);
127+
Ok(false)
126128
}
127129
}
128-
129-
// Add a small delay to ensure tasks complete
130-
tokio::time::sleep(Duration::from_millis(500)).await;
131-
132-
Ok(())
133-
}
134-
Err(e) => {
135-
log_error!(TAG, "Error stopping server: {:?}", e);
136-
Err(e)
137-
}
138-
}
139-
});
130+
})
131+
})
132+
.resolve::<ThrowRuntimeExAndDefault>();
140133

141134
// Create response string based on result
142-
let response = match stop_result {
143-
Ok(_) => "Server stopped successfully",
144-
Err(_) => "Error stopping server",
135+
let response = if stop_ok {
136+
"Server stopped successfully"
137+
} else {
138+
"Error stopping server"
145139
};
146140

147-
let output = env
148-
.new_string(response)
149-
.expect("Couldn't create java string!");
150-
151-
output.into_raw()
152-
}
153-
154-
fn with_env<F, R>(env: &mut JNIEnv, f: F) -> Result<R, Box<dyn Error>>
155-
where
156-
F: FnOnce(JNIEnv) -> Result<R, Box<dyn Error>>,
157-
{
158-
let env_ptr = env.get_native_interface();
159-
let new_env = unsafe { JNIEnv::from_raw(env_ptr).unwrap() };
160-
f(new_env)
161-
}
162-
163-
fn setup_jni_environments(
164-
env: &mut JNIEnv,
165-
context: JObject,
166-
clazz: JClass,
167-
) -> Result<(), Box<dyn Error>> {
168-
with_env(env, |env| Ok(jni_globals::init_jni(&env, clazz)));
169-
170-
let global_context = env.new_global_ref(context)?;
171-
172-
// Use a new JNIEnv for jni_smoke_test
173-
with_env(env, |env| {
174-
jni_smoke_test(env, global_context.into_jobject())
175-
})?;
176-
177-
// Use another new JNIEnv for veilid_core_setup_android
178-
with_env(env, |env| {
179-
veilid_core_setup_android(env, global_context.into_jobject());
180-
Ok(())
181-
})?;
182-
183-
Ok(())
141+
env.with_env(|env| -> JniResult<jstring> {
142+
let output = JString::from_str(env, response)?;
143+
Ok(output.into_raw())
144+
})
145+
.resolve::<ThrowRuntimeExAndDefault>()
184146
}
185147

186-
fn jni_smoke_test<'local>(
187-
mut env: JNIEnv<'local>,
188-
context: JObject<'local>,
189-
) -> Result<(), Box<dyn std::error::Error>> {
148+
fn jni_smoke_test(env: &mut Env) -> JniResult<()> {
190149
let class_name = "net/opendasharchive/openarchive/services/snowbird/SnowbirdBridge";
191150
let method_name = "updateStatusFromRust";
192151
let method_signature = "(ILjava/lang/String;)V";
@@ -196,14 +155,13 @@ fn jni_smoke_test<'local>(
196155

197156
// Create a JValue for the String parameter (can be null)
198157
let error_message = env.new_string("Test error message")?;
199-
let error_message_jvalue = JValue::Object(&error_message);
200158

201159
// Call the static method
202160
env.call_static_method(
203-
class_name,
204-
method_name,
205-
method_signature,
206-
&[JValue::Int(status_code), error_message_jvalue],
161+
jni_str!("net/opendasharchive/openarchive/services/snowbird/SnowbirdBridge"),
162+
jni_str!("updateStatusFromRust"),
163+
jni_sig!("(ILjava/lang/String;)V"),
164+
&[JValue::Int(status_code), JValue::Object(&error_message)],
207165
)?;
208166

209167
Ok(())

0 commit comments

Comments
 (0)