refactor: avoid set_var by specifying serial

This commit is contained in:
Rudxain 2024-12-07 09:20:17 -04:00
commit fed1580e20
No known key found for this signature in database
GPG key ID: 0DAC837DDEF8E96C
6 changed files with 144 additions and 117 deletions

View file

@ -1,8 +1,5 @@
use crate::core::utils::DisplayablePath;
use crate::core::{
sync::{get_android_sdk, User},
theme::Theme,
};
use crate::core::{sync::User, theme::Theme};
use crate::gui::views::settings::Settings;
use crate::CACHE_DIR;
use crate::CONFIG_DIR;
@ -36,6 +33,7 @@ pub struct BackupSettings {
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DeviceSettings {
/// Unique serial identifier
pub device_id: String,
pub disable_mode: bool,
pub multi_user_mode: bool,
@ -57,7 +55,7 @@ impl Default for DeviceSettings {
fn default() -> Self {
Self {
device_id: String::default(),
multi_user_mode: get_android_sdk() > 21,
multi_user_mode: false,
disable_mode: false,
backup: BackupSettings::default(),
}

View file

@ -1,5 +1,4 @@
use crate::core::uad_lists::PackageState;
use crate::core::utils::set_adb_serial;
use crate::gui::views::list::PackageInfo;
use crate::gui::widgets::package_row::PackageRow;
use regex::Regex;
@ -62,35 +61,37 @@ impl std::fmt::Display for User {
}
}
pub fn adb_shell_command(shell: bool, args: &str) -> Result<String, String> {
let adb_command = if shell {
vec!["shell", args]
} else {
vec![args]
pub fn adb_shell_command(shell: bool, serial: Option<&str>, args: &str) -> Result<String, String> {
// this could be a `tinyvec` or `arrayvec`
let mut adb_args = Vec::with_capacity(4);
if let Some(s) = serial {
adb_args.extend(["-s", s]);
};
if shell {
adb_args.push("shell");
}
// the rest
adb_args.push(args);
let mut command = Command::new("adb");
command.args(adb_command);
command.args(adb_args);
#[cfg(target_os = "windows")]
let command = command.creation_flags(0x08000000); // do not open a cmd window
let command = command.creation_flags(0x0800_0000); // do not open a cmd window
match command.output() {
Err(e) => {
error!("ADB: {}", e);
Err("ADB was not found".to_string())
Err("Cannot run ADB, likely not found".to_string())
}
Ok(o) => {
let stdout = String::from_utf8(o.stdout)
.map_err(|e| e.to_string())?
.trim_end()
.to_string();
if o.status.success() {
Ok(String::from_utf8(o.stdout)
.map_err(|e| e.to_string())?
.trim_end()
.to_string())
Ok(stdout)
} 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()
@ -116,7 +117,9 @@ pub enum AdbError {
Generic(String),
}
pub async fn perform_adb_commands(
/// Runs a shell command on the device.
pub async fn android_sh_cmd<S: AsRef<str>>(
device_serial: S,
action: String,
command_type: CommandType,
) -> Result<CommandType, AdbError> {
@ -125,7 +128,7 @@ pub async fn perform_adb_commands(
CommandType::Shell => "Shell",
};
match adb_shell_command(true, &action) {
match adb_shell_command(true, Some(device_serial.as_ref()), &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.
@ -155,15 +158,20 @@ pub fn user_flag(user_id: Option<&User>) -> String {
.unwrap_or_default()
}
pub fn list_all_system_packages(user_id: Option<&User>) -> String {
/// installed and uninstalled packages
pub fn list_all_system_packages(device_serial: &str, user_id: Option<&User>) -> String {
let action = format!("{PM_LIST_PACKS} -s -u{}", user_flag(user_id));
adb_shell_command(true, &action)
adb_shell_command(true, Some(device_serial), &action)
.unwrap_or_default()
.replace("package:", "")
}
pub fn hashset_system_packages(state: PackageState, user_id: Option<&User>) -> HashSet<String> {
pub fn hashset_system_packages(
state: PackageState,
device_serial: &str,
user_id: Option<&User>,
) -> HashSet<String> {
let user = user_flag(user_id);
let action = match state {
PackageState::Enabled => format!("{PM_LIST_PACKS} -s -e{user}"),
@ -171,7 +179,7 @@ pub fn hashset_system_packages(state: PackageState, user_id: Option<&User>) -> H
_ => String::default(), // You probably don't need to use this function for anything else
};
adb_shell_command(true, &action)
adb_shell_command(true, Some(device_serial), &action)
.unwrap_or_default()
.replace("package:", "")
.lines()
@ -216,45 +224,44 @@ pub fn apply_pkg_state_commands(
package: &CorePackage,
wanted_state: PackageState,
selected_user: &User,
phone: &Device,
dev: &Device,
) -> Vec<String> {
// 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_PACK],
_ => vec![], // Impossible action already prevented by the GUI
},
_ => vec![],
}
}
PackageState::Enabled => match package.state {
PackageState::Disabled => match dev.android_sdk {
i if i >= 23 => vec!["pm enable"],
_ => vec!["pm enable"],
},
PackageState::Uninstalled => match dev.android_sdk {
i if i >= 23 => vec!["cmd package install-existing"],
21 | 22 => vec!["pm unhide"],
19 | 20 => vec!["pm unblock", PM_CLEAR_PACK],
_ => unreachable!("already prevented by the GUI"),
},
_ => vec![],
},
PackageState::Disabled => match package.state {
PackageState::Uninstalled | PackageState::Enabled => match phone.android_sdk {
PackageState::Uninstalled | PackageState::Enabled => match dev.android_sdk {
sdk if sdk >= 23 => vec!["pm disable-user", "am force-stop", PM_CLEAR_PACK],
_ => vec![],
},
_ => vec![],
},
PackageState::Uninstalled => match package.state {
PackageState::Enabled | PackageState::Disabled => match phone.android_sdk {
PackageState::Enabled | PackageState::Disabled => match dev.android_sdk {
sdk if sdk >= 23 => vec!["pm uninstall"], // > Android Marshmallow (6.0)
21 | 22 => vec!["pm hide", PM_CLEAR_PACK], // Android Lollipop (5.x)
19 | 20 => vec!["pm block", PM_CLEAR_PACK], // Android KitKat (4.4/4.4W)
19 | 20 => vec!["pm block", PM_CLEAR_PACK], // Android KitKat (4.4/4.4W) and older
_ => vec!["pm block", PM_CLEAR_PACK], // 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);
}; // this should be a `tinyvec`, as `len <= 4`
let user = supports_multi_user(dev).then_some(selected_user);
request_builder(&commands, &package.name, user)
}
@ -269,9 +276,9 @@ pub fn request_builder(commands: &[&str], package: &str, user: Option<&User>) ->
.collect()
}
/// Get the current device model by querying the `ro.product.model` property.
pub fn get_phone_model() -> String {
adb_shell_command(true, "getprop ro.product.model").unwrap_or_else(|err| {
/// Get the model by querying the `ro.product.model` property.
pub fn get_device_model(serial: &str) -> String {
adb_shell_command(true, Some(serial), "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()
@ -281,40 +288,58 @@ pub fn get_phone_model() -> String {
})
}
/// Get the current device Android SDK version by querying the
/// Get Android SDK version by querying the
// `ro.build.version.sdk` property or defaulting to 0.
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_android_sdk(device_serial: &str) -> u8 {
adb_shell_command(true, Some(device_serial), "getprop ro.build.version.sdk").map_or(0, |sdk| {
sdk.parse().expect("SDK version numeral must be valid")
})
}
/// Get the current device brand by querying the `ro.product.brand` property.
pub fn get_phone_brand() -> String {
/// Get the brand by querying the `ro.product.brand` property.
pub fn get_device_brand(serial: &str) -> String {
format!(
"{} {}",
adb_shell_command(true, "getprop ro.product.brand")
adb_shell_command(true, Some(serial), "getprop ro.product.brand")
.map(|s| s.trim().to_string())
.unwrap_or_default(),
get_phone_model()
get_device_model(serial)
)
}
/// Minimum inclusive Android SDK version
/// that supports multi-user mode.
/// Lollipop 5.0
pub const MULTI_USER_SDK: u8 = 21;
/// Check if it supports multi-user mode, by comparing SDK version.
#[must_use]
pub const fn supports_multi_user(dev: &Device) -> bool {
dev.android_sdk >= MULTI_USER_SDK
}
/// Check if a `user_id` is protected on a device by trying
/// to list associated packages.
pub fn is_protected_user(user_id: &str) -> bool {
adb_shell_command(true, &format!("{PM_LIST_PACKS} -s --user {user_id}")).is_err()
pub fn is_protected_user(user_id: &str, device_serial: &str) -> bool {
adb_shell_command(
true,
Some(device_serial),
&format!("{PM_LIST_PACKS} -s --user {user_id}"),
)
.is_err()
}
pub fn get_user_list() -> Vec<User> {
pub fn get_user_list(device_serial: &str) -> Vec<User> {
#[dynamic]
static RE: Regex = Regex::new(r"\{([0-9]+)").unwrap_or_else(|_| unreachable!());
adb_shell_command(true, "pm list users")
adb_shell_command(true, Some(device_serial), "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..]),
protected: is_protected_user(&u.as_str()[1..], device_serial),
})
.collect()
})
@ -325,22 +350,19 @@ pub fn get_user_list() -> Vec<User> {
pub async fn get_devices_list() -> Vec<Device> {
retry(
Fixed::from_millis(500).take(120),
|| match adb_shell_command(false, "devices") {
|| match adb_shell_command(false, None, "devices") {
Ok(devices) => {
let mut device_list: Vec<Device> = vec![];
if !RE.is_match(&devices) {
return OperationResult::Retry(vec![]);
}
for device in RE.captures_iter(&devices) {
#[allow(unsafe_code)]
unsafe {
set_adb_serial(&device[1])
};
let serial = &device[1];
device_list.push(Device {
model: get_phone_brand(),
android_sdk: get_android_sdk(),
user_list: get_user_list(),
adb_id: device[1].to_string(),
model: get_device_brand(serial),
android_sdk: get_android_sdk(serial),
user_list: get_user_list(serial),
adb_id: serial.to_string(),
});
}
OperationResult::Ok(device_list)
@ -356,7 +378,7 @@ pub async fn get_devices_list() -> Vec<Device> {
}
pub async fn initial_load() -> bool {
match adb_shell_command(false, "devices") {
match adb_shell_command(false, None, "devices") {
Ok(_devices) => true,
Err(_err) => false,
}

View file

@ -5,7 +5,6 @@ use crate::gui::widgets::package_row::PackageRow;
use chrono::offset::Utc;
use chrono::{DateTime, Local};
use csv::Writer;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::process::Command;
use std::{fmt, fs};
@ -20,20 +19,16 @@ pub enum Error {
DialogClosed,
}
#[allow(unsafe_code)]
#[allow(
clippy::semicolon_if_nothing_returned,
reason = "fn must return whatever `set_var` returns"
)]
pub unsafe fn set_adb_serial<D: AsRef<OsStr>>(device_serial: D) {
// https://developer.android.com/tools/variables#adb
std::env::set_var("ANDROID_SERIAL", device_serial)
}
pub fn fetch_packages(uad_lists: &PackageHashMap, user_id: Option<&User>) -> Vec<PackageRow> {
let all_system_packages = list_all_system_packages(user_id); // installed and uninstalled packages
let enabled_system_packages = hashset_system_packages(PackageState::Enabled, user_id);
let disabled_system_packages = hashset_system_packages(PackageState::Disabled, user_id);
pub fn fetch_packages(
uad_lists: &PackageHashMap,
device_serial: &str,
user_id: Option<&User>,
) -> Vec<PackageRow> {
let all_system_packages = list_all_system_packages(device_serial, user_id);
let enabled_system_packages =
hashset_system_packages(PackageState::Enabled, device_serial, user_id);
let disabled_system_packages =
hashset_system_packages(PackageState::Disabled, device_serial, user_id);
let mut description;
let mut uad_list;
let mut state;

View file

@ -2,13 +2,11 @@ pub mod style;
pub mod views;
pub mod widgets;
use crate::core::sync::{
get_devices_list, initial_load, perform_adb_commands, CommandType, Device,
};
use crate::core::sync::{android_sh_cmd, get_devices_list, initial_load, CommandType, Device};
use crate::core::theme::Theme;
use crate::core::uad_lists::UadListState;
use crate::core::update::{get_latest_release, Release, SelfUpdateState, SelfUpdateStatus};
use crate::core::utils::{set_adb_serial, string_to_theme, NAME};
use crate::core::utils::{string_to_theme, NAME};
use iced::advanced::graphics::image::image_rs::ImageFormat;
use iced::font;
@ -161,10 +159,15 @@ impl Application for UadGui {
}
Message::RebootButtonPressed => {
self.apps_view = AppsView::default();
let serial = match &self.selected_device {
Some(d) => d.adb_id.clone(),
_ => String::default(),
};
self.selected_device = None;
self.devices_list = vec![];
Command::perform(
perform_adb_commands("reboot".to_string(), CommandType::Shell),
// https://android.stackexchange.com/questions/230256/adb-reboot-vs-adb-shell-reboot
android_sh_cmd(serial, "reboot".to_string(), CommandType::Shell),
|_| Message::Nothing,
)
}
@ -256,10 +259,6 @@ impl Application for UadGui {
Message::DeviceSelected(s_device) => {
self.selected_device = Some(s_device.clone());
self.view = View::List;
#[allow(unsafe_code)]
unsafe {
set_adb_serial(s_device.adb_id)
};
info!("{:-^65}", "-");
info!(
"ANDROID_SDK: {} | DEVICE: {}",

View file

@ -1,15 +1,13 @@
use crate::core::config::DeviceSettings;
use crate::core::helpers::button_primary;
use crate::core::sync::{
apply_pkg_state_commands, perform_adb_commands, AdbError, CommandType, Device, User,
android_sh_cmd, apply_pkg_state_commands, AdbError, CommandType, Device, User,
};
use crate::core::theme::Theme;
use crate::core::uad_lists::{
load_debloat_lists, Opposite, PackageHashMap, PackageState, Removal, UadList, UadListState,
};
use crate::core::utils::{
export_selection, fetch_packages, open_url, set_adb_serial, EXPORT_FILE_NAME, NAME,
};
use crate::core::utils::{export_selection, fetch_packages, open_url, EXPORT_FILE_NAME, NAME};
use crate::gui::style;
use crate::gui::widgets::navigation_menu::ICONS;
use std::path::PathBuf;
@ -174,7 +172,11 @@ impl List {
self.uad_lists.clone_from(&uad_list);
*list_update_state = list_state;
Command::perform(
Self::load_packages(uad_list, selected_device.user_list.clone()),
Self::load_packages(
uad_list,
selected_device.adb_id.clone(),
selected_device.user_list.clone(),
),
Message::ApplyFilters,
)
}
@ -851,13 +853,18 @@ impl List {
.collect();
}
#[expect(clippy::unused_async, reason = "1 call-site")]
async fn load_packages(uad_list: PackageHashMap, user_list: Vec<User>) -> Vec<Vec<PackageRow>> {
async fn load_packages<S: AsRef<str>>(
uad_list: PackageHashMap,
device_serial: S,
user_list: Vec<User>,
) -> Vec<Vec<PackageRow>> {
let serial = device_serial.as_ref();
if user_list.len() <= 1 {
vec![fetch_packages(&uad_list, None)]
vec![fetch_packages(&uad_list, serial, None)]
} else {
user_list
.iter()
.map(|user| fetch_packages(&uad_list, Some(user)))
.map(|user| fetch_packages(&uad_list, serial, Some(user)))
.collect()
}
}
@ -867,10 +874,6 @@ impl List {
let uad_lists = load_debloat_lists(remote);
match uad_lists {
Ok(list) => {
#[allow(unsafe_code)]
unsafe {
set_adb_serial(device.adb_id.clone())
};
if device.adb_id.is_empty() {
error!("AppsView ready but no phone found");
}
@ -950,7 +953,9 @@ fn build_action_pkg_commands(
let pkg = &packages[selection.0][selection.1];
let wanted_state = pkg.state.opposite(settings.disable_mode);
let mut commands = vec![];
// assume 2 actions per user
let mut commands = Vec::with_capacity(device.user_list.len() * 2);
for u in device.user_list.iter().filter(|&&u| {
!u.protected && (packages[u.index][selection.1].selected || settings.multi_user_mode)
}) {
@ -971,7 +976,13 @@ fn build_action_pkg_commands(
// In the end there is only one package state change
// even if we run multiple adb commands
commands.push(Command::perform(
perform_adb_commands(action, CommandType::PackageManager(p_info)),
android_sh_cmd(
// this is typically small,
// so it's fine.
device.adb_id.clone(),
action,
CommandType::PackageManager(p_info),
),
if j == 0 {
Message::ChangePackageState
} else {

View file

@ -1,11 +1,12 @@
use crate::core::helpers::button_primary;
use crate::core::sync::AdbError;
use crate::core::config::{BackupSettings, Config, DeviceSettings, GeneralSettings};
use crate::core::save::{
backup_phone, list_available_backup_user, list_available_backups, restore_backup,
};
use crate::core::sync::{get_android_sdk, perform_adb_commands, CommandType, Device, User};
use crate::core::sync::{
get_android_sdk, android_sh_cmd, supports_multi_user, AdbError, CommandType, Device, User,
};
use crate::core::theme::Theme;
use crate::core::utils::{
export_packages, open_folder, open_url, string_to_theme, DisplayablePath,
@ -137,7 +138,7 @@ impl Settings {
None => {
self.device = DeviceSettings {
device_id: phone.adb_id.clone(),
multi_user_mode: phone.android_sdk > 21,
multi_user_mode: supports_multi_user(phone),
disable_mode: false,
backup,
}
@ -186,7 +187,8 @@ impl Settings {
for command in p.commands.clone() {
*nb_running_async_adb_commands += 1;
commands.push(Command::perform(
perform_adb_commands(
android_sh_cmd(
phone.adb_id.clone(),
command,
CommandType::PackageManager(p_info.clone()),
),
@ -195,7 +197,7 @@ impl Settings {
}
}
if r_packages.is_empty() {
if get_android_sdk() == 0 {
if get_android_sdk(&phone.adb_id) == 0 {
self.device.backup.backup_state = "Device is not connected".to_string();
} else {
self.device.backup.backup_state =