use fission_core::{
CameraAvailability, CameraCapture, CameraCaptureRequest, CameraDevice, CameraError,
CameraFacing, CameraFlashlightRequest, CameraPermission, CameraPermissionRequest,
CANCEL_CAMERA_CAPTURE, CAPTURE_PHOTO, GET_CAMERA_AVAILABILITY, REQUEST_CAMERA_PERMISSION,
SET_CAMERA_FLASHLIGHT,
};
use fission_shell::async_host::AsyncRegistry;
use std::io::Cursor;
use std::sync::{Arc, Mutex};
pub trait CameraHost: Send + Sync + 'static {
fn availability(&self) -> Result<CameraAvailability, CameraError>;
fn request_permission(
&self,
request: CameraPermissionRequest,
) -> Result<CameraPermission, CameraError>;
fn capture_photo(&self, request: CameraCaptureRequest) -> Result<CameraCapture, CameraError>;
fn set_flashlight(&self, request: CameraFlashlightRequest) -> Result<(), CameraError>;
fn cancel_capture(&self) -> Result<(), CameraError>;
}
#[derive(Debug, Default)]
pub struct UnsupportedCameraHost;
impl CameraHost for UnsupportedCameraHost {
fn availability(&self) -> Result<CameraAvailability, CameraError> {
Ok(CameraAvailability {
permission: CameraPermission::Denied,
devices: Vec::new(),
})
}
fn request_permission(
&self,
_request: CameraPermissionRequest,
) -> Result<CameraPermission, CameraError> {
Err(CameraError::unsupported("request_permission"))
}
fn capture_photo(&self, _request: CameraCaptureRequest) -> Result<CameraCapture, CameraError> {
Err(CameraError::unsupported("capture_photo"))
}
fn set_flashlight(&self, _request: CameraFlashlightRequest) -> Result<(), CameraError> {
Err(CameraError::unsupported("set_flashlight"))
}
fn cancel_capture(&self) -> Result<(), CameraError> {
Err(CameraError::unsupported("cancel_capture"))
}
}
#[derive(Debug)]
pub struct MemoryCameraHost {
availability: CameraAvailability,
capture: CameraCapture,
flashlight_calls: Arc<Mutex<Vec<CameraFlashlightRequest>>>,
}
impl MemoryCameraHost {
pub fn new(availability: CameraAvailability, capture: CameraCapture) -> Self {
Self {
availability,
capture,
flashlight_calls: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn flashlight_calls(&self) -> Vec<CameraFlashlightRequest> {
self.flashlight_calls
.lock()
.map(|calls| calls.clone())
.unwrap_or_default()
}
}
impl Default for MemoryCameraHost {
fn default() -> Self {
Self::new(
CameraAvailability {
permission: CameraPermission::Granted,
devices: vec![CameraDevice {
id: "memory-camera".into(),
label: Some("Memory camera".into()),
facing: CameraFacing::Back,
has_flashlight: true,
}],
},
CameraCapture {
bytes: demo_capture_png(96, 72),
content_type: "image/png".into(),
width: 96,
height: 72,
camera_id: Some("memory-camera".into()),
},
)
}
}
fn demo_capture_png(width: u32, height: u32) -> Vec<u8> {
let mut image = image::RgbaImage::new(width, height);
for (x, y, pixel) in image.enumerate_pixels_mut() {
let red = 24 + ((x * 140) / width.max(1)) as u8;
let green = 120 + ((y * 90) / height.max(1)) as u8;
let blue = 180 + (((x + y) * 50) / (width + height).max(1)) as u8;
*pixel = image::Rgba([red, green, blue, 255]);
}
let mut bytes = Cursor::new(Vec::new());
image::DynamicImage::ImageRgba8(image)
.write_to(&mut bytes, image::ImageFormat::Png)
.expect("encode in-memory camera capture");
bytes.into_inner()
}
impl CameraHost for MemoryCameraHost {
fn availability(&self) -> Result<CameraAvailability, CameraError> {
Ok(self.availability.clone())
}
fn request_permission(
&self,
_request: CameraPermissionRequest,
) -> Result<CameraPermission, CameraError> {
Ok(self.availability.permission)
}
fn capture_photo(&self, _request: CameraCaptureRequest) -> Result<CameraCapture, CameraError> {
Ok(self.capture.clone())
}
fn set_flashlight(&self, request: CameraFlashlightRequest) -> Result<(), CameraError> {
self.flashlight_calls.lock().unwrap().push(request);
Ok(())
}
fn cancel_capture(&self) -> Result<(), CameraError> {
Ok(())
}
}
pub(crate) fn register_camera_capabilities(
async_registry: &mut AsyncRegistry,
host: Arc<dyn CameraHost>,
) {
let availability_host = host.clone();
async_registry.register_operation_capability(GET_CAMERA_AVAILABILITY, move |(), _| {
let host = availability_host.clone();
async move { host.availability() }
});
let permission_host = host.clone();
async_registry.register_operation_capability(REQUEST_CAMERA_PERMISSION, move |request, _| {
let host = permission_host.clone();
async move { host.request_permission(request) }
});
let capture_host = host.clone();
async_registry.register_operation_capability(CAPTURE_PHOTO, move |request, _| {
let host = capture_host.clone();
async move { host.capture_photo(request) }
});
let flashlight_host = host.clone();
async_registry.register_operation_capability(SET_CAMERA_FLASHLIGHT, move |request, _| {
let host = flashlight_host.clone();
async move { host.set_flashlight(request) }
});
async_registry.register_operation_capability(CANCEL_CAMERA_CAPTURE, move |(), _| {
let host = host.clone();
async move { host.cancel_capture() }
});
}
#[cfg(test)]
mod tests {
use super::*;
use fission_core::CameraImageFormat;
#[test]
fn unsupported_host_reports_errors() {
let host = UnsupportedCameraHost;
assert!(host
.capture_photo(CameraCaptureRequest {
format: CameraImageFormat::Jpeg,
..Default::default()
})
.is_err());
assert!(host
.set_flashlight(CameraFlashlightRequest {
enabled: true,
..Default::default()
})
.is_err());
}
#[test]
fn memory_host_returns_capture_and_records_flashlight() {
let host = MemoryCameraHost::default();
let availability = host.availability().unwrap();
assert_eq!(availability.permission, CameraPermission::Granted);
let capture = host.capture_photo(CameraCaptureRequest::default()).unwrap();
assert_eq!(capture.width, 96);
assert_eq!(capture.height, 72);
assert_eq!(capture.content_type, "image/png");
image::load_from_memory(&capture.bytes).expect("memory camera capture should decode");
let request = CameraFlashlightRequest {
enabled: true,
intensity: Some(128),
..Default::default()
};
host.set_flashlight(request.clone()).unwrap();
assert_eq!(host.flashlight_calls(), vec![request]);
}
}