1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use anyhow::Error;
use fidl_fuchsia_camera_test_virtualcamera::{
    StreamConfig, VirtualCameraDeviceMarker, VirtualCameraDeviceProxy,
};
use fuchsia_component::client::connect_to_protocol;
use serde_json::{to_value, Value};
use tracing::*;

/// Facade providing access to Virtual Camera interfaces.
#[derive(Debug)]
pub struct VirtualCameraFacade {
    /// Virtual camera device proxy that may be optionally provided for testing.
    /// The proxy is not cached during normal operation.
    camera_proxy: Option<VirtualCameraDeviceProxy>,
}

impl VirtualCameraFacade {
    pub fn new() -> VirtualCameraFacade {
        VirtualCameraFacade { camera_proxy: None }
    }

    /// Adds a stream config to the virtual device.
    ///
    /// # Arguments
    /// * `args`: JSON value containing the desired index of the stream as
    ///   uint64, pixel width as uint32, and pixel height as uint32.
    pub async fn add_stream_config(&self, args: Value) -> Result<Value, Error> {
        // Pull the args and cast them if need be.
        let config_index =
            args["index"].as_u64().ok_or(format_err!("index not a number"))?.try_into()?;

        info!("AddStreamConfig: index received {:?}", config_index);

        let config_width = (args
            .get("width")
            .ok_or(format_err!("Expected a serde_json Value width."))?
            .as_u64()
            .ok_or(format_err!("Expected u64 type for width."))?
            as u32)
            .try_into()?;

        info!("AddStreamConfig: width received {:?}", config_width);

        let config_height = (args
            .get("height")
            .ok_or(format_err!("Expected a serde_json Value height."))?
            .as_u64()
            .ok_or(format_err!("Expected u64 type for height."))?
            as u32)
            .try_into()?;

        info!("AddStreamConfig: height received {:?}", config_height);

        // Use the test proxy if one was provided, otherwise connect to the
        // discoverable Virtual Camera service.
        let camera_proxy = match self.camera_proxy.as_ref() {
            Some(proxy) => proxy.clone(),
            None => match connect_to_protocol::<VirtualCameraDeviceMarker>() {
                Ok(proxy) => proxy,
                Err(e) => bail!("Failed to connect to VirtualCameraDevice FIDL service {:?}.", e),
            },
        };

        // Set up StreamConfig struct.
        let stream_config =
            { StreamConfig { width: config_width, height: config_height, ..Default::default() } };

        // Call the FIDL method.
        info!("Stream Config specifications {:?}", stream_config);
        match camera_proxy.add_stream_config(config_index, &stream_config) {
            Ok(_) => Ok(to_value(true)?),
            Err(e) => Err(format_err!("AddStreamConfig failed with err {:?}", e)),
        }
    }

    pub async fn add_to_device_watcher(&self) -> Result<Value, Error> {
        // Use the test proxy if one was provided, otherwise connect to the discoverable
        // Virtual Camera service.
        let camera_proxy = match self.camera_proxy.as_ref() {
            Some(proxy) => proxy.clone(),
            None => match connect_to_protocol::<VirtualCameraDeviceMarker>() {
                Ok(proxy) => proxy,
                Err(e) => bail!("Failed to connect to VirtualCameraDevice FIDL service {:?}.", e),
            },
        };

        info!("AddToDeviceWatcher FIDL protocol connected");

        match camera_proxy.add_to_device_watcher().await? {
            Ok(_) => Ok(to_value(true)?),
            Err(e) => Err(format_err!("AddToDeviceWatcher failed with err {:?}", e)),
        }
    }

    // TODO(b/195762320) Add remaining method parsing for AddDataSource,
    // SetDataSourceForStreamConfig, ClearDataSourceForStreamConfig
}

#[cfg(test)]
mod tests {
    use super::*;
    use fidl::endpoints::{create_proxy, create_proxy_and_stream};
    use fidl_fuchsia_camera_test_virtualcamera::VirtualCameraDeviceRequest;
    use fuchsia_async as fasync;
    use futures::prelude::*;
    use futures::TryStreamExt;
    use serde_json::json;

    /// Tests that the `add_stream_config` method correctly sends the index, width and
    /// height to the FIDL and returns true.
    #[fasync::run_singlethreaded(test)]
    async fn test_add_stream_config() {
        let test_index = 0;
        let test_width = 100;
        let test_height = 200;

        let (proxy, mut stream) = create_proxy_and_stream::<VirtualCameraDeviceMarker>().unwrap();

        // Create a facade future that sends a request to `proxy`.
        let facade = VirtualCameraFacade { camera_proxy: Some(proxy) };

        // Set parameters and expect a return value of true from `add_stream_config`.
        let facade_fut = async move {
            assert_eq!(
                facade
                    .add_stream_config(
                        json!({"index": test_index, "width": test_width, "height": test_height})
                    )
                    .await
                    .unwrap(),
                to_value(true).unwrap()
            );
        };

        // Verify stream contents from `AddStreamConfig` match arguments passed into facade.
        let stream_fut = async move {
            match stream.try_next().await {
                Ok(Some(VirtualCameraDeviceRequest::AddStreamConfig { index, config, .. })) => {
                    assert_eq!(index, test_index);
                    assert_eq!(
                        config,
                        StreamConfig {
                            width: Some(test_width),
                            height: Some(test_height),
                            ..Default::default()
                        }
                    );
                }
                err => panic!("Err in request handler: {:?}", err),
            }
        };
        future::join(facade_fut, stream_fut).await;
    }

    /// Tests that the `add_stream_config` method does not send the index, width and
    /// height to the FIDL because format is incorrect.
    #[fasync::run_singlethreaded(test)]
    async fn test_add_stream_config_with_parameter_error() {
        // Incorrectly set AddStreamConfig parameters to strings.
        let test_index = "one";
        let test_width = "three hundred";
        let test_height = "four hundred";

        let proxy = create_proxy::<VirtualCameraDeviceMarker>().unwrap();

        // Create a facade future that sends a request to `proxy`.
        let facade = VirtualCameraFacade { camera_proxy: Some(proxy.0) };

        // Set parameters and expect a return value of false from `add_stream_config`.
        assert_eq!(
            facade
                .add_stream_config(
                    json!({"index": test_index, "width": test_width, "height": test_height})
                )
                .await
                .is_ok(),
            false
        );
    }

    /// Tests that the `add_to_device_watcher` method correctly returns true
    /// after calling the FIDL service.
    #[fasync::run_singlethreaded(test)]
    async fn test_add_to_device_watcher() {
        let (proxy, mut stream) = create_proxy_and_stream::<VirtualCameraDeviceMarker>().unwrap();

        // Create a facade future that sends a request to `proxy`.
        let facade = VirtualCameraFacade { camera_proxy: Some(proxy) };

        // Set Parameters and expect a return value of true from `add_to_device_watcher`.
        let facade_fut = async move {
            assert_eq!(facade.add_to_device_watcher().await.unwrap(), to_value(true).unwrap());
        };

        // Verify stream contents from `AddToDeviceWatcher` match arguments passed into facade.
        let stream_fut = async move {
            match stream.try_next().await {
                Ok(Some(VirtualCameraDeviceRequest::AddToDeviceWatcher { responder })) => {
                    responder.send(Ok(())).unwrap();
                }
                err => panic!("Err in request handler: {:?}", err),
            }
        };
        future::join(facade_fut, stream_fut).await;
    }

    /// Tests that the `add_to_device_watcher` method correctly returns false
    /// after calling the FIDL service with an error response.
    #[fasync::run_singlethreaded(test)]
    async fn test_add_to_device_watcher_on_error() {
        let (proxy, mut stream) = create_proxy_and_stream::<VirtualCameraDeviceMarker>().unwrap();

        // Create a facade future that sends a request to `proxy`.
        let facade = VirtualCameraFacade { camera_proxy: Some(proxy) };

        // Set parameters and expect a return value of true from `add_to_device_watcher`.
        let facade_fut = async move {
            assert_eq!(facade.add_to_device_watcher().await.is_ok(), false);
        };

        // Verify stream contents from `AddToDeviceWatcher` match arguments passed into facade.
        let stream_fut = async move {
            match stream.try_next().await {
                Ok(Some(VirtualCameraDeviceRequest::AddToDeviceWatcher { responder })) => {
                    responder.send(
                      Err(fidl_fuchsia_camera_test_virtualcamera::
                        Error::AlreadyAddedToDeviceWatcher
                      )).unwrap();
                }
                err => panic!("Err in request handler: {:?}", err),
            }
        };
        future::join(facade_fut, stream_fut).await;
    }
}