1use anyhow::{Context, Error};
6use fidl_fuchsia_camera3::{DeviceProxy, WatchDevicesEvent};
7use fuchsia_async as fasync;
8use futures::StreamExt;
9use futures::channel::mpsc::UnboundedSender;
10use futures::stream::{LocalBoxStream, SelectAll};
11use settings_camera::{
12 CameraWatcherEvent, connect_to_camera, connect_to_cameras_with_watcher, watch_camera_changes,
13};
14use settings_common::inspect::event::ExternalEventPublisher;
15use settings_common::service_context::ServiceContext;
16use settings_common::trace;
17use std::collections::BTreeMap;
18use std::rc::Rc;
19
20pub struct CameraWatcherAgent {
21 muted_txs: Vec<UnboundedSender<CameraWatcherEvent>>,
24 external_publisher: ExternalEventPublisher,
25}
26
27impl CameraWatcherAgent {
28 pub fn new(
29 muted_txs: Vec<UnboundedSender<CameraWatcherEvent>>,
30 external_publisher: ExternalEventPublisher,
31 ) -> Self {
32 Self { muted_txs, external_publisher }
33 }
34
35 pub async fn spawn(self, service_context: &ServiceContext) -> Result<(), Error> {
36 let (camera_watcher_proxy, cameras) =
39 connect_to_cameras_with_watcher(service_context, self.external_publisher.clone())
40 .await
41 .context("connecting to camera")?;
42 let camera_watcher_proxy = Rc::new(camera_watcher_proxy);
43 let mut event_handler =
44 EventHandler::new(self.muted_txs.clone(), cameras.iter().map(|(id, _)| *id));
45 fasync::Task::local(async move {
46 let id = fuchsia_trace::Id::new();
47 trace!(id, "camera_watcher_agent_handler");
52 let mut mute_streams: SelectAll<_> = cameras
53 .into_iter()
54 .map(|(camera_id, client)| watch_mute_state(camera_id, 0, client))
55 .collect();
56
57 let mut device_changes =
60 futures::stream::unfold(Rc::clone(&camera_watcher_proxy), |proxy| async move {
61 match watch_camera_changes(&proxy).await {
62 Ok(events) => Some((events, proxy)),
63 Err(e) => {
64 log::warn!("Stopped watching for camera changes: {e:?}");
65 None
66 }
67 }
68 })
69 .boxed_local()
70 .fuse();
71
72 loop {
73 futures::select! {
74 event = mute_streams.next() => {
75 let Some((camera_id, connection_id, event)) = event else {
76 continue;
77 };
78 trace!(id, "event");
79 match event {
80 CameraEvent::MuteState(sw_muted) => {
81 event_handler.handle_event(camera_id, connection_id, sw_muted);
82 }
83 CameraEvent::Disconnected => {
84 event_handler.handle_disconnect(camera_id, connection_id);
85 }
86 }
87 }
88 events = device_changes.next() => {
89 let Some(events) = events else {
90 continue;
91 };
92 for event in events {
93 match event {
94 WatchDevicesEvent::Added(camera_id) => {
95 match connect_to_camera(&camera_watcher_proxy, camera_id) {
96 Ok(client) => {
97 let connection_id =
98 event_handler.handle_added(camera_id);
99 mute_streams.push(watch_mute_state(
100 camera_id,
101 connection_id,
102 client,
103 ));
104 }
105 Err(e) => {
106 log::warn!(
107 "Failed to connect to added camera {camera_id}: \
108 {e:?}"
109 );
110 }
111 }
112 }
113 WatchDevicesEvent::Removed(camera_id) => {
114 event_handler.handle_removed(camera_id);
115 }
116 WatchDevicesEvent::Existing(_) => {}
117 }
118 }
119 }
120 complete => break,
121 }
122 }
123 log::warn!("All camera mute state streams have closed");
124 })
125 .detach();
126
127 Ok(())
128 }
129}
130
131fn watch_mute_state(
134 camera_id: u64,
135 connection_id: u64,
136 client: DeviceProxy,
137) -> LocalBoxStream<'static, (u64, u64, CameraEvent)> {
138 futures::stream::unfold(Some(client), move |client_opt| async move {
139 let client = client_opt?;
140 match client.watch_mute_state().await {
141 Ok((sw_muted, _hw_muted)) => {
142 Some(((camera_id, connection_id, CameraEvent::MuteState(sw_muted)), Some(client)))
143 }
144 Err(e) => {
145 log::warn!("Failed to watch mute state for camera {camera_id}: {e:?}");
146 Some(((camera_id, connection_id, CameraEvent::Disconnected), None))
147 }
148 }
149 })
150 .boxed_local()
151}
152
153enum CameraEvent {
154 MuteState(bool),
155 Disconnected,
156}
157
158#[derive(Clone, Copy, PartialEq, Eq)]
159enum CameraState {
160 Unknown,
161 Active(bool),
162}
163
164struct CameraEntry {
165 connection_id: u64,
166 state: CameraState,
167}
168
169struct EventHandler {
170 muted_txs: Vec<UnboundedSender<CameraWatcherEvent>>,
171 sw_muted: Option<bool>,
172 camera_states: BTreeMap<u64, CameraEntry>,
173 next_connection_id: u64,
174}
175
176impl EventHandler {
177 fn new(
178 muted_txs: Vec<UnboundedSender<CameraWatcherEvent>>,
179 camera_ids: impl IntoIterator<Item = u64>,
180 ) -> Self {
181 Self {
182 muted_txs,
183 sw_muted: None,
184 camera_states: camera_ids
185 .into_iter()
186 .map(|id| (id, CameraEntry { connection_id: 0, state: CameraState::Unknown }))
187 .collect(),
188 next_connection_id: 1,
189 }
190 }
191
192 #[cfg(test)]
193 fn is_watching(&self, camera_id: u64) -> bool {
194 self.camera_states.contains_key(&camera_id)
195 }
196
197 fn handle_event(&mut self, camera_id: u64, connection_id: u64, sw_muted: bool) {
198 let Some(entry) = self.camera_states.get_mut(&camera_id) else {
199 return;
200 };
201 if entry.connection_id != connection_id {
202 return;
203 }
204 entry.state = CameraState::Active(sw_muted);
205 self.evaluate_and_send();
206 }
207
208 fn handle_disconnect(&mut self, camera_id: u64, connection_id: u64) {
209 if self.camera_states.get(&camera_id).is_some_and(|e| e.connection_id == connection_id) {
210 let _ = self.camera_states.remove(&camera_id);
211 self.evaluate_and_send();
212 }
213 }
214
215 fn handle_removed(&mut self, camera_id: u64) {
216 if self.camera_states.remove(&camera_id).is_some() {
217 self.evaluate_and_send();
218 }
219 }
220
221 fn handle_added(&mut self, camera_id: u64) -> u64 {
226 let connection_id = self.next_connection_id;
227 self.next_connection_id += 1;
228 let _ = self
229 .camera_states
230 .insert(camera_id, CameraEntry { connection_id, state: CameraState::Unknown });
231 self.sw_muted = None;
232 self.send_event(CameraWatcherEvent::DeviceAdded);
233 connection_id
234 }
235
236 fn evaluate_and_send(&mut self) {
237 let mut has_connected = false;
238 let mut has_unknown = false;
239 let mut any_unmuted = false;
240
241 for entry in self.camera_states.values() {
242 match entry.state {
243 CameraState::Active(false) => {
244 has_connected = true;
245 any_unmuted = true;
246 }
247 CameraState::Active(true) => {
248 has_connected = true;
249 }
250 CameraState::Unknown => {
251 has_connected = true;
252 has_unknown = true;
253 }
254 }
255 }
256
257 let resolved_muted = if !has_connected {
258 None
259 } else if any_unmuted {
260 Some(false)
261 } else if !has_unknown {
262 Some(true)
263 } else {
264 None
265 };
266
267 if let Some(all_muted) = resolved_muted
268 && self.sw_muted != Some(all_muted)
269 {
270 self.sw_muted = Some(all_muted);
271 self.send_event(CameraWatcherEvent::SoftwareMuted(all_muted));
272 }
273 }
274
275 fn send_event(&self, event: CameraWatcherEvent) {
276 for muted_tx in &self.muted_txs {
277 let _ = muted_tx.unbounded_send(event);
278 }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285 use fuchsia_async::{MonotonicInstant, TestExecutor};
286 use futures::channel::mpsc;
287 use futures::lock::Mutex;
288 use settings_camera::CAMERA_WATCHER_TIMEOUT;
289 use settings_test_common::fakes::camera3_service::Camera3Service;
290 use settings_test_common::fakes::service::ServiceRegistry;
291 use settings_test_common::helpers::move_executor_forward_and_get;
292 use std::rc::Rc;
293
294 #[fuchsia::test(allow_stalls = false)]
296 async fn when_camera3_inaccessible_returns_err() {
297 let (event_tx, _event_rx) = mpsc::unbounded();
298 let external_publisher = ExternalEventPublisher::new(event_tx);
299
300 let agent = CameraWatcherAgent { muted_txs: vec![], external_publisher };
301 let service_context =
302 ServiceContext::new(Some(ServiceRegistry::serve(ServiceRegistry::create())));
303
304 let result = agent.spawn(&service_context).await;
306 assert!(result.is_err());
307 }
308
309 #[fuchsia::test(allow_stalls = false)]
311 async fn event_handler_proxies_event() {
312 let (tx1, mut rx1) = mpsc::unbounded();
313 let (tx2, mut rx2) = mpsc::unbounded();
314
315 let mut event_handler = EventHandler::new(vec![tx1.clone(), tx2.clone()], 0..1);
316
317 event_handler.handle_event(0, 0, true);
319
320 let mut channel_received = 0;
321
322 let mut next_rx1 = rx1.next();
323 let mut next_rx2 = rx2.next();
324
325 loop {
328 futures::select! {
329 event = next_rx1 => {
330 let Some(muted) = event else {
331 continue;
332 };
333 assert_eq!(muted, CameraWatcherEvent::SoftwareMuted(true));
334 tx1.close_channel();
336 channel_received += 1;
337 }
338 event = next_rx2 => {
339 let Some(muted) = event else {
340 continue;
341 };
342 assert_eq!(muted, CameraWatcherEvent::SoftwareMuted(true));
343 tx2.close_channel();
345 channel_received += 1;
346 }
347 complete => break,
348 }
349 }
350
351 assert_eq!(channel_received, 2);
352 }
353
354 #[fuchsia::test(allow_stalls = false)]
355 async fn event_handler_aggregates_multiple_cameras() {
356 let (tx, mut rx) = mpsc::unbounded();
357 let mut event_handler = EventHandler::new(vec![tx], 0..2);
358
359 event_handler.handle_event(0, 0, true);
361 assert!(rx.try_recv().is_err());
362
363 event_handler.handle_event(1, 0, true);
365 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
366
367 event_handler.handle_event(0, 0, true);
369 assert!(rx.try_recv().is_err());
370
371 event_handler.handle_event(1, 0, false);
373 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(false)));
374
375 event_handler.handle_event(0, 0, false);
377 assert!(rx.try_recv().is_err());
378 }
379
380 #[fuchsia::test(allow_stalls = false)]
381 async fn event_handler_forwards_first_unmuted_event_and_dedups() {
382 let (tx, mut rx) = mpsc::unbounded();
383 let mut event_handler = EventHandler::new(vec![tx], 0..1);
384
385 event_handler.handle_event(0, 0, false);
387 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(false)));
388
389 event_handler.handle_event(0, 0, false);
391 assert!(rx.try_recv().is_err());
392
393 event_handler.handle_event(0, 0, true);
395 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
396 }
397
398 #[fuchsia::test(allow_stalls = false)]
399 async fn event_handler_handles_camera_disconnect() {
400 let (tx, mut rx) = mpsc::unbounded();
401 let mut event_handler = EventHandler::new(vec![tx], 0..2);
402
403 event_handler.handle_event(0, 0, false);
405 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(false)));
406 event_handler.handle_event(1, 0, false);
407 assert!(rx.try_recv().is_err());
408
409 event_handler.handle_disconnect(0, 0);
411 assert!(rx.try_recv().is_err());
412
413 event_handler.handle_event(1, 0, true);
414 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
415
416 event_handler.handle_disconnect(1, 0);
418 assert!(rx.try_recv().is_err());
419 }
420
421 #[fuchsia::test(allow_stalls = false)]
422 async fn event_handler_reports_added_camera() {
423 let (tx, mut rx) = mpsc::unbounded();
424 let mut event_handler = EventHandler::new(vec![tx], 0..1);
425 event_handler.handle_event(0, 0, true);
426 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
427
428 let conn1 = event_handler.handle_added(1);
430 assert_eq!(rx.next().await, Some(CameraWatcherEvent::DeviceAdded));
431 assert!(rx.try_recv().is_err());
432
433 event_handler.handle_event(1, conn1, true);
435 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
436 event_handler.handle_event(1, conn1, true);
437 assert!(rx.try_recv().is_err());
438
439 event_handler.handle_event(1, conn1, false);
442 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(false)));
443 assert!(event_handler.is_watching(1));
444 event_handler.handle_disconnect(1, conn1);
445 assert!(!event_handler.is_watching(1));
446 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
447 event_handler.handle_event(1, conn1, false);
448 assert!(rx.try_recv().is_err());
449
450 event_handler.handle_removed(0);
453 let conn0_restarted = event_handler.handle_added(0);
454 assert_eq!(rx.next().await, Some(CameraWatcherEvent::DeviceAdded));
455 event_handler.handle_disconnect(0, 0);
456 assert!(event_handler.is_watching(0));
457 event_handler.handle_event(0, conn0_restarted, false);
458 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(false)));
459
460 let conn0_restarted_again = event_handler.handle_added(0);
463 assert_eq!(rx.next().await, Some(CameraWatcherEvent::DeviceAdded));
464 event_handler.handle_disconnect(0, conn0_restarted);
465 assert!(event_handler.is_watching(0));
466 event_handler.handle_event(0, conn0_restarted_again, true);
467 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
468 }
469
470 struct FakeServices {
471 camera3_service: Rc<Mutex<Camera3Service>>,
472 }
473
474 #[derive(PartialEq)]
475 enum CameraDevice {
476 With,
477 Without,
478 }
479
480 #[derive(PartialEq)]
481 enum DelayCamera {
482 Yes,
483 No,
484 }
485
486 async fn create_services(
491 has_camera_device: CameraDevice,
492 delay_camera_device: DelayCamera,
493 ) -> (Rc<Mutex<ServiceRegistry>>, FakeServices) {
494 let service_registry = ServiceRegistry::create();
495
496 let camera3_service_handle =
497 Rc::new(Mutex::new(if DelayCamera::Yes == delay_camera_device {
498 Camera3Service::new_delayed_devices(delay_camera_device == DelayCamera::Yes)
499 } else {
500 Camera3Service::new(has_camera_device == CameraDevice::With)
501 }));
502 service_registry.lock().await.register_service(camera3_service_handle.clone());
503
504 (service_registry, FakeServices { camera3_service: camera3_service_handle })
505 }
506
507 #[fuchsia::test(allow_stalls = false)]
508 async fn test_camera_agent_proxy() {
509 let (service_registry, fake_services) =
511 create_services(CameraDevice::With, DelayCamera::No).await;
512
513 let expected_camera_state = true;
514 fake_services.camera3_service.lock().await.set_camera_sw_muted(expected_camera_state);
515 let (event_tx, _event_rx) = mpsc::unbounded();
516 let external_publisher = ExternalEventPublisher::new(event_tx);
517 let (tx, mut rx) = mpsc::unbounded();
518
519 let agent = CameraWatcherAgent::new(vec![tx], external_publisher);
520 let service_context = ServiceContext::new(Some(ServiceRegistry::serve(service_registry)));
521 let res = agent.spawn(&service_context).await;
522
523 assert!(matches!(res, Ok(())), "agent spawn failed");
525
526 let res = rx.next().await;
528 assert_eq!(res, Some(CameraWatcherEvent::SoftwareMuted(true)));
529 }
530
531 #[fuchsia::test]
534 fn test_camera_devices_watcher_timeout() {
535 let mut executor = TestExecutor::new_with_fake_time();
538 executor.set_fake_time(MonotonicInstant::from_nanos(0));
539
540 let services_future = create_services(CameraDevice::Without, DelayCamera::No);
542 let (service_registry, fake_services) = move_executor_forward_and_get(
543 &mut executor,
544 services_future,
545 "Could not create services",
546 );
547
548 let camera_service_future = fake_services.camera3_service.lock();
550 move_executor_forward_and_get(
551 &mut executor,
552 camera_service_future,
553 "Unable to get camera service",
554 )
555 .set_camera_sw_muted(true);
556
557 let (event_tx, _event_rx) = mpsc::unbounded();
558 let external_publisher = ExternalEventPublisher::new(event_tx);
559 let agent = CameraWatcherAgent::new(vec![], external_publisher);
560 let service_context = ServiceContext::new(Some(ServiceRegistry::serve(service_registry)));
561
562 let spawn_future = agent.spawn(&service_context);
564
565 executor.set_fake_time(MonotonicInstant::from_nanos(CAMERA_WATCHER_TIMEOUT + 1));
567
568 let res =
569 move_executor_forward_and_get(&mut executor, spawn_future, "Could not complete spawn");
570
571 assert!(res.is_err(), "spawn did not hit timeout");
573 }
574
575 #[fuchsia::test]
578 async fn test_camera_agent_delayed_devices() {
579 let (service_registry, fake_services) =
580 create_services(CameraDevice::Without, DelayCamera::Yes).await;
581
582 let expected_camera_state = true;
583 fake_services.camera3_service.lock().await.set_camera_sw_muted(expected_camera_state);
584 let (tx, mut rx) = mpsc::unbounded();
585 let (event_tx, _event_rx) = mpsc::unbounded();
586 let external_publisher = ExternalEventPublisher::new(event_tx);
587 let agent = CameraWatcherAgent::new(vec![tx], external_publisher);
588 let service_context = ServiceContext::new(Some(ServiceRegistry::serve(service_registry)));
589 let res = agent.spawn(&service_context).await;
590
591 assert!(matches!(res, Ok(())), "spawn failed");
593 let muted = rx.next().await;
594 assert_eq!(muted, Some(CameraWatcherEvent::SoftwareMuted(true)));
595 }
596
597 #[fuchsia::test(allow_stalls = false)]
599 async fn test_camera_agent_watches_added_camera() {
600 let (service_registry, fake_services) =
601 create_services(CameraDevice::With, DelayCamera::No).await;
602 fake_services.camera3_service.lock().await.set_camera_sw_muted(true);
603 let (event_tx, _event_rx) = mpsc::unbounded();
604 let external_publisher = ExternalEventPublisher::new(event_tx);
605 let (tx, mut rx) = mpsc::unbounded();
606
607 let agent = CameraWatcherAgent::new(vec![tx], external_publisher);
608 let service_context = ServiceContext::new(Some(ServiceRegistry::serve(service_registry)));
609 let res = agent.spawn(&service_context).await;
610 assert!(matches!(res, Ok(())), "agent spawn failed");
611 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
612
613 fake_services.camera3_service.lock().await.add_camera_device(2, false);
615 assert_eq!(rx.next().await, Some(CameraWatcherEvent::DeviceAdded));
616 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(false)));
617
618 fake_services.camera3_service.lock().await.remove_camera_device(2);
620 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
621
622 fake_services.camera3_service.lock().await.remove_camera_device(1);
625 fake_services.camera3_service.lock().await.add_camera_device(1, false);
626 assert_eq!(rx.next().await, Some(CameraWatcherEvent::DeviceAdded));
627 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(false)));
628
629 fake_services.camera3_service.lock().await.set_camera_sw_muted(true);
630 assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
631 }
632}