diskforge_core/rules/
android.rs1use std::path::{Path, PathBuf};
2
3use crate::sizing;
4use crate::types::{Category, CleanableItem, Risk};
5
6pub fn scan_android(home: &str) -> Vec<CleanableItem> {
8 let mut items = Vec::new();
9 if let Some(sdk) = find_android_sdk(home) {
10 scan_ndk_versions(&sdk, &mut items);
11 scan_platforms(&sdk, &mut items);
12 scan_build_tools(&sdk, &mut items);
13 scan_system_images(&sdk, &mut items);
14 }
15 scan_avds(home, &mut items);
16 items
17}
18
19fn find_android_sdk(home: &str) -> Option<PathBuf> {
21 for var in &["ANDROID_HOME", "ANDROID_SDK_ROOT"] {
22 if let Ok(val) = std::env::var(var) {
23 let p = PathBuf::from(val);
24 if p.exists() {
25 return Some(p);
26 }
27 }
28 }
29 [
30 format!("{home}/Library/Android/sdk"),
31 format!("{home}/android-sdk"),
32 ]
33 .into_iter()
34 .map(PathBuf::from)
35 .find(|path| path.exists())
36}
37
38fn scan_ndk_versions(sdk: &Path, items: &mut Vec<CleanableItem>) {
40 let ndk_dir = sdk.join("ndk");
41 if !ndk_dir.exists() {
42 return;
43 }
44 let Ok(entries) = std::fs::read_dir(&ndk_dir) else {
45 return;
46 };
47 let mut versions: Vec<(PathBuf, u64)> = entries
48 .flatten()
49 .filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
50 .map(|e| {
51 let p = e.path();
52 let size = sizing::dir_size(&p);
53 (p, size)
54 })
55 .filter(|(_, size)| *size > 1024 * 1024)
56 .collect();
57
58 versions.sort_by(|a, b| b.0.file_name().cmp(&a.0.file_name()));
60
61 for (i, (path, size)) in versions.into_iter().enumerate() {
62 let version = path
63 .file_name()
64 .map(|n| n.to_string_lossy().to_string())
65 .unwrap_or_default();
66 let risk = if i == 0 { Risk::Medium } else { Risk::Low };
67 let last_modified = sizing::dir_last_modified(&path);
68 items.push(CleanableItem {
69 category: Category::Android,
70 path,
71 size,
72 risk,
73 regenerates: false,
74 regeneration_hint: Some("Android Studio → SDK Manager → NDK to reinstall".into()),
75 last_modified,
76 description: format!("Android NDK {version}"),
77 cleanup_command: None,
78 });
79 }
80}
81
82fn scan_platforms(sdk: &Path, items: &mut Vec<CleanableItem>) {
84 let dir = sdk.join("platforms");
85 if !dir.exists() {
86 return;
87 }
88 let Ok(entries) = std::fs::read_dir(&dir) else {
89 return;
90 };
91 for entry in entries.flatten() {
92 let path = entry.path();
93 if !path.is_dir() {
94 continue;
95 }
96 let size = sizing::dir_size(&path);
97 if size < 1024 * 1024 {
98 continue;
99 }
100 let name = path
101 .file_name()
102 .map(|n| n.to_string_lossy().to_string())
103 .unwrap_or_default();
104 let last_modified = sizing::dir_last_modified(&path);
105 items.push(CleanableItem {
106 category: Category::Android,
107 path,
108 size,
109 risk: Risk::Low,
110 regenerates: false,
111 regeneration_hint: Some(
112 "Android Studio → SDK Manager → SDK Platforms to reinstall".into(),
113 ),
114 last_modified,
115 description: format!("Android Platform: {name}"),
116 cleanup_command: None,
117 });
118 }
119}
120
121fn scan_build_tools(sdk: &Path, items: &mut Vec<CleanableItem>) {
123 let dir = sdk.join("build-tools");
124 if !dir.exists() {
125 return;
126 }
127 let Ok(entries) = std::fs::read_dir(&dir) else {
128 return;
129 };
130 let mut versions: Vec<(PathBuf, u64)> = entries
131 .flatten()
132 .filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
133 .map(|e| {
134 let p = e.path();
135 let size = sizing::dir_size(&p);
136 (p, size)
137 })
138 .filter(|(_, size)| *size > 1024 * 1024)
139 .collect();
140
141 versions.sort_by(|a, b| b.0.file_name().cmp(&a.0.file_name()));
142
143 for (i, (path, size)) in versions.into_iter().enumerate() {
144 let version = path
145 .file_name()
146 .map(|n| n.to_string_lossy().to_string())
147 .unwrap_or_default();
148 let risk = if i == 0 { Risk::Medium } else { Risk::Low };
149 let last_modified = sizing::dir_last_modified(&path);
150 items.push(CleanableItem {
151 category: Category::Android,
152 path,
153 size,
154 risk,
155 regenerates: false,
156 regeneration_hint: Some("Android Studio → SDK Manager → SDK Tools to reinstall".into()),
157 last_modified,
158 description: format!("Android Build Tools {version}"),
159 cleanup_command: None,
160 });
161 }
162}
163
164fn scan_system_images(sdk: &Path, items: &mut Vec<CleanableItem>) {
166 let dir = sdk.join("system-images");
167 if !dir.exists() {
168 return;
169 }
170 let Ok(api_entries) = std::fs::read_dir(&dir) else {
172 return;
173 };
174 for api_entry in api_entries.flatten() {
175 let api_path = api_entry.path();
176 if !api_path.is_dir() {
177 continue;
178 }
179 let api_name = api_path
180 .file_name()
181 .map(|n| n.to_string_lossy().to_string())
182 .unwrap_or_default();
183 let Ok(variant_entries) = std::fs::read_dir(&api_path) else {
184 continue;
185 };
186 for variant_entry in variant_entries.flatten() {
187 let variant_path = variant_entry.path();
188 if !variant_path.is_dir() {
189 continue;
190 }
191 let variant_name = variant_path
192 .file_name()
193 .map(|n| n.to_string_lossy().to_string())
194 .unwrap_or_default();
195 let Ok(arch_entries) = std::fs::read_dir(&variant_path) else {
196 continue;
197 };
198 for arch_entry in arch_entries.flatten() {
199 let arch_path = arch_entry.path();
200 if !arch_path.is_dir() {
201 continue;
202 }
203 let size = sizing::dir_size(&arch_path);
204 if size < 100 * 1024 * 1024 {
205 continue;
207 }
208 let arch_name = arch_path
209 .file_name()
210 .map(|n| n.to_string_lossy().to_string())
211 .unwrap_or_default();
212 let last_modified = sizing::dir_last_modified(&arch_path);
213 items.push(CleanableItem {
214 category: Category::Android,
215 path: arch_path,
216 size,
217 risk: Risk::Low,
218 regenerates: false,
219 regeneration_hint: Some(
220 "Android Studio → SDK Manager → SDK Images to reinstall".into(),
221 ),
222 last_modified,
223 description: format!(
224 "Android System Image: {api_name}/{variant_name}/{arch_name}"
225 ),
226 cleanup_command: None,
227 });
228 }
229 }
230 }
231}
232
233fn scan_avds(home: &str, items: &mut Vec<CleanableItem>) {
235 let avd_dir = PathBuf::from(format!("{home}/.android/avd"));
236 if !avd_dir.exists() {
237 return;
238 }
239 let Ok(entries) = std::fs::read_dir(&avd_dir) else {
240 return;
241 };
242 for entry in entries.flatten() {
243 let path = entry.path();
244 if path.extension().map(|e| e != "avd").unwrap_or(true) {
245 continue;
246 }
247 let size = sizing::dir_size(&path);
248 if size < 1024 * 1024 {
249 continue;
250 }
251 let name = path
252 .file_stem()
253 .map(|n| format!("Android AVD: {}", n.to_string_lossy()))
254 .unwrap_or_else(|| "Android AVD".into());
255 let last_modified = sizing::dir_last_modified(&path);
256 items.push(CleanableItem {
257 category: Category::Android,
258 path,
259 size,
260 risk: Risk::Medium,
261 regenerates: false,
262 regeneration_hint: Some("Android Studio → Device Manager to recreate".into()),
263 last_modified,
264 description: name,
265 cleanup_command: None,
266 });
267 }
268}