fix(android): run set_audio_mode_communication on Tauri main thread

spawn_blocking uses arbitrary thread-pool threads that don't have the
Android JNI context initialized, causing ndk_context::android_context()
to panic. Switch to run_on_main_thread (where the context is always
valid) via a oneshot channel, with a 2s timeout. Panic is caught and
forwarded as an Err so the debug log captures it rather than crashing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-05-25 08:18:18 +04:00
parent 77b036439b
commit bc1668ed96
2 changed files with 32 additions and 25 deletions

View File

@@ -96,6 +96,35 @@ pub fn set_audio_mode_communication() -> Result<(), String> {
Ok(())
}
/// Run `set_audio_mode_communication` on Tauri's main thread, where the
/// Android context is initialized. Calling it from arbitrary Tokio blocking
/// workers panics inside `ndk_context::android_context()`.
pub async fn set_audio_mode_communication_on_main(
app: tauri::AppHandle,
) -> Result<(), String> {
let (tx, rx) = tokio::sync::oneshot::channel();
app.run_on_main_thread(move || {
let result = std::panic::catch_unwind(set_audio_mode_communication)
.map_err(|panic| {
if let Some(s) = panic.downcast_ref::<&str>() {
format!("panic: {s}")
} else if let Some(s) = panic.downcast_ref::<String>() {
format!("panic: {s}")
} else {
"panic: unknown".to_string()
}
})
.and_then(|r| r);
let _ = tx.send(result);
})
.map_err(|e| format!("run_on_main_thread: {e}"))?;
tokio::time::timeout(std::time::Duration::from_secs(2), rx)
.await
.map_err(|_| "set_audio_mode_communication timed out after 2s".to_string())?
.map_err(|_| "set_audio_mode_communication result channel closed".to_string())?
}
/// Restore `AudioManager.MODE_NORMAL`. Call when a VoIP call ends.
pub fn set_audio_mode_normal() -> Result<(), String> {
let (vm, activity) = jvm_and_activity()?;

View File

@@ -663,15 +663,13 @@ impl CallEngine {
"connect:audio_mode_start",
serde_json::json!({ "t_ms": call_t0.elapsed().as_millis() }),
);
let audio_mode_task =
tokio::task::spawn_blocking(crate::android_audio::set_audio_mode_communication);
match tokio::time::timeout(std::time::Duration::from_secs(2), audio_mode_task).await {
Ok(Ok(Ok(()))) => crate::emit_call_debug(
match crate::android_audio::set_audio_mode_communication_on_main(app.clone()).await {
Ok(()) => crate::emit_call_debug(
&app,
"connect:audio_mode_done",
serde_json::json!({ "t_ms": call_t0.elapsed().as_millis() }),
),
Ok(Ok(Err(e))) => {
Err(e) => {
tracing::warn!("set_audio_mode_communication failed: {e}");
crate::emit_call_debug(
&app,
@@ -682,26 +680,6 @@ impl CallEngine {
}),
);
}
Ok(Err(e)) => {
crate::emit_call_debug(
&app,
"connect:audio_mode_panic",
serde_json::json!({
"t_ms": call_t0.elapsed().as_millis(),
"error": e.to_string(),
}),
);
}
Err(_) => {
crate::emit_call_debug(
&app,
"connect:audio_mode_timeout",
serde_json::json!({
"t_ms": call_t0.elapsed().as_millis(),
"timeout_ms": 2000,
}),
);
}
}
}