1use std::fmt;
5use std::future::Future;
6use std::time::{Duration, Instant};
7
8use tonic::metadata::{Ascii, MetadataValue};
9use tonic::service::Interceptor;
10use tonic::service::interceptor::InterceptedService;
11use tonic::transport::Channel;
12
13use super::endpoint::{Endpoint, Unavailable};
14use super::frame::{Frame, FrameError};
15use super::hardware::{Hardware, scaled_size};
16use super::proto;
17use super::proto::emulator_controller_client::EmulatorControllerClient;
18use super::proto::image_format::ImgFormat;
19
20pub const MAX_MESSAGE: usize = 64 << 20;
23
24pub const DOWN_PRESSURE: i32 = 512;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct Options {
30 pub deadline: Duration,
32 pub connect_deadline: Duration,
34}
35
36impl Default for Options {
37 fn default() -> Self {
38 Self {
39 deadline: Duration::from_secs(2),
40 connect_deadline: Duration::from_secs(2),
41 }
42 }
43}
44
45#[derive(Debug)]
47pub enum Error {
48 Deadline { call: &'static str, after: Duration },
50 Status {
52 call: &'static str,
53 status: tonic::Status,
54 },
55 Frame {
57 call: &'static str,
58 error: FrameError,
59 },
60 StreamEnded,
62}
63
64impl fmt::Display for Error {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 Error::Deadline { call, after } => write!(f, "{call}: no answer within {after:?}"),
68 Error::Status { call, status } => {
69 write!(f, "{call}: {:?}: {}", status.code(), status.message())
70 }
71 Error::Frame { call, error } => write!(f, "{call}: {error}"),
72 Error::StreamEnded => write!(f, "the emulator ended the frame stream"),
73 }
74 }
75}
76
77impl std::error::Error for Error {}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub struct TouchPoint {
82 pub id: i32,
84 pub x: i32,
86 pub y: i32,
88 pub pressure: i32,
90}
91
92impl TouchPoint {
93 pub fn down(x: i32, y: i32) -> Self {
95 Self {
96 id: 0,
97 x,
98 y,
99 pressure: DOWN_PRESSURE,
100 }
101 }
102
103 pub fn up(x: i32, y: i32) -> Self {
105 Self {
106 pressure: 0,
107 ..Self::down(x, y)
108 }
109 }
110}
111
112#[derive(Clone)]
114struct Bearer(Option<MetadataValue<Ascii>>);
115
116impl Interceptor for Bearer {
117 fn call(
118 &mut self,
119 mut request: tonic::Request<()>,
120 ) -> Result<tonic::Request<()>, tonic::Status> {
121 if let Some(value) = &self.0 {
122 request
123 .metadata_mut()
124 .insert("authorization", value.clone());
125 }
126 Ok(request)
127 }
128}
129
130type Client = EmulatorControllerClient<InterceptedService<Channel, Bearer>>;
131
132fn is_deadline(status: &tonic::Status) -> bool {
135 status.code() == tonic::Code::DeadlineExceeded
136 || (status.code() == tonic::Code::Cancelled
137 && status.message() == tonic::TimeoutExpired(()).to_string())
138}
139
140async fn within<T>(
142 call: &'static str,
143 deadline: Duration,
144 response: impl Future<Output = Result<tonic::Response<T>, tonic::Status>>,
145) -> Result<T, Error> {
146 match tokio::time::timeout(deadline, response).await {
147 Err(_) => Err(Error::Deadline {
148 call,
149 after: deadline,
150 }),
151 Ok(Err(status)) if is_deadline(&status) => Err(Error::Deadline {
152 call,
153 after: deadline,
154 }),
155 Ok(Err(status)) => Err(Error::Status { call, status }),
156 Ok(Ok(response)) => Ok(response.into_inner()),
157 }
158}
159
160fn unary<T>(message: T, deadline: Duration) -> tonic::Request<T> {
162 let mut request = tonic::Request::new(message);
163 request.set_timeout(deadline);
164 request
165}
166
167fn chain(error: &dyn std::error::Error) -> String {
170 let mut text = error.to_string();
171 let mut last = text.clone();
172 let mut source = error.source();
173 while let Some(cause) = source {
174 let message = cause.to_string();
175 if message != last {
176 text.push_str(": ");
177 text.push_str(&message);
178 }
179 last = message;
180 source = cause.source();
181 }
182 text
183}
184
185async fn channel(uri: &str, options: &Options) -> Result<Channel, String> {
187 let endpoint = tonic::transport::Endpoint::from_shared(uri.to_owned())
188 .map_err(|e| format!("bad endpoint {uri}: {}", chain(&e)))?
189 .connect_timeout(options.connect_deadline)
190 .tcp_nodelay(true);
191 match tokio::time::timeout(options.connect_deadline, endpoint.connect()).await {
192 Err(_) => Err(format!(
193 "no connection to {uri} within {:?}",
194 options.connect_deadline
195 )),
196 Ok(Err(e)) => Err(format!("connecting to {uri}: {}", chain(&e))),
197 Ok(Ok(channel)) => Ok(channel),
198 }
199}
200
201fn client(channel: Channel, bearer: Bearer) -> Client {
203 EmulatorControllerClient::with_interceptor(channel, bearer)
204 .max_decoding_message_size(MAX_MESSAGE)
205 .max_encoding_message_size(MAX_MESSAGE)
206}
207
208fn frame_from(image: proto::Image, call: &'static str) -> Result<Frame, Error> {
210 let (width, height) = image
211 .format
212 .as_ref()
213 .map_or((0, 0), |format| (format.width, format.height));
214 Frame::new(width, height, image.image)
215 .map(|frame| frame.with_timing(image.seq, image.timestamp_us))
216 .map_err(|error| Error::Frame { call, error })
217}
218
219fn rgb888(width: u32, height: u32) -> proto::ImageFormat {
222 proto::ImageFormat {
223 format: ImgFormat::Rgb888 as i32,
224 width,
225 height,
226 ..Default::default()
227 }
228}
229
230#[derive(Clone)]
238pub struct Controller {
239 serial: String,
240 frames: Client,
241 input: Client,
242 hardware: Hardware,
243 deadline: Duration,
244}
245
246impl fmt::Debug for Controller {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 f.debug_struct("Controller")
249 .field("serial", &self.serial)
250 .field("hardware", &self.hardware)
251 .field("deadline", &self.deadline)
252 .finish()
253 }
254}
255
256impl Controller {
257 pub async fn connect(endpoint: &Endpoint, options: Options) -> Result<Self, Unavailable> {
261 let unavailable =
262 |reason: String| Unavailable::new(&endpoint.serial, endpoint.console_port, reason);
263 let bearer = match &endpoint.token {
264 None => Bearer(None),
265 Some(token) => Bearer(Some(
266 format!("Bearer {token}")
267 .parse::<MetadataValue<Ascii>>()
268 .map_err(|_| unavailable("the token is not a valid header value".into()))?,
269 )),
270 };
271 let frames = client(
272 channel(&endpoint.uri, &options)
273 .await
274 .map_err(unavailable)?,
275 bearer.clone(),
276 );
277 let input = client(
278 channel(&endpoint.uri, &options)
279 .await
280 .map_err(unavailable)?,
281 bearer,
282 );
283 let status = within(
284 "getStatus",
285 options.deadline,
286 frames.clone().get_status(unary((), options.deadline)),
287 )
288 .await
289 .map_err(|e| unavailable(e.to_string()))?;
290 let hardware = Hardware::from_status(&status).map_err(unavailable)?;
291 Ok(Self {
292 serial: endpoint.serial.clone(),
293 frames,
294 input,
295 hardware,
296 deadline: options.deadline,
297 })
298 }
299
300 pub fn serial(&self) -> &str {
301 &self.serial
302 }
303
304 pub fn hardware(&self) -> Hardware {
306 self.hardware
307 }
308
309 pub fn keys_supported(&self) -> bool {
312 self.hardware.keyboard
313 }
314
315 pub async fn touch(&self, points: &[TouchPoint]) -> Result<Duration, Error> {
319 let event = proto::TouchEvent {
320 touches: points
321 .iter()
322 .map(|p| proto::Touch {
323 x: p.x,
324 y: p.y,
325 identifier: p.id,
326 pressure: p.pressure,
327 ..Default::default()
328 })
329 .collect(),
330 display: 0,
331 };
332 let started = Instant::now();
333 within(
334 "sendTouch",
335 self.deadline,
336 self.input.clone().send_touch(unary(event, self.deadline)),
337 )
338 .await?;
339 Ok(started.elapsed())
340 }
341
342 pub async fn tap(&self, x: i32, y: i32) -> Result<(), Error> {
344 self.touch(&[TouchPoint::down(x, y)]).await?;
345 self.touch(&[TouchPoint::up(x, y)]).await?;
346 Ok(())
347 }
348
349 pub async fn screenshot(&self) -> Result<Frame, Error> {
351 let call = "getScreenshot";
352 let image = within(
353 call,
354 self.deadline,
355 self.frames
356 .clone()
357 .get_screenshot(unary(rgb888(0, 0), self.deadline)),
358 )
359 .await?;
360 frame_from(image, call)
361 }
362
363 pub async fn frames(&self, working_width: u32) -> Result<FrameStream, Error> {
367 let size = scaled_size(self.hardware.display, working_width);
368 let request = tonic::Request::new(rgb888(size.0, size.1));
370 let inner = within(
371 "streamScreenshot",
372 self.deadline,
373 self.frames.clone().stream_screenshot(request),
374 )
375 .await?;
376 Ok(FrameStream { inner, size })
377 }
378}
379
380pub struct FrameStream {
382 inner: tonic::Streaming<proto::Image>,
383 size: (u32, u32),
384}
385
386impl FrameStream {
387 pub fn requested_size(&self) -> (u32, u32) {
389 self.size
390 }
391
392 pub async fn next(&mut self, wait: Duration) -> Result<Option<Frame>, Error> {
395 let call = "streamScreenshot";
396 match tokio::time::timeout(wait, self.inner.message()).await {
397 Err(_) => Ok(None),
398 Ok(Err(status)) => Err(Error::Status { call, status }),
399 Ok(Ok(None)) => Err(Error::StreamEnded),
400 Ok(Ok(Some(image))) => frame_from(image, call).map(Some),
401 }
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use std::sync::Arc;
409 use std::sync::atomic::{AtomicUsize, Ordering};
410 use std::time::Instant;
411 use tokio::net::TcpListener;
412
413 fn endpoint(port: u16, token: Option<&str>) -> Endpoint {
414 Endpoint {
415 serial: "emulator-5554".into(),
416 uri: format!("http://127.0.0.1:{port}"),
417 token: token.map(str::to_owned),
418 console_port: Some(5554),
419 discovery_file: None,
420 }
421 }
422
423 fn quick() -> Options {
424 Options {
425 deadline: Duration::from_millis(300),
426 connect_deadline: Duration::from_millis(300),
427 }
428 }
429
430 async fn silent_port() -> (u16, Arc<AtomicUsize>) {
433 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
434 let port = listener.local_addr().unwrap().port();
435 let accepted = Arc::new(AtomicUsize::new(0));
436 let count = accepted.clone();
437 tokio::spawn(async move {
438 let mut held = Vec::new();
439 while let Ok((socket, _)) = listener.accept().await {
440 count.fetch_add(1, Ordering::SeqCst);
441 held.push(socket);
442 }
443 });
444 (port, accepted)
445 }
446
447 #[tokio::test]
448 async fn a_silent_endpoint_fails_at_the_deadline() {
449 let (port, _) = silent_port().await;
450 let started = Instant::now();
451 let err = Controller::connect(&endpoint(port, None), quick())
452 .await
453 .unwrap_err();
454 let elapsed = started.elapsed();
455 assert!(elapsed < Duration::from_secs(2), "took {elapsed:?}");
456 assert!(err.reason.contains("getStatus"), "{}", err.reason);
457 assert!(err.reason.contains("no answer within"), "{}", err.reason);
458 assert!(err.warning().contains("-grpc 8554 -grpc-use-token"));
459 }
460
461 #[tokio::test]
462 async fn input_travels_on_a_second_connection() {
463 let (port, accepted) = silent_port().await;
464 let _ = Controller::connect(&endpoint(port, None), quick()).await;
465 assert_eq!(accepted.load(Ordering::SeqCst), 2);
466 }
467
468 #[tokio::test]
469 async fn a_closed_port_is_unavailable_with_the_fallback_warning() {
470 let port = {
471 let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
472 listener.local_addr().unwrap().port()
473 };
474 let err = Controller::connect(&endpoint(port, Some("token")), quick())
475 .await
476 .unwrap_err();
477 assert!(
478 err.reason.contains(&format!("127.0.0.1:{port}")),
479 "{}",
480 err.reason
481 );
482 let warning = err.warning();
483 assert!(warning.contains("-grpc 8554 -grpc-use-token"), "{warning}");
484 assert!(warning.contains("`grpc 8554`"), "{warning}");
485 }
486
487 #[tokio::test]
488 async fn a_token_that_is_no_header_value_is_refused_before_connecting() {
489 let (port, accepted) = silent_port().await;
490 let err = Controller::connect(&endpoint(port, Some("bad\ntoken")), quick())
491 .await
492 .unwrap_err();
493 assert!(err.reason.contains("token"), "{}", err.reason);
494 assert_eq!(accepted.load(Ordering::SeqCst), 0);
495 }
496
497 #[test]
498 fn touch_points_press_and_lift_contact_zero() {
499 assert_eq!(
500 TouchPoint::down(300, 60),
501 TouchPoint {
502 id: 0,
503 x: 300,
504 y: 60,
505 pressure: DOWN_PRESSURE
506 }
507 );
508 assert_eq!(TouchPoint::up(300, 60).pressure, 0);
509 }
510}