Skip to main content

settings_camera_watcher_agent/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    /// Sends an event whenever the aggregate camera muted state changes, or when a camera is
22    /// connected after startup.
23    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        // Multiple cameras are not yet supported individually, and individual camera mute/unmute
37        // is not exposed, so all reported cameras are watched and aggregated together.
38        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            // Here we don't care about hw_muted state because the input service would pick
48            // up mute changes directly from the switch. We care about sw changes because
49            // other clients of the camera3 service could change the sw mute state but not
50            // notify the settings service.
51            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            // Cameras that are added after startup, for example because they were hot-plugged
58            // or their device restarted, are watched as well.
59            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
131/// Returns a stream of mute state events for the camera with the given id and connection
132/// generation. The stream ends after reporting that the camera disconnected.
133fn 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    /// Starts tracking a camera that was connected after startup and returns its connection
222    /// generation. Recipients are told about the new camera first, and the next resolved aggregate
223    /// state is always sent afterwards, even if it matches the last state sent, so that recipients
224    /// can apply their state to the new camera.
225    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    // Tests that the agent cannot start without a camera3 service.
295    #[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        // Try to initiate the Service lifespan without providing the camera3 fidl interface.
305        let result = agent.spawn(&service_context).await;
306        assert!(result.is_err());
307    }
308
309    // Tests that events can be sent to the intended recipients.
310    #[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        // Send the events.
318        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 over the select so we can handle the messages as they come in. When all messages
326        // have been handled, the senders a closed to ensure the complete case can be hit below.
327        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                    // Close channel so we can exit select loop.
335                    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                    // Close channel so we can exit select loop.
344                    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        // Muting only camera 0 should not emit a muted event because camera 1 is still unmuted.
360        event_handler.handle_event(0, 0, true);
361        assert!(rx.try_recv().is_err());
362
363        // Muting camera 1 makes all cameras muted, emitting `true` once.
364        event_handler.handle_event(1, 0, true);
365        assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
366
367        // Duplicate `true` on camera 0 does not emit another event.
368        event_handler.handle_event(0, 0, true);
369        assert!(rx.try_recv().is_err());
370
371        // Unmuting camera 1 immediately emits `false`.
372        event_handler.handle_event(1, 0, false);
373        assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(false)));
374
375        // Unmuting camera 0 afterwards does not emit a duplicate `false`.
376        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        // The first resolved state is forwarded even when it is `false`.
386        event_handler.handle_event(0, 0, false);
387        assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(false)));
388
389        // An identical follow-up event is not forwarded.
390        event_handler.handle_event(0, 0, false);
391        assert!(rx.try_recv().is_err());
392
393        // A change is forwarded.
394        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        // Initial state: both cameras unmuted (`false`), emitting initial `false`.
404        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        // Disconnecting camera 0 while unmuted does not prevent camera 1 from muting the aggregate.
410        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        // Disconnecting the last remaining camera does not emit a state change.
417        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        // An added camera is reported first, and the aggregate is not resolved until it reports.
429        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        // The next resolved state is sent even though it matches the last state sent.
434        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        // A removed camera no longer contributes to the aggregate, and a late event from its
440        // stream cannot add it back.
441        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        // If a camera with the same id is removed and re-added before the old connection's stream
451        // ends, the old connection's disconnect does not remove the new connection.
452        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        // If a camera with the same id is added before its old connection's stream reports a
461        // disconnect, the new connection replaces the old one and the old disconnect is ignored.
462        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    // Returns a registry and input related services with which it is populated. If delay_camera_device
487    // is true, then has_camera_device is ignored. It sends back the camera device after a delay.
488    // Otherwise, if has_camera_device is true, it will immediately respond with the populated camera
489    // device. If has_camera_device is false, it will immediately respond with an empty device list.
490    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        // Setup the fake services.
510        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        // Validate that the setup is complete.
524        assert!(matches!(res, Ok(())), "agent spawn failed");
525
526        // Track the events to make sure they came in.
527        let res = rx.next().await;
528        assert_eq!(res, Some(CameraWatcherEvent::SoftwareMuted(true)));
529    }
530
531    // Tests that an error is returned if the camera watcher cannot find a camera device
532    // after the timeout is reached.
533    #[fuchsia::test]
534    fn test_camera_devices_watcher_timeout() {
535        // Custom executor for this test so that we can advance the clock arbitrarily and verify the
536        // state of the executor at any given point.
537        let mut executor = TestExecutor::new_with_fake_time();
538        executor.set_fake_time(MonotonicInstant::from_nanos(0));
539
540        // Setup the fake services.
541        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        // Mute the camera via software.
549        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        // Create and send the invocation with faked services.
563        let spawn_future = agent.spawn(&service_context);
564
565        // Advance time past the timeout.
566        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        // Validate that the setup is complete.
572        assert!(res.is_err(), "spawn did not hit timeout");
573    }
574
575    // Tests that the camera agent is able to handle an empty device list first, and then
576    // a second update with the device in it that comes in before the timeout.
577    #[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        // Validate that the setup is complete.
592        assert!(matches!(res, Ok(())), "spawn failed");
593        let muted = rx.next().await;
594        assert_eq!(muted, Some(CameraWatcherEvent::SoftwareMuted(true)));
595    }
596
597    // Tests that a camera added after startup is reported and watched.
598    #[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        // A camera is added unmuted. It is reported, and the aggregate mute state becomes false.
614        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        // Removing the unmuted camera restores the aggregate mute state from the remaining camera.
619        fake_services.camera3_service.lock().await.remove_camera_device(2);
620        assert_eq!(rx.next().await, Some(CameraWatcherEvent::SoftwareMuted(true)));
621
622        // A camera that restarts with the same id is watched on its new connection, and mute
623        // changes on it keep being reported.
624        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}