1use fission_core::{
2 single_chunk_data_stream, CameraAvailability, CameraCapture, CameraCaptureRequest,
3 CameraDevice, CameraError, CameraFacing, CameraFlashlightRequest, CameraPermission,
4 CameraPermissionRequest, CapabilityCtx, CANCEL_CAMERA_CAPTURE, CAPTURE_PHOTO,
5 GET_CAMERA_AVAILABILITY, REQUEST_CAMERA_PERMISSION, SET_CAMERA_FLASHLIGHT,
6};
7use fission_shell::async_host::AsyncRegistry;
8use std::io::Cursor;
9use std::sync::{Arc, Mutex};
10
11pub trait CameraHost: Send + Sync + 'static {
13 fn availability(&self) -> Result<CameraAvailability, CameraError>;
15 fn request_permission(
17 &self,
18 request: CameraPermissionRequest,
19 ) -> Result<CameraPermission, CameraError>;
20 fn capture_photo(
22 &self,
23 request: CameraCaptureRequest,
24 ctx: &CapabilityCtx,
25 ) -> Result<CameraCapture, CameraError>;
26 fn set_flashlight(&self, request: CameraFlashlightRequest) -> Result<(), CameraError>;
28 fn cancel_capture(&self) -> Result<(), CameraError>;
30}
31
32#[derive(Debug, Default)]
33pub struct UnsupportedCameraHost;
34
35impl CameraHost for UnsupportedCameraHost {
36 fn availability(&self) -> Result<CameraAvailability, CameraError> {
37 Ok(CameraAvailability {
38 permission: CameraPermission::Denied,
39 devices: Vec::new(),
40 })
41 }
42
43 fn request_permission(
44 &self,
45 _request: CameraPermissionRequest,
46 ) -> Result<CameraPermission, CameraError> {
47 Err(CameraError::unsupported("request_permission"))
48 }
49
50 fn capture_photo(
51 &self,
52 _request: CameraCaptureRequest,
53 _ctx: &CapabilityCtx,
54 ) -> Result<CameraCapture, CameraError> {
55 Err(CameraError::unsupported("capture_photo"))
56 }
57
58 fn set_flashlight(&self, _request: CameraFlashlightRequest) -> Result<(), CameraError> {
59 Err(CameraError::unsupported("set_flashlight"))
60 }
61
62 fn cancel_capture(&self) -> Result<(), CameraError> {
63 Err(CameraError::unsupported("cancel_capture"))
64 }
65}
66
67#[derive(Debug)]
68pub struct MemoryCameraHost {
69 availability: CameraAvailability,
70 capture_bytes: Arc<Vec<u8>>,
71 content_type: String,
72 width: u32,
73 height: u32,
74 camera_id: Option<String>,
75 flashlight_calls: Arc<Mutex<Vec<CameraFlashlightRequest>>>,
76}
77
78impl MemoryCameraHost {
79 pub fn new(
80 availability: CameraAvailability,
81 capture_bytes: Vec<u8>,
82 content_type: impl Into<String>,
83 width: u32,
84 height: u32,
85 camera_id: Option<String>,
86 ) -> Self {
87 Self {
88 availability,
89 capture_bytes: Arc::new(capture_bytes),
90 content_type: content_type.into(),
91 width,
92 height,
93 camera_id,
94 flashlight_calls: Arc::new(Mutex::new(Vec::new())),
95 }
96 }
97
98 pub fn flashlight_calls(&self) -> Vec<CameraFlashlightRequest> {
99 self.flashlight_calls
100 .lock()
101 .map(|calls| calls.clone())
102 .unwrap_or_default()
103 }
104}
105
106impl Default for MemoryCameraHost {
107 fn default() -> Self {
108 Self::new(
109 CameraAvailability {
110 permission: CameraPermission::Granted,
111 devices: vec![CameraDevice {
112 id: "memory-camera".into(),
113 label: Some("Memory camera".into()),
114 facing: CameraFacing::Back,
115 has_flashlight: true,
116 }],
117 },
118 demo_capture_png(96, 72),
119 "image/png",
120 96,
121 72,
122 Some("memory-camera".into()),
123 )
124 }
125}
126
127fn demo_capture_png(width: u32, height: u32) -> Vec<u8> {
128 let mut image = image::RgbaImage::new(width, height);
129 for (x, y, pixel) in image.enumerate_pixels_mut() {
130 let red = 24 + ((x * 140) / width.max(1)) as u8;
131 let green = 120 + ((y * 90) / height.max(1)) as u8;
132 let blue = 180 + (((x + y) * 50) / (width + height).max(1)) as u8;
133 *pixel = image::Rgba([red, green, blue, 255]);
134 }
135 let mut bytes = Cursor::new(Vec::new());
136 image::DynamicImage::ImageRgba8(image)
137 .write_to(&mut bytes, image::ImageFormat::Png)
138 .expect("encode in-memory camera capture");
139 bytes.into_inner()
140}
141
142impl CameraHost for MemoryCameraHost {
143 fn availability(&self) -> Result<CameraAvailability, CameraError> {
144 Ok(self.availability.clone())
145 }
146
147 fn request_permission(
148 &self,
149 _request: CameraPermissionRequest,
150 ) -> Result<CameraPermission, CameraError> {
151 Ok(self.availability.permission)
152 }
153
154 fn capture_photo(
155 &self,
156 _request: CameraCaptureRequest,
157 ctx: &CapabilityCtx,
158 ) -> Result<CameraCapture, CameraError> {
159 let stream =
160 ctx.register_data_stream(single_chunk_data_stream((*self.capture_bytes).clone()));
161 Ok(CameraCapture {
162 stream,
163 byte_len: Some(self.capture_bytes.len() as u64),
164 content_type: self.content_type.clone(),
165 width: self.width,
166 height: self.height,
167 camera_id: self.camera_id.clone(),
168 })
169 }
170
171 fn set_flashlight(&self, request: CameraFlashlightRequest) -> Result<(), CameraError> {
172 self.flashlight_calls.lock().unwrap().push(request);
173 Ok(())
174 }
175
176 fn cancel_capture(&self) -> Result<(), CameraError> {
177 Ok(())
178 }
179}
180
181pub(crate) fn register_camera_capabilities(
182 async_registry: &mut AsyncRegistry,
183 host: Arc<dyn CameraHost>,
184) {
185 let availability_host = host.clone();
186 async_registry.register_operation_capability(GET_CAMERA_AVAILABILITY, move |(), _| {
187 let host = availability_host.clone();
188 async move { host.availability() }
189 });
190
191 let permission_host = host.clone();
192 async_registry.register_operation_capability(REQUEST_CAMERA_PERMISSION, move |request, _| {
193 let host = permission_host.clone();
194 async move { host.request_permission(request) }
195 });
196
197 let capture_host = host.clone();
198 async_registry.register_operation_capability(CAPTURE_PHOTO, move |request, ctx| {
199 let host = capture_host.clone();
200 async move { host.capture_photo(request, &ctx) }
201 });
202
203 let flashlight_host = host.clone();
204 async_registry.register_operation_capability(SET_CAMERA_FLASHLIGHT, move |request, _| {
205 let host = flashlight_host.clone();
206 async move { host.set_flashlight(request) }
207 });
208
209 async_registry.register_operation_capability(CANCEL_CAMERA_CAPTURE, move |(), _| {
210 let host = host.clone();
211 async move { host.cancel_capture() }
212 });
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use fission_core::{CameraImageFormat, DataStreamRegistry};
219
220 #[test]
221 fn unsupported_host_reports_errors() {
222 let host = UnsupportedCameraHost;
223 let ctx = CapabilityCtx::new_runtime(1, DataStreamRegistry::new());
224 assert!(host
225 .capture_photo(
226 CameraCaptureRequest {
227 format: CameraImageFormat::Jpeg,
228 ..Default::default()
229 },
230 &ctx,
231 )
232 .is_err());
233 assert!(host
234 .set_flashlight(CameraFlashlightRequest {
235 enabled: true,
236 ..Default::default()
237 })
238 .is_err());
239 }
240
241 #[test]
242 fn memory_host_returns_capture_and_records_flashlight() {
243 let host = MemoryCameraHost::default();
244 let availability = host.availability().unwrap();
245 assert_eq!(availability.permission, CameraPermission::Granted);
246
247 let ctx = CapabilityCtx::new_runtime(1, DataStreamRegistry::new());
248 let capture = host
249 .capture_photo(CameraCaptureRequest::default(), &ctx)
250 .unwrap();
251 assert_eq!(capture.width, 96);
252 assert_eq!(capture.height, 72);
253 assert_eq!(capture.content_type, "image/png");
254 assert!(capture.stream.0 > 0);
255
256 let request = CameraFlashlightRequest {
257 enabled: true,
258 intensity: Some(128),
259 ..Default::default()
260 };
261 host.set_flashlight(request.clone()).unwrap();
262 assert_eq!(host.flashlight_calls(), vec![request]);
263 }
264}