Skip to content

Commit 764b913

Browse files
authored
feat(cli): restart Android emulator if it is disconnected from adb (#14313)
* feat(cli): restart Android emulator if it is disconnected from adb needs tauri-apps/cargo-mobile2#495 and tauri-apps/cargo-mobile2#493 * lint
1 parent 1035f12 commit 764b913

5 files changed

Lines changed: 179 additions & 18 deletions

File tree

.changes/restart-emulator.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@tauri-apps/cli": minor:feat
3+
"tauri-cli": minor:feat
4+
---
5+
6+
Prompt to restart the Android emulator if it is not connected to adb.

crates/tauri-bundler/src/bundle/macos/icon.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ pub fn create_icns_file(out_dir: &Path, settings: &Settings) -> crate::Result<Op
6565
for icon_path in settings.icon_files() {
6666
let icon_path = icon_path?;
6767

68-
if icon_path.extension().map_or(false, |ext| ext == "car") {
68+
if icon_path.extension().is_some_and(|ext| ext == "car") {
6969
continue;
7070
}
7171

crates/tauri-bundler/src/bundle/macos/ios.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ fn generate_icon_files(bundle_dir: &Path, settings: &Settings) -> crate::Result<
108108
let icon_path = icon_path?;
109109
if icon_path
110110
.extension()
111-
.map_or(false, |ext| ext == "png" || ext == "car")
111+
.is_some_and(|ext| ext == "png" || ext == "car")
112112
{
113113
continue;
114114
} else if icon_path.extension() == Some(OsStr::new("icns")) {

crates/tauri-cli/src/mobile/android/android_studio_script.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
use clap::{ArgAction, Parser};
1414

1515
use cargo_mobile2::{
16-
android::{adb, target::Target},
16+
android::{adb, device::ConnectionStatus, target::Target},
1717
opts::Profile,
1818
target::{call_for_targets_with_fallback, TargetTrait},
1919
};
@@ -194,7 +194,11 @@ fn adb_forward_port(
194194
let forward = format!("tcp:{port}");
195195
log::info!("Forwarding port {port} with adb");
196196

197-
let mut devices = adb::device_list(env).unwrap_or_default();
197+
let mut devices = adb::device_list(env)
198+
.unwrap_or_default()
199+
.into_iter()
200+
.filter(|d| d.status() == ConnectionStatus::Connected)
201+
.collect::<Vec<_>>();
198202
// if we could not detect any running device, let's wait a few seconds, it might be booting up
199203
if devices.is_empty() {
200204
log::warn!(
@@ -206,7 +210,11 @@ fn adb_forward_port(
206210
loop {
207211
std::thread::sleep(std::time::Duration::from_secs(1));
208212

209-
devices = adb::device_list(env).unwrap_or_default();
213+
devices = adb::device_list(env)
214+
.unwrap_or_default()
215+
.into_iter()
216+
.filter(|d| d.status() == ConnectionStatus::Connected)
217+
.collect::<Vec<_>>();
210218
if !devices.is_empty() {
211219
break;
212220
}

crates/tauri-cli/src/mobile/android/mod.rs

Lines changed: 160 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use cargo_mobile2::{
66
android::{
77
adb,
88
config::{Config as AndroidConfig, Metadata as AndroidMetadata, Raw as RawAndroidConfig},
9-
device::Device,
9+
device::{ConnectionStatus, Device},
1010
emulator,
1111
env::Env,
1212
target::Target,
@@ -678,7 +678,11 @@ fn delete_codegen_vars() {
678678
}
679679

680680
fn adb_device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
681-
let device_list = adb::device_list(env).context("failed to detect connected Android devices")?;
681+
let device_list = adb::device_list(env)
682+
.context("failed to detect connected Android devices")?
683+
.into_iter()
684+
.filter(|d| d.status() == ConnectionStatus::Connected)
685+
.collect::<Vec<_>>();
682686
if !device_list.is_empty() {
683687
let device = if let Some(t) = target {
684688
let (device, score) = device_list
@@ -764,29 +768,172 @@ fn emulator_prompt(env: &'_ Env, target: Option<&str>) -> Result<emulator::Emula
764768
}
765769
}
766770

771+
enum EmulatorStatus {
772+
Offline { serial_no: String },
773+
Connected,
774+
}
775+
767776
fn device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
768777
if let Ok(device) = adb_device_prompt(env, target) {
769778
Ok(device)
770779
} else {
771780
let emulator = emulator_prompt(env, target)?;
772-
log::info!("Starting emulator {}", emulator.name());
773-
emulator
774-
.start_detached(env)
775-
.context("failed to start emulator")?;
781+
let emulator_status = match adb::device_list(env) {
782+
Ok(devices) => {
783+
// emulator might be running but disconnected from adb
784+
devices
785+
.iter()
786+
.find(|d| d.name() == emulator.name())
787+
.and_then(|d| match d.status() {
788+
ConnectionStatus::Offline | ConnectionStatus::Unauthorized => {
789+
Some(EmulatorStatus::Offline {
790+
serial_no: d.serial_no().to_string(),
791+
})
792+
}
793+
ConnectionStatus::Connected => Some(EmulatorStatus::Connected),
794+
_ => None,
795+
})
796+
}
797+
// failed to get device information, check if the device name matches the emulator name
798+
Err(
799+
adb::device_list::Error::ModelFailed {
800+
serial_no,
801+
error: adb::get_prop::Error::CommandFailed { command: _, error },
802+
}
803+
| adb::device_list::Error::AbiFailed {
804+
serial_no,
805+
error: adb::get_prop::Error::CommandFailed { command: _, error },
806+
},
807+
) => {
808+
if error.kind() == std::io::ErrorKind::TimedOut {
809+
// if the device name matches the emulator name, the emulator is already running and marked as connected
810+
// but we cannot connect to it
811+
adb::device_name(env, &serial_no).map_or(None, |device_name| {
812+
if device_name == emulator.name() {
813+
Some(EmulatorStatus::Offline { serial_no })
814+
} else {
815+
None
816+
}
817+
})
818+
} else {
819+
None
820+
}
821+
}
822+
Err(_) => None,
823+
};
824+
825+
let emulator_already_running = emulator_status.is_some();
826+
match emulator_status {
827+
Some(EmulatorStatus::Offline { serial_no }) => {
828+
// emulator is available but not connected to adb, we must restart it
829+
log::info!("Emulator is not connected, we need to restart it");
830+
restart_emulator(env, &serial_no, &emulator)?;
831+
}
832+
Some(EmulatorStatus::Connected) => {
833+
// emulator is already connected to adb
834+
// this is technically unreachable because we queried the device list with adb_device_prompt
835+
}
836+
None => {
837+
log::info!("Starting emulator {}", emulator.name());
838+
emulator
839+
.start_detached(env)
840+
.context("failed to start emulator")?;
841+
}
842+
}
843+
776844
let mut tries = 0;
777845
loop {
778846
sleep(Duration::from_secs(2));
779-
if let Ok(device) = adb_device_prompt(env, Some(emulator.name())) {
780-
return Ok(device);
847+
// we do not filter for connected devices to detect emulators that are not connected to our adb anymore
848+
match adb::device_list(env) {
849+
Ok(devices) => {
850+
if let Some(device) = devices.into_iter().find(|d| d.name() == emulator.name()) {
851+
if device.status() == ConnectionStatus::Connected {
852+
return Ok(device);
853+
}
854+
}
855+
856+
if tries >= 3 {
857+
log::info!("Waiting for emulator to start... (maybe the emulator is unauthorized or offline, run `adb devices` to check)");
858+
} else {
859+
log::info!("Waiting for emulator to start...");
860+
}
861+
tries += 1;
862+
}
863+
Err(
864+
adb::device_list::Error::ModelFailed {
865+
serial_no,
866+
error: adb::get_prop::Error::CommandFailed { command: _, error },
867+
}
868+
| adb::device_list::Error::AbiFailed {
869+
serial_no,
870+
error: adb::get_prop::Error::CommandFailed { command: _, error },
871+
},
872+
) => {
873+
if emulator_already_running && error.kind() == std::io::ErrorKind::TimedOut {
874+
log::info!("Emulator is not responding, we need to restart it");
875+
restart_emulator(env, &serial_no, &emulator)?;
876+
tries = 0;
877+
} else {
878+
log::error!("failed to get properties for device {serial_no}: {error}");
879+
}
880+
}
881+
Err(e) => {
882+
log::error!("failed to list devices with adb: {e}");
883+
tries += 1;
884+
}
781885
}
782-
if tries >= 3 {
783-
log::info!("Waiting for emulator to start... (maybe the emulator is unauthorized or offline, run `adb devices` to check)");
784-
} else {
785-
log::info!("Waiting for emulator to start...");
886+
}
887+
}
888+
}
889+
890+
fn restart_emulator(env: &Env, serial_no: &str, emulator: &emulator::Emulator) -> Result<()> {
891+
let granted_permission_to_restart =
892+
crate::helpers::prompts::confirm("Do you want to restart the emulator?", Some(true))
893+
.unwrap_or_default();
894+
if !granted_permission_to_restart {
895+
crate::error::bail!(
896+
"Cannot connect to the emulator, please restart it manually (a full boot might be required)"
897+
);
898+
}
899+
900+
adb::adb(env, &["-s", serial_no, "emu", "kill"])
901+
.run()
902+
.context("failed to reboot emulator")?;
903+
904+
log::info!("Waiting for emulator to exit...");
905+
loop {
906+
let devices = adb::device_list(env).unwrap_or_default();
907+
if devices
908+
.into_iter()
909+
.find(|d| d.serial_no() == serial_no)
910+
.is_none()
911+
{
912+
break;
913+
}
914+
sleep(Duration::from_secs(1));
915+
}
916+
917+
log::info!("Restarting emulator with full boot");
918+
let mut tries = 0;
919+
loop {
920+
// wait a bit to make sure we can restart the emulator
921+
sleep(Duration::from_secs(2));
922+
923+
match emulator.start_detached_with_options(env, emulator::StartOptions::new().full_boot()) {
924+
Ok(_) => break,
925+
Err(e) => {
926+
tries += 1;
927+
if tries >= 3 {
928+
return Err(e).context("failed to start emulator");
929+
} else {
930+
log::error!("failed to start emulator, retrying...");
931+
}
786932
}
787-
tries += 1;
788933
}
789934
}
935+
936+
Ok(())
790937
}
791938

792939
fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {

0 commit comments

Comments
 (0)