use async_trait::async_trait;
use smix_simctl::{DeviceControlError, SimctlClient, SimctlPermission};
use std::path::Path;
pub use crate::PermissionAction;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Permission {
Camera,
Microphone,
PhotoLibrary,
Location,
LocationAlways,
Notifications,
Contacts,
Calendar,
Reminders,
Bluetooth,
Motion,
Media,
Health,
FaceId,
HomeKit,
Storage,
PostNotifications,
}
impl Permission {
#[must_use]
pub fn to_simctl(self) -> Option<SimctlPermission> {
match self {
Permission::Camera => Some(SimctlPermission::Camera),
Permission::Microphone => Some(SimctlPermission::Microphone),
Permission::PhotoLibrary => Some(SimctlPermission::Photos),
Permission::Location => Some(SimctlPermission::Location),
Permission::LocationAlways => Some(SimctlPermission::LocationAlways),
Permission::Notifications | Permission::PostNotifications => {
Some(SimctlPermission::Notifications)
}
Permission::Contacts => Some(SimctlPermission::Contacts),
Permission::Calendar => Some(SimctlPermission::Calendar),
Permission::Reminders => Some(SimctlPermission::Reminders),
Permission::Bluetooth => Some(SimctlPermission::Bluetooth),
Permission::Motion => Some(SimctlPermission::Motion),
Permission::Media => Some(SimctlPermission::Media),
Permission::Health => Some(SimctlPermission::Health),
Permission::FaceId => Some(SimctlPermission::Faceid),
Permission::HomeKit => Some(SimctlPermission::HomeKit),
Permission::Storage => None,
}
}
#[must_use]
pub fn from_simctl(perm: SimctlPermission) -> Self {
match perm {
SimctlPermission::Camera => Permission::Camera,
SimctlPermission::Microphone => Permission::Microphone,
SimctlPermission::Photos => Permission::PhotoLibrary,
SimctlPermission::Location => Permission::Location,
SimctlPermission::LocationAlways => Permission::LocationAlways,
SimctlPermission::Notifications => Permission::Notifications,
SimctlPermission::Contacts => Permission::Contacts,
SimctlPermission::Calendar => Permission::Calendar,
SimctlPermission::Reminders => Permission::Reminders,
SimctlPermission::Bluetooth => Permission::Bluetooth,
SimctlPermission::Motion => Permission::Motion,
SimctlPermission::Media => Permission::Media,
SimctlPermission::Health => Permission::Health,
SimctlPermission::Faceid => Permission::FaceId,
SimctlPermission::HomeKit => Permission::HomeKit,
SimctlPermission::AddressBook => Permission::Contacts,
}
}
#[must_use]
pub fn to_android(self) -> Option<&'static str> {
match self {
Permission::Camera => Some("android.permission.CAMERA"),
Permission::Microphone => Some("android.permission.RECORD_AUDIO"),
Permission::PhotoLibrary => Some("android.permission.READ_MEDIA_IMAGES"),
Permission::Location => Some("android.permission.ACCESS_FINE_LOCATION"),
Permission::LocationAlways => Some("android.permission.ACCESS_BACKGROUND_LOCATION"),
Permission::Notifications | Permission::PostNotifications => {
Some("android.permission.POST_NOTIFICATIONS")
}
Permission::Contacts => Some("android.permission.READ_CONTACTS"),
Permission::Calendar => Some("android.permission.READ_CALENDAR"),
Permission::Bluetooth => Some("android.permission.BLUETOOTH_CONNECT"),
Permission::Motion => Some("android.permission.ACTIVITY_RECOGNITION"),
Permission::Media => Some("android.permission.READ_MEDIA_AUDIO"),
Permission::Storage => Some("android.permission.WRITE_EXTERNAL_STORAGE"),
Permission::Reminders
| Permission::Health
| Permission::FaceId
| Permission::HomeKit => None,
}
}
}
#[async_trait]
pub trait DeviceControl: Send + Sync {
fn platform(&self) -> smix_driver::Platform;
fn as_ios_simctl(&self) -> Option<&SimctlClient> {
None
}
async fn launch(&self, udid: &str, bundle_id: &str) -> Result<u32, DeviceControlError>;
async fn launch_with_args(
&self,
udid: &str,
bundle_id: &str,
args: &[String],
activity: Option<&str>,
) -> Result<u32, DeviceControlError>;
async fn terminate(&self, udid: &str, bundle_id: &str) -> Result<(), DeviceControlError>;
async fn install(&self, udid: &str, app_path: &str) -> Result<(), DeviceControlError>;
async fn uninstall(&self, udid: &str, bundle_id: &str) -> Result<(), DeviceControlError>;
async fn keychain_reset(&self, udid: &str) -> Result<(), DeviceControlError>;
async fn set_animations_quiet(
&self,
_id: &str,
_quiet: bool,
) -> Result<(), DeviceControlError> {
Ok(())
}
async fn privacy_reset_all(
&self,
udid: &str,
bundle_id: &str,
) -> Result<(), DeviceControlError>;
async fn clear_app_sandbox(
&self,
udid: &str,
bundle_id: &str,
) -> Result<(), DeviceControlError>;
async fn user_defaults_delete(
&self,
_udid: &str,
_bundle_id: &str,
_key: &str,
) -> Result<bool, DeviceControlError> {
Err(DeviceControlError::non_zero_exit(
"user-defaults-delete",
1,
"clearUserDefaults is not supported on this platform (iOS simulator only — \
Android SharedPreferences has no host-side per-key deletion; use clearAppData \
for a full store wipe)",
))
}
async fn open_url(&self, udid: &str, url: &str) -> Result<(), DeviceControlError>;
async fn send_push(
&self,
udid: &str,
bundle_id: &str,
apns_json_path: &str,
) -> Result<(), DeviceControlError>;
async fn screenshot(&self, udid: &str) -> Result<Vec<u8>, DeviceControlError>;
async fn capture_bgra(
&self,
udid: &str,
) -> Result<smix_simctl::surface_capture::CapturedFrame, DeviceControlError> {
self.screenshot(udid)
.await
.map(smix_simctl::surface_capture::CapturedFrame::Png)
}
async fn pasteboard_set(&self, udid: &str, text: &str) -> Result<(), DeviceControlError>;
async fn pasteboard_get(&self, udid: &str) -> Result<String, DeviceControlError>;
async fn add_media(&self, udid: &str, paths: &[String]) -> Result<(), DeviceControlError>;
async fn location_set(&self, udid: &str, lat: f64, lon: f64) -> Result<(), DeviceControlError>;
async fn location_start(
&self,
udid: &str,
points: &[(f64, f64)],
speed_mps: Option<f64>,
) -> Result<(), DeviceControlError>;
async fn set_permission(
&self,
udid: &str,
bundle_id: &str,
permission: Permission,
action: PermissionAction,
) -> Result<(), DeviceControlError>;
async fn start_recording(
&self,
udid: &str,
output_path: &Path,
) -> Result<(), DeviceControlError>;
async fn stop_recording(&self) -> Result<(), DeviceControlError>;
}