Skip to main content

fission_core/
platform_camera.rs

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