Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(ADB): improve internal API #757

Merged
merged 49 commits into from
Feb 7, 2025
Merged

refactor(ADB): improve internal API #757

merged 49 commits into from
Feb 7, 2025

Conversation

Rudxain
Copy link
Member

@Rudxain Rudxain commented Dec 14, 2024

The goal is to make ADB commands:

I am sorry to whoever is going to review this 😅. I didn't want the diff to be so big 💀

@Rudxain Rudxain added documentation Improvements or additions to documentation refactor Rework of code — behaviour doesn't change labels Dec 14, 2024
@Rudxain Rudxain self-assigned this Dec 14, 2024
@Rudxain Rudxain changed the title Refactor/adb api refactor(ADB): improve internal API Dec 14, 2024

This comment was marked as outdated.

@Rudxain Rudxain marked this pull request as ready for review January 24, 2025 02:13
@Rudxain Rudxain requested a review from a team January 24, 2025 02:14
@Rudxain

This comment was marked as duplicate.

@Rudxain

This comment was marked as resolved.

@Rudxain
Copy link
Member Author

Rudxain commented Jan 24, 2025

After adding an unconditional info logger to the new API, I've found something interesting:

INFO  [src/gui/views/list.rs:157] -----------------------------------------------------------------
INFO  [src/gui/views/list.rs:158] ANDROID_SDK: 0 | DEVICE: fetching devices...
INFO  [src/gui/views/list.rs:162] -----------------------------------------------------------------
ERROR [src/gui/views/list.rs:877] AppsView ready but no phone found
INFO  [src/core/adb.rs:120] Ran command: adb 'shell' 'pm' 'list' 'packages' '-s' '-u'
INFO  [src/core/adb.rs:120] Ran command: adb 'shell' 'pm' 'list' 'packages' '-s' '-e'
INFO  [src/core/adb.rs:120] Ran command: adb 'shell' 'pm' 'list' 'packages' '-s' '-d'

This implies fetch_packages is being called even if there's no device (sub-optimal):

pub fn fetch_packages(
uad_lists: &PackageHashMap,
device_serial: &str,
user_id: Option<u16>,
) -> Vec<PackageRow> {
let all_sys_packs = AdbCommand::new()
.shell(device_serial)
.pm()
.list_packages_sys(Some(PmListPacksFlag::IncludeUninstalled), user_id)
.unwrap_or_default();
let enabled_sys_packs: HashSet<String> = AdbCommand::new()
.shell(device_serial)
.pm()
.list_packages_sys(Some(PmListPacksFlag::OnlyEnabled), user_id)
.unwrap_or_default()
.into_iter()
.collect();
let disabled_sys_packs: HashSet<String> = AdbCommand::new()
.shell(device_serial)
.pm()
.list_packages_sys(Some(PmListPacksFlag::OnlyDisabled), user_id)

This also seems to happen on the main branch:

async fn load_packages(uad_list: PackageHashMap, user_list: Vec<User>) -> Vec<Vec<PackageRow>> {
if user_list.len() <= 1 {
vec![fetch_packages(&uad_list, None)]

Message::LoadUadList(remote) => {
info!("{:-^65}", "-");
info!(
"ANDROID_SDK: {} | DEVICE: {}",
selected_device.android_sdk, selected_device.model
);
info!("{:-^65}", "-");
self.loading_state = LoadingState::DownloadingList;
Command::perform(
Self::init_apps_view(remote, selected_device.clone()),
Message::LoadPhonePackages,
)
}
Message::LoadPhonePackages((uad_list, list_state)) => {
self.loading_state = LoadingState::LoadingPackages;
self.uad_lists.clone_from(&uad_list);
*list_update_state = list_state;
Command::perform(
Self::load_packages(uad_list, selected_device.user_list.clone()),

Comment on lines +103 to +104
// status
dev_stat[(tab_idx + 1)..].to_string(),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found info on adb help:

scripting:
 wait-for[-TRANSPORT]-STATE...
     wait for device to be in a given state
     STATE: device, recovery, rescue, sideload, bootloader, or disconnect
     TRANSPORT: usb, local, or any [default=any]

So now we know the full list of states, which we can turn into a (non_exhaustive) enum!

As a bonus, wait-for-device allows UAD-NG to stop polling adb devices (no need for retry!)

@Rudxain
Copy link
Member Author

Rudxain commented Jan 29, 2025

I've just realized a minor performance oversight: It's possible to "batch" multiple adb shell commands in one run, but this new API makes batching impossible. An easy fix would be to not implicitly run commands, and allow "finished" builders to become ShellCommand objects for every built argument-list, with an implicit ";" in between each finished command.

So instead of:

// Imagine this is an unrolled loop,
// not actual code written manually

// adb shell pm uninstall --user 0 a.a
ACommand::new()
    .shell("")
    .pm()
    .uninstall(0, PackageId::new("a.a").unwrap());
// adb shell pm uninstall --user 0 b.b
ACommand::new()
    .shell("")
    .pm()
    .uninstall(0, PackageId::new("b.b").unwrap());

It would be:

// adb shell 'pm uninstall --user 0 a.a; pm uninstall --user 0 b.b'
ACommand::new()
    .shell("")
    .pm()
    .uninstall(0, PackageId::new("a.a").unwrap())
    .pm()
    .uninstall(0, PackageId::new("b.b").unwrap())
    .run();

This guarantees adb and mksh are executed only once, even when changing the states of thousands of packs.

The biggest obstacle to this, are commands whose output must be parsed: Batching pm list commands would actually be slower, as we would need to iterate over every pack, trying to find the boundaries between the outputs of distinct flags:

# ambiguity!
adb shell 'pm list packages -e; pm list packages -d'

A hacky solution is to insert a delimiter between outputs:

adb shell '\
pm list packages -e;\
echo;\
pm list packages -d\
'

Now we simply find "\n\n", and we've efficiently resolved the ambiguity! This would work for the "raw" list_packages_sys (not in debug mode) but not for list_packages_sys_parsed. So I'll have to add a dedicated batching API for those purposes.

IMO, we shouldn't worry too much about this. It's needlessly complicated just to "improve" performance. Also, too many args (or long args) are more likely to cause crashes

@Rudxain

This comment was marked as resolved.

@Rudxain Rudxain dismissed adhirajsinghchauhan’s stale review February 7, 2025 10:44

Suggestions have been applied

@Rudxain Rudxain merged commit 231fd76 into main Feb 7, 2025
18 checks passed
@Rudxain Rudxain deleted the refactor/adb-api branch February 7, 2025 10:44
@Rudxain
Copy link
Member Author

Rudxain commented Feb 8, 2025

I forgot to emphasize this, but we have to test the multi-user handling before release. I'm not confident that adb::list_users or sync::list_users_parsed work 100% properly. I don't have devices with multiple users, so I would have to install an emulator or something

@AnonymousWP
Copy link
Member

Hey @Rudxain, just wanted to thank you for your tremendous work on this PR, it was probably a lot of work. Feel free to convert one of your comments into an issue that are deemed important enough.

Could someone test this? I don't have multiple users either. Perhaps create an issue and add it to the 1.1.1 milestone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation refactor Rework of code — behaviour doesn't change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants