Skip to main content

fission_core/
platform_camera.rs

1//! Camera and flashlight host capabilities.
2
3use crate::capability::{CapabilityType, OperationCapability};
4use crate::DataStreamId;
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
8pub enum CameraPermission {
9    #[default]
10    Unknown,
11    Granted,
12    Denied,
13    Restricted,
14}
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
17pub enum CameraFacing {
18    Front,
19    Back,
20    External,
21    #[default]
22    Unspecified,
23}
24
25#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
26pub enum CameraImageFormat {
27    #[default]
28    Jpeg,
29    Png,
30    Heif,
31    Raw,
32}
33
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
35pub enum CameraFlashMode {
36    #[default]
37    Off,
38    On,
39    Auto,
40}
41
42#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
43pub struct CameraResolution {
44    pub width: u32,
45    pub height: u32,
46}
47
48#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
49pub struct CameraDevice {
50    pub id: String,
51    pub label: Option<String>,
52    pub facing: CameraFacing,
53    pub has_flashlight: bool,
54}
55
56#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
57pub struct CameraAvailability {
58    pub permission: CameraPermission,
59    pub devices: Vec<CameraDevice>,
60}
61
62#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
63pub struct CameraPermissionRequest {
64    pub reason: Option<String>,
65}
66
67#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
68pub struct CameraCaptureRequest {
69    pub camera_id: Option<String>,
70    pub facing: CameraFacing,
71    pub resolution: Option<CameraResolution>,
72    pub format: CameraImageFormat,
73    pub flash: CameraFlashMode,
74    pub quality: Option<u8>,
75}
76
77#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
78pub struct CameraCapture {
79    /// Runtime-owned stream containing the encoded image bytes.
80    pub stream: DataStreamId,
81    /// Total encoded byte length when the host can determine it up front.
82    pub byte_len: Option<u64>,
83    /// MIME type for the encoded image, for example `image/jpeg`.
84    pub content_type: String,
85    /// Captured image width in pixels.
86    pub width: u32,
87    /// Captured image height in pixels.
88    pub height: u32,
89    /// Host camera id that produced the capture, when known.
90    pub camera_id: Option<String>,
91}
92
93#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
94pub struct CameraFlashlightRequest {
95    pub camera_id: Option<String>,
96    pub enabled: bool,
97    pub intensity: Option<u8>,
98}
99
100#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
101pub struct CameraError {
102    pub code: String,
103    pub message: String,
104}
105
106impl CameraError {
107    /// Creates a portable camera error payload.
108    ///
109    /// `code` should be a stable, machine-readable reason such as
110    /// `unsupported`, `permission_denied`, or `timeout`. `message` should be a
111    /// concise human-readable explanation suitable for logs or developer-facing
112    /// diagnostics.
113    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
114        Self {
115            code: code.into(),
116            message: message.into(),
117        }
118    }
119
120    /// Creates the standard unsupported-operation error for this capability.
121    ///
122    /// `operation` should name the attempted camera operation. Use this
123    /// from hosts that implement the capability contract but cannot provide this
124    /// operation on the current platform or hardware.
125    pub fn unsupported(operation: impl Into<String>) -> Self {
126        Self::new(
127            "unsupported",
128            format!(
129                "camera operation `{}` is not supported by this host",
130                operation.into()
131            ),
132        )
133    }
134}
135
136pub struct GetCameraAvailabilityCapability;
137impl OperationCapability for GetCameraAvailabilityCapability {
138    type Request = ();
139    type Ok = CameraAvailability;
140    type Err = CameraError;
141}
142
143pub struct RequestCameraPermissionCapability;
144impl OperationCapability for RequestCameraPermissionCapability {
145    type Request = CameraPermissionRequest;
146    type Ok = CameraPermission;
147    type Err = CameraError;
148}
149
150pub struct CapturePhotoCapability;
151impl OperationCapability for CapturePhotoCapability {
152    type Request = CameraCaptureRequest;
153    type Ok = CameraCapture;
154    type Err = CameraError;
155}
156
157pub struct SetCameraFlashlightCapability;
158impl OperationCapability for SetCameraFlashlightCapability {
159    type Request = CameraFlashlightRequest;
160    type Ok = ();
161    type Err = CameraError;
162}
163
164pub struct CancelCameraCaptureCapability;
165impl OperationCapability for CancelCameraCaptureCapability {
166    type Request = ();
167    type Ok = ();
168    type Err = CameraError;
169}
170
171pub const GET_CAMERA_AVAILABILITY: CapabilityType<GetCameraAvailabilityCapability> =
172    CapabilityType::new("fission.camera.get_availability");
173pub const REQUEST_CAMERA_PERMISSION: CapabilityType<RequestCameraPermissionCapability> =
174    CapabilityType::new("fission.camera.request_permission");
175pub const CAPTURE_PHOTO: CapabilityType<CapturePhotoCapability> =
176    CapabilityType::new("fission.camera.capture_photo");
177pub const SET_CAMERA_FLASHLIGHT: CapabilityType<SetCameraFlashlightCapability> =
178    CapabilityType::new("fission.camera.set_flashlight");
179pub const CANCEL_CAMERA_CAPTURE: CapabilityType<CancelCameraCaptureCapability> =
180    CapabilityType::new("fission.camera.cancel_capture");
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn camera_capture_request_round_trips() {
188        let request = CameraCaptureRequest {
189            camera_id: Some("back".into()),
190            facing: CameraFacing::Back,
191            resolution: Some(CameraResolution {
192                width: 1920,
193                height: 1080,
194            }),
195            format: CameraImageFormat::Heif,
196            flash: CameraFlashMode::Auto,
197            quality: Some(90),
198        };
199
200        let bytes = serde_json::to_vec(&request).unwrap();
201        let decoded: CameraCaptureRequest = serde_json::from_slice(&bytes).unwrap();
202
203        assert_eq!(decoded, request);
204    }
205
206    #[test]
207    fn camera_availability_round_trips() {
208        let availability = CameraAvailability {
209            permission: CameraPermission::Granted,
210            devices: vec![CameraDevice {
211                id: "front".into(),
212                label: Some("Front camera".into()),
213                facing: CameraFacing::Front,
214                has_flashlight: false,
215            }],
216        };
217
218        let bytes = serde_json::to_vec(&availability).unwrap();
219        let decoded: CameraAvailability = serde_json::from_slice(&bytes).unwrap();
220
221        assert_eq!(decoded, availability);
222    }
223}