2 files changed
+59
-6
crates/xcrs/src/main.rs
+1
-1
@@ -94,7 +94,7 @@ async fn main() -> Result<()> {
94
println!("Installed XCRS AndroidKit on {serial}");
95
}
96
Commands::AndroidKit(AndroidKitCommand::Start { serial }) => {
97
- AndroidDebugBridge::new().start_androidkit(&serial)?;
97
+ AndroidDebugBridge::new().start_androidkit(&serial).await?;
98
println!("Started XCRS AndroidKit on {serial}");
99
}
100
Commands::AndroidKit(AndroidKitCommand::Status { serial, json }) => {
crates/xcrs/src/xcrs.rs
+58
-5
@@ -12,6 +12,10 @@ pub mod mcp;
12
const IOS_SIMULATOR_DESTINATION_PREFIX: &str = "platform=iOS Simulator,id=";
13
const CONTROLKIT_METHOD_NOT_FOUND: i64 = -32601;
14
const CONTROLKIT_RUNNER_INFO_TIMEOUT: Duration = Duration::from_secs(2);
15
+const ANDROIDKIT_CALL_TIMEOUT: Duration = Duration::from_secs(10);
16
+const ANDROIDKIT_START_TIMEOUT: Duration = Duration::from_secs(10);
17
+const ANDROIDKIT_START_PROBE_TIMEOUT: Duration = Duration::from_millis(500);
18
+const ANDROIDKIT_START_RETRY_DELAY: Duration = Duration::from_millis(100);
19
20
pub fn encode_base64(data: impl AsRef<[u8]>) -> String {
21
use base64::{engine::general_purpose::STANDARD, Engine};
@@ -689,12 +693,14 @@ pub struct AndroidDebugBridge {
693
#[derive(Debug, Clone)]
694
pub struct AndroidKit {
695
client: reqwest::Client,
696
+ adb_path: PathBuf,
697
}
698
699
impl Default for AndroidKit {
700
fn default() -> Self {
701
Self {
702
client: reqwest::Client::new(),
703
+ adb_path: discover_adb_path(),
704
}
705
}
706
}
@@ -712,7 +718,25 @@ impl AndroidKit {
718
method: &str,
719
params: serde_json::Value,
720
) -> Result<serde_json::Value> {
715
- let bridge = AndroidDebugBridge::new();
721
+ self.call_with_timeout(serial, method, params, ANDROIDKIT_CALL_TIMEOUT)
722
+ .await
723
+ }
724
+
725
+ fn with_adb_path(adb_path: impl Into<PathBuf>) -> Self {
726
+ Self {
727
+ client: reqwest::Client::new(),
728
+ adb_path: adb_path.into(),
729
+ }
730
+ }
731
+
732
+ async fn call_with_timeout(
733
+ &self,
734
+ serial: String,
735
+ method: &str,
736
+ params: serde_json::Value,
737
+ timeout: Duration,
738
+ ) -> Result<serde_json::Value> {
739
+ let bridge = AndroidDebugBridge::with_path(self.adb_path.clone());
740
let local_port = tokio::task::spawn_blocking({
741
let serial = serial.clone();
742
move || bridge.forward_androidkit(&serial)
@@ -723,7 +747,7 @@ impl AndroidKit {
747
let response = self
748
.client
749
.post(format!("http://127.0.0.1:{local_port}/rpc"))
726
- .timeout(Duration::from_secs(10))
750
+ .timeout(timeout)
751
.json(&serde_json::json!({
752
"jsonrpc": "2.0",
753
"id": 1,
@@ -734,7 +758,7 @@ impl AndroidKit {
758
.await
759
.with_context(|| format!("failed to connect to XCRS AndroidKit for {serial}"));
760
737
- let cleanup_bridge = AndroidDebugBridge::new();
761
+ let cleanup_bridge = AndroidDebugBridge::with_path(self.adb_path.clone());
762
let cleanup_serial = serial.clone();
763
let cleanup = tokio::task::spawn_blocking(move || {
764
cleanup_bridge.remove_forward(&cleanup_serial, local_port)
@@ -769,6 +793,32 @@ impl AndroidKit {
793
.cloned()
794
.ok_or_else(|| anyhow!("AndroidKit response for '{method}' did not contain a result"))
795
}
796
+
797
+ async fn wait_until_ready(&self, serial: &str) -> Result<()> {
798
+ let deadline = tokio::time::Instant::now() + ANDROIDKIT_START_TIMEOUT;
799
+ loop {
800
+ match self
801
+ .call_with_timeout(
802
+ serial.to_string(),
803
+ "device.info",
804
+ serde_json::json!({}),
805
+ ANDROIDKIT_START_PROBE_TIMEOUT,
806
+ )
807
+ .await
808
+ {
809
+ Ok(_) => return Ok(()),
810
+ Err(error) if tokio::time::Instant::now() >= deadline => {
811
+ return Err(error).with_context(|| {
812
+ format!(
813
+ "AndroidKit did not become ready on {serial} within {} seconds",
814
+ ANDROIDKIT_START_TIMEOUT.as_secs()
815
+ )
816
+ });
817
+ }
818
+ Err(_) => tokio::time::sleep(ANDROIDKIT_START_RETRY_DELAY).await,
819
+ }
820
+ }
821
+ }
822
}
823
824
impl Default for AndroidDebugBridge {
@@ -956,13 +1006,16 @@ impl AndroidDebugBridge {
1006
Ok(())
1007
}
1008
959
- pub fn start_androidkit(&self, serial: &str) -> Result<()> {
1009
+ pub async fn start_androidkit(&self, serial: &str) -> Result<()> {
1010
self.run_shell(
1011
serial,
1012
"nohup am instrument -w -r xyz.smbcloud.xcrs.androidkit/.AndroidKitInstrumentation >/dev/null 2>&1 &",
1013
)
1014
.with_context(|| format!("failed to start AndroidKit on {serial}"))?;
965
- Ok(())
1015
+ AndroidKit::with_adb_path(self.adb_path.clone())
1016
+ .wait_until_ready(serial)
1017
+ .await
1018
+ .with_context(|| format!("failed to start AndroidKit on {serial}"))
1019
}
1020
1021
pub fn stop_androidkit(&self, serial: &str) -> Result<()> {