use crate::core::uad_lists::PackageState; use crate::core::utils::ANDROID_SERIAL; use crate::gui::views::list::PackageInfo; use crate::gui::widgets::package_row::PackageRow; use regex::Regex; use retry::{delay::Fixed, retry, OperationResult}; use serde::{Deserialize, Serialize}; use static_init::dynamic; use std::collections::HashSet; use std::env; use std::process::Command; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; #[dynamic] static RE: Regex = Regex::new(r"\n(\S+)\s+device").unwrap(); #[derive(Debug, Clone, PartialEq, Eq)] pub struct Phone { pub model: String, pub android_sdk: u8, pub user_list: Vec, pub adb_id: String, } impl Default for Phone { fn default() -> Self { Self { model: "fetching devices...".to_string(), android_sdk: 0, user_list: vec![], adb_id: String::default(), } } } impl std::fmt::Display for Phone { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.model) } } #[derive(Default, Debug, Clone, PartialEq, Eq, Copy)] pub struct User { pub id: u16, pub index: usize, pub protected: bool, } impl std::fmt::Display for User { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "user {}", self.id) } } pub fn adb_shell_command(shell: bool, args: &str) -> Result { let adb_command = if shell { vec!["shell", args] } else { vec![args] }; let mut command = Command::new("adb"); command.args(adb_command); #[cfg(target_os = "windows")] let command = command.creation_flags(0x08000000); // do not open a cmd window match command.output() { Err(e) => { error!("ADB: {}", e); Err("ADB was not found".to_string()) } Ok(o) => { if o.status.success() { Ok(String::from_utf8(o.stdout) .map_err(|e| e.to_string())? .trim_end() .to_string()) } else { let stdout = String::from_utf8(o.stdout) .map_err(|e| e.to_string())? .trim_end() .to_string(); let stderr = String::from_utf8(o.stderr) .map_err(|e| e.to_string())? .trim_end() .to_string(); // ADB does really weird things. Some errors are not redirected to stderr let err = if stdout.is_empty() { stderr } else { stdout }; Err(err) } } } } #[derive(Debug, Clone)] pub enum CommandType { PackageManager(PackageInfo), Shell, } /// An enum to contain different variants for errors yielded by ADB. #[derive(Debug, Clone)] pub enum AdbError { Generic(String), } pub async fn perform_adb_commands( action: String, command_type: CommandType, ) -> Result { let label = match &command_type { CommandType::PackageManager(p) => &p.removal, CommandType::Shell => "Shell", }; match adb_shell_command(true, &action) { Ok(o) => { // On old devices, adb commands can return the '0' exit code even if there // is an error. On Android 4.4, ADB doesn't check if the package exists. // It does not return any error if you try to `pm block` a non-existent package. // Some commands are even killed by ADB before finishing and UAD-ng can't catch // the output. if ["Error", "Failure"].iter().any(|&e| o.contains(e)) { return Err(AdbError::Generic(format!( "[{}] {} -> {}", label, action, o ))); } info!("[{}] {} -> {}", label, action, o); Ok(command_type) } Err(err) => { if !err.contains("[not installed for") { return Err(AdbError::Generic(format!( "[{}] {} -> {}", label, action, err ))); } Err(AdbError::Generic(err)) } } } pub fn user_flag(user_id: Option<&User>) -> String { user_id .map(|user| format!(" --user {}", user.id)) .unwrap_or_default() } pub fn list_all_system_packages(user_id: Option<&User>) -> String { let action = format!("pm list packages -s -u{}", user_flag(user_id)); adb_shell_command(true, &action) .unwrap_or_default() .replace("package:", "") } pub fn hashset_system_packages(state: PackageState, user_id: Option<&User>) -> HashSet { let user = user_flag(user_id); let action = match state { PackageState::Enabled => format!("pm list packages -s -e{user}"), PackageState::Disabled => format!("pm list package -s -d{user}"), _ => String::default(), // You probably don't need to use this function for anything else }; adb_shell_command(true, &action) .unwrap_or_default() .replace("package:", "") .lines() .map(String::from) .collect() } // Minimum information for processing adb commands #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct CorePackage { pub name: String, pub state: PackageState, } impl From<&mut PackageRow> for CorePackage { fn from(pr: &mut PackageRow) -> Self { Self { name: pr.name.clone(), state: pr.state, } } } impl From for CorePackage { fn from(pr: PackageRow) -> Self { Self { name: pr.name.clone(), state: pr.state, } } } impl From<&PackageRow> for CorePackage { fn from(pr: &PackageRow) -> Self { Self { name: pr.name.clone(), state: pr.state, } } } pub fn apply_pkg_state_commands( package: &CorePackage, wanted_state: PackageState, selected_user: &User, phone: &Phone, ) -> Vec { // https://github.com/Universal-Debloater-Alliance/universal-android-debloater/wiki/ADB-reference // ALWAYS PUT THE COMMAND THAT CHANGES THE PACKAGE STATE FIRST! let commands = match wanted_state { PackageState::Enabled => { match package.state { PackageState::Disabled => match phone.android_sdk { i if i >= 23 => vec!["pm enable"], _ => vec!["pm enable"], }, PackageState::Uninstalled => match phone.android_sdk { i if i >= 23 => vec!["cmd package install-existing"], 21 | 22 => vec!["pm unhide"], 19 | 20 => vec!["pm unblock", "pm clear"], _ => vec![], // Impossible action already prevented by the GUI }, _ => vec![], } } PackageState::Disabled => match package.state { PackageState::Uninstalled | PackageState::Enabled => match phone.android_sdk { sdk if sdk >= 23 => vec!["pm disable-user", "am force-stop", "pm clear"], _ => vec![], }, _ => vec![], }, PackageState::Uninstalled => match package.state { PackageState::Enabled | PackageState::Disabled => match phone.android_sdk { sdk if sdk >= 23 => vec!["pm uninstall"], // > Android Marshmallow (6.0) 21 | 22 => vec!["pm hide", "pm clear"], // Android Lollipop (5.x) 19 | 20 => vec!["pm block", "pm clear"], // Android KitKat (4.4/4.4W) _ => vec!["pm uninstall"], // Disable mode is unavailable on older devices because the specific ADB commands need root }, _ => vec![], }, PackageState::All => vec![], }; let user = (phone.android_sdk >= 21).then_some(selected_user); request_builder(&commands, &package.name, user) } pub fn request_builder(commands: &[&str], package: &str, user: Option<&User>) -> Vec { #[allow(clippy::option_if_let_else)] match user { Some(u) => commands .iter() .map(|c| format!("{} --user {} {}", c, u.id, package)) .collect(), None => commands.iter().map(|c| format!("{c} {package}")).collect(), } } pub fn get_phone_model() -> String { adb_shell_command(true, "getprop ro.product.model").unwrap_or_else(|err| { println!("ERROR: {err}"); if err.contains("adb: no devices/emulators found") { "no devices/emulators found".to_string() } else { err } }) } pub fn get_android_sdk() -> u8 { adb_shell_command(true, "getprop ro.build.version.sdk").map_or(0, |sdk| sdk.parse().unwrap()) } pub fn get_phone_brand() -> String { format!( "{} {}", adb_shell_command(true, "getprop ro.product.brand") .map(|s| s.trim().to_string()) .unwrap_or_default(), get_phone_model() ) } pub fn is_protected_user(user_id: &str) -> bool { adb_shell_command(true, &format!("pm list packages -s --user {user_id}")).is_err() } pub fn get_user_list() -> Vec { #[dynamic] static RE: Regex = Regex::new(r"\{([0-9]+)").unwrap(); adb_shell_command(true, "pm list users") .map(|users| { RE.find_iter(&users) .enumerate() .map(|(i, u)| User { id: u.as_str()[1..].parse().unwrap(), index: i, protected: is_protected_user(&u.as_str()[1..]), }) .collect() }) .unwrap_or_default() } // getprop ro.serialno pub async fn get_devices_list() -> Vec { retry( Fixed::from_millis(500).take(120), || match adb_shell_command(false, "devices") { Ok(devices) => { let mut device_list: Vec = vec![]; if !RE.is_match(&devices) { return OperationResult::Retry(vec![]); } for device in RE.captures_iter(&devices) { env::set_var(ANDROID_SERIAL, &device[1]); device_list.push(Phone { model: get_phone_brand(), android_sdk: get_android_sdk(), user_list: get_user_list(), adb_id: device[1].to_string(), }); } OperationResult::Ok(device_list) } Err(err) => { error!("get_device_list() -> {}", err); let test: Vec = vec![]; OperationResult::Retry(test) } }, ) .unwrap_or_default() } pub async fn initial_load() -> bool { match adb_shell_command(false, "devices") { Ok(_devices) => true, Err(_err) => false, } }