diff --git a/.changes/restart-emulator.md b/.changes/restart-emulator.md new file mode 100644 index 000000000000..94186ea82655 --- /dev/null +++ b/.changes/restart-emulator.md @@ -0,0 +1,6 @@ +--- +"@tauri-apps/cli": minor:feat +"tauri-cli": minor:feat +--- + +Prompt to restart the Android emulator if it is not connected to adb. diff --git a/Cargo.lock b/Cargo.lock index 88243907045a..e19c327f5f3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,6 +234,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-log", + "tauri-plugin-process", "tauri-plugin-sample", "tiny_http", ] @@ -1055,9 +1056,9 @@ dependencies = [ [[package]] name = "cargo-mobile2" -version = "0.21.1" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcea7efeaac9f0fd9f886f43a13dde186a1e2266fe6b53a42659e4e0689570de" +checksum = "bab92dd12ad373b27af98de101f2ec7524634617c354c9ff34b880b3da8e5de3" dependencies = [ "colored", "core-foundation 0.10.0", @@ -2078,9 +2079,9 @@ checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" [[package]] name = "duct" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6ce170a0e8454fa0f9b0e5ca38a6ba17ed76a50916839d217eb5357e05cdfde" +checksum = "d7478638a31d1f1f3d6c9f5e57c76b906a04ac4879d6fd0fb6245bc88f73fd0b" dependencies = [ "libc", "os_pipe", @@ -4280,9 +4281,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libfuzzer-sys" @@ -5394,12 +5395,12 @@ dependencies = [ [[package]] name = "os_pipe" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -7816,19 +7817,20 @@ dependencies = [ [[package]] name = "shared_child" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" dependencies = [ "libc", - "windows-sys 0.59.0", + "sigchld", + "windows-sys 0.60.2", ] [[package]] name = "shared_thread" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a6f98357c6bb0ebace19b22220e5543801d9de90ffe77f8abb27c056bac064" +checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0" [[package]] name = "shell-words" @@ -7842,11 +7844,22 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -8840,6 +8853,16 @@ dependencies = [ "time", ] +[[package]] +name = "tauri-plugin-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7461c622a5ea00eb9cd9f7a08dbd3bf79484499fd5c21aa2964677f64ca651ab" +dependencies = [ + "tauri", + "tauri-plugin", +] + [[package]] name = "tauri-plugin-sample" version = "0.1.0" diff --git a/crates/tauri-cli/Cargo.toml b/crates/tauri-cli/Cargo.toml index d46bf7ef27ad..2c51eae2359e 100644 --- a/crates/tauri-cli/Cargo.toml +++ b/crates/tauri-cli/Cargo.toml @@ -36,7 +36,7 @@ name = "cargo-tauri" path = "src/main.rs" [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies] -cargo-mobile2 = { version = "0.21.1", default-features = false } +cargo-mobile2 = { version = "0.22.1", default-features = false } [dependencies] jsonrpsee = { version = "0.24", features = ["server"] } diff --git a/crates/tauri-cli/src/mobile/android/android_studio_script.rs b/crates/tauri-cli/src/mobile/android/android_studio_script.rs index c3dfdff4b95e..2581bcdd8758 100644 --- a/crates/tauri-cli/src/mobile/android/android_studio_script.rs +++ b/crates/tauri-cli/src/mobile/android/android_studio_script.rs @@ -13,7 +13,7 @@ use crate::{ use clap::{ArgAction, Parser}; use cargo_mobile2::{ - android::{adb, target::Target}, + android::{adb, device::ConnectionStatus, target::Target}, opts::Profile, target::{call_for_targets_with_fallback, TargetTrait}, }; @@ -214,7 +214,11 @@ fn adb_forward_port( let forward = format!("tcp:{port}"); log::info!("Forwarding port {port} with adb"); - let mut devices = adb::device_list(env).unwrap_or_default(); + let mut devices = adb::device_list(env) + .unwrap_or_default() + .into_iter() + .filter(|d| d.status() == ConnectionStatus::Connected) + .collect::>(); // if we could not detect any running device, let's wait a few seconds, it might be booting up if devices.is_empty() { log::warn!( @@ -226,7 +230,11 @@ fn adb_forward_port( loop { std::thread::sleep(std::time::Duration::from_secs(1)); - devices = adb::device_list(env).unwrap_or_default(); + devices = adb::device_list(env) + .unwrap_or_default() + .into_iter() + .filter(|d| d.status() == ConnectionStatus::Connected) + .collect::>(); if !devices.is_empty() { break; } diff --git a/crates/tauri-cli/src/mobile/android/mod.rs b/crates/tauri-cli/src/mobile/android/mod.rs index 1990cf2f4065..3e5ca4a50ea8 100644 --- a/crates/tauri-cli/src/mobile/android/mod.rs +++ b/crates/tauri-cli/src/mobile/android/mod.rs @@ -6,7 +6,7 @@ use cargo_mobile2::{ android::{ adb, config::{Config as AndroidConfig, Metadata as AndroidMetadata, Raw as RawAndroidConfig}, - device::Device, + device::{ConnectionStatus, Device}, emulator, env::Env, target::Target, @@ -444,7 +444,11 @@ fn delete_codegen_vars() { } fn adb_device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result> { - let device_list = adb::device_list(env).context("failed to detect connected Android devices")?; + let device_list = adb::device_list(env) + .context("failed to detect connected Android devices")? + .into_iter() + .filter(|d| d.status() == ConnectionStatus::Connected) + .collect::>(); if !device_list.is_empty() { let device = if let Some(t) = target { let (device, score) = device_list @@ -530,29 +534,172 @@ fn emulator_prompt(env: &'_ Env, target: Option<&str>) -> Result(env: &'_ Env, target: Option<&str>) -> Result> { if let Ok(device) = adb_device_prompt(env, target) { Ok(device) } else { let emulator = emulator_prompt(env, target)?; - log::info!("Starting emulator {}", emulator.name()); - emulator - .start_detached(env) - .context("failed to start emulator")?; + let emulator_status = match adb::device_list(env) { + Ok(devices) => { + // emulator might be running but disconnected from adb + devices + .iter() + .find(|d| d.name() == emulator.name()) + .and_then(|d| match d.status() { + ConnectionStatus::Offline | ConnectionStatus::Unauthorized => { + Some(EmulatorStatus::Offline { + serial_no: d.serial_no().to_string(), + }) + } + ConnectionStatus::Connected => Some(EmulatorStatus::Connected), + _ => None, + }) + } + // failed to get device information, check if the device name matches the emulator name + Err( + adb::device_list::Error::ModelFailed { + serial_no, + error: adb::get_prop::Error::CommandFailed { command: _, error }, + } + | adb::device_list::Error::AbiFailed { + serial_no, + error: adb::get_prop::Error::CommandFailed { command: _, error }, + }, + ) => { + if error.kind() == std::io::ErrorKind::TimedOut { + // if the device name matches the emulator name, the emulator is already running and marked as connected + // but we cannot connect to it + adb::device_name(env, &serial_no).map_or(None, |device_name| { + if device_name == emulator.name() { + Some(EmulatorStatus::Offline { serial_no }) + } else { + None + } + }) + } else { + None + } + } + Err(_) => None, + }; + + let emulator_already_running = emulator_status.is_some(); + match emulator_status { + Some(EmulatorStatus::Offline { serial_no }) => { + // emulator is available but not connected to adb, we must restart it + log::info!("Emulator is not connected, we need to restart it"); + restart_emulator(&env, &serial_no, &emulator)?; + } + Some(EmulatorStatus::Connected) => { + // emulator is already connected to adb + // this is technically unreachable because we queried the device list with adb_device_prompt + } + None => { + log::info!("Starting emulator {}", emulator.name()); + emulator + .start_detached(env) + .context("failed to start emulator")?; + } + } + let mut tries = 0; loop { sleep(Duration::from_secs(2)); - if let Ok(device) = adb_device_prompt(env, Some(emulator.name())) { - return Ok(device); + // we do not filter for connected devices to detect emulators that are not connected to our adb anymore + match adb::device_list(env) { + Ok(devices) => { + if let Some(device) = devices.into_iter().find(|d| d.name() == emulator.name()) { + if device.status() == ConnectionStatus::Connected { + return Ok(device); + } + } + + if tries >= 3 { + log::info!("Waiting for emulator to start... (maybe the emulator is unauthorized or offline, run `adb devices` to check)"); + } else { + log::info!("Waiting for emulator to start..."); + } + tries += 1; + } + Err( + adb::device_list::Error::ModelFailed { + serial_no, + error: adb::get_prop::Error::CommandFailed { command: _, error }, + } + | adb::device_list::Error::AbiFailed { + serial_no, + error: adb::get_prop::Error::CommandFailed { command: _, error }, + }, + ) => { + if emulator_already_running && error.kind() == std::io::ErrorKind::TimedOut { + log::info!("Emulator is not responding, we need to restart it"); + restart_emulator(&env, &serial_no, &emulator)?; + tries = 0; + } else { + log::error!("failed to get properties for device {serial_no}: {error}"); + } + } + Err(e) => { + log::error!("failed to list devices with adb: {e}"); + tries += 1; + } } - if tries >= 3 { - log::info!("Waiting for emulator to start... (maybe the emulator is unauthorized or offline, run `adb devices` to check)"); - } else { - log::info!("Waiting for emulator to start..."); + } + } +} + +fn restart_emulator(env: &Env, serial_no: &str, emulator: &emulator::Emulator) -> Result<()> { + let granted_permission_to_restart = + crate::helpers::prompts::confirm("Do you want to restart the emulator?", Some(true)) + .unwrap_or_default(); + if !granted_permission_to_restart { + crate::error::bail!( + "Cannot connect to the emulator, please restart it manually (a full boot might be required)" + ); + } + + adb::adb(env, &["-s", &serial_no, "emu", "kill"]) + .run() + .context("failed to reboot emulator")?; + + log::info!("Waiting for emulator to exit..."); + loop { + let devices = adb::device_list(env).unwrap_or_default(); + if devices + .into_iter() + .find(|d| d.serial_no() == serial_no) + .is_none() + { + break; + } + sleep(Duration::from_secs(1)); + } + + log::info!("Restarting emulator with full boot"); + let mut tries = 0; + loop { + // wait a bit to make sure we can restart the emulator + sleep(Duration::from_secs(2)); + + match emulator.start_detached_with_options(env, emulator::StartOptions::new().full_boot()) { + Ok(_) => break, + Err(e) => { + tries += 1; + if tries >= 3 { + return Err(e).context("failed to start emulator"); + } else { + log::error!("failed to start emulator, retrying..."); + } } - tries += 1; } } + + Ok(()) } fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {