1use aios_spec::traits::PrivacySanitizer;
7use aios_spec::{
8 AppTransitionRawEvent, BinderTxEvent, FsAccessEvent, FsActivityType, InteractionType,
9 NotificationRawEvent, ProcStateEvent, RawEvent, SanitizedEvent, SanitizedEventType, ScriptHint,
10 SourceTier, TextHint,
11};
12use uuid::Uuid;
13
14use crate::text_analysis::{analyze_text, classify_extension, extract_semantic_hints};
15
16pub struct DefaultPrivacyAirGap;
18
19impl PrivacySanitizer for DefaultPrivacyAirGap {
20 fn sanitize(&self, raw: RawEvent) -> SanitizedEvent {
21 match raw {
22 RawEvent::AppTransition(e) => sanitize_app_transition(e),
23 RawEvent::BinderTransaction(e) => sanitize_binder(e),
24 RawEvent::ProcStateChange(e) => sanitize_proc(e),
25 RawEvent::FileSystemAccess(e) => sanitize_fs(e),
26 RawEvent::NotificationPosted(e) => sanitize_notification(e),
27 RawEvent::NotificationInteraction(e) => SanitizedEvent {
28 event_id: new_id(),
29 timestamp_ms: e.timestamp_ms,
30 event_type: SanitizedEventType::Notification {
31 source_package: e.package_name.clone(),
32 category: None,
33 channel_id: None,
34 title_hint: TextHint {
35 length_chars: 0,
36 script: ScriptHint::Unknown,
37 is_emoji_only: false,
38 },
39 text_hint: TextHint {
40 length_chars: 0,
41 script: ScriptHint::Unknown,
42 is_emoji_only: false,
43 },
44 semantic_hints: vec![],
45 is_ongoing: false,
46 group_key: None,
55 },
56 source_tier: SourceTier::PublicApi,
57 app_package: Some(e.package_name),
58 uid: None,
59 },
60 RawEvent::ScreenState(e) => SanitizedEvent {
61 event_id: new_id(),
62 timestamp_ms: e.timestamp_ms,
63 event_type: SanitizedEventType::Screen { state: e.state },
64 source_tier: SourceTier::PublicApi,
65 app_package: None,
66 uid: None,
67 },
68 RawEvent::SystemState(e) => SanitizedEvent {
69 event_id: new_id(),
70 timestamp_ms: e.timestamp_ms,
71 event_type: SanitizedEventType::SystemStatus {
72 battery_pct: e.battery_pct,
73 is_charging: e.is_charging,
74 network: e.network,
75 ringer_mode: e.ringer_mode,
76 location_type: e.location_type,
77 headphone_connected: e.headphone_connected,
78 },
79 source_tier: SourceTier::PublicApi,
80 app_package: None,
81 uid: None,
82 },
83 }
84 }
85}
86
87fn sanitize_app_transition(e: AppTransitionRawEvent) -> SanitizedEvent {
90 SanitizedEvent {
91 event_id: new_id(),
92 timestamp_ms: e.timestamp_ms,
93 event_type: SanitizedEventType::AppTransition {
94 package_name: e.package_name.clone(),
95 activity_class: e.activity_class,
96 transition: e.transition,
97 },
98 source_tier: SourceTier::PublicApi,
99 app_package: Some(e.package_name),
100 uid: None,
101 }
102}
103
104fn sanitize_binder(e: BinderTxEvent) -> SanitizedEvent {
105 let interaction_type = match e.target_method.as_str() {
106 m if m.contains("enqueueNotification") => InteractionType::NotifyPost,
107 m if m.contains("startActivity") || m.contains("startActivityAsUser") => {
108 InteractionType::ActivityLaunch
109 },
110 m if m.contains("share") || m.contains("sendIntent") => InteractionType::ShareIntent,
111 m if m.contains("bindService") || m.contains("bindIsolatedService") => {
112 InteractionType::ServiceBind
113 },
114 _ => {
115 return SanitizedEvent {
116 event_id: new_id(),
117 timestamp_ms: e.timestamp_ms,
118 event_type: SanitizedEventType::InterAppInteraction {
119 source_package: None,
120 target_service: e.target_service,
121 interaction_type: InteractionType::ServiceBind,
122 },
123 source_tier: SourceTier::Daemon,
124 app_package: None,
125 uid: Some(e.source_uid),
126 };
127 },
128 };
129
130 SanitizedEvent {
131 event_id: new_id(),
132 timestamp_ms: e.timestamp_ms,
133 event_type: SanitizedEventType::InterAppInteraction {
134 source_package: None,
135 target_service: e.target_service,
136 interaction_type,
137 },
138 source_tier: SourceTier::Daemon,
139 app_package: None,
140 uid: Some(e.source_uid),
141 }
142}
143
144fn sanitize_proc(e: ProcStateEvent) -> SanitizedEvent {
145 SanitizedEvent {
146 event_id: new_id(),
147 timestamp_ms: e.timestamp_ms,
148 event_type: SanitizedEventType::ProcessResource {
149 pid: e.pid,
150 package_name: e.package_name.clone(),
151 vm_rss_mb: (e.vm_rss_kb / 1024) as u32,
152 vm_swap_mb: (e.vm_swap_kb / 1024) as u32,
153 thread_count: e.threads,
154 oom_score: e.oom_score,
155 },
156 source_tier: SourceTier::Daemon,
157 app_package: e.package_name,
158 uid: Some(e.uid),
159 }
160}
161
162fn sanitize_fs(e: FsAccessEvent) -> SanitizedEvent {
163 let ext_cat = classify_extension(&e.file_path);
164 SanitizedEvent {
165 event_id: new_id(),
166 timestamp_ms: e.timestamp_ms,
167 event_type: SanitizedEventType::FileActivity {
168 package_name: None,
169 extension_category: ext_cat,
170 activity_type: match e.access_type {
171 aios_spec::FsAccessType::OpenRead => FsActivityType::Read,
172 aios_spec::FsAccessType::OpenWrite => FsActivityType::Write,
173 aios_spec::FsAccessType::Create => FsActivityType::Create,
174 aios_spec::FsAccessType::Delete => FsActivityType::Delete,
175 },
176 is_hot_file: false,
177 },
178 source_tier: SourceTier::Daemon,
179 app_package: None,
180 uid: Some(e.uid),
181 }
182}
183
184fn sanitize_notification(e: NotificationRawEvent) -> SanitizedEvent {
185 let title_hint = analyze_text(&e.raw_title);
186 let text_hint = analyze_text(&e.raw_text);
187 let semantic_hints = extract_semantic_hints(&e.raw_title, &e.raw_text);
188
189 SanitizedEvent {
190 event_id: new_id(),
191 timestamp_ms: e.timestamp_ms,
192 event_type: SanitizedEventType::Notification {
193 source_package: e.package_name.clone(),
194 category: e.category,
195 channel_id: e.channel_id,
196 title_hint,
197 text_hint,
198 semantic_hints,
199 is_ongoing: e.is_ongoing,
200 group_key: e.group_key,
201 },
202 source_tier: SourceTier::PublicApi,
203 app_package: Some(e.package_name),
204 uid: None,
205 }
206}
207
208fn new_id() -> String {
209 Uuid::new_v4().to_string()
210}