1use crate::device::ADB;
2use crate::error::{ADBError, ADBResult};
3use log::{debug, info, warn};
4use regex::Regex;
5use std::process::Command;
6use std::str::FromStr;
7use std::time::{Duration, Instant};
8
9#[derive(Debug, Clone)]
11pub struct PackageInfo {
12 pub package_name: String,
13 pub version_name: Option<String>,
14 pub version_code: Option<i32>,
15 pub install_time: Option<String>,
16 pub update_time: Option<String>,
17 pub uid: Option<i32>,
18 pub target_sdk: Option<i32>,
19 pub min_sdk: Option<i32>,
20 pub flags: Vec<String>,
21 pub permissions: Vec<String>,
22 pub activities: Vec<String>,
23 pub services: Vec<String>,
24 pub install_source: Option<String>,
25 pub raw_data: Option<String>,
26}
27
28impl PackageInfo {
29 pub fn new(package_name: &str) -> Self {
31 Self {
32 package_name: package_name.to_string(),
33 version_name: None,
34 version_code: None,
35 install_time: None,
36 update_time: None,
37 uid: None,
38 target_sdk: None,
39 min_sdk: None,
40 flags: Vec::new(),
41 permissions: Vec::new(),
42 activities: Vec::new(),
43 services: Vec::new(),
44 install_source: None,
45 raw_data: None,
46 }
47 }
48
49 pub fn builder(package_name: &str) -> crate::app::PackageInfoBuilder {
51 crate::app::PackageInfoBuilder::new(package_name)
52 }
53}
54
55#[derive(Debug)]
57pub struct PackageInfoBuilder {
58 info: PackageInfo,
59}
60
61impl PackageInfoBuilder {
62 pub fn new(package_name: &str) -> Self {
63 Self {
64 info: PackageInfo::new(package_name),
65 }
66 }
67
68 pub fn with_version_name(mut self, version: &str) -> Self {
69 self.info.version_name = Some(version.to_string());
70 self
71 }
72
73 pub fn with_version_code(mut self, code: i32) -> Self {
74 self.info.version_code = Some(code);
75 self
76 }
77
78 pub fn with_install_time(mut self, time: &str) -> Self {
79 self.info.install_time = Some(time.to_string());
80 self
81 }
82
83 pub fn with_update_time(mut self, time: &str) -> Self {
84 self.info.update_time = Some(time.to_string());
85 self
86 }
87
88 pub fn with_uid(mut self, uid: i32) -> Self {
89 self.info.uid = Some(uid);
90 self
91 }
92
93 pub fn with_target_sdk(mut self, sdk: i32) -> Self {
94 self.info.target_sdk = Some(sdk);
95 self
96 }
97
98 pub fn with_min_sdk(mut self, sdk: i32) -> Self {
99 self.info.min_sdk = Some(sdk);
100 self
101 }
102
103 pub fn add_flag(mut self, flag: &str) -> Self {
104 self.info.flags.push(flag.to_string());
105 self
106 }
107
108 pub fn add_permission(mut self, permission: &str) -> Self {
109 self.info.permissions.push(permission.to_string());
110 self
111 }
112
113 pub fn add_activity(mut self, activity: &str) -> Self {
114 self.info.activities.push(activity.to_string());
115 self
116 }
117
118 pub fn add_service(mut self, service: &str) -> Self {
119 self.info.services.push(service.to_string());
120 self
121 }
122
123 pub fn with_install_source(mut self, source: &str) -> Self {
124 self.info.install_source = Some(source.to_string());
125 self
126 }
127
128 pub fn with_raw_data(mut self, data: &str) -> Self {
129 self.info.raw_data = Some(data.to_string());
130 self
131 }
132
133 pub fn build(self) -> PackageInfo {
134 self.info
135 }
136}
137
138impl ADB {
139 pub fn get_package_info(&self, device_id: &str, package_name: &str) -> ADBResult<PackageInfo> {
141 self.get_package_info_enhanced(device_id, package_name)
142 }
143
144 pub fn get_package_info_enhanced(
146 &self,
147 device_id: &str,
148 package_name: &str,
149 ) -> ADBResult<PackageInfo> {
150 let command = format!("dumpsys package {}", package_name);
151 let output = self.shell(device_id, &command)?;
152
153 let mut info = PackageInfo::new(package_name);
155 info.raw_data = Some(output.clone());
156
157 if let Some(re_version) = Regex::new(r"versionName=([^\s]+)").ok() {
159 if let Some(caps) = re_version.captures(&output) {
160 if let Some(ver) = caps.get(1) {
161 info.version_name = Some(ver.as_str().to_string());
162 }
163 }
164 }
165
166 if let Some(re_code) = Regex::new(r"versionCode=(\d+)").ok() {
167 if let Some(caps) = re_code.captures(&output) {
168 if let Some(code) = caps.get(1) {
169 if let Ok(code_int) = i32::from_str(code.as_str()) {
170 info.version_code = Some(code_int);
171 }
172 }
173 }
174 }
175
176 if let Some(re_install) = Regex::new(r"firstInstallTime=([^\s]+)").ok() {
178 if let Some(caps) = re_install.captures(&output) {
179 if let Some(time) = caps.get(1) {
180 info.install_time = Some(time.as_str().to_string());
181 }
182 }
183 }
184
185 if let Some(re_update) = Regex::new(r"lastUpdateTime=([^\s]+)").ok() {
187 if let Some(caps) = re_update.captures(&output) {
188 if let Some(time) = caps.get(1) {
189 info.update_time = Some(time.as_str().to_string());
190 }
191 }
192 }
193
194 if let Some(re_uid) = Regex::new(r"userId=(\d+)").ok() {
196 if let Some(caps) = re_uid.captures(&output) {
197 if let Some(uid) = caps.get(1) {
198 if let Ok(uid_int) = i32::from_str(uid.as_str()) {
199 info.uid = Some(uid_int);
200 }
201 }
202 }
203 }
204
205 if let Some(re_target_sdk) = Regex::new(r"targetSdk=(\d+)").ok() {
207 if let Some(caps) = re_target_sdk.captures(&output) {
208 if let Some(sdk) = caps.get(1) {
209 if let Ok(sdk_int) = i32::from_str(sdk.as_str()) {
210 info.target_sdk = Some(sdk_int);
211 }
212 }
213 }
214 }
215
216 if let Some(re_min_sdk) = Regex::new(r"minSdk=(\d+)").ok() {
217 if let Some(caps) = re_min_sdk.captures(&output) {
218 if let Some(sdk) = caps.get(1) {
219 if let Ok(sdk_int) = i32::from_str(sdk.as_str()) {
220 info.min_sdk = Some(sdk_int);
221 }
222 }
223 }
224 }
225
226 if let Some(re_install_source) = Regex::new(r"installerPackageName=([^\s]+)").ok() {
228 if let Some(caps) = re_install_source.captures(&output) {
229 if let Some(source) = caps.get(1) {
230 info.install_source = Some(source.as_str().to_string());
231 }
232 }
233 }
234
235 let lines = output.lines().collect::<Vec<&str>>();
237 let mut in_permissions = false;
238
239 for line in &lines {
240 if line.contains("requested permissions:") {
241 in_permissions = true;
242 continue;
243 } else if in_permissions && line.trim().is_empty() {
244 in_permissions = false;
245 continue;
246 }
247
248 if in_permissions && line.contains(": granted=") {
249 if let Some(perm) = line.split(':').next() {
250 let perm = perm.trim();
251 if !perm.is_empty() {
252 info.permissions.push(perm.to_string());
253 }
254 }
255 }
256 }
257
258 let mut in_activities = false;
260 for line in &lines {
261 if line.contains("Activity Resolver Table:") {
262 in_activities = true;
263 continue;
264 } else if in_activities && line.trim().is_empty() {
265 in_activities = false;
266 continue;
267 }
268
269 if in_activities && line.contains(package_name) {
270 if let Some(activity) = Regex::new(r"/([^/\s]+)")
271 .ok()
272 .and_then(|re| re.captures(line))
273 .and_then(|caps| caps.get(1))
274 .map(|m| m.as_str())
275 {
276 info.activities.push(activity.to_string());
277 }
278 }
279 }
280
281 Ok(info)
282 }
283
284 pub fn is_package_running(
286 &self,
287 device_id: &str,
288 package_name: &str,
289 ) -> ADBResult<(bool, Option<i32>)> {
290 if let Ok(Some(pid)) = self.get_pid(device_id, package_name) {
292 return Ok((true, Some(pid)));
293 }
294
295 let command = format!("ps -A | grep -i {}", package_name);
297 let output = self.shell(device_id, &command)?;
298
299 if !output.trim().is_empty() {
301 let lines = output.lines().next();
302 if let Some(line) = lines {
303 let parts: Vec<&str> = line.split_whitespace().collect();
304 if parts.len() > 1 {
305 if let Ok(pid) = i32::from_str(parts[1]) {
306 debug!("找到 PID: {}", pid);
307 return Ok((true, Some(pid)));
308 }
309 }
310 }
311 return Ok((true, None));
312 }
313
314 let command = format!("dumpsys activity services | grep -i {}", package_name);
316 let output = self.shell(device_id, &command)?;
317 if !output.trim().is_empty() {
318 return Ok((true, None));
319 }
320
321 Ok((false, None))
322 }
323
324 pub fn get_pid(&self, device_id: &str, package_name: &str) -> ADBResult<Option<i32>> {
326 let methods = [
328 format!("pidof {}", package_name),
330 format!("ps -A | grep {} | grep -v grep", package_name),
332 format!("ps -A -o PID,NAME | grep {} | grep -v grep", package_name),
334 format!(
336 "ps -A -o PID,CMDLINE | grep {} | grep -v grep",
337 package_name
338 ),
339 ];
340
341 for method in &methods {
342 let output = self.shell(device_id, method);
343
344 if let Ok(pid_str) = output {
345 if pid_str.trim().is_empty() {
346 continue;
347 }
348
349 if let Some(line) = pid_str.lines().next() {
351 let parts: Vec<&str> = line.split_whitespace().collect();
352
353 if parts.is_empty() {
355 continue;
356 }
357
358 if method.starts_with("pidof") {
360 if let Ok(pid) = i32::from_str(parts[0]) {
361 debug!("通过 pidof 获取 PID: {}", pid);
362 return Ok(Some(pid));
363 }
364 }
365 else if parts.len() > 1 {
367 if let Ok(pid) = i32::from_str(parts[0]) {
368 debug!("通过 ps 获取 PID: {}", pid);
369 return Ok(Some(pid));
370 }
371 }
372 }
373 }
374 }
375
376 debug!("无法找到包 {} 的 PID", package_name);
377 Ok(None)
378 }
379
380 pub fn start_app_and_wait(
382 &self,
383 device_id: &str,
384 package_name: &str,
385 activity: Option<&str>,
386 timeout_secs: Option<u64>,
387 ) -> ADBResult<bool> {
388 let timeout = timeout_secs.unwrap_or(30);
389 let start_time = Instant::now();
390
391 let command = if let Some(act) = activity {
393 format!("am start -W -n {}/{}", package_name, act)
394 } else {
395 format!(
396 "monkey -p {} -c android.intent.category.LAUNCHER 1",
397 package_name
398 )
399 };
400
401 let output = self.shell(device_id, &command)?;
403
404 if output.contains("Error") || output.contains("Exception") || output.contains("failed") {
406 debug!("应用程序启动失败: {}", output);
407 return Ok(false);
408 }
409
410 info!("等待应用 {} 完全启动...", package_name);
412
413 loop {
414 if start_time.elapsed().as_secs() > timeout {
415 warn!("等待应用启动超时 ({} 秒)", timeout);
416 return Ok(false);
417 }
418
419 let current_app_cmd = "dumpsys window windows | grep -E 'mCurrentFocus'";
421 let current_app = self.shell(device_id, current_app_cmd)?;
422
423 if current_app.contains(package_name) {
424 debug!("应用 {} 已成功启动并处于前台", package_name);
425 return Ok(true);
426 }
427
428 std::thread::sleep(Duration::from_millis(500));
430 }
431 }
432
433 pub fn start_app(
435 &self,
436 device_id: &str,
437 package_name: &str,
438 activity: Option<&str>,
439 ) -> ADBResult<bool> {
440 let command = if let Some(act) = activity {
441 format!("am start -n {}/{}", package_name, act)
442 } else {
443 format!(
444 "monkey -p {} -c android.intent.category.LAUNCHER 1",
445 package_name
446 )
447 };
448
449 let output = self.shell(device_id, &command)?;
450
451 if output.contains("Error") || output.contains("Exception") || output.contains("failed") {
453 debug!("启动应用程序失败: {}", output);
454 return Ok(false);
455 } else {
456 debug!("应用程序启动命令执行成功");
457 return Ok(true);
458 }
459 }
460
461 pub fn stop_app(&self, device_id: &str, package_name: &str) -> ADBResult<()> {
463 let command = format!("am force-stop {}", package_name);
464 self.shell(device_id, &command)?;
465 Ok(())
466 }
467
468 pub fn install_app(&self, device_id: &str, apk_path: &str) -> ADBResult<()> {
470 self.with_retry(|| {
471 let mut cmd = Command::new(&self.config.path);
472 if !device_id.is_empty() {
473 cmd.arg("-s").arg(device_id);
474 }
475
476 let output = cmd
477 .arg("install")
478 .arg("-r") .arg(apk_path)
480 .output()
481 .map_err(|e| ADBError::CommandError(format!("无法安装 APK: {}", e)))?;
482
483 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
484 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
485
486 if !output.status.success() || stdout.contains("Failure") || stderr.contains("Failure")
487 {
488 let error_msg = if stdout.contains("Failure") {
489 format!("APK 安装失败: {}", stdout)
490 } else if !stderr.is_empty() {
491 format!("APK 安装失败: {}", stderr)
492 } else {
493 "APK 安装失败,未知错误".to_string()
494 };
495
496 return Err(ADBError::CommandError(error_msg));
497 }
498
499 debug!("成功安装 APK: {}", apk_path);
500 Ok(())
501 })
502 }
503
504 pub fn uninstall_app(&self, device_id: &str, package_name: &str) -> ADBResult<()> {
506 self.with_retry(|| {
507 let mut cmd = Command::new(&self.config.path);
508 if !device_id.is_empty() {
509 cmd.arg("-s").arg(device_id);
510 }
511
512 let output = cmd
513 .arg("uninstall")
514 .arg(package_name)
515 .output()
516 .map_err(|e| {
517 ADBError::CommandError(format!("无法卸载应用: {}", e))
518 })?;
519
520 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
521 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
522
523 if !output.status.success() || stdout.contains("Failure") || stderr.contains("Failure")
524 {
525 let error_msg = if stdout.contains("Failure") {
526 format!("应用卸载失败: {}", stdout)
527 } else if !stderr.is_empty() {
528 format!("应用卸载失败: {}", stderr)
529 } else {
530 "应用卸载失败,未知错误".to_string()
531 };
532
533 return Err(ADBError::CommandError(error_msg));
534 }
535
536 debug!("成功卸载应用: {}", package_name);
537 Ok(())
538 })
539 }
540
541 pub fn uninstall_app_smart(
543 &self,
544 device_id: &str,
545 package_name: &str,
546 keep_data: bool,
547 ) -> ADBResult<()> {
548 let _ = self.stop_app(device_id, package_name);
550
551 if keep_data {
553 let _ = self.shell(device_id, &format!("pm clear {}", package_name));
554 }
555
556 self.with_retry(|| {
558 let mut cmd = Command::new(&self.config.path);
559 if !device_id.is_empty() {
560 cmd.arg("-s").arg(device_id);
561 }
562
563 let mut args = vec!["uninstall"];
564
565 if keep_data {
566 args.push("-k"); }
568
569 args.push(package_name);
570
571 for arg in args {
572 cmd.arg(arg);
573 }
574
575 let output = cmd
576 .output()
577 .map_err(|e| ADBError::CommandError(format!("卸载包失败: {}", e)))?;
578
579 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
580 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
581
582 if !output.status.success() || stdout.contains("Failure") || stderr.contains("Failure")
583 {
584 let error_msg = if stdout.contains("Failure") {
585 format!("卸载应用失败: {}", stdout)
586 } else if !stderr.is_empty() {
587 format!("卸载应用失败: {}", stderr)
588 } else {
589 "卸载应用失败,未知错误".to_string()
590 };
591
592 return Err(ADBError::CommandError(error_msg));
593 }
594
595 debug!("成功卸载应用: {}", package_name);
596 Ok(())
597 })
598 }
599
600 pub fn list_packages(
602 &self,
603 device_id: &str,
604 only_system: bool,
605 only_third_party: bool,
606 ) -> ADBResult<Vec<String>> {
607 let mut command = "pm list packages".to_string();
608
609 if only_system {
610 command.push_str(" -s");
611 } else if only_third_party {
612 command.push_str(" -3");
613 }
614
615 let output = self.shell(device_id, &command)?;
616 let mut packages = Vec::new();
617
618 for line in output.lines() {
619 if line.starts_with("package:") {
620 let package = line.trim_start_matches("package:").trim();
621 packages.push(package.to_string());
622 }
623 }
624
625 Ok(packages)
626 }
627}