diff --git a/CHANGELOG.md b/CHANGELOG.md index a5393353a..fe7e0d138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,14 +12,18 @@ and `Removed`. ### Added - [[#52](https://github.com/0x192/universal-android-debloater/issues/52)] `uk.co.ee.myee` to the debloat lists (thanks [@lawson58](https://github.com/lawson85)). +- [[#58](https://github.com/0x192/universal-android-debloater/issues/52)] `android` to the debloat lists with the tag `Unsafe`. - [[#49](https://github.com/0x192/universal-android-debloater/issues/49)] Multi-device support: You are now able to select a device among the list of all ADB connected devices/emulators. - [[#44](https://github.com/0x192/universal-android-debloater/issues/44)] Persistent settings: Settings (only `theme` for now) are saved to a config file. Its location follows [the standards of the different platforms](https://github.com/dirs-dev/dirs-rs#example). ### Changed - Review of the package lists recommendations. The `Recommended` debloat list is now safer (less likely to remove something you'd want to keep). +- [[#65](https://github.com/0x192/universal-android-debloater/issues/65)] ADB commands now run in parallel and asynchronously! This means no more UI freeze when performing long/many actions! :rocket: +- UI now updates itself in real time when performing ADB actions (thanks to async & multithreading). Before, it waited for the end of all actions. ### Fixed - Miscellaneous minor issues in some package descriptions. +- Several bad recommendations. - [[#50](https://github.com/0x192/universal-android-debloater/issues/50)] Resync button flipping theme back to `Lupin`. ## [0.3] - 2021-10-10 diff --git a/src/core/sync.rs b/src/core/sync.rs index 7c67ff7ca..e65929026 100644 --- a/src/core/sync.rs +++ b/src/core/sync.rs @@ -10,6 +10,9 @@ use std::process::Command; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; +#[dynamic] +static RE: Regex = Regex::new(r"\n([[:ascii:]]+)\s+device").unwrap(); + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Phone { pub model: String, @@ -121,15 +124,24 @@ pub fn action_handler( package: &PackageRow, phone: &Phone, settings: &SettingsPhone, -) -> Result { - let actions: Vec = match package.state { +) -> Vec { + // https://github.com/0x192/universal-android-debloater/wiki/ADB-reference + match package.state { PackageState::Enabled => match settings.disable_mode { true => { - if phone.android_sdk < 21 { + // < Android Ice Cream Sandwich (4.0) + if phone.android_sdk < 14 { + vec![ + format!("pm disable {}", package.name), + format!("pm clear {}", package.name), + ] + } + // < Android Lollipop (5.0) + else if phone.android_sdk < 17 { vec![ - format!("am force-stop {}", package.name), format!("pm disable-user {}", package.name), format!("pm clear {}", package.name), + format!("pm hide {}", package.name), ] } else if settings.multi_user_mode { phone @@ -137,15 +149,15 @@ pub fn action_handler( .iter() .flat_map(|u| { [ - format!("am force-stop --user {} {}", u.id, package.name), format!("pm disable-user --user {} {}", u.id, package.name), + format!("am force-stop --user {} {}", u.id, package.name), format!("pm clear --user {} {}", u.id, package.name), + //format!("pm hide --user {} {}", u.id, package.name), ] }) .collect() } else { vec![ - format!("am force-stop --user {} {}", selected_user.id, package.name), format!( "pm disable-user --user {} {}", selected_user.id, package.name @@ -209,23 +221,7 @@ pub fn action_handler( } } PackageState::All => vec![], // This can't happen (like... never) - }; - - for action in actions { - match adb_shell_command(true, &action) { - Ok(_) => { - info!("[{}] {}", package.removal, action); - } - Err(err) => { - if err.contains("[not installed for") { - } else { - error!("[{}] {} -> {}", package.removal, action, err); - return Err(false); - } - } - } } - Ok(true) } pub fn get_phone_model() -> String { @@ -276,15 +272,12 @@ pub fn get_user_list() -> Vec { } // getprop ro.serialno -pub fn get_device_list() -> Vec { - #[dynamic] - static RE: Regex = Regex::new(r"\n([[:ascii:]]+)\s+device").unwrap(); - +pub async fn get_device_list() -> Vec { match adb_shell_command(false, "devices") { Ok(devices) => { let mut device_list: Vec = vec![]; for device in RE.captures_iter(&devices) { - env::set_var("ANDROID_SERIAL", device[1].to_string()); + env::set_var("ANDROID_SERIAL", &device[1]); device_list.push(Phone { model: get_phone_brand(), diff --git a/src/gui/mod.rs b/src/gui/mod.rs index c70cf9ead..ccd1039de 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -62,7 +62,8 @@ pub enum Message { AppsAction(AppsMessage), SettingsAction(SettingsMessage), RefreshButtonPressed, - Init, + LoadDeviceList(Vec), + Init(Vec), } impl Application for UadGui { @@ -73,21 +74,24 @@ impl Application for UadGui { fn new(_flags: ()) -> (Self, Command) { ( Self::default(), - Command::perform(Self::init(), |_| Message::Init), + Command::perform(get_device_list(), Message::Init), ) } fn title(&self) -> String { - String::from("UadGui") + String::from("Universal Android Debloater") } fn update(&mut self, message: Message) -> Command { match message { - Message::Init => { + Message::Init(device_list) => { + self.device_list = device_list; self.selected_device = Some(Phone::default()); Command::perform(Self::refresh(10), Message::LoadDevices) } - Message::RefreshButtonPressed => { + Message::LoadDeviceList(device_list) => { + self.device_list = device_list; + // Save the current selected device let i = self .device_list @@ -97,9 +101,15 @@ impl Application for UadGui { self.selected_device = Some(Phone::default()); Command::perform(Self::refresh(i), Message::LoadDevices) } + Message::RefreshButtonPressed => { + self.apps_view.ready = false; + Command::batch([ + Command::perform(Self::please_wait(), Message::AppsAction), + Command::perform(Self::device_lists(), Message::LoadDeviceList), + ]) + } Message::LoadDevices(last_selected_device) => { self.settings_view.phone = SettingsPhone::default(); - self.device_list = get_device_list(); // Try to reload last selected phone if !self.device_list.is_empty() { @@ -110,19 +120,12 @@ impl Application for UadGui { None => Some(Phone::default()), }, }; - env::set_var( - "ANDROID_SERIAL", - self.selected_device.clone().unwrap().adb_id, - ); - info!("{:-^65}", "-"); - info!( - "ANDROID_SDK: {} | PHONE: {}", - self.selected_device.as_ref().unwrap().android_sdk, - self.selected_device.as_ref().unwrap().model - ); self.apps_view = AppsView::default(); self.view = View::List; - Command::perform(Self::load_phone_packages(), Message::AppsAction) + Command::perform( + Self::load_phone_packages(self.selected_device.clone().unwrap()), + Message::AppsAction, + ) } else { self.selected_device = None; Command::none() @@ -154,19 +157,12 @@ impl Application for UadGui { } Message::DeviceSelected(device) => { self.selected_device = Some(device); - env::set_var( - "ANDROID_SERIAL", - self.selected_device.clone().unwrap().adb_id, - ); - info!("{:-^65}", "-"); - info!( - "ANDROID_SDK: {} | PHONE: {}", - self.selected_device.as_ref().unwrap().android_sdk, - self.selected_device.as_ref().unwrap().model - ); self.apps_view = AppsView::default(); self.view = View::List; - Command::perform(Self::load_phone_packages(), Message::AppsAction) + Command::perform( + Self::load_phone_packages(self.selected_device.clone().unwrap()), + Message::AppsAction, + ) } } } @@ -267,17 +263,25 @@ impl UadGui { Self::run(settings).unwrap_err(); } - pub async fn load_phone_packages() -> AppsMessage { + pub async fn load_phone_packages(phone: Phone) -> AppsMessage { + env::set_var("ANDROID_SERIAL", phone.adb_id); + info!("{:-^65}", "-"); + info!( + "ANDROID_SDK: {} | PHONE: {}", + phone.android_sdk, phone.model + ); AppsMessage::LoadPackages } - pub async fn init() -> Message { - Message::Init - } - pub async fn refresh(i: usize) -> usize { i } + pub async fn device_lists() -> Vec { + get_device_list().await + } + pub async fn please_wait() -> AppsMessage { + AppsMessage::Nothing + } } fn refresh_icon() -> Text { diff --git a/src/gui/views/list.rs b/src/gui/views/list.rs index d1d9ffbfe..97fa411e0 100644 --- a/src/gui/views/list.rs +++ b/src/gui/views/list.rs @@ -1,4 +1,4 @@ -use crate::core::sync::{action_handler, Phone, User}; +use crate::core::sync::{action_handler, adb_shell_command, Phone, User}; use crate::core::uad_lists::{ load_debloat_lists, Opposite, Package, PackageState, Removal, UadList, }; @@ -51,7 +51,7 @@ pub enum Action { #[derive(Default, Debug, Clone)] pub struct List { - ready: bool, + pub ready: bool, phone_packages: Vec>, // packages of all users of the phone filtered_packages: Vec, // phone_packages indexes of the selected user (= what you see on screen) pub selection: Selection, @@ -87,6 +87,9 @@ pub enum Message { ExportSelectionPressed, List(usize, RowMessage), ExportedSelection(Result), + ActionIsDone(usize), + PackagesLoaded(Vec>), + Nothing, } impl List { @@ -98,21 +101,19 @@ impl List { ) -> Command { let i_user = &self.selected_user.unwrap_or(User { id: 0, index: 0 }).index; // for readability match message { + Message::Nothing => Command::none(), Message::LoadPackages => { self.selected_package_state = Some(PackageState::Enabled); self.selected_list = Some(UadList::All); self.selected_removal = Some(Removal::Recommended); self.selected_user = Some(User { id: 0, index: 0 }); - - match phone.user_list.len() { - 0 | 1 => self.phone_packages.push(fetch_packages(&UAD_LISTS, &None)), - _ => { - for user in &phone.user_list { - self.phone_packages - .push(fetch_packages(&UAD_LISTS, &Some(user))); - } - } - } + Command::perform( + Self::load_packages(phone.user_list.clone()), + Message::PackagesLoaded, + ) + } + Message::PackagesLoaded(packages) => { + self.phone_packages = packages; self.filtered_packages = (0..self.phone_packages[*i_user].len()).collect(); Self::filter_package_lists(self); @@ -148,79 +149,48 @@ impl List { .update(row_message.clone()) .map(move |row_message| Message::List(i, row_message)); + let package = &mut self.phone_packages[*i_user][i]; + match row_message { RowMessage::ToggleSelection(toggle) => { - if self.phone_packages[*i_user][i].removal == Removal::Unsafe - && !settings.expert_mode - { - self.phone_packages[*i_user][i].selected = false; + if package.removal == Removal::Unsafe && !settings.expert_mode { + package.selected = false; } else { - self.phone_packages[*i_user][i].selected = toggle; + package.selected = toggle; - if self.phone_packages[*i_user][i].selected { + if package.selected { self.selection.selected_packages.push(i); - update_selection_count( - &mut self.selection, - self.phone_packages[*i_user][i].state, - true, - ); } else { self.selection .selected_packages .drain_filter(|s_i| *s_i == i); - update_selection_count( - &mut self.selection, - self.phone_packages[*i_user][i].state, - false, - ); } + update_selection_count( + &mut self.selection, + package.state, + package.selected, + ); } + Command::none() } RowMessage::ActionPressed => { - let success = action_handler( - &self.selected_user.unwrap(), - &self.phone_packages[*i_user][i], - phone, - settings, - ) - .unwrap_or_else(|err| err); - - if success { - let i = self.phone_packages[*i_user] - .iter() - .position(|p| p.name == self.phone_packages[*i_user][i].name) - .unwrap(); - if self.phone_packages[*i_user][i].selected { - update_selection_count( - &mut self.selection, - self.phone_packages[*i_user][i].state, - false, - ); - } - - self.phone_packages[*i_user][i].state = self.phone_packages[*i_user][i] - .state - .opposite(settings.disable_mode); - - if settings.multi_user_mode { - for u in &phone.user_list { - self.phone_packages[u.index][i].state = - self.phone_packages[*i_user][i].state; - self.phone_packages[u.index][i].selected = false; - } - } - self.selection - .selected_packages - .drain_filter(|s_i| *s_i == i); - self.phone_packages[*i_user][i].selected = false; - Self::filter_package_lists(self); + let mut commands = vec![]; + let actions = + action_handler(&self.selected_user.unwrap(), package, phone, settings); + + for action in actions { + commands.push(Command::perform( + Self::perform_commands(action, i, package.removal), + Message::ActionIsDone, + )); } + Command::batch(commands) } RowMessage::PackagePressed => { - self.description = self.phone_packages[*i_user][i].clone().description; + self.description = package.clone().description; + Command::none() } } - Command::none() } Message::ApplyActionOnSelection(action) => { let mut selected_packages = self.selection.selected_packages.clone(); @@ -237,42 +207,26 @@ impl List { }); } } - + let mut commands = vec![]; for i in selected_packages { - let success = action_handler( + let actions = action_handler( &self.selected_user.unwrap(), &self.phone_packages[*i_user][i], phone, settings, - ) - .unwrap_or_else(|err| err); - - if success { - update_selection_count( - &mut self.selection, - self.phone_packages[*i_user][i].state, - false, - ); - if !settings.multi_user_mode { - self.phone_packages[*i_user][i].state = self.phone_packages[*i_user][i] - .state - .opposite(settings.disable_mode); - self.phone_packages[*i_user][i].selected = false; - } - - for u in &phone.user_list { - self.phone_packages[u.index][i].state = self.phone_packages[u.index][i] - .state - .opposite(settings.disable_mode); - self.phone_packages[u.index][i].selected = false; - } - self.selection - .selected_packages - .drain_filter(|s_i| *s_i == i); + ); + for action in actions { + commands.push(Command::perform( + Self::perform_commands( + action, + i, + self.phone_packages[*i_user][i].removal, + ), + Message::ActionIsDone, + )); } } - Self::filter_package_lists(self); - Command::none() + Command::batch(commands) } Message::SelectAllPressed => { for i in self.filtered_packages.clone() { @@ -311,6 +265,28 @@ impl List { Self::filter_package_lists(self); Command::none() } + Message::ActionIsDone(i) => { + let package = &mut self.phone_packages[*i_user][i]; + update_selection_count(&mut self.selection, package.state, false); + + if !settings.multi_user_mode { + package.state = package.state.opposite(settings.disable_mode); + package.selected = false; + } else { + for u in &phone.user_list { + self.phone_packages[u.index][i].state = self.phone_packages[u.index][i] + .state + .opposite(settings.disable_mode); + self.phone_packages[u.index][i].selected = false; + } + } + + self.selection + .selected_packages + .drain_filter(|s_i| *s_i == i); + Self::filter_package_lists(self); + Command::none() + } } } @@ -510,6 +486,31 @@ impl List { .map(|(i, _)| i) .collect(); } + + async fn perform_commands(action: String, i: usize, recommendation: Removal) -> usize { + match adb_shell_command(true, &action) { + Ok(_) => info!("[{}] {}", recommendation, action), + Err(err) => { + if !err.contains("[not installed for") { + error!("[{}] {} -> {}", recommendation, action, err); + } + } + }; + i + } + + async fn load_packages(user_list: Vec) -> Vec> { + let mut temp = vec![]; + match user_list.len() { + 0 | 1 => temp.push(fetch_packages(&UAD_LISTS, &None)), + _ => { + for user in &user_list { + temp.push(fetch_packages(&UAD_LISTS, &Some(user))); + } + } + } + temp + } } fn loading_data<'a>(settings: &Settings) -> Element<'a, Message> {