1use crate::{power, startup};
6use anyhow::{Context as _, Error, anyhow};
7use fidl::endpoints::{ClientEnd, ServerEnd, create_proxy};
8use fidl_fuchsia_component as fcomponent;
9use fidl_fuchsia_component_decl as fdecl;
10use fidl_fuchsia_io as fio;
11use fidl_fuchsia_power_broker as fbroker;
12use fidl_fuchsia_session as fsession;
13use fidl_fuchsia_session_power as fpower;
14use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
15use fuchsia_inspect_contrib::nodes::BoundedListNode;
16use fuchsia_sync::Mutex;
17use futures::{StreamExt, TryFutureExt, TryStreamExt};
18use log::{error, warn};
19use std::sync::Arc;
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_fuchsia_component as fcomponent;
566 use fidl_fuchsia_io as fio;
567 use fidl_fuchsia_session as fsession;
568 use fidl_test_util::spawn_stream_handler;
569 use futures::channel::mpsc;
570 use futures::prelude::*;
571 use session_testing::{spawn_directory_server, spawn_noop_directory_server, spawn_server};
572 use std::sync::LazyLock;
573 use test_util::Counter;
574
575 fn serve_launcher(session_manager: SessionManager) -> fsession::LauncherProxy {
576 let (launcher_proxy, launcher_stream) =
577 create_proxy_and_stream::<fsession::LauncherMarker>();
578 {
579 let mut session_manager_ = session_manager.clone();
580 fuchsia_async::Task::spawn(async move {
581 session_manager_
582 .handle_launcher_request_stream(launcher_stream)
583 .await
584 .expect("Session launcher request stream got an error.");
585 })
586 .detach();
587 }
588 launcher_proxy
589 }
590
591 fn serve_restarter(session_manager: SessionManager) -> fsession::RestarterProxy {
592 let (restarter_proxy, restarter_stream) =
593 create_proxy_and_stream::<fsession::RestarterMarker>();
594 {
595 let mut session_manager_ = session_manager.clone();
596 fuchsia_async::Task::spawn(async move {
597 session_manager_
598 .handle_restarter_request_stream(restarter_stream)
599 .await
600 .expect("Session restarter request stream got an error.");
601 })
602 .detach();
603 }
604 restarter_proxy
605 }
606
607 fn serve_lifecycle(session_manager: SessionManager) -> fsession::LifecycleProxy {
608 let (lifecycle_proxy, lifecycle_stream) =
609 create_proxy_and_stream::<fsession::LifecycleMarker>();
610 {
611 let mut session_manager_ = session_manager.clone();
612 fuchsia_async::Task::spawn(async move {
613 session_manager_
614 .handle_lifecycle_request_stream(lifecycle_stream)
615 .await
616 .expect("Session lifecycle request stream got an error.");
617 })
618 .detach();
619 }
620 lifecycle_proxy
621 }
622
623 fn spawn_noop_controller_server(server_end: ServerEnd<fcomponent::ControllerMarker>) {
624 spawn_server(server_end, move |controller_request| match controller_request {
625 fcomponent::ControllerRequest::Start { responder, .. } => {
626 let _ = responder.send(Ok(()));
627 }
628 fcomponent::ControllerRequest::IsStarted { .. } => unimplemented!(),
629 fcomponent::ControllerRequest::GetExposedDictionary { .. } => {
630 unimplemented!()
631 }
632 fcomponent::ControllerRequest::GetOutputDictionary { .. } => {
633 unimplemented!()
634 }
635 fcomponent::ControllerRequest::OpenExposedDir { .. } => {
636 unimplemented!()
637 }
638 fcomponent::ControllerRequest::Destroy { .. } => {
639 unimplemented!()
640 }
641 fcomponent::ControllerRequest::_UnknownMethod { .. } => {
642 unimplemented!()
643 }
644 });
645 }
646
647 fn open_session_exposed_dir(
648 session_manager: SessionManager,
649 path: &str,
650 server_end: ServerEnd<fio::DirectoryMarker>,
651 ) {
652 session_manager
653 .state
654 .inner
655 .lock()
656 .exposed_dir
657 .open(path, fio::PERM_READABLE, &fio::Options::default(), server_end.into_channel())
658 .unwrap();
659 }
660
661 #[fuchsia::test]
663 async fn test_launch() {
664 let session_url = "session";
665
666 let realm = spawn_stream_handler(move |realm_request| async move {
667 match realm_request {
668 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
669 let _ = responder.send(Ok(()));
670 }
671 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
672 assert_eq!(decl.url.unwrap(), session_url);
673 spawn_noop_controller_server(args.controller.unwrap());
674 let _ = responder.send(Ok(()));
675 }
676 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
677 spawn_noop_directory_server(exposed_dir);
678 let _ = responder.send(Ok(()));
679 }
680 _ => panic!("Realm handler received an unexpected request"),
681 }
682 });
683
684 let inspector = fuchsia_inspect::Inspector::default();
685 let session_manager = SessionManager::new_default(realm, &inspector);
686 let launcher = serve_launcher(session_manager);
687
688 assert!(
689 launcher
690 .launch(&fsession::LaunchConfiguration {
691 session_url: Some(session_url.to_string()),
692 ..Default::default()
693 })
694 .await
695 .is_ok()
696 );
697 assert_data_tree!(inspector, root: {
698 session_started_at: {
699 "0": {
700 "@time": AnyProperty
701 }
702 }
703 });
704 }
705
706 #[fuchsia::test]
708 async fn test_restarter_restart() {
709 let session_url = "session";
710
711 let realm = spawn_stream_handler(move |realm_request| async move {
712 match realm_request {
713 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
714 let _ = responder.send(Ok(()));
715 }
716 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
717 assert_eq!(decl.url.unwrap(), session_url);
718 spawn_noop_controller_server(args.controller.unwrap());
719 let _ = responder.send(Ok(()));
720 }
721 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
722 spawn_noop_directory_server(exposed_dir);
723 let _ = responder.send(Ok(()));
724 }
725 _ => panic!("Realm handler received an unexpected request"),
726 }
727 });
728
729 let inspector = fuchsia_inspect::Inspector::default();
730 let session_manager = SessionManager::new_default(realm, &inspector);
731 let launcher = serve_launcher(session_manager.clone());
732 let restarter = serve_restarter(session_manager);
733
734 assert!(
735 launcher
736 .launch(&fsession::LaunchConfiguration {
737 session_url: Some(session_url.to_string()),
738 ..Default::default()
739 })
740 .await
741 .expect("could not call Launch")
742 .is_ok()
743 );
744
745 assert!(restarter.restart().await.expect("could not call Restart").is_ok());
746
747 assert_data_tree!(inspector, root: {
748 session_started_at: {
749 "0": {
750 "@time": AnyProperty
751 },
752 "1": {
753 "@time": AnyProperty
754 }
755 }
756 });
757 }
758
759 #[fuchsia::test]
761 async fn test_restarter_restart_error_not_running() {
762 let realm = spawn_stream_handler(move |_realm_request| async move {
763 panic!("Realm should not receive any requests as there is no session to launch")
764 });
765
766 let inspector = fuchsia_inspect::Inspector::default();
767 let session_manager = SessionManager::new_default(realm, &inspector);
768 let restarter = serve_restarter(session_manager);
769
770 assert_eq!(
771 Err(fsession::RestartError::NotRunning),
772 restarter.restart().await.expect("could not call Restart")
773 );
774
775 assert_data_tree!(inspector, root: {
776 session_started_at: {}
777 });
778 }
779
780 #[fuchsia::test]
782 async fn test_start() {
783 let session_url = "session";
784
785 let realm = spawn_stream_handler(move |realm_request| async move {
786 match realm_request {
787 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
788 let _ = responder.send(Ok(()));
789 }
790 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
791 assert_eq!(decl.url.unwrap(), session_url);
792 spawn_noop_controller_server(args.controller.unwrap());
793 let _ = responder.send(Ok(()));
794 }
795 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
796 spawn_noop_directory_server(exposed_dir);
797 let _ = responder.send(Ok(()));
798 }
799 _ => panic!("Realm handler received an unexpected request"),
800 }
801 });
802
803 let inspector = fuchsia_inspect::Inspector::default();
804 let session_manager = SessionManager::new_default(realm, &inspector);
805 let lifecycle = serve_lifecycle(session_manager);
806
807 assert!(
808 lifecycle
809 .start(&fsession::LifecycleStartRequest {
810 session_url: Some(session_url.to_string()),
811 ..Default::default()
812 })
813 .await
814 .is_ok()
815 );
816 assert_data_tree!(inspector, root: {
817 session_started_at: {
818 "0": {
819 "@time": AnyProperty
820 }
821 }
822 });
823 }
824
825 #[fuchsia::test]
827 async fn test_start_default() {
828 let default_session_url = "session";
829
830 let realm = spawn_stream_handler(move |realm_request| async move {
831 match realm_request {
832 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
833 let _ = responder.send(Ok(()));
834 }
835 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
836 assert_eq!(decl.url.unwrap(), default_session_url);
837 spawn_noop_controller_server(args.controller.unwrap());
838 let _ = responder.send(Ok(()));
839 }
840 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
841 spawn_noop_directory_server(exposed_dir);
842 let _ = responder.send(Ok(()));
843 }
844 _ => panic!("Realm handler received an unexpected request"),
845 }
846 });
847
848 let inspector = fuchsia_inspect::Inspector::default();
849 let session_manager = SessionManager::new(
850 realm,
851 &inspector,
852 Some(default_session_url.to_owned()),
853 false,
854 false,
855 );
856 let lifecycle = serve_lifecycle(session_manager);
857
858 assert!(
859 lifecycle
860 .start(&fsession::LifecycleStartRequest { session_url: None, ..Default::default() })
861 .await
862 .is_ok()
863 );
864 assert_data_tree!(inspector, root: {
865 session_started_at: {
866 "0": {
867 "@time": AnyProperty
868 }
869 }
870 });
871 }
872
873 #[fuchsia::test]
875 async fn test_stop_destroys_component() {
876 static NUM_DESTROY_CHILD_CALLS: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
877
878 let session_url = "session";
879
880 let realm = spawn_stream_handler(move |realm_request| async move {
881 match realm_request {
882 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
883 NUM_DESTROY_CHILD_CALLS.inc();
884 let _ = responder.send(Ok(()));
885 }
886 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
887 assert_eq!(decl.url.unwrap(), session_url);
888 spawn_noop_controller_server(args.controller.unwrap());
889 let _ = responder.send(Ok(()));
890 }
891 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
892 spawn_noop_directory_server(exposed_dir);
893 let _ = responder.send(Ok(()));
894 }
895 _ => panic!("Realm handler received an unexpected request"),
896 }
897 });
898
899 let inspector = fuchsia_inspect::Inspector::default();
900 let session_manager = SessionManager::new_default(realm, &inspector);
901 let lifecycle = serve_lifecycle(session_manager);
902
903 assert!(
904 lifecycle
905 .start(&fsession::LifecycleStartRequest {
906 session_url: Some(session_url.to_string()),
907 ..Default::default()
908 })
909 .await
910 .is_ok()
911 );
912 assert_eq!(NUM_DESTROY_CHILD_CALLS.get(), 1);
914 assert_data_tree!(inspector, root: {
915 session_started_at: {
916 "0": {
917 "@time": AnyProperty
918 }
919 }
920 });
921
922 assert!(lifecycle.stop().await.is_ok());
923 assert_eq!(NUM_DESTROY_CHILD_CALLS.get(), 2);
924 }
925
926 #[fuchsia::test]
928 async fn test_lifecycle_restart() {
929 let session_url = "session";
930
931 let realm = spawn_stream_handler(move |realm_request| async move {
932 match realm_request {
933 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
934 let _ = responder.send(Ok(()));
935 }
936 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
937 assert_eq!(decl.url.unwrap(), session_url);
938 spawn_noop_controller_server(args.controller.unwrap());
939 let _ = responder.send(Ok(()));
940 }
941 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
942 spawn_noop_directory_server(exposed_dir);
943 let _ = responder.send(Ok(()));
944 }
945 _ => panic!("Realm handler received an unexpected request"),
946 }
947 });
948
949 let inspector = fuchsia_inspect::Inspector::default();
950 let session_manager = SessionManager::new_default(realm, &inspector);
951 let lifecycle = serve_lifecycle(session_manager.clone());
952
953 assert!(
954 lifecycle
955 .start(&fsession::LifecycleStartRequest {
956 session_url: Some(session_url.to_string()),
957 ..Default::default()
958 })
959 .await
960 .expect("could not call Launch")
961 .is_ok()
962 );
963
964 assert!(lifecycle.restart().await.expect("could not call Restart").is_ok());
965
966 assert_data_tree!(inspector, root: {
967 session_started_at: {
968 "0": {
969 "@time": AnyProperty
970 },
971 "1": {
972 "@time": AnyProperty
973 }
974 }
975 });
976 }
977
978 #[fuchsia::test]
981 async fn test_svc_from_session_before_start() -> Result<(), Error> {
982 let session_url = "session";
983 let svc_path = "foo";
984
985 let (path_sender, mut path_receiver) = mpsc::channel(1);
986
987 let session_exposed_dir_handler = move |directory_request| match directory_request {
988 fio::DirectoryRequest::Open { path, .. } => {
989 let mut path_sender: mpsc::Sender<String> = path_sender.clone();
990 path_sender.try_send(path).unwrap();
991 }
992 _ => panic!("Directory handler received an unexpected request"),
993 };
994
995 let realm = spawn_stream_handler(move |realm_request| {
996 let session_exposed_dir_handler = session_exposed_dir_handler.clone();
997 async move {
998 match realm_request {
999 fcomponent::RealmRequest::DestroyChild { responder, .. } => {
1000 let _ = responder.send(Ok(()));
1001 }
1002 fcomponent::RealmRequest::CreateChild { args, responder, .. } => {
1003 spawn_noop_controller_server(args.controller.unwrap());
1004 let _ = responder.send(Ok(()));
1005 }
1006 fcomponent::RealmRequest::OpenExposedDir { exposed_dir, responder, .. } => {
1007 spawn_directory_server(exposed_dir, session_exposed_dir_handler);
1008 let _ = responder.send(Ok(()));
1009 }
1010 _ => panic!("Realm handler received an unexpected request"),
1011 }
1012 }
1013 });
1014
1015 let inspector = fuchsia_inspect::Inspector::default();
1016 let session_manager = SessionManager::new_default(realm, &inspector);
1017 let lifecycle = serve_lifecycle(session_manager.clone());
1018
1019 let (_client_end, server_end) = fidl::endpoints::create_proxy();
1022
1023 open_session_exposed_dir(session_manager, svc_path, server_end);
1024 lifecycle
1026 .start(&fsession::LifecycleStartRequest {
1027 session_url: Some(session_url.to_string()),
1028 ..Default::default()
1029 })
1030 .await?
1031 .map_err(|err| anyhow!("failed to start: {err:?}"))?;
1032
1033 assert_eq!(path_receiver.next().await.unwrap(), svc_path);
1035
1036 Ok(())
1037 }
1038
1039 #[fuchsia::test]
1042 async fn test_svc_from_session_after_start() -> Result<(), Error> {
1043 let session_url = "session";
1044 let svc_path = "foo";
1045
1046 let (path_sender, mut path_receiver) = mpsc::channel(1);
1047
1048 let session_exposed_dir_handler = move |directory_request| match directory_request {
1049 fio::DirectoryRequest::Open { path, .. } => {
1050 let mut path_sender = path_sender.clone();
1051 path_sender.try_send(path).unwrap();
1052 }
1053 _ => panic!("Directory handler received an unexpected request"),
1054 };
1055
1056 let realm = spawn_stream_handler(move |realm_request| {
1057 let session_exposed_dir_handler = session_exposed_dir_handler.clone();
1058 async move {
1059 match realm_request {
1060 fcomponent::RealmRequest::DestroyChild { responder, .. } => {
1061 let _ = responder.send(Ok(()));
1062 }
1063 fcomponent::RealmRequest::CreateChild { args, responder, .. } => {
1064 spawn_noop_controller_server(args.controller.unwrap());
1065 let _ = responder.send(Ok(()));
1066 }
1067 fcomponent::RealmRequest::OpenExposedDir { exposed_dir, responder, .. } => {
1068 spawn_directory_server(exposed_dir, session_exposed_dir_handler);
1069 let _ = responder.send(Ok(()));
1070 }
1071 _ => panic!("Realm handler received an unexpected request"),
1072 }
1073 }
1074 });
1075
1076 let inspector = fuchsia_inspect::Inspector::default();
1077 let session_manager = SessionManager::new_default(realm, &inspector);
1078 let lifecycle = serve_lifecycle(session_manager.clone());
1079
1080 lifecycle
1081 .start(&fsession::LifecycleStartRequest {
1082 session_url: Some(session_url.to_string()),
1083 ..Default::default()
1084 })
1085 .await?
1086 .map_err(|err| anyhow!("failed to start: {err:?}"))?;
1087
1088 let (_client_end, server_end) = fidl::endpoints::create_proxy();
1091
1092 open_session_exposed_dir(session_manager, svc_path, server_end);
1093
1094 assert_eq!(path_receiver.next().await.unwrap(), svc_path);
1095
1096 Ok(())
1097 }
1098
1099 #[fuchsia::test]
1101 async fn test_stop_resets_debug_state() {
1102 let session_url = "session";
1103
1104 let realm = spawn_stream_handler(move |realm_request| async move {
1105 match realm_request {
1106 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
1107 let _ = responder.send(Ok(()));
1108 }
1109 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
1110 assert_eq!(decl.url.unwrap(), session_url);
1111 spawn_noop_controller_server(args.controller.unwrap());
1112 let _ = responder.send(Ok(()));
1113 }
1114 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
1115 spawn_noop_directory_server(exposed_dir);
1116 let _ = responder.send(Ok(()));
1117 }
1118 _ => panic!("Realm handler received an unexpected request"),
1119 }
1120 });
1121
1122 let inspector = fuchsia_inspect::Inspector::default();
1123 let session_manager = SessionManager::new(realm, &inspector, None, false, true);
1124 let lifecycle = serve_lifecycle(session_manager.clone());
1125
1126 assert!(
1127 lifecycle
1128 .start(&fsession::LifecycleStartRequest {
1129 session_url: Some(session_url.to_string()),
1130 ..Default::default()
1131 })
1132 .await
1133 .is_ok()
1134 );
1135
1136 session_manager.state.debug.set_button_press_state_count(3);
1137 assert_eq!(session_manager.state.debug.get_button_press_state_count(), 3);
1138
1139 assert!(lifecycle.stop().await.is_ok());
1140
1141 assert_eq!(session_manager.state.debug.get_button_press_state_count(), 0);
1142 }
1143}