-
Notifications
You must be signed in to change notification settings - Fork 112
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
Changes from 45 commits
Commits
Show all changes
49 commits
Select commit
Hold shift + click to select a range
8ddbe68
refactor(sync): `const` `pm {list packages | clear}`
Rudxain 8a80cdc
refactor(set_var-serial): define `set_adb_serial`
Rudxain 6032b8e
build(lint): improve warns about `unsafe`
Rudxain 8bead68
style: rm unused imports
Rudxain 63707f3
style(sync): readable `const`s
Rudxain c0fa888
chore: `derive` `Copy` for `Error`
Rudxain cf054c8
docs: `Phone` -> `Device`, and explain fields
Rudxain fed1580
refactor: avoid `set_var` by specifying serial
Rudxain db15305
style: format code with Rustfmt
deepsource-autofix[bot] 5300bef
refactor: `str`s are `Option`s already!
Rudxain 5761e8d
Merge branch 'main' into refactor/sync-and-set_var
Rudxain 65349ec
Merge branch 'main' into refactor/sync-and-set_var
Rudxain 1da5b69
refactor: `cargo clippy --fix`
Rudxain 7052646
style: format code with Rustfmt
deepsource-autofix[bot] f0f6c51
docs: vec cap can't overflow
Rudxain dc1ce19
style: vec macro
Rudxain ac056dd
perf: Optimize `*system_packages` and improve `list_all_system_packag…
Rudxain 89680fc
perf: use more `Vec::with_capacity`
Rudxain 04b3ef5
docs(sync): mention `pm list` format assumption
Rudxain b74947e
feat: add type-state cmd builders
Rudxain 87fa332
docs(sync): Mention type-sys patterns on `AdbCmd`
Rudxain 30359cb
refactor: use `AdbCmd`, finish `list_packs` and add `list_users`
Rudxain c172bae
fix: `User` not `&User`
Rudxain a566733
refactor: add `PackId`, replace `list_all_system_packages` by `list_p…
Rudxain 240dde1
refactor: create `adb` module, and add **LOTS** of docs
Rudxain 77a52be
refactor: more pre-parsing
Rudxain 4c61dc3
docs(ADB): more docs for `ls_packs*`, and validated returns `Set`
Rudxain 4ec1acd
refactor: replace `hashset_system_packages` by new API
Rudxain 9aeba11
refactor: `user_id: u16` not `u: User`
Rudxain 2e2114d
docs(ADB): more specific; mention assumptions
Rudxain e84e081
refactor(ADB): add `getprop` API, inline `adb_cmd` into `adb_sh_cmd`
Rudxain 5e27a52
refactor(utils): less dupes in `open_url`
Rudxain fc72efc
docs: `iced::Command as ICmd`, and potential RCE warn
Rudxain ef16036
Merge branch 'main' into refactor/adb-api
Rudxain 12ca4f6
chore: forgot to rm `UNINSTALLED_PACKAGES_FILE_NAME`
Rudxain 74a5c5e
style: `cargo fmt`
Rudxain 0f48489
docs(ADB): readable names, and better comments
Rudxain a1762c2
style(settings-view): fully-qualified `iced::Command`
Rudxain 09f3ce1
style: revert rename of `Phone` to `Device` (now "Phone")
Rudxain 4f484ae
refactor: undo questionable `with_capacity` usage
Rudxain a16525a
feat(log): `info` logs for ADB API `run`
Rudxain bfb97d5
refactor: make use of `reboot` ADB API
Rudxain 8e8395f
docs(adb): add `_sys` to `list_packages*` names
Rudxain 83f053c
style: revert another "phone" rename. `ls_users_parsed`->`list_users_…
Rudxain 0238982
docs(sync): explain why multi-user is unreliable
Rudxain 080fef9
docs(list_users): fmt
Rudxain 0636134
docs(ADB): more correct module comments
Rudxain 81a6f84
test: add `PackageId` debug-assertion to `list_packages_sys`
Rudxain 0045a6c
test(ADB): add tests for `PackageId::new`
Rudxain File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,291 @@ | ||
//! This module is intended to group everything that's "intrinsic" of ADB. | ||
//! | ||
//! Following the design philosophy of `Vec` and `thread`, | ||
//! `*Command` are intended to be thin wrappers ("low-level" abstractions) | ||
//! around the ADB CLI or `adb_client` | ||
//! ([in the future](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/issues/700) ), | ||
//! which implies: | ||
//! - no "magic" | ||
//! - no custom commands | ||
//! - no chaining ("piping") of existing commands | ||
//! | ||
//! This guarantees a 1-to-1 mapping between methods and cmds, | ||
//! thereby reducing surprises such as: | ||
//! - Non-atomic operations: consider what happens if a pack changes state | ||
//! in the middle of listing enabled and disabled packs! | ||
//! - Non-standard semantics: what would happen if a new ADB version | ||
//! supports a feature we already defined, | ||
//! but has _slightly_ different behavior? | ||
//! | ||
//! Despite being "low-level", we can still "have cake and eat it too"; | ||
//! After all, what's the point of an abstraction if it doesn't come with goodies?: | ||
//! We can reserve some artistic license, such as: | ||
//! - shorter names, complemented by context | ||
//! - pre-parsing or validanting output, to provide types with invariants | ||
//! - strongly-typed rather than "stringly-typed" APIs | ||
//! - nicer IDE support | ||
//! - compile-time prevention of malformed cmds | ||
//! - implicit enforcement of a narrow set of operations | ||
//! | ||
//! About that last point, if there's ever a need for an ADB feature | ||
//! which these APIs don't expose, | ||
//! please, **PLEASE** refrain from falling-back to any `Command`-like API. | ||
//! Rather, please extend these APIs in a consistent way. | ||
//! | ||
//! Thank you! ❤️ | ||
//! | ||
//! For comprehensive info about ADB, | ||
//! [see this](https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/master/docs/) | ||
|
||
use regex::Regex; | ||
use serde::{Deserialize, Serialize}; | ||
use static_init::dynamic; | ||
use std::{collections::HashSet, process::Command}; | ||
|
||
#[cfg(target_os = "windows")] | ||
use std::os::windows::process::CommandExt; | ||
|
||
pub fn to_trimmed_utf8(v: Vec<u8>) -> String { | ||
String::from_utf8(v) | ||
.expect("ADB should always output valid ASCII (or UTF-8, at least)") | ||
.trim_end() | ||
.to_string() | ||
} | ||
|
||
/// Builder object for an Android Debug Bridge CLI command, | ||
/// using the type-state and new-type patterns. | ||
/// | ||
/// This is not intended to model the entire ADB API. | ||
/// It only models the subset that concerns UADNG. | ||
/// | ||
/// [More info here](https://developer.android.com/tools/adb) | ||
#[derive(Debug)] | ||
pub struct ACommand(Command); | ||
impl ACommand { | ||
/// `adb` command builder | ||
pub fn new() -> Self { | ||
Self(Command::new("adb")) | ||
} | ||
/// `shell` sub-command builder. | ||
/// | ||
/// If `device_serial` is empty, it lets ADB choose the default device. | ||
pub fn shell<S: AsRef<str>>(mut self, device_serial: S) -> ShellCommand { | ||
let serial = device_serial.as_ref(); | ||
if !serial.is_empty() { | ||
self.0.args(["-s", serial]); | ||
} | ||
self.0.arg("shell"); | ||
ShellCommand(self) | ||
} | ||
/// Header-less list of attached devices (as serials) and their statuses: | ||
/// - USB | ||
/// - TCP/IP: WIFI, Ethernet, etc... | ||
/// - Local emulators | ||
/// Status can be (but not limited to): | ||
/// - "unauthorized" | ||
/// - "device" | ||
pub fn devices(mut self) -> Result<Vec<(String, String)>, String> { | ||
self.0.arg("devices"); | ||
Ok(self | ||
.run()? | ||
.lines() | ||
.skip(1) // header | ||
.map(|dev_stat| { | ||
let tab_idx = dev_stat | ||
// OS-specific? | ||
.find('\t') | ||
// True on Linux, | ||
// no matter if ADB is piped or connected to terminal | ||
.expect("There must be 1 tab after serial"); | ||
( | ||
// serial | ||
dev_stat[..tab_idx].to_string(), | ||
// status | ||
dev_stat[(tab_idx + 1)..].to_string(), | ||
) | ||
}) | ||
.collect()) | ||
} | ||
/// Reboots default device | ||
pub fn reboot(mut self) -> Result<String, String> { | ||
self.0.arg("reboot"); | ||
self.run() | ||
} | ||
/// General executor | ||
fn run(self) -> Result<String, String> { | ||
let mut cmd = self.0; | ||
#[cfg(target_os = "windows")] | ||
let cmd = cmd.creation_flags(0x0800_0000); // do not open a cmd window | ||
|
||
info!( | ||
"Ran command: adb '{}'", | ||
cmd.get_args() | ||
.map(|s| s.to_str().unwrap_or_else(|| unreachable!())) | ||
.collect::<Vec<_>>() | ||
.join("' '") | ||
); | ||
match cmd.output() { | ||
Err(e) => { | ||
error!("ADB: {}", e); | ||
Err("Cannot run ADB, likely not found".to_string()) | ||
} | ||
Ok(o) => { | ||
let stdout = to_trimmed_utf8(o.stdout); | ||
if o.status.success() { | ||
Ok(stdout) | ||
} else { | ||
let stderr = to_trimmed_utf8(o.stderr); | ||
// ADB does really weird things: | ||
// Some errors are not redirected to `stderr` | ||
let err = if stdout.is_empty() { stderr } else { stdout }; | ||
Err(err) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// Builder object for a command that runs on the device's default `sh` implementation. | ||
/// Typically MKSH, but could be Ash. | ||
/// | ||
/// [More info](https://chromium.googlesource.com/aosp/platform/system/core/+/refs/heads/upstream/shell_and_utilities). | ||
#[derive(Debug)] | ||
pub struct ShellCommand(ACommand); | ||
impl ShellCommand { | ||
/// `pm` command builder | ||
pub fn pm(mut self) -> PmCommand { | ||
self.0 .0.arg("pm"); | ||
PmCommand(self) | ||
} | ||
/// Query a device property value, by its key. | ||
/// These can be of any type: | ||
/// - `boolean` | ||
/// - `int` | ||
/// - chars | ||
/// - etc... | ||
/// So to avoid lossy conversions, we return strs. | ||
pub fn getprop(mut self, key: &str) -> Result<String, String> { | ||
self.0 .0.args(["getprop", key]); | ||
self.0.run() | ||
} | ||
/// Reboots device | ||
pub fn reboot(mut self) -> Result<String, String> { | ||
self.0 .0.arg("reboot"); | ||
self.0.run() | ||
} | ||
} | ||
|
||
/// `String` with the invariant of being a valid package-name. | ||
/// See its `new` constructor for more info. | ||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)] | ||
pub struct PackageId(String); | ||
impl PackageId { | ||
/// Creates a package-ID if it's valid according to | ||
/// [this](https://developer.android.com/build/configure-app-module#set-application-id) | ||
pub fn new<S: AsRef<str>>(pid: S) -> Option<Self> { | ||
#[dynamic] | ||
static RE: Regex = Regex::new(r"^[a-zA-Z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)+$") | ||
.unwrap_or_else(|_| unreachable!()); | ||
|
||
let pid = pid.as_ref(); | ||
|
||
if RE.is_match(pid) { | ||
Some(Self(pid.to_string())) | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
/// `pm list packages` flag/state/type | ||
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
pub enum PmListPacksFlag { | ||
/// `-u`, not to be confused with `-a` | ||
IncludeUninstalled, | ||
/// `-e` | ||
OnlyEnabled, | ||
/// `-d` | ||
OnlyDisabled, | ||
} | ||
impl PmListPacksFlag { | ||
// is there a trait for this? | ||
fn to_str(self) -> &'static str { | ||
match self { | ||
Self::IncludeUninstalled => "-u", | ||
Self::OnlyEnabled => "-e", | ||
Self::OnlyDisabled => "-d", | ||
} | ||
} | ||
} | ||
#[expect(clippy::to_string_trait_impl, reason = "This is not user-facing")] | ||
impl ToString for PmListPacksFlag { | ||
fn to_string(&self) -> String { | ||
self.to_str().to_string() | ||
} | ||
} | ||
|
||
const PACK_PREFIX: &str = "package:"; | ||
|
||
pub const PM_CLEAR_PACK: &str = "pm clear"; | ||
|
||
/// Builder object for an Android Package Manager command. | ||
/// | ||
/// [More info](https://developer.android.com/tools/adb#pm) | ||
#[derive(Debug)] | ||
pub struct PmCommand(ShellCommand); | ||
impl PmCommand { | ||
/// `list packages -s` sub-command, [`PACK_PREFIX`] stripped. | ||
/// This is "the rawest" version (minimal overhead). | ||
/// | ||
/// `Ok` variant: | ||
/// - isn't sorted | ||
/// - duplicates never _seem_ to happen, but don't assume uniqueness | ||
/// | ||
/// See also [`list_packages_sys_valid`]. | ||
pub fn list_packages_sys( | ||
Rudxain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
mut self, | ||
f: Option<PmListPacksFlag>, | ||
user_id: Option<u16>, | ||
) -> Result<Vec<String>, String> { | ||
let cmd = &mut self.0 .0 .0; | ||
cmd.args(["list", "packages", "-s"]); | ||
if let Some(s) = f { | ||
cmd.arg(s.to_str()); | ||
}; | ||
if let Some(u) = user_id { | ||
cmd.arg("--user"); | ||
cmd.arg(u.to_string()); | ||
}; | ||
self.0 .0.run().map(|pack_ls| { | ||
pack_ls | ||
.lines() | ||
.map(|p_ln| { | ||
debug_assert!(p_ln.starts_with(PACK_PREFIX)); | ||
String::from(&p_ln[PACK_PREFIX.len()..]) | ||
}) | ||
.collect() | ||
}) | ||
} | ||
/// `list packages -s` sub-command, pre-validated. | ||
/// This is strongly-typed, at the cost of regex & hash overhead. | ||
/// | ||
/// See also [`list_packages_sys`]. | ||
pub fn list_packages_sys_valid( | ||
self, | ||
f: Option<PmListPacksFlag>, | ||
user_id: Option<u16>, | ||
) -> Result<HashSet<PackageId>, String> { | ||
Ok(self.list_packages_sys(f, user_id)? | ||
.into_iter() | ||
.map(|p| PackageId::new(p).expect("One of these is wrong: `PackId` regex, ADB implementation. Or the spec now allows a wider char-set")).collect()) | ||
} | ||
#[allow(clippy::doc_markdown, reason = "Multi URL")] | ||
/// `list users` sub-command (header-less). | ||
/// - https://source.android.com/docs/devices/admin/multi-user-testing | ||
/// - https://stackoverflow.com/questions/37495126/android-get-list-of-users-and-profile-name | ||
pub fn list_users(mut self) -> Result<Vec<String>, String> { | ||
self.0 .0 .0.args(["list", "users"]); | ||
// is it actually multi-line? | ||
Ok(self.0 .0.run()?.lines().skip(1).map(String::from).collect()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
pub mod adb; | ||
pub mod config; | ||
pub mod helpers; | ||
pub mod save; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
: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 pollingadb devices
(no need forretry
!)