1use crate::config::SpatialConfig;
7use crate::core::SpatialProcessor;
8use crate::types::Position3D;
9use crate::{Error, Result};
10use serde::{Deserialize, Serialize};
11use std::time::{Duration, Instant};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub enum MobilePlatform {
16 Ios,
18 Android,
20 Generic,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct MobileConfig {
27 pub platform: MobilePlatform,
29 pub battery_optimization: f32,
31 pub thermal_threshold: f32,
33 pub max_cpu_usage: f32,
35 pub adaptive_quality: bool,
37 pub background_processing: bool,
39 pub low_power_max_sources: usize,
41 pub battery_sample_rate: f32,
43 pub device_optimizations: bool,
45 pub media_integration: bool,
47}
48
49impl Default for MobileConfig {
50 fn default() -> Self {
51 Self {
52 platform: MobilePlatform::Generic,
53 battery_optimization: 0.3,
54 thermal_threshold: 40.0,
55 max_cpu_usage: 25.0,
56 adaptive_quality: true,
57 background_processing: false,
58 low_power_max_sources: 8,
59 battery_sample_rate: 24000.0,
60 device_optimizations: true,
61 media_integration: true,
62 }
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct MobileDevice {
69 pub model: String,
71 pub cpu_cores: usize,
73 pub ram_mb: u32,
75 pub battery_capacity: u32,
77 pub has_audio_hardware: bool,
79 pub has_gpu_acceleration: bool,
81 pub max_sample_rate: f32,
83 pub native_spatial_support: bool,
85}
86
87impl Default for MobileDevice {
88 fn default() -> Self {
89 Self {
90 model: "Unknown".to_string(),
91 cpu_cores: 4,
92 ram_mb: 4096,
93 battery_capacity: 3000,
94 has_audio_hardware: true,
95 has_gpu_acceleration: false,
96 max_sample_rate: 48000.0,
97 native_spatial_support: false,
98 }
99 }
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
104pub enum PowerState {
105 Performance,
107 Balanced,
109 PowerSaver,
111 UltraLowPower,
113 Throttled,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
119pub enum QualityPreset {
120 Ultra,
122 High,
124 Medium,
126 Low,
128 Minimal,
130}
131
132impl QualityPreset {
133 pub fn as_float(&self) -> f32 {
135 match self {
136 QualityPreset::Ultra => 1.0,
137 QualityPreset::High => 0.8,
138 QualityPreset::Medium => 0.6,
139 QualityPreset::Low => 0.4,
140 QualityPreset::Minimal => 0.2,
141 }
142 }
143
144 pub fn max_sources(&self) -> usize {
146 match self {
147 QualityPreset::Ultra => 32,
148 QualityPreset::High => 16,
149 QualityPreset::Medium => 12,
150 QualityPreset::Low => 8,
151 QualityPreset::Minimal => 4,
152 }
153 }
154
155 pub fn sample_rate(&self) -> f32 {
157 match self {
158 QualityPreset::Ultra => 48000.0,
159 QualityPreset::High => 44100.0,
160 QualityPreset::Medium => 32000.0,
161 QualityPreset::Low => 24000.0,
162 QualityPreset::Minimal => 16000.0,
163 }
164 }
165}
166
167pub struct MobileOptimizer {
169 config: MobileConfig,
170 device: MobileDevice,
171 current_power_state: PowerState,
172 current_quality: QualityPreset,
173 battery_level: f32,
174 thermal_state: f32,
175 cpu_usage_history: Vec<f32>,
176 performance_metrics: MobileMetrics,
177 last_optimization: Instant,
178}
179
180#[derive(Debug, Clone, Default)]
182pub struct MobileMetrics {
183 pub cpu_usage: f32,
185 pub thermal_state: f32,
187 pub battery_drain_rate: f32,
189 pub processing_latency: f32,
191 pub quality_level: f32,
193 pub sources_processed: u32,
195 pub frame_drops: f32,
197 pub memory_usage: f32,
199}
200
201impl MobileOptimizer {
202 pub fn new(config: MobileConfig, device: MobileDevice) -> Self {
204 let initial_quality = if config.battery_optimization > 0.7 {
205 QualityPreset::Low
206 } else if config.battery_optimization > 0.5 {
207 QualityPreset::Medium
208 } else {
209 QualityPreset::High
210 };
211
212 Self {
213 config,
214 device,
215 current_power_state: PowerState::Balanced,
216 current_quality: initial_quality,
217 battery_level: 1.0,
218 thermal_state: 0.0,
219 cpu_usage_history: Vec::with_capacity(60), performance_metrics: MobileMetrics::default(),
221 last_optimization: Instant::now(),
222 }
223 }
224
225 pub fn update_state(&mut self, battery_level: f32, thermal_temp: f32, cpu_usage: f32) {
227 self.battery_level = battery_level.clamp(0.0, 1.0);
228 self.thermal_state = (thermal_temp - 20.0) / (self.config.thermal_threshold - 20.0);
229 self.thermal_state = self.thermal_state.clamp(0.0, 1.0);
230
231 self.cpu_usage_history.push(cpu_usage);
233 if self.cpu_usage_history.len() > 60 {
234 self.cpu_usage_history.remove(0);
235 }
236
237 self.performance_metrics.cpu_usage = cpu_usage;
239 self.performance_metrics.thermal_state = self.thermal_state;
240
241 self.current_power_state = self.determine_power_state();
243
244 if self.config.adaptive_quality {
246 self.current_quality = self.determine_optimal_quality();
247 }
248 }
249
250 fn determine_power_state(&self) -> PowerState {
252 if self.thermal_state > 0.9 {
254 return PowerState::Throttled;
255 }
256
257 if self.battery_level < 0.1 {
259 return PowerState::UltraLowPower;
260 }
261
262 if self.battery_level < 0.2 || self.config.battery_optimization > 0.7 {
264 return PowerState::PowerSaver;
265 }
266
267 if self.battery_level > 0.8 && self.config.battery_optimization < 0.3 {
269 return PowerState::Performance;
270 }
271
272 PowerState::Balanced
274 }
275
276 fn determine_optimal_quality(&self) -> QualityPreset {
278 let avg_cpu = if self.cpu_usage_history.is_empty() {
279 0.0
280 } else {
281 self.cpu_usage_history.iter().sum::<f32>() / self.cpu_usage_history.len() as f32
282 };
283
284 match self.current_power_state {
285 PowerState::Performance => {
286 if avg_cpu < 20.0 {
287 QualityPreset::Ultra
288 } else {
289 QualityPreset::High
290 }
291 }
292 PowerState::Balanced => {
293 if avg_cpu < 15.0 {
294 QualityPreset::High
295 } else {
296 QualityPreset::Medium
297 }
298 }
299 PowerState::PowerSaver => {
300 if avg_cpu < 10.0 {
301 QualityPreset::Medium
302 } else {
303 QualityPreset::Low
304 }
305 }
306 PowerState::UltraLowPower | PowerState::Throttled => QualityPreset::Minimal,
307 }
308 }
309
310 pub fn get_optimized_config(&self) -> SpatialConfig {
312 let mut config = SpatialConfig::default();
313
314 config.quality_level = self.current_quality.as_float();
316 config.max_sources = self.current_quality.max_sources();
317 config.sample_rate = self.current_quality.sample_rate() as u32;
318
319 match self.config.platform {
321 MobilePlatform::Ios => {
322 config.use_gpu = self.device.has_gpu_acceleration
324 && matches!(
325 self.current_power_state,
326 PowerState::Performance | PowerState::Balanced
327 );
328 config.buffer_size = if self.current_power_state == PowerState::UltraLowPower {
329 2048
330 } else {
331 1024
332 };
333 }
334 MobilePlatform::Android => {
335 config.use_gpu = self.device.has_gpu_acceleration
337 && !matches!(
338 self.current_power_state,
339 PowerState::PowerSaver | PowerState::UltraLowPower
340 );
341 config.buffer_size = if self.current_power_state == PowerState::UltraLowPower {
342 4096
343 } else {
344 1024
345 };
346 }
347 MobilePlatform::Generic => {
348 config.use_gpu = false;
350 config.buffer_size = 2048;
351 }
352 }
353
354 if self.current_power_state == PowerState::Throttled {
356 config.quality_level *= 0.5;
357 config.max_sources = (config.max_sources / 2).max(2);
358 config.use_gpu = false;
359 }
360
361 config
362 }
363
364 pub fn process_mobile_audio(
366 &mut self,
367 processor: &mut SpatialProcessor,
368 audio_data: &[f32],
369 listener_pos: Position3D,
370 sources: &[(Position3D, &[f32])],
371 ) -> Result<Vec<f32>> {
372 let start_time = Instant::now();
373
374 let max_sources = self.current_quality.max_sources();
376 let limited_sources = if sources.len() > max_sources {
377 &sources[..max_sources]
378 } else {
379 sources
380 };
381
382 let output = vec![0.0f32; audio_data.len()];
385
386 let processing_time = start_time.elapsed();
388 self.performance_metrics.processing_latency = processing_time.as_secs_f32() * 1000.0;
389 self.performance_metrics.sources_processed = limited_sources.len() as u32;
390 self.performance_metrics.quality_level = self.current_quality.as_float();
391
392 if processing_time > Duration::from_millis(20) {
394 self.performance_metrics.frame_drops += 1.0;
396 }
397
398 Ok(output)
399 }
400
401 pub fn get_metrics(&self) -> MobileMetrics {
403 self.performance_metrics.clone()
404 }
405
406 pub fn get_power_state(&self) -> PowerState {
408 self.current_power_state
409 }
410
411 pub fn get_quality_preset(&self) -> QualityPreset {
413 self.current_quality
414 }
415
416 pub fn set_quality_preset(&mut self, preset: QualityPreset) {
418 self.current_quality = preset;
419 }
420
421 pub fn set_background_processing(&mut self, enabled: bool) {
423 self.config.background_processing = enabled;
424 }
425
426 pub fn is_background_processing_enabled(&self) -> bool {
428 self.config.background_processing
429 }
430}
431
432pub mod ios {
434 use super::*;
435
436 pub fn detect_device() -> MobileDevice {
438 MobileDevice {
439 model: "iOS Device".to_string(),
440 cpu_cores: 6, ram_mb: 6144, battery_capacity: 3200,
443 has_audio_hardware: true, has_gpu_acceleration: true, max_sample_rate: 48000.0,
446 native_spatial_support: true, }
448 }
449
450 pub fn configure_audio_session() -> Result<()> {
452 Ok(())
455 }
456
457 pub fn enable_spatial_audio() -> Result<()> {
459 Ok(())
461 }
462}
463
464pub mod android {
466 use super::*;
467
468 pub fn detect_device() -> MobileDevice {
470 MobileDevice {
471 model: "Android Device".to_string(),
472 cpu_cores: 8, ram_mb: 8192, battery_capacity: 4000,
475 has_audio_hardware: true,
476 has_gpu_acceleration: true, max_sample_rate: 48000.0,
478 native_spatial_support: false, }
480 }
481
482 pub fn configure_audio_track() -> Result<()> {
484 Ok(())
486 }
487
488 pub fn enable_spatial_audio() -> Result<()> {
490 Ok(())
492 }
493}
494
495#[cfg(target_os = "ios")]
497pub mod ios_optimizations {
498 use super::*;
499
500 #[derive(Debug, Clone, Serialize, Deserialize)]
502 pub struct IosAudioConfig {
503 pub use_av_audio_engine: bool,
505 pub native_spatial_audio: bool,
507 pub handle_audio_interruptions: bool,
509 pub airpods_head_tracking: bool,
511 pub use_core_audio: bool,
513 pub ios_buffer_size: usize,
515 }
516
517 impl Default for IosAudioConfig {
518 fn default() -> Self {
519 Self {
520 use_av_audio_engine: true,
521 native_spatial_audio: true,
522 handle_audio_interruptions: true,
523 airpods_head_tracking: true,
524 use_core_audio: true,
525 ios_buffer_size: 512,
526 }
527 }
528 }
529
530 pub struct IosDeviceDetector;
532
533 impl IosDeviceDetector {
534 pub fn detect_device() -> MobileDevice {
536 MobileDevice {
539 model: "iOS Device".to_string(),
540 cpu_cores: Self::detect_cpu_cores(),
541 ram_mb: Self::detect_ram(),
542 battery_capacity: 3000, has_audio_hardware: true,
544 has_gpu_acceleration: Self::has_metal_support(),
545 max_sample_rate: 48000.0,
546 native_spatial_support: Self::has_spatial_audio_support(),
547 }
548 }
549
550 fn detect_cpu_cores() -> usize {
551 std::thread::available_parallelism()
553 .map(|n| n.get())
554 .unwrap_or(4)
555 }
556
557 fn detect_ram() -> u32 {
558 4096 }
561
562 fn has_metal_support() -> bool {
563 true }
566
567 fn has_spatial_audio_support() -> bool {
568 true }
571 }
572
573 pub struct IosPowerManager {
575 config: IosAudioConfig,
576 current_state: PowerState,
577 }
578
579 impl IosPowerManager {
580 pub fn new(config: IosAudioConfig) -> Self {
581 Self {
582 config,
583 current_state: PowerState::Balanced,
584 }
585 }
586
587 pub fn handle_app_state_change(&mut self, entering_background: bool) {
589 if entering_background {
590 self.current_state = PowerState::PowerSaver;
591 } else {
592 self.current_state = PowerState::Balanced;
593 }
594 }
595
596 pub fn handle_audio_interruption(&mut self, interrupted: bool) -> Result<()> {
598 if self.config.handle_audio_interruptions {
599 if interrupted {
600 self.current_state = PowerState::UltraLowPower;
601 } else {
602 self.current_state = PowerState::Balanced;
603 }
604 }
605 Ok(())
606 }
607
608 pub fn get_buffer_size(&self) -> usize {
610 match self.current_state {
611 PowerState::Performance => 256,
612 PowerState::Balanced => 512,
613 PowerState::PowerSaver => 1024,
614 PowerState::UltraLowPower => 2048,
615 PowerState::Throttled => 4096,
616 }
617 }
618 }
619}
620
621#[cfg(target_os = "android")]
623pub mod android_optimizations {
624 use super::*;
625
626 #[derive(Debug, Clone, Serialize, Deserialize)]
628 pub struct AndroidAudioConfig {
629 pub use_aaudio: bool,
631 pub use_opensl_es: bool,
633 pub use_audio_track: bool,
635 pub enable_pro_audio: bool,
637 pub use_mmap: bool,
639 pub performance_class: AndroidPerformanceClass,
641 pub target_latency_ms: f32,
643 }
644
645 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
647 pub enum AndroidPerformanceClass {
648 Premium,
650 Standard,
652 Basic,
654 Legacy,
656 }
657
658 impl Default for AndroidAudioConfig {
659 fn default() -> Self {
660 Self {
661 use_aaudio: true,
662 use_opensl_es: true,
663 use_audio_track: true,
664 enable_pro_audio: true,
665 use_mmap: true,
666 performance_class: AndroidPerformanceClass::Standard,
667 target_latency_ms: 20.0,
668 }
669 }
670 }
671
672 pub struct AndroidDeviceDetector;
674
675 impl AndroidDeviceDetector {
676 pub fn detect_device() -> MobileDevice {
678 MobileDevice {
679 model: "Android Device".to_string(),
680 cpu_cores: Self::detect_cpu_cores(),
681 ram_mb: Self::detect_ram(),
682 battery_capacity: Self::detect_battery_capacity(),
683 has_audio_hardware: Self::has_audio_hardware(),
684 has_gpu_acceleration: Self::has_vulkan_support(),
685 max_sample_rate: Self::detect_max_sample_rate(),
686 native_spatial_support: Self::has_spatial_audio_support(),
687 }
688 }
689
690 fn detect_cpu_cores() -> usize {
691 std::thread::available_parallelism()
692 .map(|n| n.get())
693 .unwrap_or(4)
694 }
695
696 fn detect_ram() -> u32 {
697 4096 }
700
701 fn detect_battery_capacity() -> u32 {
702 3500 }
705
706 fn has_audio_hardware() -> bool {
707 true
709 }
710
711 fn has_vulkan_support() -> bool {
712 false }
715
716 fn detect_max_sample_rate() -> f32 {
717 48000.0
719 }
720
721 fn has_spatial_audio_support() -> bool {
722 false }
725
726 pub fn detect_performance_class() -> AndroidPerformanceClass {
728 AndroidPerformanceClass::Standard
731 }
732 }
733
734 pub struct AndroidAudioOptimizer {
736 config: AndroidAudioConfig,
737 performance_class: AndroidPerformanceClass,
738 }
739
740 impl AndroidAudioOptimizer {
741 pub fn new(config: AndroidAudioConfig) -> Self {
742 let performance_class = AndroidDeviceDetector::detect_performance_class();
743 Self {
744 config,
745 performance_class,
746 }
747 }
748
749 pub fn get_optimal_buffer_size(&self) -> usize {
751 match self.performance_class {
752 AndroidPerformanceClass::Premium => {
753 if self.config.use_mmap {
754 128
755 } else {
756 256
757 }
758 }
759 AndroidPerformanceClass::Standard => {
760 if self.config.use_aaudio {
761 256
762 } else {
763 512
764 }
765 }
766 AndroidPerformanceClass::Basic => 1024,
767 AndroidPerformanceClass::Legacy => 2048,
768 }
769 }
770
771 pub fn has_low_latency_audio(&self) -> bool {
773 match self.performance_class {
774 AndroidPerformanceClass::Premium | AndroidPerformanceClass::Standard => {
775 self.config.use_aaudio || self.config.enable_pro_audio
776 }
777 _ => false,
778 }
779 }
780
781 pub fn get_optimal_sample_rate(&self) -> f32 {
783 match self.performance_class {
784 AndroidPerformanceClass::Premium => 48000.0,
785 AndroidPerformanceClass::Standard => 44100.0,
786 AndroidPerformanceClass::Basic => 44100.0,
787 AndroidPerformanceClass::Legacy => 22050.0,
788 }
789 }
790
791 pub fn handle_audio_focus_change(&mut self, has_focus: bool) -> Result<()> {
793 if !has_focus {
795 }
797 Ok(())
798 }
799 }
800}
801
802pub struct MobilePlatformOptimizer {
804 #[cfg(target_os = "ios")]
805 ios_config: ios_optimizations::IosAudioConfig,
806 #[cfg(target_os = "android")]
807 android_config: android_optimizations::AndroidAudioConfig,
808 mobile_config: MobileConfig,
809}
810
811impl MobilePlatformOptimizer {
812 pub fn new(mobile_config: MobileConfig) -> Self {
814 Self {
815 #[cfg(target_os = "ios")]
816 ios_config: ios_optimizations::IosAudioConfig::default(),
817 #[cfg(target_os = "android")]
818 android_config: android_optimizations::AndroidAudioConfig::default(),
819 mobile_config,
820 }
821 }
822
823 pub fn get_platform_buffer_size(&self) -> usize {
825 #[cfg(target_os = "ios")]
826 {
827 let power_manager = ios_optimizations::IosPowerManager::new(self.ios_config.clone());
828 power_manager.get_buffer_size()
829 }
830 #[cfg(target_os = "android")]
831 {
832 let optimizer =
833 android_optimizations::AndroidAudioOptimizer::new(self.android_config.clone());
834 optimizer.get_optimal_buffer_size()
835 }
836 #[cfg(not(any(target_os = "ios", target_os = "android")))]
837 {
838 1024 }
840 }
841
842 pub fn get_platform_sample_rate(&self) -> f32 {
844 #[cfg(target_os = "ios")]
845 {
846 48000.0 }
848 #[cfg(target_os = "android")]
849 {
850 let optimizer =
851 android_optimizations::AndroidAudioOptimizer::new(self.android_config.clone());
852 optimizer.get_optimal_sample_rate()
853 }
854 #[cfg(not(any(target_os = "ios", target_os = "android")))]
855 {
856 44100.0 }
858 }
859
860 pub fn has_low_latency_support(&self) -> bool {
862 #[cfg(target_os = "ios")]
863 {
864 self.ios_config.use_core_audio || self.ios_config.use_av_audio_engine
865 }
866 #[cfg(target_os = "android")]
867 {
868 let optimizer =
869 android_optimizations::AndroidAudioOptimizer::new(self.android_config.clone());
870 optimizer.has_low_latency_audio()
871 }
872 #[cfg(not(any(target_os = "ios", target_os = "android")))]
873 {
874 false
875 }
876 }
877}
878#[cfg(test)]
879mod tests {
880 use super::*;
881
882 #[test]
883 fn test_mobile_config_creation() {
884 let config = MobileConfig::default();
885 assert_eq!(config.platform, MobilePlatform::Generic);
886 assert!(config.adaptive_quality);
887 }
888
889 #[test]
890 fn test_quality_preset_values() {
891 assert_eq!(QualityPreset::Ultra.as_float(), 1.0);
892 assert_eq!(QualityPreset::Low.as_float(), 0.4);
893 assert_eq!(QualityPreset::Ultra.max_sources(), 32);
894 assert_eq!(QualityPreset::Minimal.max_sources(), 4);
895 }
896
897 #[test]
898 fn test_mobile_optimizer_creation() {
899 let config = MobileConfig::default();
900 let device = MobileDevice::default();
901 let optimizer = MobileOptimizer::new(config, device);
902
903 assert_eq!(optimizer.current_power_state, PowerState::Balanced);
904 assert_eq!(optimizer.battery_level, 1.0);
905 }
906
907 #[test]
908 fn test_power_state_determination() {
909 let config = MobileConfig::default();
910 let device = MobileDevice::default();
911 let mut optimizer = MobileOptimizer::new(config, device);
912
913 optimizer.update_state(0.05, 25.0, 10.0);
915 assert_eq!(optimizer.get_power_state(), PowerState::UltraLowPower);
916
917 optimizer.update_state(0.8, 45.0, 10.0);
919 assert_eq!(optimizer.get_power_state(), PowerState::Throttled);
920
921 optimizer.update_state(0.6, 30.0, 15.0);
923 assert_eq!(optimizer.get_power_state(), PowerState::Balanced);
924 }
925
926 #[test]
927 fn test_quality_adaptation() {
928 let config = MobileConfig {
929 adaptive_quality: true,
930 ..Default::default()
931 };
932 let device = MobileDevice::default();
933 let mut optimizer = MobileOptimizer::new(config, device);
934
935 optimizer.update_state(0.5, 30.0, 35.0);
937 let quality = optimizer.get_quality_preset();
938 assert!(matches!(
939 quality,
940 QualityPreset::Low | QualityPreset::Medium
941 ));
942 }
943
944 #[test]
945 fn test_optimized_config_generation() {
946 let config = MobileConfig::default();
947 let device = MobileDevice::default();
948 let optimizer = MobileOptimizer::new(config, device);
949
950 let spatial_config = optimizer.get_optimized_config();
951 assert!(spatial_config.quality_level > 0.0);
952 assert!(spatial_config.max_sources > 0);
953 }
954
955 #[test]
956 fn test_ios_device_detection() {
957 let device = ios::detect_device();
958 assert_eq!(device.model, "iOS Device");
959 assert!(device.has_audio_hardware);
960 assert!(device.native_spatial_support);
961 }
962
963 #[test]
964 fn test_android_device_detection() {
965 let device = android::detect_device();
966 assert_eq!(device.model, "Android Device");
967 assert!(device.has_audio_hardware);
968 assert!(!device.native_spatial_support);
969 }
970
971 #[tokio::test]
972 async fn test_mobile_audio_processing() {
973 let config = MobileConfig::default();
974 let device = MobileDevice::default();
975 let mut optimizer = MobileOptimizer::new(config, device);
976
977 let spatial_config = SpatialConfig::default();
979 let mut processor = SpatialProcessor::new(spatial_config).await.unwrap();
980
981 let audio_data = vec![0.5; 1024];
982 let listener_pos = Position3D::new(0.0, 0.0, 0.0);
983 let sources = vec![
984 (Position3D::new(1.0, 0.0, 0.0), audio_data.as_slice()),
985 (Position3D::new(-1.0, 0.0, 0.0), audio_data.as_slice()),
986 ];
987
988 let result =
989 optimizer.process_mobile_audio(&mut processor, &audio_data, listener_pos, &sources);
990 assert!(result.is_ok());
991
992 let output = result.unwrap();
993 assert_eq!(output.len(), audio_data.len());
994 }
995
996 #[test]
997 fn test_metrics_collection() {
998 let config = MobileConfig::default();
999 let device = MobileDevice::default();
1000 let optimizer = MobileOptimizer::new(config, device);
1001
1002 let metrics = optimizer.get_metrics();
1003 assert!(metrics.cpu_usage >= 0.0);
1004 assert!(metrics.quality_level >= 0.0 && metrics.quality_level <= 1.0);
1005 }
1006}