From bc1668ed965dd75a0c3be2fd9d24546e14da45b5 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Mon, 25 May 2026 08:18:18 +0400 Subject: [PATCH] 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 --- desktop/src-tauri/src/android_audio.rs | 29 ++++++++++++++++++++++++++ desktop/src-tauri/src/engine.rs | 28 +++---------------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/desktop/src-tauri/src/android_audio.rs b/desktop/src-tauri/src/android_audio.rs index 8f9c001..d49d7f2 100644 --- a/desktop/src-tauri/src/android_audio.rs +++ b/desktop/src-tauri/src/android_audio.rs @@ -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::() { + 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()?; diff --git a/desktop/src-tauri/src/engine.rs b/desktop/src-tauri/src/engine.rs index 793a90c..489dca6 100644 --- a/desktop/src-tauri/src/engine.rs +++ b/desktop/src-tauri/src/engine.rs @@ -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, - }), - ); - } } }