Skip to main content

android/session/
mod.rs

1//! The agent session: an attached agent behind the mirror's backend traits
2//! (task 5.3; design D5 and D7).
3//!
4//! - [`Session::backend`] hands a `mirror::Backend` over: a tree source over
5//!   the agent's `snapshot` requests, an event stream carrying the agent's
6//!   events and, on an emulator with its controller, the controller's frames
7//!   with status-bar-free hashes, an input sink over the agent's input and
8//!   text requests or the controller's touches, and the system clock.
9//! - The agent's snapshot passes through as it came: the window metadata it
10//!   carries (whether each window sends change events, its owner, whether it
11//!   is modal) and the scroll semantics are what the mirror reads.
12//! - Input the agent did not inject, the controller's touches, leaves the
13//!   agent's cache of Compose windows stale, so the next read waits until
14//!   the input is a quiet window old and empties that cache first.
15//! - Without a frame source, the mirror's settle probes a few nodes' bounds
16//!   through the agent's `bounds` (task 10.11), when the agent advertises
17//!   it, and reads windows whose nodes moved with their cache evicted.
18//! - The end of the connection or three seconds of silence (the protocol
19//!   crate's liveness) reach the mirror as `Disconnected`.
20
21mod controller;
22mod events;
23mod input;
24mod paths;
25mod tree;
26
27use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
28use std::sync::mpsc::{self, Receiver};
29use std::sync::{Arc, Mutex, PoisonError};
30use std::time::{Duration, Instant};
31
32use mirror::backend::{Backend, FrameSource, ReadReason, StreamItem, SystemClock, Timed};
33use protocol::code;
34use protocol::event::Stream;
35use protocol::message::{Call, Reply, Screenshot, ScreenshotParams};
36use protocol::session::{DisconnectReason, HostEvent};
37
38use self::controller::{ControllerFrames, FramePump, FrameStore};
39use self::events::AgentEvents;
40use self::input::SessionInput;
41use self::tree::AgentTree;
42use crate::adb;
43use crate::agent::{AgentInfo, Attached, Connection, DeployError, Deployer, Outcome, Stopped};
44
45pub use controller::{
46    ControllerLink, INPUT_QUIET, encode_jpeg, frame_meta, resize_rgb, rotate_rgb,
47};
48pub use events::stream_item;
49pub use input::{agent_call, input_ack, swipe_touches};
50pub use paths::{ControllerChoice, Path, Paths, Selection, Status, select_paths};
51pub use tree::{BACKEND, has_event_less_window, scale_from_density, snapshot_params};
52
53/// How the session reads the tree.
54#[derive(Clone, Debug, PartialEq, Eq)]
55pub struct ReadOptions {
56    /// Node cap per read; `None` takes the agent's (10,000).
57    pub max_nodes: Option<u32>,
58    /// Depth cap below each window root; `None` takes the agent's (256).
59    pub max_depth: Option<u32>,
60    /// Idle bound of reads the target's own changes trigger (events, polls,
61    /// frames), in milliseconds: the mirror's settle replaces the platform's
62    /// idle wait (design D8), so 0.
63    pub idle_ms: u32,
64    /// Idle bound of reads after an attach, an action or a resync, which
65    /// should see the screen settled; `None` takes the agent's default
66    /// (500 ms, ending 200 ms after the last event or action).
67    pub settle_idle_ms: Option<u32>,
68    /// Deadline of each snapshot request.
69    pub deadline: Duration,
70}
71
72impl Default for ReadOptions {
73    fn default() -> ReadOptions {
74        ReadOptions {
75            max_nodes: None,
76            max_depth: None,
77            idle_ms: 0,
78            settle_idle_ms: None,
79            deadline: Duration::from_secs(10),
80        }
81    }
82}
83
84/// How the session works.
85#[derive(Clone, Debug, PartialEq, Eq)]
86pub struct SessionOptions {
87    pub read: ReadOptions,
88    /// Deadline of input requests, added to a gesture's own duration.
89    pub input_deadline: Duration,
90    /// Width of the controller's frames (spike S7: 540 px at about 45 fps).
91    pub frame_width: u32,
92}
93
94impl Default for SessionOptions {
95    fn default() -> SessionOptions {
96        SessionOptions {
97            read: ReadOptions::default(),
98            input_deadline: Duration::from_secs(5),
99            frame_width: 540,
100        }
101    }
102}
103
104/// What the last tree read returned.
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub struct ReadStats {
107    /// The screen's node count as the agent reported it.
108    pub node_count: u32,
109    /// Nodes the read carried.
110    pub serialized: u32,
111    pub windows: u32,
112    /// From the start of the read, any wait after foreign input included, to
113    /// its answer.
114    pub took: Duration,
115    pub reason: ReadReason,
116    /// Whether the read asked the agent to evict windows' cache first: the
117    /// windows that send no change events, after input it did not inject,
118    /// or the windows whose nodes bounds probes found moved.
119    pub cleared_cache: bool,
120}
121
122/// Why a session could not start.
123#[derive(Debug)]
124pub enum SessionError {
125    Adb(adb::Error),
126    /// The display density could not be read.
127    Density(String),
128}
129
130impl std::fmt::Display for SessionError {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        match self {
133            SessionError::Adb(error) => write!(f, "adb: {error}"),
134            SessionError::Density(output) => {
135                write!(
136                    f,
137                    "cannot read the display density from `wm density`: {output}"
138                )
139            }
140        }
141    }
142}
143
144impl std::error::Error for SessionError {}
145
146impl From<adb::Error> for SessionError {
147    fn from(error: adb::Error) -> SessionError {
148        SessionError::Adb(error)
149    }
150}
151
152/// An attached agent serving a mirror. Clones share it.
153#[derive(Clone)]
154pub struct Session {
155    inner: Arc<Inner>,
156}
157
158/// What the session's backend parts share.
159struct Inner {
160    connection: Connection,
161    agent: AgentInfo,
162    outcome: Outcome,
163    deployer: Option<Deployer>,
164    scale: f64,
165    options: SessionOptions,
166    /// The paths chosen when the session started.
167    paths: Paths,
168    controller: Option<ControllerLink>,
169    frames: Arc<Mutex<FrameStore>>,
170    frames_received: Arc<AtomicU64>,
171    frame_error: Arc<Mutex<Option<String>>>,
172    pump_stop: Arc<AtomicBool>,
173    /// The event stream's receiving end, until the backend takes it.
174    items: Mutex<Option<Receiver<Timed<StreamItem>>>>,
175    /// Whether the last read showed a window that sends no change events.
176    event_less: AtomicBool,
177    /// Input the agent did not inject since the last read.
178    foreign_input: Mutex<Option<Instant>>,
179    /// Streams whose resync failed, to retry on the next read.
180    failed_resyncs: Arc<Mutex<Vec<Stream>>>,
181    last_read: Mutex<Option<ReadStats>>,
182    reads: AtomicU64,
183    controller_inputs: AtomicU64,
184    agent_inputs: AtomicU64,
185    /// Why the controller stopped taking input, once it failed.
186    controller_failed: Mutex<Option<String>>,
187}
188
189impl Inner {
190    fn note_foreign_input(&self, at: Instant) {
191        *self
192            .foreign_input
193            .lock()
194            .unwrap_or_else(PoisonError::into_inner) = Some(at);
195    }
196}
197
198impl Drop for Inner {
199    fn drop(&mut self) {
200        self.pump_stop.store(true, Ordering::SeqCst);
201    }
202}
203
204impl std::fmt::Debug for Session {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        f.debug_struct("Session")
207            .field("agent", &self.inner.agent)
208            .field("scale", &self.inner.scale)
209            .field("controller", &self.inner.controller.is_some())
210            .finish_non_exhaustive()
211    }
212}
213
214/// How long `shutdown` may take to be answered.
215const SHUTDOWN_DEADLINE: Duration = Duration::from_secs(2);
216
217/// Deadline of a screenshot through the agent.
218const SCREENSHOT_DEADLINE: Duration = Duration::from_secs(10);
219
220impl Session {
221    /// A session over `attached`: reads the display density for the scale,
222    /// and uses `controller`, when given, for touches and frames.
223    pub fn start(
224        attached: Attached,
225        options: SessionOptions,
226        controller: Option<ControllerLink>,
227    ) -> Result<Session, SessionError> {
228        let selection = Selection {
229            paths: controller
230                .as_ref()
231                .map_or_else(|| Paths::agent(false, None), Paths::controller),
232            link: controller,
233        };
234        Session::start_selected(attached, options, selection)
235    }
236
237    /// A session over `attached` on the paths `selection` chose (see
238    /// [`select_paths`]).
239    pub fn start_selected(
240        attached: Attached,
241        options: SessionOptions,
242        selection: Selection,
243    ) -> Result<Session, SessionError> {
244        let density = attached.deployer().device().shell("wm density")?;
245        let text = density.stdout_text();
246        let scale = scale_from_density(&text)
247            .ok_or_else(|| SessionError::Density(format!("{text}{}", density.stderr_text())))?;
248        let (connection, agent, outcome, deployer) = attached.into_parts();
249        Ok(Session::over(
250            connection,
251            agent,
252            outcome,
253            Some(deployer),
254            scale,
255            options,
256            selection,
257        ))
258    }
259
260    /// A session over `connection`, whose handshake completed, at `scale`.
261    /// Without a deployer, `stop` only asks the agent to shut down.
262    pub(crate) fn over(
263        connection: Connection,
264        agent: AgentInfo,
265        outcome: Outcome,
266        deployer: Option<Deployer>,
267        scale: f64,
268        options: SessionOptions,
269        selection: Selection,
270    ) -> Session {
271        let Selection {
272            link: controller,
273            paths,
274        } = selection;
275        let (sender, items) = mpsc::channel();
276        let failed_resyncs = Arc::new(Mutex::new(Vec::new()));
277        let events = sender.clone();
278        let failed = Arc::clone(&failed_resyncs);
279        connection.set_sink(Box::new(move |at, event| {
280            if let HostEvent::ResyncFailed { stream, .. } = &event {
281                failed
282                    .lock()
283                    .unwrap_or_else(PoisonError::into_inner)
284                    .push(*stream);
285            }
286            if let Some(item) = stream_item(event) {
287                let _ = events.send(Timed { at, item });
288            }
289        }));
290        let pump = FramePump {
291            store: Arc::new(Mutex::new(FrameStore::default())),
292            items: sender,
293            received: Arc::new(AtomicU64::new(0)),
294            stop: Arc::new(AtomicBool::new(false)),
295            error: Arc::new(Mutex::new(None)),
296        };
297        if let Some(link) = &controller {
298            link.start_frames(options.frame_width, pump.clone());
299        }
300        Session {
301            inner: Arc::new(Inner {
302                connection,
303                agent,
304                outcome,
305                deployer,
306                scale,
307                options,
308                paths,
309                controller,
310                frames: pump.store,
311                frames_received: pump.received,
312                frame_error: pump.error,
313                pump_stop: pump.stop,
314                items: Mutex::new(Some(items)),
315                event_less: AtomicBool::new(false),
316                foreign_input: Mutex::new(None),
317                failed_resyncs,
318                last_read: Mutex::new(None),
319                reads: AtomicU64::new(0),
320                controller_inputs: AtomicU64::new(0),
321                agent_inputs: AtomicU64::new(0),
322                controller_failed: Mutex::new(None),
323            }),
324        }
325    }
326
327    /// Notes input the agent did not inject at `at`, such as a controller
328    /// touch: the next read waits until it is [`INPUT_QUIET`] old and first
329    /// empties the agent's node cache when a window sends no change events.
330    #[cfg(test)]
331    pub(crate) fn note_foreign_input(&self, at: Instant) {
332        self.inner.note_foreign_input(at);
333    }
334
335    /// What the session reports: the agent, the active paths, the keyboard
336    /// capability, the fallback warning and what failed since.
337    pub fn status(&self) -> Status {
338        let inner = &*self.inner;
339        let controller_error = self.controller_error();
340        let mut paths = inner.paths.clone();
341        if controller_error.is_some() {
342            paths.input = Path::Agent;
343        }
344        Status {
345            serial: inner.agent.serial.clone(),
346            agent: inner.agent.agent.clone(),
347            protocol: inner.agent.protocol,
348            pid: inner.agent.pid,
349            socket: inner.agent.socket.clone(),
350            reused: inner.outcome == Outcome::Reused,
351            paths,
352            inputs: self.inputs(),
353            frames_received: self.frames_received(),
354            controller_error,
355            frame_error: self.frame_error(),
356            disconnected: self.close_reason().map(|reason| reason.to_string()),
357        }
358    }
359
360    /// The backend a `mirror::Mirror` runs on. Taken once: the event stream
361    /// has one reader.
362    ///
363    /// # Panics
364    ///
365    /// When called a second time.
366    pub fn backend(&self) -> Backend {
367        let items = self
368            .inner
369            .items
370            .lock()
371            .unwrap_or_else(PoisonError::into_inner)
372            .take()
373            .expect("the backend of a session is taken once");
374        let frames = self.inner.controller.as_ref().map(|_| {
375            Box::new(ControllerFrames {
376                store: Arc::clone(&self.inner.frames),
377            }) as Box<dyn FrameSource>
378        });
379        Backend {
380            tree: Box::new(AgentTree {
381                inner: Arc::clone(&self.inner),
382            }),
383            events: Box::new(AgentEvents { items }),
384            frames,
385            input: Box::new(SessionInput {
386                inner: Arc::clone(&self.inner),
387            }),
388            clock: Box::new(SystemClock),
389        }
390    }
391
392    pub fn agent(&self) -> &AgentInfo {
393        &self.inner.agent
394    }
395
396    /// Whether the agent was launched or reused.
397    pub fn outcome(&self) -> &Outcome {
398        &self.inner.outcome
399    }
400
401    /// Physical pixels per device point.
402    pub fn scale(&self) -> f64 {
403        self.inner.scale
404    }
405
406    /// Whether touches and frames go through the emulator's controller.
407    pub fn uses_controller(&self) -> bool {
408        self.inner.controller.is_some()
409    }
410
411    /// What the last tree read returned.
412    pub fn last_read(&self) -> Option<ReadStats> {
413        *self
414            .inner
415            .last_read
416            .lock()
417            .unwrap_or_else(PoisonError::into_inner)
418    }
419
420    /// Tree reads made so far.
421    pub fn reads(&self) -> u64 {
422        self.inner.reads.load(Ordering::SeqCst)
423    }
424
425    /// Frames received from the controller so far.
426    pub fn frames_received(&self) -> u64 {
427        self.inner.frames_received.load(Ordering::SeqCst)
428    }
429
430    /// Inputs sent so far: through the controller, and through the agent.
431    pub fn inputs(&self) -> (u64, u64) {
432        (
433            self.inner.controller_inputs.load(Ordering::SeqCst),
434            self.inner.agent_inputs.load(Ordering::SeqCst),
435        )
436    }
437
438    /// Why the controller stopped taking input, once it failed; input then
439    /// goes through the agent.
440    pub fn controller_error(&self) -> Option<String> {
441        self.inner
442            .controller_failed
443            .lock()
444            .unwrap_or_else(PoisonError::into_inner)
445            .clone()
446    }
447
448    /// The last failure of the controller's frame stream, which the session
449    /// subscribes to again.
450    pub fn frame_error(&self) -> Option<String> {
451        self.inner
452            .frame_error
453            .lock()
454            .unwrap_or_else(PoisonError::into_inner)
455            .clone()
456    }
457
458    /// A screenshot through the agent.
459    pub fn screenshot(&self, params: ScreenshotParams) -> Result<Screenshot, protocol::Error> {
460        match self
461            .inner
462            .connection
463            .call(Call::Screenshot(params), SCREENSHOT_DEADLINE)?
464        {
465            Reply::Screenshot(shot) => Ok(shot),
466            _ => Err(protocol::Error::new(
467                code::PROTOCOL_ERROR,
468                "the agent answered a screenshot request with another result",
469            )),
470        }
471    }
472
473    /// Why the connection to the agent closed, once it has.
474    pub fn close_reason(&self) -> Option<DisconnectReason> {
475        self.inner.connection.close_reason()
476    }
477
478    /// Stops the agent: `shutdown`, then a kill by pid past `timeout`.
479    pub fn stop(&self, timeout: Duration) -> Result<Stopped, DeployError> {
480        let inner = &*self.inner;
481        inner.pump_stop.store(true, Ordering::SeqCst);
482        match &inner.deployer {
483            Some(deployer) => deployer.stop_agent(
484                &inner.connection,
485                inner.agent.pid,
486                &inner.agent.socket,
487                timeout,
488            ),
489            None => {
490                let _ = inner.connection.call(Call::Shutdown, SHUTDOWN_DEADLINE);
491                inner.connection.close();
492                Ok(Stopped::Exited)
493            }
494        }
495    }
496}
497
498#[cfg(test)]
499mod tests;