settings/input/
input_controller.rs

1// Copyright 2020 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 crate::base::{SettingInfo, SettingType};
6use crate::config::default_settings::DefaultSetting;
7use crate::handler::base::Request;
8use crate::handler::setting_handler::persist::{controller as data_controller, ClientProxy};
9use crate::handler::setting_handler::{
10    controller, ControllerError, ControllerStateResult, IntoHandlerResult, SettingHandlerResult,
11    State,
12};
13use crate::input::common::connect_to_camera;
14use crate::input::input_device_configuration::InputConfiguration;
15use crate::input::types::{
16    DeviceState, DeviceStateSource, InputDevice, InputDeviceType, InputInfo, InputInfoSources,
17    InputState, Microphone,
18};
19use crate::input::MediaButtons;
20use settings_storage::device_storage::{DeviceStorage, DeviceStorageCompatible};
21use settings_storage::storage_factory::{NoneT, StorageAccess};
22
23use anyhow::Error;
24use async_trait::async_trait;
25use fuchsia_trace as ftrace;
26use futures::lock::Mutex;
27use serde::{Deserialize, Serialize};
28use std::rc::Rc;
29
30pub(crate) const DEFAULT_CAMERA_NAME: &str = "camera";
31pub(crate) const DEFAULT_MIC_NAME: &str = "microphone";
32
33impl DeviceStorageCompatible for InputInfoSources {
34    type Loader = NoneT;
35    const KEY: &'static str = "input_info";
36
37    fn try_deserialize_from(value: &str) -> Result<Self, Error> {
38        Self::extract(value).or_else(|e| {
39            log::info!("Failed to deserialize InputInfoSources. Falling back to V2: {e:?}");
40            InputInfoSourcesV2::try_deserialize_from(value).map(Self::from)
41        })
42    }
43}
44
45impl From<InputInfoSourcesV2> for InputInfoSources {
46    fn from(v2: InputInfoSourcesV2) -> Self {
47        let mut input_state = v2.input_device_state;
48
49        // Convert the old states into an input device.
50        input_state.set_source_state(
51            InputDeviceType::MICROPHONE,
52            DEFAULT_MIC_NAME.to_string(),
53            DeviceStateSource::HARDWARE,
54            if v2.hw_microphone.muted { DeviceState::MUTED } else { DeviceState::AVAILABLE },
55        );
56        input_state.set_source_state(
57            InputDeviceType::MICROPHONE,
58            DEFAULT_MIC_NAME.to_string(),
59            DeviceStateSource::SOFTWARE,
60            if v2.sw_microphone.muted { DeviceState::MUTED } else { DeviceState::AVAILABLE },
61        );
62
63        InputInfoSources { input_device_state: input_state }
64    }
65}
66
67impl From<InputInfoSources> for SettingInfo {
68    fn from(info: InputInfoSources) -> SettingInfo {
69        SettingInfo::Input(info.into())
70    }
71}
72
73impl From<InputInfoSources> for InputInfo {
74    fn from(info: InputInfoSources) -> InputInfo {
75        InputInfo { input_device_state: info.input_device_state }
76    }
77}
78
79impl From<InputInfo> for SettingInfo {
80    fn from(info: InputInfo) -> SettingInfo {
81        SettingInfo::Input(info)
82    }
83}
84
85#[derive(PartialEq, Default, Debug, Clone, Serialize, Deserialize)]
86pub struct InputInfoSourcesV2 {
87    hw_microphone: Microphone,
88    sw_microphone: Microphone,
89    input_device_state: InputState,
90}
91
92impl DeviceStorageCompatible for InputInfoSourcesV2 {
93    type Loader = NoneT;
94    const KEY: &'static str = "input_info_sources_v2";
95
96    fn try_deserialize_from(value: &str) -> Result<Self, Error> {
97        Self::extract(value).or_else(|e| {
98            log::info!("Failed to deserialize InputInfoSourcesV2. Falling back to V1: {e:?}");
99            InputInfoSourcesV1::try_deserialize_from(value).map(Self::from)
100        })
101    }
102}
103
104impl From<InputInfoSourcesV1> for InputInfoSourcesV2 {
105    fn from(v1: InputInfoSourcesV1) -> Self {
106        InputInfoSourcesV2 {
107            hw_microphone: v1.hw_microphone,
108            sw_microphone: v1.sw_microphone,
109            input_device_state: InputState::new(),
110        }
111    }
112}
113
114#[derive(PartialEq, Default, Debug, Clone, Copy, Serialize, Deserialize)]
115pub struct InputInfoSourcesV1 {
116    pub hw_microphone: Microphone,
117    pub sw_microphone: Microphone,
118}
119
120impl DeviceStorageCompatible for InputInfoSourcesV1 {
121    type Loader = NoneT;
122    const KEY: &'static str = "input_info_sources_v1";
123}
124
125type InputControllerInnerHandle = Rc<Mutex<InputControllerInner>>;
126
127/// Inner struct for the InputController.
128///
129/// Allows the controller to use a lock on its contents.
130struct InputControllerInner {
131    /// Client to communicate with persistent store and notify on.
132    client: ClientProxy,
133
134    /// Local tracking of the input device states.
135    input_device_state: InputState,
136
137    /// Configuration for this device.
138    input_device_config: InputConfiguration,
139}
140
141impl InputControllerInner {
142    // Wrapper around client.read() that fills in the config
143    // as the default value if the read value is empty. It may be empty
144    // after a migration from a previous InputInfoSources version
145    // or on pave.
146    async fn get_stored_info(&self) -> InputInfo {
147        let mut input_info = self.client.read_setting::<InputInfo>(ftrace::Id::new()).await;
148        if input_info.input_device_state.is_empty() {
149            input_info.input_device_state = self.input_device_config.clone().into();
150        }
151        input_info
152    }
153
154    /// Gets the input state.
155    async fn get_info(&mut self) -> Result<InputInfo, ControllerError> {
156        Ok(InputInfo { input_device_state: self.input_device_state.clone() })
157    }
158
159    /// Restores the input state.
160    async fn restore(&mut self) -> ControllerStateResult {
161        let input_info = self.get_stored_info().await;
162        self.input_device_state = input_info.input_device_state;
163
164        if self.input_device_config.devices.iter().any(|d| d.device_type == InputDeviceType::CAMERA)
165        {
166            match self.get_cam_sw_state() {
167                Ok(state) => {
168                    // Camera setup failure should not prevent start of service. This also allows
169                    // clients to see that the camera may not be usable.
170                    if let Err(e) = self.push_cam_sw_state(state).await {
171                        log::error!("Unable to restore camera state: {e:?}");
172                        self.set_cam_err_state(state);
173                    }
174                }
175                Err(e) => {
176                    log::error!("Unable to load cam sw state: {e:?}");
177                    self.set_cam_err_state(DeviceState::ERROR);
178                }
179            }
180        }
181        Ok(())
182    }
183
184    async fn set_sw_camera_mute(&mut self, disabled: bool, name: String) -> SettingHandlerResult {
185        let mut input_info = self.get_stored_info().await;
186        input_info.input_device_state.set_source_state(
187            InputDeviceType::CAMERA,
188            name.clone(),
189            DeviceStateSource::SOFTWARE,
190            if disabled { DeviceState::MUTED } else { DeviceState::AVAILABLE },
191        );
192
193        self.input_device_state.set_source_state(
194            InputDeviceType::CAMERA,
195            name.clone(),
196            DeviceStateSource::SOFTWARE,
197            if disabled { DeviceState::MUTED } else { DeviceState::AVAILABLE },
198        );
199        let id = ftrace::Id::new();
200        self.client.write_setting(input_info.into(), id).await.into_handler_result()
201    }
202
203    /// Sets the hardware mic/cam state from the muted states in `media_buttons`.
204    async fn set_hw_media_buttons_state(
205        &mut self,
206        media_buttons: MediaButtons,
207    ) -> SettingHandlerResult {
208        let mut states_to_process = Vec::new();
209        if let Some(mic_mute) = media_buttons.mic_mute {
210            states_to_process.push((InputDeviceType::MICROPHONE, mic_mute));
211        }
212        if let Some(camera_disable) = media_buttons.camera_disable {
213            states_to_process.push((InputDeviceType::CAMERA, camera_disable));
214        }
215
216        let mut input_info = self.get_stored_info().await;
217
218        for (device_type, muted) in states_to_process.into_iter() {
219            // Fetch current state.
220            let hw_state_res = input_info.input_device_state.get_source_state(
221                device_type,
222                device_type.to_string(),
223                DeviceStateSource::HARDWARE,
224            );
225
226            let mut hw_state = hw_state_res.map_err(|err| {
227                ControllerError::UnexpectedError(
228                    format!("Could not fetch current hw mute state: {err:?}").into(),
229                )
230            })?;
231
232            if muted {
233                // Unset available and set muted.
234                hw_state &= !DeviceState::AVAILABLE;
235                hw_state |= DeviceState::MUTED;
236            } else {
237                // Set available and unset muted.
238                hw_state |= DeviceState::AVAILABLE;
239                hw_state &= !DeviceState::MUTED;
240            }
241
242            // Set the updated state.
243            input_info.input_device_state.set_source_state(
244                device_type,
245                device_type.to_string(),
246                DeviceStateSource::HARDWARE,
247                hw_state,
248            );
249            self.input_device_state.set_source_state(
250                device_type,
251                device_type.to_string(),
252                DeviceStateSource::HARDWARE,
253                hw_state,
254            );
255        }
256
257        // Store the newly set value.
258        let id = ftrace::Id::new();
259        self.client.write_setting(input_info.into(), id).await.into_handler_result()
260    }
261
262    /// Sets state for the given input devices.
263    async fn set_input_states(
264        &mut self,
265        input_devices: Vec<InputDevice>,
266        source: DeviceStateSource,
267    ) -> SettingHandlerResult {
268        let mut input_info = self.get_stored_info().await;
269        let device_types = input_info.input_device_state.device_types();
270
271        let cam_state = self.get_cam_sw_state().ok();
272
273        for input_device in input_devices.iter() {
274            if !device_types.contains(&input_device.device_type) {
275                return Err(ControllerError::UnsupportedError(SettingType::Input));
276            }
277            input_info.input_device_state.insert_device(input_device.clone(), source);
278            self.input_device_state.insert_device(input_device.clone(), source);
279        }
280
281        // If the device has a camera, it should successfully get the sw state, and
282        // push the state if it has changed. If the device does not have a camera,
283        // it should be None both here and above, and thus not detect a change.
284        let modified_cam_state = self.get_cam_sw_state().ok();
285        if cam_state != modified_cam_state {
286            if let Some(state) = modified_cam_state {
287                self.push_cam_sw_state(state).await?;
288            }
289        }
290
291        // Store the newly set value.
292        let id = ftrace::Id::new();
293        self.client.write_setting(input_info.into(), id).await.into_handler_result()
294    }
295
296    #[allow(clippy::result_large_err)] // TODO(https://fxbug.dev/42069089)
297    /// Pulls the current software state of the camera from the device state.
298    fn get_cam_sw_state(&self) -> Result<DeviceState, ControllerError> {
299        self.input_device_state
300            .get_source_state(
301                InputDeviceType::CAMERA,
302                DEFAULT_CAMERA_NAME.to_string(),
303                DeviceStateSource::SOFTWARE,
304            )
305            .map_err(|_| {
306                ControllerError::UnexpectedError("Could not find camera software state".into())
307            })
308    }
309
310    /// Set the camera state into an error condition.
311    fn set_cam_err_state(&mut self, mut state: DeviceState) {
312        state.set(DeviceState::ERROR, true);
313        self.input_device_state.set_source_state(
314            InputDeviceType::CAMERA,
315            DEFAULT_CAMERA_NAME.to_string(),
316            DeviceStateSource::SOFTWARE,
317            state,
318        )
319    }
320
321    /// Forwards the given software state to the camera3 api. Will first establish
322    /// a connection to the camera3.DeviceWatcher api. This function should only be called
323    /// when there is a camera included in the config. The config is used to populate the
324    /// stored input_info, so the input_info's input_device_state can be checked whether its
325    /// device_types contains Camera prior to calling this function.
326    async fn push_cam_sw_state(&mut self, cam_state: DeviceState) -> Result<(), ControllerError> {
327        let is_muted = cam_state.has_state(DeviceState::MUTED);
328
329        // Start up a connection to the camera device watcher and connect to the
330        // camera proxy using the id that is returned. The connection will drop out
331        // of scope after the mute state is sent.
332        let camera_proxy =
333            connect_to_camera(self.client.get_service_context()).await.map_err(|e| {
334                ControllerError::UnexpectedError(
335                    format!("Could not connect to camera device: {e:?}").into(),
336                )
337            })?;
338
339        camera_proxy.set_software_mute_state(is_muted).await.map_err(|e| {
340            ControllerError::ExternalFailure(
341                SettingType::Input,
342                "fuchsia.camera3.Device".into(),
343                "SetSoftwareMuteState".into(),
344                format!("{e:?}").into(),
345            )
346        })
347    }
348}
349
350pub struct InputController {
351    /// Handle so that a lock can be used in the Handle trait implementation.
352    inner: InputControllerInnerHandle,
353}
354
355impl StorageAccess for InputController {
356    type Storage = DeviceStorage;
357    type Data = InputInfoSources;
358    const STORAGE_KEY: &'static str = InputInfoSources::KEY;
359}
360
361impl InputController {
362    /// Alternate constructor that allows specifying a configuration.
363    pub(crate) fn create_with_config(
364        client: ClientProxy,
365        input_device_config: InputConfiguration,
366    ) -> Result<Self, ControllerError> {
367        Ok(Self {
368            inner: Rc::new(Mutex::new(InputControllerInner {
369                client,
370                input_device_state: InputState::new(),
371                input_device_config,
372            })),
373        })
374    }
375
376    // Whether the configuration for this device contains a specific |device_type|.
377    async fn has_input_device(&self, device_type: InputDeviceType) -> bool {
378        let input_device_config_state: InputState =
379            self.inner.lock().await.input_device_config.clone().into();
380        input_device_config_state.device_types().contains(&device_type)
381    }
382}
383
384impl data_controller::CreateWith for InputController {
385    type Data = Rc<std::sync::Mutex<DefaultSetting<InputConfiguration, &'static str>>>;
386    fn create_with(client: ClientProxy, data: Self::Data) -> Result<Self, ControllerError> {
387        if let Ok(Some(config)) = data.lock().unwrap().load_default_value() {
388            InputController::create_with_config(client, config)
389        } else {
390            Err(ControllerError::InitFailure("Invalid default input device config".into()))
391        }
392    }
393}
394
395#[async_trait(?Send)]
396impl controller::Handle for InputController {
397    async fn handle(&self, request: Request) -> Option<SettingHandlerResult> {
398        match request {
399            Request::Restore => Some(self.inner.lock().await.restore().await.map(|_| None)),
400            Request::Get => Some(
401                self.inner.lock().await.get_info().await.map(|info| Some(SettingInfo::Input(info))),
402            ),
403            Request::OnCameraSWState(is_muted) => {
404                let old_state = match self
405                    .inner
406                    .lock()
407                    .await
408                    .get_stored_info()
409                    .await
410                    .input_device_state
411                    .get_source_state(
412                        InputDeviceType::CAMERA,
413                        DEFAULT_CAMERA_NAME.to_string(),
414                        DeviceStateSource::SOFTWARE,
415                    )
416                    .map_err(|_| {
417                        ControllerError::UnexpectedError(
418                            "Could not find camera software state".into(),
419                        )
420                    }) {
421                    Ok(state) => state,
422                    Err(e) => return Some(Err(e)),
423                };
424                Some(if old_state.has_state(DeviceState::MUTED) != is_muted {
425                    self.inner
426                        .lock()
427                        .await
428                        .set_sw_camera_mute(is_muted, DEFAULT_CAMERA_NAME.to_string())
429                        .await
430                } else {
431                    Ok(None)
432                })
433            }
434            Request::OnButton(mut buttons) => {
435                if buttons.mic_mute.is_some()
436                    && !self.has_input_device(InputDeviceType::MICROPHONE).await
437                {
438                    buttons.set_mic_mute(None);
439                }
440                if buttons.camera_disable.is_some()
441                    && !self.has_input_device(InputDeviceType::CAMERA).await
442                {
443                    buttons.set_camera_disable(None);
444                }
445                Some(self.inner.lock().await.set_hw_media_buttons_state(buttons).await)
446            }
447            Request::SetInputStates(input_states) => Some(
448                self.inner
449                    .lock()
450                    .await
451                    .set_input_states(input_states, DeviceStateSource::SOFTWARE)
452                    .await,
453            ),
454            _ => None,
455        }
456    }
457
458    async fn change_state(&mut self, state: State) -> Option<ControllerStateResult> {
459        match state {
460            State::Startup => Some(self.inner.lock().await.restore().await),
461            _ => None,
462        }
463    }
464}
465
466#[cfg(test)]
467mod tests {
468    use crate::handler::setting_handler::controller::Handle;
469    use crate::handler::setting_handler::ClientImpl;
470    use crate::input::input_device_configuration::{InputDeviceConfiguration, SourceState};
471    use crate::inspect::config_logger::InspectConfigLogger;
472    use crate::service_context::ServiceContext;
473    use crate::storage::{Payload as StoragePayload, StorageRequest, StorageResponse};
474    use crate::tests::fakes::service_registry::ServiceRegistry;
475    use crate::{service, Address};
476
477    use fuchsia_async as fasync;
478    use fuchsia_inspect::component;
479    use settings_storage::UpdateState;
480
481    use super::*;
482
483    #[fuchsia::test]
484    fn test_input_migration_v1_to_current() {
485        const MUTED_MIC: Microphone = Microphone { muted: true };
486        let mut v1 = InputInfoSourcesV1::default();
487        v1.sw_microphone = MUTED_MIC;
488
489        let serialized_v1 = v1.serialize_to();
490        let current = InputInfoSources::try_deserialize_from(&serialized_v1)
491            .expect("deserialization should succeed");
492        let mut expected_input_state = InputState::new();
493        expected_input_state.set_source_state(
494            InputDeviceType::MICROPHONE,
495            DEFAULT_MIC_NAME.to_string(),
496            DeviceStateSource::SOFTWARE,
497            DeviceState::MUTED,
498        );
499        expected_input_state.set_source_state(
500            InputDeviceType::MICROPHONE,
501            DEFAULT_MIC_NAME.to_string(),
502            DeviceStateSource::HARDWARE,
503            DeviceState::AVAILABLE,
504        );
505        assert_eq!(current.input_device_state, expected_input_state);
506    }
507
508    #[fuchsia::test]
509    fn test_input_migration_v1_to_v2() {
510        const MUTED_MIC: Microphone = Microphone { muted: true };
511        let mut v1 = InputInfoSourcesV1::default();
512        v1.sw_microphone = MUTED_MIC;
513
514        let serialized_v1 = v1.serialize_to();
515        let v2 = InputInfoSourcesV2::try_deserialize_from(&serialized_v1)
516            .expect("deserialization should succeed");
517
518        assert_eq!(v2.hw_microphone, Microphone { muted: false });
519        assert_eq!(v2.sw_microphone, MUTED_MIC);
520        assert_eq!(v2.input_device_state, InputState::new());
521    }
522
523    #[fuchsia::test]
524    fn test_input_migration_v2_to_current() {
525        const DEFAULT_CAMERA_NAME: &str = "camera";
526        const MUTED_MIC: Microphone = Microphone { muted: true };
527        let mut v2 = InputInfoSourcesV2::default();
528        v2.input_device_state.set_source_state(
529            InputDeviceType::CAMERA,
530            DEFAULT_CAMERA_NAME.to_string(),
531            DeviceStateSource::SOFTWARE,
532            DeviceState::AVAILABLE,
533        );
534        v2.input_device_state.set_source_state(
535            InputDeviceType::CAMERA,
536            DEFAULT_CAMERA_NAME.to_string(),
537            DeviceStateSource::HARDWARE,
538            DeviceState::MUTED,
539        );
540        v2.sw_microphone = MUTED_MIC;
541
542        let serialized_v2 = v2.serialize_to();
543        let current = InputInfoSources::try_deserialize_from(&serialized_v2)
544            .expect("deserialization should succeed");
545        let mut expected_input_state = InputState::new();
546
547        expected_input_state.set_source_state(
548            InputDeviceType::MICROPHONE,
549            DEFAULT_MIC_NAME.to_string(),
550            DeviceStateSource::SOFTWARE,
551            DeviceState::MUTED,
552        );
553        expected_input_state.set_source_state(
554            InputDeviceType::MICROPHONE,
555            DEFAULT_MIC_NAME.to_string(),
556            DeviceStateSource::HARDWARE,
557            DeviceState::AVAILABLE,
558        );
559        expected_input_state.set_source_state(
560            InputDeviceType::CAMERA,
561            DEFAULT_CAMERA_NAME.to_string(),
562            DeviceStateSource::SOFTWARE,
563            DeviceState::AVAILABLE,
564        );
565        expected_input_state.set_source_state(
566            InputDeviceType::CAMERA,
567            DEFAULT_CAMERA_NAME.to_string(),
568            DeviceStateSource::HARDWARE,
569            DeviceState::MUTED,
570        );
571
572        assert_eq!(current.input_device_state, expected_input_state);
573    }
574
575    #[fasync::run_until_stalled(test)]
576    async fn test_camera_error_on_restore() {
577        let message_hub = service::MessageHub::create_hub();
578
579        // Create a fake storage receptor used to receive and respond to storage messages.
580        let (_, mut storage_receptor) = message_hub
581            .create(service::message::MessengerType::Addressable(Address::Storage))
582            .await
583            .expect("Unable to create agent messenger");
584
585        // Spawn a task that mimics the storage agent by responding to read/write calls.
586        fasync::Task::local(async move {
587            loop {
588                if let Ok((payload, message_client)) = storage_receptor.next_payload().await {
589                    if let Ok(StoragePayload::Request(storage_request)) =
590                        StoragePayload::try_from(payload)
591                    {
592                        match storage_request {
593                            StorageRequest::Read(_, _) => {
594                                // Just respond with the default value as we're not testing storage.
595                                let _ = message_client.reply(service::Payload::Storage(
596                                    StoragePayload::Response(StorageResponse::Read(
597                                        InputInfoSources::default().into(),
598                                    )),
599                                ));
600                            }
601                            StorageRequest::Write(_, _) => {
602                                // Just respond with Unchanged as we're not testing storage.
603                                let _ = message_client.reply(service::Payload::Storage(
604                                    StoragePayload::Response(StorageResponse::Write(Ok(
605                                        UpdateState::Unchanged,
606                                    ))),
607                                ));
608                            }
609                        }
610                    }
611                }
612            }
613        })
614        .detach();
615
616        let client_proxy = create_proxy(message_hub).await;
617
618        let controller = InputController::create_with_config(
619            client_proxy,
620            InputConfiguration {
621                devices: vec![InputDeviceConfiguration {
622                    device_name: DEFAULT_CAMERA_NAME.to_string(),
623                    device_type: InputDeviceType::CAMERA,
624                    source_states: vec![SourceState {
625                        source: DeviceStateSource::SOFTWARE,
626                        state: 0,
627                    }],
628                    mutable_toggle_state: 0,
629                }],
630            },
631        )
632        .expect("Should have controller");
633
634        // Restore should pass.
635        let result = controller.handle(Request::Restore).await;
636        assert_eq!(result, Some(Ok(None)));
637
638        // But the camera state should show an error.
639        let result = controller.handle(Request::Get).await;
640        let Some(Ok(Some(SettingInfo::Input(input_info)))) = result else {
641            panic!("Expected Input response. Got {result:?}");
642        };
643        let camera_state = input_info
644            .input_device_state
645            .get_state(InputDeviceType::CAMERA, DEFAULT_CAMERA_NAME.to_string())
646            .unwrap();
647        assert!(camera_state.has_state(DeviceState::ERROR));
648    }
649
650    #[fasync::run_until_stalled(test)]
651    async fn test_controller_creation_with_default_config() {
652        use crate::handler::setting_handler::persist::controller::CreateWith;
653
654        let message_hub = service::MessageHub::create_hub();
655        let client_proxy = create_proxy(message_hub).await;
656        let config_logger = InspectConfigLogger::new(component::inspector().root());
657        let default_setting = DefaultSetting::new(
658            Some(InputConfiguration::default()),
659            "/config/data/input_device_config.json",
660            Rc::new(std::sync::Mutex::new(config_logger)),
661        );
662        let _controller = InputController::create_with(
663            client_proxy,
664            Rc::new(std::sync::Mutex::new(default_setting)),
665        )
666        .expect("Should have controller");
667    }
668
669    async fn create_proxy(message_hub: service::message::Delegate) -> ClientProxy {
670        // Create the messenger that the client proxy uses to send messages.
671        let (controller_messenger, _) = message_hub
672            .create(service::message::MessengerType::Unbound)
673            .await
674            .expect("Unable to create agent messenger");
675
676        // Note that no camera service is registered, to mimic scenarios where devices do not
677        // have a functioning camera service.
678        let service_registry = ServiceRegistry::create();
679
680        let service_context =
681            ServiceContext::new(Some(ServiceRegistry::serve(service_registry)), None);
682
683        // This isn't actually the signature for the notifier, but it's unused in this test, so just
684        // provide the signature of its own messenger to the client proxy.
685        let signature = controller_messenger.get_signature();
686
687        ClientProxy::new(
688            Rc::new(ClientImpl::for_test(
689                Default::default(),
690                controller_messenger,
691                signature,
692                Rc::new(service_context),
693                SettingType::Input,
694            )),
695            SettingType::Input,
696        )
697        .await
698    }
699}