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