1use crate::{power, startup};
6use anyhow::{Context as _, Error, anyhow};
7use fidl::endpoints::{ClientEnd, ServerEnd, create_proxy};
8use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
9use fuchsia_inspect_contrib::nodes::BoundedListNode;
10use fuchsia_sync::Mutex;
11use futures::{StreamExt, TryFutureExt, TryStreamExt};
12use log::{error, warn};
13use std::sync::Arc;
14use zx::HandleBased;
15use {
16 fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
17 fidl_fuchsia_io as fio, fidl_fuchsia_power_broker as fbroker, fidl_fuchsia_session as fsession,
18 fidl_fuchsia_session_power as fpower,
19};
20
21const MAX_CONCURRENT_CONNECTIONS: usize = 10_000;
23
24const DIAGNOSTICS_SESSION_STARTED_AT_NAME: &str = "session_started_at";
26
27const DIAGNOSTICS_SESSION_STARTED_AT_SIZE: usize = 100;
29
30const DIAGNOSTICS_TIME_PROPERTY_NAME: &str = "@time";
33
34pub enum IncomingRequest {
36 Launcher(fsession::LauncherRequestStream),
37 Restarter(fsession::RestarterRequestStream),
38 Lifecycle(fsession::LifecycleRequestStream),
39 Handoff(fpower::HandoffRequestStream),
40}
41
42struct Diagnostics {
43 session_started_at: BoundedListNode,
45}
46
47impl Diagnostics {
48 pub fn record_session_start(&mut self) {
49 self.session_started_at.add_entry(|node| {
50 node.record_int(
51 DIAGNOSTICS_TIME_PROPERTY_NAME,
52 zx::MonotonicInstant::get().into_nanos(),
53 );
54 });
55 }
56}
57
58struct PendingSession {
60 pub exposed_dir_server_end: ServerEnd<fio::DirectoryMarker>,
64}
65
66impl PendingSession {
67 fn new() -> (fio::DirectoryProxy, Self) {
68 let (exposed_dir, exposed_dir_server_end) = create_proxy::<fio::DirectoryMarker>();
69 (exposed_dir, Self { exposed_dir_server_end })
70 }
71}
72
73struct StartedSession {
78 url: String,
80}
81
82enum Session {
83 Pending(PendingSession),
84 Started(StartedSession),
85}
86
87impl Session {
88 fn new_pending() -> (fio::DirectoryProxy, Self) {
89 let (proxy, pending_session) = PendingSession::new();
90 (proxy, Self::Pending(pending_session))
91 }
92}
93
94struct PowerState {
95 power_element: futures::lock::Mutex<Option<power::PowerElement>>,
100
101 suspend_enabled: bool,
103}
104
105impl PowerState {
106 pub fn new(suspend_enabled: bool) -> Self {
107 Self { power_element: futures::lock::Mutex::default(), suspend_enabled }
108 }
109
110 pub async fn ensure_power_lease(&self) {
114 if !self.suspend_enabled {
115 return;
116 }
117 let power_element = &mut *self.power_element.lock().await;
118 if let Some(power_element) = power_element
119 && power_element.has_lease()
120 {
121 return;
122 }
123 *power_element = match power::PowerElement::new().await {
124 Ok(element) => Some(element),
125 Err(err) => {
126 warn!("Failed to create power element: {err}");
127 None
128 }
129 };
130 }
131
132 pub async fn take_power_lease(
133 &self,
134 ) -> Result<ClientEnd<fbroker::LeaseControlMarker>, fpower::HandoffError> {
135 if !self.suspend_enabled {
136 log::warn!(
137 "Session component wants to take our power lease, but the platform is \
138 configured to not support suspend"
139 );
140 return Err(fpower::HandoffError::Unavailable);
141 }
142 log::info!("Session component is taking our power lease");
143 let lease = match &mut *self.power_element.lock().await {
144 Some(power_element) => power_element.take_lease(),
145 None => return Err(fpower::HandoffError::Unavailable),
146 }
147 .ok_or(fpower::HandoffError::AlreadyTaken)?;
148 Ok(lease)
149 }
150}
151
152struct SessionManagerState {
153 default_session_url: Option<String>,
155
156 session: futures::lock::Mutex<Session>,
158
159 realm: fcomponent::RealmProxy,
161
162 power: PowerState,
164
165 debug: Arc<crate::debug::DebugManager>,
167
168 inner: Mutex<Inner>,
170}
171
172struct Inner {
173 diagnostics: Diagnostics,
175
176 exposed_dir: fio::DirectoryProxy,
178}
179
180impl SessionManagerState {
181 async fn start_default(&self) -> Result<(), Error> {
187 let session_url = self
188 .default_session_url
189 .as_ref()
190 .ok_or_else(|| anyhow!("no default session URL configured"))?
191 .clone();
192 self.start(session_url, vec![]).await?;
193 Ok(())
194 }
195
196 async fn start(
198 &self,
199 url: String,
200 config_capabilities: Vec<fdecl::Configuration>,
201 ) -> Result<(), startup::StartupError> {
202 self.power.ensure_power_lease().await;
203 self.start_impl(&mut *self.session.lock().await, config_capabilities, url).await
204 }
205
206 async fn start_impl(
207 &self,
208 session: &mut Session,
209 config_capabilities: Vec<fdecl::Configuration>,
210 url: String,
211 ) -> Result<(), startup::StartupError> {
212 let (proxy_on_failure, new_pending) = Session::new_pending();
213 let pending_session = std::mem::replace(session, new_pending);
214 let pending = match pending_session {
215 Session::Pending(pending) => pending,
216 Session::Started(_) => {
217 let (proxy, pending) = PendingSession::new();
218 self.inner.lock().exposed_dir = proxy;
219 pending
220 }
221 };
222 if let Err(e) = startup::launch_session(
223 &url,
224 config_capabilities,
225 pending.exposed_dir_server_end,
226 &self.realm,
227 )
228 .await
229 {
230 self.inner.lock().exposed_dir = proxy_on_failure;
231 return Err(e);
232 }
233 *session = Session::Started(StartedSession { url });
234 self.inner.lock().diagnostics.record_session_start();
235
236 self.debug.clone().start_media_buttons_listener();
237
238 Ok(())
239 }
240
241 async fn stop(&self) -> Result<(), startup::StartupError> {
243 self.power.ensure_power_lease().await;
244 self.debug.stop();
245 let mut session = self.session.lock().await;
246 if let Session::Started(_) = &*session {
247 let (proxy, new_pending) = Session::new_pending();
248 *session = new_pending;
249 self.inner.lock().exposed_dir = proxy;
250 startup::stop_session(&self.realm).await?;
251 }
252 Ok(())
253 }
254
255 async fn restart(&self) -> Result<(), startup::StartupError> {
257 self.power.ensure_power_lease().await;
258 let mut session = self.session.lock().await;
259 let Session::Started(StartedSession { url }) = &mut *session else {
260 return Err(startup::StartupError::NotRunning);
261 };
262 let url = url.clone();
263 self.start_impl(&mut session, vec![], url).await?;
264 Ok(())
265 }
266
267 async fn take_power_lease(
268 &self,
269 ) -> Result<ClientEnd<fbroker::LeaseControlMarker>, fpower::HandoffError> {
270 let lease = self.power.take_power_lease().await?;
271 Ok(lease)
272 }
273}
274
275impl vfs::remote::GetRemoteDir for SessionManagerState {
276 #[allow(clippy::unwrap_in_result)]
277 fn get_remote_dir(&self) -> Result<fio::DirectoryProxy, zx::Status> {
278 Ok(Clone::clone(&self.inner.lock().exposed_dir))
279 }
280}
281
282#[derive(Clone)]
284pub struct SessionManager {
285 state: Arc<SessionManagerState>,
286}
287
288impl SessionManager {
289 pub fn new(
294 realm: fcomponent::RealmProxy,
295 inspector: &fuchsia_inspect::Inspector,
296 default_session_url: Option<String>,
297 suspend_enabled: bool,
298 enable_debug_shortcut: bool,
299 ) -> Self {
300 let session_started_at = BoundedListNode::new(
301 inspector.root().create_child(DIAGNOSTICS_SESSION_STARTED_AT_NAME),
302 DIAGNOSTICS_SESSION_STARTED_AT_SIZE,
303 );
304 let diagnostics = Diagnostics { session_started_at };
305 let (proxy, new_pending) = Session::new_pending();
306 let state = SessionManagerState {
307 default_session_url,
308 session: futures::lock::Mutex::new(new_pending),
309 realm,
310 power: PowerState::new(suspend_enabled),
311 debug: Arc::new(crate::debug::DebugState::new(enable_debug_shortcut)),
312 inner: Mutex::new(Inner { exposed_dir: proxy, diagnostics }),
313 };
314 SessionManager { state: Arc::new(state) }
315 }
316
317 #[cfg(test)]
318 pub fn new_default(
319 realm: fcomponent::RealmProxy,
320 inspector: &fuchsia_inspect::Inspector,
321 ) -> Self {
322 Self::new(realm, inspector, None, false, false)
323 }
324
325 pub async fn start_default_session(&mut self) -> Result<(), Error> {
331 self.state.start_default().await?;
332 Ok(())
333 }
334
335 pub async fn serve(
342 &mut self,
343 fs: &mut ServiceFs<ServiceObjLocal<'_, IncomingRequest>>,
344 ) -> Result<(), Error> {
345 fs.dir("svc")
346 .add_fidl_service(IncomingRequest::Launcher)
347 .add_fidl_service(IncomingRequest::Restarter)
348 .add_fidl_service(IncomingRequest::Lifecycle)
349 .add_fidl_service(IncomingRequest::Handoff);
350
351 fs.add_entry_at("svc_from_session", self.state.clone());
353
354 fs.take_and_serve_directory_handle()?;
355
356 fs.for_each_concurrent(MAX_CONCURRENT_CONNECTIONS, |request| {
357 let mut session_manager = self.clone();
358 async move {
359 session_manager
360 .handle_incoming_request(request)
361 .unwrap_or_else(|err| error!("{err:?}"))
362 .await
363 }
364 })
365 .await;
366
367 Ok(())
368 }
369
370 async fn handle_incoming_request(&mut self, request: IncomingRequest) -> Result<(), Error> {
377 match request {
378 IncomingRequest::Launcher(request_stream) => {
379 self.handle_launcher_request_stream(request_stream)
380 .await
381 .context("Session Launcher request stream got an error.")?;
382 }
383 IncomingRequest::Restarter(request_stream) => {
384 self.handle_restarter_request_stream(request_stream)
385 .await
386 .context("Session Restarter request stream got an error.")?;
387 }
388 IncomingRequest::Lifecycle(request_stream) => {
389 self.handle_lifecycle_request_stream(request_stream)
390 .await
391 .context("Session Lifecycle request stream got an error.")?;
392 }
393 IncomingRequest::Handoff(request_stream) => {
394 self.handle_handoff_request_stream(request_stream)
395 .await
396 .context("Session Handoff request stream got an error.")?;
397 }
398 }
399
400 Ok(())
401 }
402
403 pub async fn handle_launcher_request_stream(
411 &mut self,
412 mut request_stream: fsession::LauncherRequestStream,
413 ) -> Result<(), Error> {
414 while let Some(request) =
415 request_stream.try_next().await.context("Error handling Launcher request stream")?
416 {
417 match request {
418 fsession::LauncherRequest::Launch { configuration, responder } => {
419 let result = self.handle_launch_request(configuration).await;
420 let _ = responder.send(result);
421 }
422 }
423 }
424 Ok(())
425 }
426
427 pub async fn handle_restarter_request_stream(
435 &mut self,
436 mut request_stream: fsession::RestarterRequestStream,
437 ) -> Result<(), Error> {
438 while let Some(request) =
439 request_stream.try_next().await.context("Error handling Restarter request stream")?
440 {
441 match request {
442 fsession::RestarterRequest::Restart { responder } => {
443 let result = self.handle_restart_request().await;
444 let _ = responder.send(result);
445 }
446 }
447 }
448 Ok(())
449 }
450
451 pub async fn handle_lifecycle_request_stream(
459 &mut self,
460 mut request_stream: fsession::LifecycleRequestStream,
461 ) -> Result<(), Error> {
462 while let Some(request) =
463 request_stream.try_next().await.context("Error handling Lifecycle request stream")?
464 {
465 match request {
466 fsession::LifecycleRequest::Start { payload, responder } => {
467 let result = self.handle_lifecycle_start_request(payload.session_url).await;
468 let _ = responder.send(result);
469 }
470 fsession::LifecycleRequest::Stop { responder } => {
471 let result = self.handle_lifecycle_stop_request().await;
472 let _ = responder.send(result);
473 }
474 fsession::LifecycleRequest::Restart { responder } => {
475 let result = self.handle_lifecycle_restart_request().await;
476 let _ = responder.send(result);
477 }
478 fsession::LifecycleRequest::_UnknownMethod { ordinal, .. } => {
479 warn!(ordinal:%; "Lifecycle received an unknown method");
480 }
481 }
482 }
483 Ok(())
484 }
485
486 pub async fn handle_handoff_request_stream(
487 &mut self,
488 mut request_stream: fpower::HandoffRequestStream,
489 ) -> Result<(), Error> {
490 while let Some(request) =
491 request_stream.try_next().await.context("Error handling Handoff request stream")?
492 {
493 match request {
494 fpower::HandoffRequest::Take { responder } => {
495 let result = self.handle_handoff_take_request().await;
496 let _ = responder.send(result.map(|lease| lease.into_channel().into_handle()));
497 }
498 fpower::HandoffRequest::_UnknownMethod { ordinal, .. } => {
499 warn!(ordinal:%; "Lifecycle received an unknown method")
500 }
501 }
502 }
503 Ok(())
504 }
505
506 async fn handle_launch_request(
511 &mut self,
512 configuration: fsession::LaunchConfiguration,
513 ) -> Result<(), fsession::LaunchError> {
514 let session_url = configuration.session_url.ok_or(fsession::LaunchError::InvalidArgs)?;
515 let config_capabilities = configuration.config_capabilities.unwrap_or_default();
516 self.state.start(session_url, config_capabilities).await.map_err(Into::into)
517 }
518
519 async fn handle_restart_request(&mut self) -> Result<(), fsession::RestartError> {
521 self.state.restart().await.map_err(Into::into)
522 }
523
524 async fn handle_lifecycle_start_request(
529 &mut self,
530 session_url: Option<String>,
531 ) -> Result<(), fsession::LifecycleError> {
532 let session_url = session_url
533 .as_ref()
534 .or(self.state.default_session_url.as_ref())
535 .ok_or(fsession::LifecycleError::NotFound)?
536 .to_owned();
537 self.state.start(session_url, vec![]).await.map_err(Into::into)
538 }
539
540 async fn handle_lifecycle_stop_request(&mut self) -> Result<(), fsession::LifecycleError> {
542 self.state.stop().await.map_err(Into::into)
543 }
544
545 async fn handle_lifecycle_restart_request(&mut self) -> Result<(), fsession::LifecycleError> {
547 self.state.restart().await.map_err(Into::into)
548 }
549
550 async fn handle_handoff_take_request(
552 &mut self,
553 ) -> Result<ClientEnd<fbroker::LeaseControlMarker>, fpower::HandoffError> {
554 self.state.take_power_lease().await
555 }
556}
557
558#[cfg(test)]
559#[allow(clippy::unwrap_used)]
560mod tests {
561 use super::SessionManager;
562 use anyhow::{Error, anyhow};
563 use diagnostics_assertions::{AnyProperty, assert_data_tree};
564 use fidl::endpoints::{ServerEnd, create_proxy_and_stream};
565 use fidl_test_util::spawn_stream_handler;
566 use futures::channel::mpsc;
567 use futures::prelude::*;
568 use session_testing::{spawn_directory_server, spawn_noop_directory_server, spawn_server};
569 use std::sync::LazyLock;
570 use test_util::Counter;
571 use {
572 fidl_fuchsia_component as fcomponent, fidl_fuchsia_io as fio,
573 fidl_fuchsia_session as fsession,
574 };
575
576 fn serve_launcher(session_manager: SessionManager) -> fsession::LauncherProxy {
577 let (launcher_proxy, launcher_stream) =
578 create_proxy_and_stream::<fsession::LauncherMarker>();
579 {
580 let mut session_manager_ = session_manager.clone();
581 fuchsia_async::Task::spawn(async move {
582 session_manager_
583 .handle_launcher_request_stream(launcher_stream)
584 .await
585 .expect("Session launcher request stream got an error.");
586 })
587 .detach();
588 }
589 launcher_proxy
590 }
591
592 fn serve_restarter(session_manager: SessionManager) -> fsession::RestarterProxy {
593 let (restarter_proxy, restarter_stream) =
594 create_proxy_and_stream::<fsession::RestarterMarker>();
595 {
596 let mut session_manager_ = session_manager.clone();
597 fuchsia_async::Task::spawn(async move {
598 session_manager_
599 .handle_restarter_request_stream(restarter_stream)
600 .await
601 .expect("Session restarter request stream got an error.");
602 })
603 .detach();
604 }
605 restarter_proxy
606 }
607
608 fn serve_lifecycle(session_manager: SessionManager) -> fsession::LifecycleProxy {
609 let (lifecycle_proxy, lifecycle_stream) =
610 create_proxy_and_stream::<fsession::LifecycleMarker>();
611 {
612 let mut session_manager_ = session_manager.clone();
613 fuchsia_async::Task::spawn(async move {
614 session_manager_
615 .handle_lifecycle_request_stream(lifecycle_stream)
616 .await
617 .expect("Session lifecycle request stream got an error.");
618 })
619 .detach();
620 }
621 lifecycle_proxy
622 }
623
624 fn spawn_noop_controller_server(server_end: ServerEnd<fcomponent::ControllerMarker>) {
625 spawn_server(server_end, move |controller_request| match controller_request {
626 fcomponent::ControllerRequest::Start { responder, .. } => {
627 let _ = responder.send(Ok(()));
628 }
629 fcomponent::ControllerRequest::IsStarted { .. } => unimplemented!(),
630 fcomponent::ControllerRequest::GetExposedDictionary { .. } => {
631 unimplemented!()
632 }
633 fcomponent::ControllerRequest::GetOutputDictionary { .. } => {
634 unimplemented!()
635 }
636 fcomponent::ControllerRequest::OpenExposedDir { .. } => {
637 unimplemented!()
638 }
639 fcomponent::ControllerRequest::Destroy { .. } => {
640 unimplemented!()
641 }
642 fcomponent::ControllerRequest::_UnknownMethod { .. } => {
643 unimplemented!()
644 }
645 });
646 }
647
648 fn open_session_exposed_dir(
649 session_manager: SessionManager,
650 path: &str,
651 server_end: ServerEnd<fio::DirectoryMarker>,
652 ) {
653 session_manager
654 .state
655 .inner
656 .lock()
657 .exposed_dir
658 .open(path, fio::PERM_READABLE, &fio::Options::default(), server_end.into_channel())
659 .unwrap();
660 }
661
662 #[fuchsia::test]
664 async fn test_launch() {
665 let session_url = "session";
666
667 let realm = spawn_stream_handler(move |realm_request| async move {
668 match realm_request {
669 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
670 let _ = responder.send(Ok(()));
671 }
672 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
673 assert_eq!(decl.url.unwrap(), session_url);
674 spawn_noop_controller_server(args.controller.unwrap());
675 let _ = responder.send(Ok(()));
676 }
677 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
678 spawn_noop_directory_server(exposed_dir);
679 let _ = responder.send(Ok(()));
680 }
681 _ => panic!("Realm handler received an unexpected request"),
682 }
683 });
684
685 let inspector = fuchsia_inspect::Inspector::default();
686 let session_manager = SessionManager::new_default(realm, &inspector);
687 let launcher = serve_launcher(session_manager);
688
689 assert!(
690 launcher
691 .launch(&fsession::LaunchConfiguration {
692 session_url: Some(session_url.to_string()),
693 ..Default::default()
694 })
695 .await
696 .is_ok()
697 );
698 assert_data_tree!(inspector, root: {
699 session_started_at: {
700 "0": {
701 "@time": AnyProperty
702 }
703 }
704 });
705 }
706
707 #[fuchsia::test]
709 async fn test_restarter_restart() {
710 let session_url = "session";
711
712 let realm = spawn_stream_handler(move |realm_request| async move {
713 match realm_request {
714 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
715 let _ = responder.send(Ok(()));
716 }
717 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
718 assert_eq!(decl.url.unwrap(), session_url);
719 spawn_noop_controller_server(args.controller.unwrap());
720 let _ = responder.send(Ok(()));
721 }
722 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
723 spawn_noop_directory_server(exposed_dir);
724 let _ = responder.send(Ok(()));
725 }
726 _ => panic!("Realm handler received an unexpected request"),
727 }
728 });
729
730 let inspector = fuchsia_inspect::Inspector::default();
731 let session_manager = SessionManager::new_default(realm, &inspector);
732 let launcher = serve_launcher(session_manager.clone());
733 let restarter = serve_restarter(session_manager);
734
735 assert!(
736 launcher
737 .launch(&fsession::LaunchConfiguration {
738 session_url: Some(session_url.to_string()),
739 ..Default::default()
740 })
741 .await
742 .expect("could not call Launch")
743 .is_ok()
744 );
745
746 assert!(restarter.restart().await.expect("could not call Restart").is_ok());
747
748 assert_data_tree!(inspector, root: {
749 session_started_at: {
750 "0": {
751 "@time": AnyProperty
752 },
753 "1": {
754 "@time": AnyProperty
755 }
756 }
757 });
758 }
759
760 #[fuchsia::test]
762 async fn test_restarter_restart_error_not_running() {
763 let realm = spawn_stream_handler(move |_realm_request| async move {
764 panic!("Realm should not receive any requests as there is no session to launch")
765 });
766
767 let inspector = fuchsia_inspect::Inspector::default();
768 let session_manager = SessionManager::new_default(realm, &inspector);
769 let restarter = serve_restarter(session_manager);
770
771 assert_eq!(
772 Err(fsession::RestartError::NotRunning),
773 restarter.restart().await.expect("could not call Restart")
774 );
775
776 assert_data_tree!(inspector, root: {
777 session_started_at: {}
778 });
779 }
780
781 #[fuchsia::test]
783 async fn test_start() {
784 let session_url = "session";
785
786 let realm = spawn_stream_handler(move |realm_request| async move {
787 match realm_request {
788 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
789 let _ = responder.send(Ok(()));
790 }
791 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
792 assert_eq!(decl.url.unwrap(), session_url);
793 spawn_noop_controller_server(args.controller.unwrap());
794 let _ = responder.send(Ok(()));
795 }
796 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
797 spawn_noop_directory_server(exposed_dir);
798 let _ = responder.send(Ok(()));
799 }
800 _ => panic!("Realm handler received an unexpected request"),
801 }
802 });
803
804 let inspector = fuchsia_inspect::Inspector::default();
805 let session_manager = SessionManager::new_default(realm, &inspector);
806 let lifecycle = serve_lifecycle(session_manager);
807
808 assert!(
809 lifecycle
810 .start(&fsession::LifecycleStartRequest {
811 session_url: Some(session_url.to_string()),
812 ..Default::default()
813 })
814 .await
815 .is_ok()
816 );
817 assert_data_tree!(inspector, root: {
818 session_started_at: {
819 "0": {
820 "@time": AnyProperty
821 }
822 }
823 });
824 }
825
826 #[fuchsia::test]
828 async fn test_start_default() {
829 let default_session_url = "session";
830
831 let realm = spawn_stream_handler(move |realm_request| async move {
832 match realm_request {
833 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
834 let _ = responder.send(Ok(()));
835 }
836 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
837 assert_eq!(decl.url.unwrap(), default_session_url);
838 spawn_noop_controller_server(args.controller.unwrap());
839 let _ = responder.send(Ok(()));
840 }
841 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
842 spawn_noop_directory_server(exposed_dir);
843 let _ = responder.send(Ok(()));
844 }
845 _ => panic!("Realm handler received an unexpected request"),
846 }
847 });
848
849 let inspector = fuchsia_inspect::Inspector::default();
850 let session_manager = SessionManager::new(
851 realm,
852 &inspector,
853 Some(default_session_url.to_owned()),
854 false,
855 false,
856 );
857 let lifecycle = serve_lifecycle(session_manager);
858
859 assert!(
860 lifecycle
861 .start(&fsession::LifecycleStartRequest { session_url: None, ..Default::default() })
862 .await
863 .is_ok()
864 );
865 assert_data_tree!(inspector, root: {
866 session_started_at: {
867 "0": {
868 "@time": AnyProperty
869 }
870 }
871 });
872 }
873
874 #[fuchsia::test]
876 async fn test_stop_destroys_component() {
877 static NUM_DESTROY_CHILD_CALLS: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
878
879 let session_url = "session";
880
881 let realm = spawn_stream_handler(move |realm_request| async move {
882 match realm_request {
883 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
884 NUM_DESTROY_CHILD_CALLS.inc();
885 let _ = responder.send(Ok(()));
886 }
887 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
888 assert_eq!(decl.url.unwrap(), session_url);
889 spawn_noop_controller_server(args.controller.unwrap());
890 let _ = responder.send(Ok(()));
891 }
892 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
893 spawn_noop_directory_server(exposed_dir);
894 let _ = responder.send(Ok(()));
895 }
896 _ => panic!("Realm handler received an unexpected request"),
897 }
898 });
899
900 let inspector = fuchsia_inspect::Inspector::default();
901 let session_manager = SessionManager::new_default(realm, &inspector);
902 let lifecycle = serve_lifecycle(session_manager);
903
904 assert!(
905 lifecycle
906 .start(&fsession::LifecycleStartRequest {
907 session_url: Some(session_url.to_string()),
908 ..Default::default()
909 })
910 .await
911 .is_ok()
912 );
913 assert_eq!(NUM_DESTROY_CHILD_CALLS.get(), 1);
915 assert_data_tree!(inspector, root: {
916 session_started_at: {
917 "0": {
918 "@time": AnyProperty
919 }
920 }
921 });
922
923 assert!(lifecycle.stop().await.is_ok());
924 assert_eq!(NUM_DESTROY_CHILD_CALLS.get(), 2);
925 }
926
927 #[fuchsia::test]
929 async fn test_lifecycle_restart() {
930 let session_url = "session";
931
932 let realm = spawn_stream_handler(move |realm_request| async move {
933 match realm_request {
934 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
935 let _ = responder.send(Ok(()));
936 }
937 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
938 assert_eq!(decl.url.unwrap(), session_url);
939 spawn_noop_controller_server(args.controller.unwrap());
940 let _ = responder.send(Ok(()));
941 }
942 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
943 spawn_noop_directory_server(exposed_dir);
944 let _ = responder.send(Ok(()));
945 }
946 _ => panic!("Realm handler received an unexpected request"),
947 }
948 });
949
950 let inspector = fuchsia_inspect::Inspector::default();
951 let session_manager = SessionManager::new_default(realm, &inspector);
952 let lifecycle = serve_lifecycle(session_manager.clone());
953
954 assert!(
955 lifecycle
956 .start(&fsession::LifecycleStartRequest {
957 session_url: Some(session_url.to_string()),
958 ..Default::default()
959 })
960 .await
961 .expect("could not call Launch")
962 .is_ok()
963 );
964
965 assert!(lifecycle.restart().await.expect("could not call Restart").is_ok());
966
967 assert_data_tree!(inspector, root: {
968 session_started_at: {
969 "0": {
970 "@time": AnyProperty
971 },
972 "1": {
973 "@time": AnyProperty
974 }
975 }
976 });
977 }
978
979 #[fuchsia::test]
982 async fn test_svc_from_session_before_start() -> Result<(), Error> {
983 let session_url = "session";
984 let svc_path = "foo";
985
986 let (path_sender, mut path_receiver) = mpsc::channel(1);
987
988 let session_exposed_dir_handler = move |directory_request| match directory_request {
989 fio::DirectoryRequest::Open { path, .. } => {
990 let mut path_sender: mpsc::Sender<String> = path_sender.clone();
991 path_sender.try_send(path).unwrap();
992 }
993 _ => panic!("Directory handler received an unexpected request"),
994 };
995
996 let realm = spawn_stream_handler(move |realm_request| {
997 let session_exposed_dir_handler = session_exposed_dir_handler.clone();
998 async move {
999 match realm_request {
1000 fcomponent::RealmRequest::DestroyChild { responder, .. } => {
1001 let _ = responder.send(Ok(()));
1002 }
1003 fcomponent::RealmRequest::CreateChild { args, responder, .. } => {
1004 spawn_noop_controller_server(args.controller.unwrap());
1005 let _ = responder.send(Ok(()));
1006 }
1007 fcomponent::RealmRequest::OpenExposedDir { exposed_dir, responder, .. } => {
1008 spawn_directory_server(exposed_dir, session_exposed_dir_handler);
1009 let _ = responder.send(Ok(()));
1010 }
1011 _ => panic!("Realm handler received an unexpected request"),
1012 }
1013 }
1014 });
1015
1016 let inspector = fuchsia_inspect::Inspector::default();
1017 let session_manager = SessionManager::new_default(realm, &inspector);
1018 let lifecycle = serve_lifecycle(session_manager.clone());
1019
1020 let (_client_end, server_end) = fidl::endpoints::create_proxy();
1023
1024 open_session_exposed_dir(session_manager, svc_path, server_end);
1025 lifecycle
1027 .start(&fsession::LifecycleStartRequest {
1028 session_url: Some(session_url.to_string()),
1029 ..Default::default()
1030 })
1031 .await?
1032 .map_err(|err| anyhow!("failed to start: {err:?}"))?;
1033
1034 assert_eq!(path_receiver.next().await.unwrap(), svc_path);
1036
1037 Ok(())
1038 }
1039
1040 #[fuchsia::test]
1043 async fn test_svc_from_session_after_start() -> Result<(), Error> {
1044 let session_url = "session";
1045 let svc_path = "foo";
1046
1047 let (path_sender, mut path_receiver) = mpsc::channel(1);
1048
1049 let session_exposed_dir_handler = move |directory_request| match directory_request {
1050 fio::DirectoryRequest::Open { path, .. } => {
1051 let mut path_sender = path_sender.clone();
1052 path_sender.try_send(path).unwrap();
1053 }
1054 _ => panic!("Directory handler received an unexpected request"),
1055 };
1056
1057 let realm = spawn_stream_handler(move |realm_request| {
1058 let session_exposed_dir_handler = session_exposed_dir_handler.clone();
1059 async move {
1060 match realm_request {
1061 fcomponent::RealmRequest::DestroyChild { responder, .. } => {
1062 let _ = responder.send(Ok(()));
1063 }
1064 fcomponent::RealmRequest::CreateChild { args, responder, .. } => {
1065 spawn_noop_controller_server(args.controller.unwrap());
1066 let _ = responder.send(Ok(()));
1067 }
1068 fcomponent::RealmRequest::OpenExposedDir { exposed_dir, responder, .. } => {
1069 spawn_directory_server(exposed_dir, session_exposed_dir_handler);
1070 let _ = responder.send(Ok(()));
1071 }
1072 _ => panic!("Realm handler received an unexpected request"),
1073 }
1074 }
1075 });
1076
1077 let inspector = fuchsia_inspect::Inspector::default();
1078 let session_manager = SessionManager::new_default(realm, &inspector);
1079 let lifecycle = serve_lifecycle(session_manager.clone());
1080
1081 lifecycle
1082 .start(&fsession::LifecycleStartRequest {
1083 session_url: Some(session_url.to_string()),
1084 ..Default::default()
1085 })
1086 .await?
1087 .map_err(|err| anyhow!("failed to start: {err:?}"))?;
1088
1089 let (_client_end, server_end) = fidl::endpoints::create_proxy();
1092
1093 open_session_exposed_dir(session_manager, svc_path, server_end);
1094
1095 assert_eq!(path_receiver.next().await.unwrap(), svc_path);
1096
1097 Ok(())
1098 }
1099
1100 #[fuchsia::test]
1102 async fn test_stop_resets_debug_state() {
1103 let session_url = "session";
1104
1105 let realm = spawn_stream_handler(move |realm_request| async move {
1106 match realm_request {
1107 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
1108 let _ = responder.send(Ok(()));
1109 }
1110 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
1111 assert_eq!(decl.url.unwrap(), session_url);
1112 spawn_noop_controller_server(args.controller.unwrap());
1113 let _ = responder.send(Ok(()));
1114 }
1115 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
1116 spawn_noop_directory_server(exposed_dir);
1117 let _ = responder.send(Ok(()));
1118 }
1119 _ => panic!("Realm handler received an unexpected request"),
1120 }
1121 });
1122
1123 let inspector = fuchsia_inspect::Inspector::default();
1124 let session_manager = SessionManager::new(realm, &inspector, None, false, true);
1125 let lifecycle = serve_lifecycle(session_manager.clone());
1126
1127 assert!(
1128 lifecycle
1129 .start(&fsession::LifecycleStartRequest {
1130 session_url: Some(session_url.to_string()),
1131 ..Default::default()
1132 })
1133 .await
1134 .is_ok()
1135 );
1136
1137 session_manager.state.debug.set_button_press_state_count(3);
1138 assert_eq!(session_manager.state.debug.get_button_press_state_count(), 3);
1139
1140 assert!(lifecycle.stop().await.is_ok());
1141
1142 assert_eq!(session_manager.state.debug.get_button_press_state_count(), 0);
1143 }
1144}