adb_kit/
app.rs

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/// 包信息结构体
10#[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    /// 创建新的包信息实例
30    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    /// 创建一个 PackageInfo 构建器
50    pub fn builder(package_name: &str) -> crate::app::PackageInfoBuilder {
51        crate::app::PackageInfoBuilder::new(package_name)
52    }
53}
54
55/// 包信息构建器
56#[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    /// 获取包信息 (增强版本)
140    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    /// 获取包信息 (增强版本)
145    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        // 存储原始输出以便调试和完整访问
154        let mut info = PackageInfo::new(package_name);
155        info.raw_data = Some(output.clone());
156
157        // 使用正则表达式解析更多信息
158        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        // 提取首次安装时间
177        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        // 提取最后更新时间
186        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        // 提取 UID
195        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        // 提取 SDK 版本信息
206        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        // 提取安装来源
227        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        // 提取权限
236        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        // 提取 Activities
259        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    /// 检查包是否运行
285    pub fn is_package_running(
286        &self,
287        device_id: &str,
288        package_name: &str,
289    ) -> ADBResult<(bool, Option<i32>)> {
290        // 先尝试使用更准确的方法检查是否运行中
291        if let Ok(Some(pid)) = self.get_pid(device_id, package_name) {
292            return Ok((true, Some(pid)));
293        }
294
295        // 备用方法 - 使用 ps 命令
296        let command = format!("ps -A | grep -i {}", package_name);
297        let output = self.shell(device_id, &command)?;
298
299        // 如果找到输出,则尝试提取 PID
300        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        // 最后的检查 - 使用 dumpsys
315        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    /// 获取进程 ID
325    pub fn get_pid(&self, device_id: &str, package_name: &str) -> ADBResult<Option<i32>> {
326        // 尝试多种方法获取 PID
327        let methods = [
328            // 方法1: 使用 pidof (适用于较新的 Android 系统)
329            format!("pidof {}", package_name),
330            // 方法2: 使用 ps 带有 grep 过滤 (兼容不同 Android 版本)
331            format!("ps -A | grep {} | grep -v grep", package_name),
332            // 方法3: 使用 ps -o 带有 NAME 过滤 (Android 8+)
333            format!("ps -A -o PID,NAME | grep {} | grep -v grep", package_name),
334            // 方法4: 使用 ps -o 带有 CMDLINE 过滤 (用于某些特殊情况)
335            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                // 尝试解析 PID
350                if let Some(line) = pid_str.lines().next() {
351                    let parts: Vec<&str> = line.split_whitespace().collect();
352
353                    // 处理不同命令的不同输出格式
354                    if parts.is_empty() {
355                        continue;
356                    }
357
358                    // pidof 直接返回 PID
359                    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                    // ps 命令通常在第二列返回 PID
366                    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    /// 启动一个应用并等待直到完全启动
381    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        // 构建启动命令
392        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        // 执行启动命令
402        let output = self.shell(device_id, &command)?;
403
404        // 检查是否有即时错误
405        if output.contains("Error") || output.contains("Exception") || output.contains("failed") {
406            debug!("应用程序启动失败: {}", output);
407            return Ok(false);
408        }
409
410        // 等待应用完全启动
411        info!("等待应用 {} 完全启动...", package_name);
412
413        loop {
414            if start_time.elapsed().as_secs() > timeout {
415                warn!("等待应用启动超时 ({} 秒)", timeout);
416                return Ok(false);
417            }
418
419            // 检查应用是否在前台运行
420            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            // 短暂休眠后再次检查
429            std::thread::sleep(Duration::from_millis(500));
430        }
431    }
432
433    /// 启动应用程序
434    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        // 分析输出以确定启动是否成功
452        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    /// 强制停止应用程序
462    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    /// 安装应用程序
469    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") // Replace existing app
479                .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    /// 卸载应用程序
505    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    /// 智能卸载应用(清除数据和缓存)
542    pub fn uninstall_app_smart(
543        &self,
544        device_id: &str,
545        package_name: &str,
546        keep_data: bool,
547    ) -> ADBResult<()> {
548        // 首先停止应用
549        let _ = self.stop_app(device_id, package_name);
550
551        // 如果需要保留数据,先清除缓存
552        if keep_data {
553            let _ = self.shell(device_id, &format!("pm clear {}", package_name));
554        }
555
556        // 执行卸载
557        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"); // 保留数据和缓存文件
567            }
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    /// 获取设备上已安装的应用列表
601    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}