mirror of
https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation.git
synced 2026-08-09 15:19:11 +02:00
feat(adb): add built-in Rust ADB backend
This commit is contained in:
parent
596120a215
commit
6c851d1112
16 changed files with 1187 additions and 361 deletions
538
Cargo.lock
generated
538
Cargo.lock
generated
|
|
@ -18,6 +18,31 @@ version = "0.1.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
|
||||
[[package]]
|
||||
name = "adb_client"
|
||||
version = "3.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4974ac06396fce4424d9d2193f80ea5a177d6e2ddde6214ff7c14aaedd2a3104"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"image",
|
||||
"log",
|
||||
"num-bigint-dig",
|
||||
"num-traits",
|
||||
"num_enum",
|
||||
"quick-protobuf",
|
||||
"rand 0.10.1",
|
||||
"rcgen",
|
||||
"regex",
|
||||
"rsa",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
|
|
@ -37,6 +62,15 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned"
|
||||
version = "0.4.3"
|
||||
|
|
@ -227,6 +261,45 @@ dependencies = [
|
|||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
|
||||
dependencies = [
|
||||
"asn1-rs-derive",
|
||||
"asn1-rs-impl",
|
||||
"displaydoc",
|
||||
"nom 7.1.3",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-derive"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-impl"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
|
|
@ -473,7 +546,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"arrayvec",
|
||||
"log",
|
||||
"nom",
|
||||
"nom 8.0.0",
|
||||
"num-rational",
|
||||
"v_frame",
|
||||
]
|
||||
|
|
@ -493,13 +566,19 @@ version = "0.22.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
"bit-vec 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -508,6 +587,15 @@ version = "0.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.3"
|
||||
|
|
@ -541,6 +629,15 @@ version = "0.1.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.5.1"
|
||||
|
|
@ -604,6 +701,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
|
|
@ -704,10 +807,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.43"
|
||||
name = "chacha20"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
|
|
@ -854,6 +968,12 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
|
|
@ -986,6 +1106,24 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
|
|
@ -1039,6 +1177,16 @@ dependencies = [
|
|||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.4.0"
|
||||
|
|
@ -1086,6 +1234,37 @@ dependencies = [
|
|||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der-parser"
|
||||
version = "10.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"displaydoc",
|
||||
"nom 7.1.3",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.6"
|
||||
|
|
@ -1095,6 +1274,17 @@ dependencies = [
|
|||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
|
|
@ -1597,6 +1787,16 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "1.1.0"
|
||||
|
|
@ -1639,6 +1839,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"rand_core 0.10.1",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
|
@ -2177,9 +2378,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.9"
|
||||
version = "0.25.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
|
||||
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
|
|
@ -2195,8 +2396,8 @@ dependencies = [
|
|||
"rayon",
|
||||
"rgb",
|
||||
"tiff",
|
||||
"zune-core 0.5.1",
|
||||
"zune-jpeg 0.5.12",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2351,6 +2552,9 @@ name = "lazy_static"
|
|||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
|
|
@ -2457,9 +2661,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
version = "0.4.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||
dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
|
|
@ -2537,6 +2741,12 @@ dependencies = [
|
|||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
|
|
@ -2560,9 +2770,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.7.11"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
||||
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
|
|
@ -2682,6 +2892,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
|
|
@ -2707,6 +2927,23 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libm",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.0"
|
||||
|
|
@ -2733,6 +2970,17 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
|
|
@ -2766,9 +3014,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
|
||||
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
|
|
@ -2776,9 +3024,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
|
||||
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
|
|
@ -3145,6 +3393,15 @@ dependencies = [
|
|||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oid-registry"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
|
|
@ -3242,6 +3499,25 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
|
|
@ -3291,6 +3567,27 @@ dependencies = [
|
|||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs8",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
|
|
@ -3446,6 +3743,15 @@ version = "2.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-protobuf"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.4"
|
||||
|
|
@ -3501,6 +3807,17 @@ dependencies = [
|
|||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom 0.4.1",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
|
|
@ -3539,6 +3856,12 @@ dependencies = [
|
|||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "range-alloc"
|
||||
version = "0.1.4"
|
||||
|
|
@ -3588,9 +3911,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ravif"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285"
|
||||
checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45"
|
||||
dependencies = [
|
||||
"avif-serialize",
|
||||
"imgref",
|
||||
|
|
@ -3627,6 +3950,20 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rcgen"
|
||||
version = "0.14.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"time",
|
||||
"x509-parser",
|
||||
"yasna",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "read-fonts"
|
||||
version = "0.35.0"
|
||||
|
|
@ -3676,6 +4013,35 @@ dependencies = [
|
|||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
|
||||
|
||||
[[package]]
|
||||
name = "renderdoc-sys"
|
||||
version = "1.1.0"
|
||||
|
|
@ -3744,6 +4110,26 @@ version = "0.20.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
|
|
@ -3765,6 +4151,15 @@ dependencies = [
|
|||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusticata-macros"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
|
||||
dependencies = [
|
||||
"nom 7.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
|
|
@ -3793,9 +4188,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.36"
|
||||
version = "0.23.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
|
||||
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
|
|
@ -3808,9 +4203,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.0"
|
||||
version = "1.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
||||
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
|
@ -3969,6 +4364,17 @@ dependencies = [
|
|||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
|
@ -3985,6 +4391,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.8"
|
||||
|
|
@ -4144,6 +4560,12 @@ dependencies = [
|
|||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spirv"
|
||||
version = "0.3.0+sdk-1.3.268.0"
|
||||
|
|
@ -4153,6 +4575,16 @@ dependencies = [
|
|||
"bitflags 2.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
|
|
@ -4306,16 +4738,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.10.3"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
|
||||
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
||||
dependencies = [
|
||||
"fax",
|
||||
"flate2",
|
||||
"half",
|
||||
"quick-error",
|
||||
"weezl",
|
||||
"zune-jpeg 0.4.21",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4532,6 +4964,12 @@ dependencies = [
|
|||
"core_maths",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "uad-cli"
|
||||
version = "1.2.0"
|
||||
|
|
@ -4556,6 +4994,7 @@ dependencies = [
|
|||
name = "uad-core"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"adb_client",
|
||||
"chrono",
|
||||
"csv",
|
||||
"dirs",
|
||||
|
|
@ -5119,7 +5558,7 @@ checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7"
|
|||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bit-vec 0.8.0",
|
||||
"bitflags 2.10.0",
|
||||
"bytemuck",
|
||||
"cfg_aliases 0.2.1",
|
||||
|
|
@ -5891,6 +6330,24 @@ version = "0.13.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||
|
||||
[[package]]
|
||||
name = "x509-parser"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"data-encoding",
|
||||
"der-parser",
|
||||
"lazy_static",
|
||||
"nom 7.1.3",
|
||||
"oid-registry",
|
||||
"ring",
|
||||
"rusticata-macros",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.6.1"
|
||||
|
|
@ -5938,6 +6395,16 @@ version = "0.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
|
||||
|
||||
[[package]]
|
||||
name = "yasna"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
|
||||
dependencies = [
|
||||
"bit-vec 0.9.1",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yazi"
|
||||
version = "0.2.1"
|
||||
|
|
@ -6120,12 +6587,6 @@ version = "1.0.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.1"
|
||||
|
|
@ -6141,22 +6602,13 @@ dependencies = [
|
|||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
|
||||
dependencies = [
|
||||
"zune-core 0.4.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe"
|
||||
dependencies = [
|
||||
"zune-core 0.5.1",
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ edition = "2024"
|
|||
|
||||
[workspace.dependencies]
|
||||
# Core dependencies
|
||||
adb_client = "3.2.2"
|
||||
serde = { version = "^1.0", features = ["derive"] }
|
||||
serde_json = "^1.0"
|
||||
log = "^0.4"
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ use clap::CommandFactory;
|
|||
use clap_complete::{Shell, generate};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::io::Write;
|
||||
use uad_core::adb::{ACommand, PmListPacksFlag};
|
||||
use uad_core::adb::{ACommand, AdbBackend, PmListPacksFlag};
|
||||
use uad_core::sync::{
|
||||
CorePackage, Phone, User, apply_pkg_state_commands, get_devices_list, get_package_state,
|
||||
run_adb_shell_action,
|
||||
run_adb_shell_action_with_backend,
|
||||
};
|
||||
use uad_core::uad_lists::{Package, PackageState, Removal, UadList, load_debloat_lists};
|
||||
use uad_core::utils::{matches_search, truncate_description};
|
||||
|
|
@ -15,9 +15,12 @@ use crate::filters::{ListFilter, RemovalFilter, StateFilter};
|
|||
use crate::{Cli, print_or_exit, println_or_exit};
|
||||
|
||||
/// List all connected Android devices
|
||||
pub fn list_devices() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println_or_exit!("Scanning for connected devices...");
|
||||
let devices = get_devices_list();
|
||||
pub fn list_devices(backend: AdbBackend) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println_or_exit!(
|
||||
"Scanning for connected devices (using {} backend)...",
|
||||
backend
|
||||
);
|
||||
let devices = get_devices_list(backend);
|
||||
|
||||
if devices.is_empty() {
|
||||
return Err(NO_DEVICES_FOUND.into());
|
||||
|
|
@ -58,28 +61,24 @@ impl PackageListContext {
|
|||
pkg_info: Option<&Package>,
|
||||
pkg_state: PackageState,
|
||||
) -> bool {
|
||||
// Removal filter
|
||||
if let Some(removal) = self.removal_filter {
|
||||
if !removal.matches(pkg_info) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// State filter
|
||||
if let Some(state) = self.state_filter {
|
||||
if !state.matches(pkg_state) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// List filter
|
||||
if let Some(list) = self.list_filter {
|
||||
if !list.matches(pkg_info) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Search filter
|
||||
if let Some(ref search_term) = self.search {
|
||||
let description = pkg_info.map(|p| p.description.as_str());
|
||||
if !matches_search(pkg_name, search_term, description) {
|
||||
|
|
@ -104,8 +103,18 @@ pub struct DisplayConfig {
|
|||
pub show_removal: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_pm_flag(state_filter: Option<StateFilter>) -> Option<PmListPacksFlag> {
|
||||
match state_filter {
|
||||
Some(StateFilter::Enabled | StateFilter::Disabled) => {
|
||||
state_filter.and_then(StateFilter::to_pm_flag)
|
||||
}
|
||||
_ => Some(PmListPacksFlag::IncludeUninstalled),
|
||||
}
|
||||
}
|
||||
|
||||
/// List packages on a device with filtering
|
||||
pub fn list_packages(
|
||||
backend: AdbBackend,
|
||||
device: Option<String>,
|
||||
state_filter: Option<StateFilter>,
|
||||
removal_filter: Option<RemovalFilter>,
|
||||
|
|
@ -113,7 +122,7 @@ pub fn list_packages(
|
|||
search: Option<String>,
|
||||
user_id: Option<u16>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let target_device = get_target_device(device)?;
|
||||
let target_device = get_target_device(backend, device)?;
|
||||
let uad_lists = load_debloat_lists(false).unwrap_or_else(|lists| lists);
|
||||
|
||||
println_or_exit!(
|
||||
|
|
@ -129,8 +138,8 @@ pub fn list_packages(
|
|||
search,
|
||||
};
|
||||
|
||||
let pm_flag = state_filter.and_then(StateFilter::to_pm_flag);
|
||||
let system_packages = ACommand::new()
|
||||
let pm_flag = resolve_pm_flag(state_filter);
|
||||
let system_packages = ACommand::with_backend(backend)
|
||||
.shell(&target_device.adb_id)
|
||||
.pm()
|
||||
.list_packages_sys(pm_flag, user_id)?;
|
||||
|
|
@ -141,6 +150,7 @@ pub fn list_packages(
|
|||
&target_device.adb_id,
|
||||
user_id,
|
||||
&context,
|
||||
backend,
|
||||
)?;
|
||||
|
||||
if displayed_count == 0 {
|
||||
|
|
@ -159,18 +169,19 @@ pub fn display_package_list(
|
|||
device_serial: &str,
|
||||
user_id: Option<u16>,
|
||||
context: &PackageListContext,
|
||||
backend: AdbBackend,
|
||||
) -> Result<usize, Box<dyn std::error::Error>> {
|
||||
let config = context.display_config();
|
||||
let mut displayed_count = 0;
|
||||
|
||||
let enabled_packages: HashSet<String> = ACommand::new()
|
||||
let enabled_packages: HashSet<String> = ACommand::with_backend(backend)
|
||||
.shell(device_serial)
|
||||
.pm()
|
||||
.list_packages_sys(Some(PmListPacksFlag::OnlyEnabled), user_id)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.collect();
|
||||
let disabled_packages: HashSet<String> = ACommand::new()
|
||||
let disabled_packages: HashSet<String> = ACommand::with_backend(backend)
|
||||
.shell(device_serial)
|
||||
.pm()
|
||||
.list_packages_sys(Some(PmListPacksFlag::OnlyDisabled), user_id)
|
||||
|
|
@ -241,6 +252,7 @@ pub fn display_package_entry(
|
|||
|
||||
/// Change the state of one or more packages
|
||||
pub fn change_package_state(
|
||||
backend: AdbBackend,
|
||||
packages: &[String],
|
||||
device: Option<String>,
|
||||
user_id: Option<u16>,
|
||||
|
|
@ -252,7 +264,7 @@ pub fn change_package_state(
|
|||
return Err("No packages specified".into());
|
||||
}
|
||||
|
||||
let target_device = get_target_device(device)?;
|
||||
let target_device = get_target_device(backend, device)?;
|
||||
let user = get_user(&target_device, user_id)?;
|
||||
let uad_lists = load_debloat_lists(false).unwrap_or_else(|lists| lists);
|
||||
|
||||
|
|
@ -275,6 +287,7 @@ pub fn change_package_state(
|
|||
user,
|
||||
target_state,
|
||||
dry_run,
|
||||
backend,
|
||||
&uad_lists,
|
||||
)?;
|
||||
println!();
|
||||
|
|
@ -296,6 +309,7 @@ fn process_package_state_change(
|
|||
user: User,
|
||||
target_state: PackageState,
|
||||
dry_run: bool,
|
||||
backend: AdbBackend,
|
||||
uad_lists: &HashMap<String, Package>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let current_state = get_package_state(&device.adb_id, pkg_name, Some(user.id))
|
||||
|
|
@ -337,6 +351,7 @@ fn process_package_state_change(
|
|||
user,
|
||||
device,
|
||||
&commands,
|
||||
backend,
|
||||
" ",
|
||||
)?;
|
||||
}
|
||||
|
|
@ -345,6 +360,10 @@ fn process_package_state_change(
|
|||
}
|
||||
|
||||
/// Execute commands and verify package state with fallback
|
||||
#[allow(
|
||||
clippy::too_many_arguments,
|
||||
reason = "Keeps the shared fallback helper simple across CLI and REPL callers"
|
||||
)]
|
||||
pub fn execute_with_fallback(
|
||||
package: &str,
|
||||
target_state: PackageState,
|
||||
|
|
@ -352,15 +371,14 @@ pub fn execute_with_fallback(
|
|||
user: User,
|
||||
device: &Phone,
|
||||
commands: &[String],
|
||||
backend: AdbBackend,
|
||||
indent: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Capture the before-state of packages on other users for cross-user detection
|
||||
let before_cross_user_states =
|
||||
uad_core::sync::capture_cross_user_states(package, &device.adb_id, user.id, device);
|
||||
|
||||
// Execute commands
|
||||
for cmd in commands {
|
||||
match run_adb_shell_action(&device.adb_id, cmd.as_str()) {
|
||||
match run_adb_shell_action_with_backend(&device.adb_id, cmd.as_str(), backend) {
|
||||
Ok(_) => println!("{}✓ {}", indent, cmd),
|
||||
Err(e) => {
|
||||
return Err(format!("{indent}✗ Failed to execute `{cmd}`: {e:?}").into());
|
||||
|
|
@ -368,7 +386,6 @@ pub fn execute_with_fallback(
|
|||
}
|
||||
}
|
||||
|
||||
// Verify package state and attempt fallback if needed
|
||||
let actual_state = match get_package_state(&device.adb_id, package, Some(user.id)) {
|
||||
Some(state) => state,
|
||||
None if target_state == PackageState::Uninstalled => PackageState::Uninstalled,
|
||||
|
|
@ -387,7 +404,6 @@ pub fn execute_with_fallback(
|
|||
indent, target_state, actual_state
|
||||
);
|
||||
|
||||
// Attempt fallback
|
||||
if let Ok(fallback_action) =
|
||||
uad_core::sync::attempt_fallback(core_pkg, target_state, actual_state, user, device)
|
||||
{
|
||||
|
|
@ -397,7 +413,6 @@ pub fn execute_with_fallback(
|
|||
}
|
||||
}
|
||||
|
||||
// Check for cross-user behavior if operation succeeded
|
||||
if actual_state == target_state {
|
||||
if let Some(notification) = uad_core::sync::detect_cross_user_behavior(
|
||||
package,
|
||||
|
|
@ -417,6 +432,7 @@ pub fn execute_with_fallback(
|
|||
|
||||
/// Show detailed information about a package
|
||||
pub fn show_package_info(
|
||||
backend: AdbBackend,
|
||||
package: &str,
|
||||
device: Option<String>,
|
||||
user_id: Option<u16>,
|
||||
|
|
@ -436,7 +452,7 @@ pub fn show_package_info(
|
|||
}
|
||||
|
||||
if let Some(device_id) = device {
|
||||
let target_device = get_target_device(Some(device_id))?;
|
||||
let target_device = get_target_device(backend, Some(device_id))?;
|
||||
let user = get_user(&target_device, user_id)?;
|
||||
println!("Device: {} ({})", target_device.model, target_device.adb_id);
|
||||
println!("User: {}", user.id);
|
||||
|
|
@ -468,3 +484,19 @@ pub fn generate_completions(shell: Shell) {
|
|||
let name = cmd.get_name().to_string();
|
||||
generate(shell, &mut cmd, name, &mut std::io::stdout());
|
||||
}
|
||||
|
||||
/// Show ADB backend and version information
|
||||
pub fn show_adb_info(backend: AdbBackend) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("ADB Backend: {}\n", backend);
|
||||
|
||||
match ACommand::with_backend(backend).version() {
|
||||
Ok(version) => {
|
||||
println!("{version}");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to get ADB version: {e}");
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
use uad_core::adb::AdbBackend;
|
||||
use uad_core::sync::{Phone, User, get_devices_list};
|
||||
|
||||
pub const NO_DEVICES_FOUND: &str =
|
||||
"No devices found. Make sure ADB is installed and devices are connected.";
|
||||
|
||||
/// Get target device, either by serial or first available
|
||||
pub fn get_target_device(device: Option<String>) -> Result<Phone, Box<dyn std::error::Error>> {
|
||||
let devices = get_devices_list();
|
||||
pub fn get_target_device(
|
||||
backend: AdbBackend,
|
||||
device: Option<String>,
|
||||
) -> Result<Phone, Box<dyn std::error::Error>> {
|
||||
let devices = get_devices_list(backend);
|
||||
|
||||
if devices.is_empty() {
|
||||
return Err(NO_DEVICES_FOUND.into());
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@
|
|||
clippy::uninlined_format_args,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::unnecessary_wraps,
|
||||
clippy::exit,
|
||||
reason = "Suppress non-critical pedantic/style lints to keep build green"
|
||||
)]
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
use clap_complete::Shell;
|
||||
use std::process::ExitCode;
|
||||
use uad_core::adb::AdbBackend;
|
||||
use uad_core::uad_lists::PackageState;
|
||||
|
||||
mod commands;
|
||||
|
|
@ -20,12 +22,40 @@ mod repl;
|
|||
|
||||
use filters::{ListFilter, RemovalFilter, StateFilter};
|
||||
|
||||
/// CLI-compatible ADB backend selection
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
||||
pub enum AdbBackendArg {
|
||||
/// Built-in ADB implementation (no external dependencies)
|
||||
Builtin,
|
||||
/// Use system-installed adb binary
|
||||
System,
|
||||
}
|
||||
|
||||
impl From<AdbBackendArg> for AdbBackend {
|
||||
fn from(arg: AdbBackendArg) -> Self {
|
||||
match arg {
|
||||
AdbBackendArg::Builtin => AdbBackend::Builtin,
|
||||
AdbBackendArg::System => AdbBackend::System,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "uad")]
|
||||
#[command(about = "Universal Android Debloater - Command Line Interface", long_about = None)]
|
||||
#[command(version)]
|
||||
#[command(propagate_version = true)]
|
||||
pub struct Cli {
|
||||
/// ADB backend to use: system (default, uses adb binary) or builtin (no dependencies)
|
||||
#[arg(
|
||||
short = 'B',
|
||||
long = "backend",
|
||||
value_enum,
|
||||
global = true,
|
||||
default_value = "system"
|
||||
)]
|
||||
backend: AdbBackendArg,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
|
@ -136,6 +166,9 @@ enum Commands {
|
|||
/// Update UAD package lists from remote repository
|
||||
Update,
|
||||
|
||||
/// Show ADB backend and version information
|
||||
Adb,
|
||||
|
||||
/// Generate shell completion script
|
||||
Completions {
|
||||
/// Shell to generate completions for
|
||||
|
|
@ -169,10 +202,11 @@ async fn main() -> ExitCode {
|
|||
|
||||
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = Cli::parse();
|
||||
let backend: AdbBackend = cli.backend.into();
|
||||
|
||||
match cli.command {
|
||||
Commands::Devices => {
|
||||
commands::list_devices()?;
|
||||
commands::list_devices(backend)?;
|
||||
}
|
||||
Commands::List {
|
||||
device,
|
||||
|
|
@ -182,7 +216,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
search,
|
||||
user,
|
||||
} => {
|
||||
commands::list_packages(device, state, removal, list, search, user)?;
|
||||
commands::list_packages(backend, device, state, removal, list, search, user)?;
|
||||
}
|
||||
Commands::Uninstall {
|
||||
packages,
|
||||
|
|
@ -191,6 +225,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
dry_run,
|
||||
} => {
|
||||
commands::change_package_state(
|
||||
backend,
|
||||
&packages,
|
||||
device,
|
||||
user,
|
||||
|
|
@ -206,6 +241,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
dry_run,
|
||||
} => {
|
||||
commands::change_package_state(
|
||||
backend,
|
||||
&packages,
|
||||
device,
|
||||
user,
|
||||
|
|
@ -221,6 +257,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
dry_run,
|
||||
} => {
|
||||
commands::change_package_state(
|
||||
backend,
|
||||
&packages,
|
||||
device,
|
||||
user,
|
||||
|
|
@ -234,16 +271,19 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
device,
|
||||
user,
|
||||
} => {
|
||||
commands::show_package_info(&package, device, user)?;
|
||||
commands::show_package_info(backend, &package, device, user)?;
|
||||
}
|
||||
Commands::Update => {
|
||||
commands::update_lists()?;
|
||||
}
|
||||
Commands::Adb => {
|
||||
commands::show_adb_info(backend)?;
|
||||
}
|
||||
Commands::Completions { shell } => {
|
||||
commands::generate_completions(shell);
|
||||
}
|
||||
Commands::Repl { device, user } => {
|
||||
repl::repl_mode(device, user)?;
|
||||
repl::repl_mode(backend, device, user)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,22 @@
|
|||
#[macro_export]
|
||||
macro_rules! println_or_exit {
|
||||
() => {
|
||||
let _ = writeln!(std::io::stdout());
|
||||
if writeln!(std::io::stdout()).is_err() {
|
||||
std::process::exit(0);
|
||||
}
|
||||
};
|
||||
($($arg:tt)*) => {
|
||||
let _ = writeln!(std::io::stdout(), $($arg)*);
|
||||
if writeln!(std::io::stdout(), $($arg)*).is_err() {
|
||||
std::process::exit(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print_or_exit {
|
||||
($($arg:tt)*) => {
|
||||
let _ = write!(std::io::stdout(), $($arg)*);
|
||||
if write!(std::io::stdout(), $($arg)*).is_err() {
|
||||
std::process::exit(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use rustyline::DefaultEditor;
|
|||
use rustyline::error::ReadlineError;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use uad_core::adb::ACommand;
|
||||
use uad_core::adb::{ACommand, AdbBackend};
|
||||
use uad_core::sync::{CorePackage, Phone, User, apply_pkg_state_commands, get_package_state};
|
||||
use uad_core::uad_lists::{Package, PackageState, Removal, UadList, load_debloat_lists};
|
||||
|
||||
|
|
@ -18,13 +18,15 @@ enum ReplAction {
|
|||
|
||||
/// Start interactive REPL mode
|
||||
pub fn repl_mode(
|
||||
backend: AdbBackend,
|
||||
device: Option<String>,
|
||||
user_id: Option<u16>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Universal Android Debloater - Interactive Mode");
|
||||
println!("Using {} ADB backend", backend);
|
||||
println!("Type 'help' for available commands, 'exit' or 'quit' to leave\n");
|
||||
|
||||
let target_device = get_target_device(device)?;
|
||||
let target_device = get_target_device(backend, device)?;
|
||||
let user = get_user(&target_device, user_id)?;
|
||||
|
||||
println!(
|
||||
|
|
@ -46,7 +48,15 @@ pub fn repl_mode(
|
|||
let readline = rl.readline("uad> ");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
match handle_repl_line(&line, &mut rl, &target_device, user, user_id, &uad_lists) {
|
||||
match handle_repl_line(
|
||||
&line,
|
||||
&mut rl,
|
||||
&target_device,
|
||||
user,
|
||||
user_id,
|
||||
&uad_lists,
|
||||
backend,
|
||||
) {
|
||||
Ok(ReplAction::Continue) => {}
|
||||
Ok(ReplAction::Exit) => break,
|
||||
Err(e) => {
|
||||
|
|
@ -88,6 +98,7 @@ fn handle_repl_line(
|
|||
user: User,
|
||||
user_id: Option<u16>,
|
||||
uad_lists: &HashMap<String, Package>,
|
||||
backend: AdbBackend,
|
||||
) -> Result<ReplAction, Box<dyn std::error::Error>> {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
|
|
@ -108,7 +119,7 @@ fn handle_repl_line(
|
|||
return Ok(ReplAction::Exit);
|
||||
}
|
||||
"list" | "ls" => {
|
||||
handle_list_command(&parts[1..], device, user_id, uad_lists)?;
|
||||
handle_list_command(&parts[1..], device, user_id, uad_lists, backend)?;
|
||||
}
|
||||
"info" => {
|
||||
handle_info_command(&parts[1..], device, user.id, uad_lists)?;
|
||||
|
|
@ -121,6 +132,7 @@ fn handle_repl_line(
|
|||
PackageState::Uninstalled,
|
||||
"Uninstalling",
|
||||
uad_lists,
|
||||
backend,
|
||||
)?;
|
||||
}
|
||||
"enable" | "restore" => {
|
||||
|
|
@ -131,6 +143,7 @@ fn handle_repl_line(
|
|||
PackageState::Enabled,
|
||||
"Enabling",
|
||||
uad_lists,
|
||||
backend,
|
||||
)?;
|
||||
}
|
||||
"disable" => {
|
||||
|
|
@ -141,12 +154,13 @@ fn handle_repl_line(
|
|||
PackageState::Disabled,
|
||||
"Disabling",
|
||||
uad_lists,
|
||||
backend,
|
||||
)?;
|
||||
}
|
||||
"device" => {
|
||||
println!(
|
||||
"Device: {} ({}), Android SDK: {}, User: {}",
|
||||
device.model, device.adb_id, device.android_sdk, user.id
|
||||
"Device: {} ({}), Android SDK: {}, User: {}, Backend: {}",
|
||||
device.model, device.adb_id, device.android_sdk, user.id, backend
|
||||
);
|
||||
}
|
||||
"clear" => {
|
||||
|
|
@ -216,11 +230,12 @@ fn handle_list_command(
|
|||
device: &Phone,
|
||||
user_id: Option<u16>,
|
||||
uad_lists: &HashMap<String, Package>,
|
||||
backend: AdbBackend,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let parsed = ReplListArgs::parse(args)?;
|
||||
|
||||
let pm_flag = parsed.state_filter.and_then(StateFilter::to_pm_flag);
|
||||
let system_packages = ACommand::new()
|
||||
let system_packages = ACommand::with_backend(backend)
|
||||
.shell(&device.adb_id)
|
||||
.pm()
|
||||
.list_packages_sys(pm_flag, user_id)?;
|
||||
|
|
@ -238,6 +253,7 @@ fn handle_list_command(
|
|||
&device.adb_id,
|
||||
user_id,
|
||||
&context,
|
||||
backend,
|
||||
)?;
|
||||
|
||||
if displayed_count == 0 {
|
||||
|
|
@ -287,6 +303,7 @@ fn handle_state_change_command(
|
|||
target_state: PackageState,
|
||||
action_name: &str,
|
||||
uad_lists: &HashMap<String, Package>,
|
||||
backend: AdbBackend,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if args.is_empty() {
|
||||
eprintln!(
|
||||
|
|
@ -297,7 +314,15 @@ fn handle_state_change_command(
|
|||
}
|
||||
|
||||
for pkg_name in args {
|
||||
process_package_change(pkg_name, device, user, target_state, action_name, uad_lists)?;
|
||||
process_package_change(
|
||||
pkg_name,
|
||||
device,
|
||||
user,
|
||||
target_state,
|
||||
action_name,
|
||||
uad_lists,
|
||||
backend,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -311,6 +336,7 @@ fn process_package_change(
|
|||
target_state: PackageState,
|
||||
action_name: &str,
|
||||
uad_lists: &HashMap<String, Package>,
|
||||
backend: AdbBackend,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let current_state = get_package_state(&device.adb_id, pkg_name, Some(user.id))
|
||||
.ok_or("Package not found on device")?;
|
||||
|
|
@ -346,6 +372,7 @@ fn process_package_change(
|
|||
user,
|
||||
device,
|
||||
&commands,
|
||||
backend,
|
||||
" ",
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ default = []
|
|||
self-update = ["dep:flate2", "dep:tar"]
|
||||
|
||||
[dependencies]
|
||||
adb_client.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
log.workspace = true
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@
|
|||
//!
|
||||
//! Following the design philosophy of most of Rust `std`,
|
||||
//! `*Command` are intended to be "thin wrappers" (low-overhead abstractions)
|
||||
//! around the ADB CLI or `adb_client`
|
||||
//! ([in the future](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/issues/700) ),
|
||||
//! around `adb_client` or the system ADB CLI,
|
||||
//! which implies:
|
||||
//! - no "magic"
|
||||
//! - no custom commands
|
||||
|
|
@ -33,11 +32,24 @@
|
|||
//! please, **PLEASE** refrain from falling-back to any `Command`-like API.
|
||||
//! Rather, please extend these APIs in a consistent way.
|
||||
//!
|
||||
//! ## Backend Selection
|
||||
//!
|
||||
//! This module supports two ADB backends:
|
||||
//! - **Builtin** (`adb_client`): Pure Rust implementation, no external dependencies
|
||||
//! - **System**: Uses the system-installed `adb` binary
|
||||
//!
|
||||
//! Use [`ACommand::with_backend`] to select a specific backend,
|
||||
//! or [`ACommand::new`] to use the default (System backend).
|
||||
//!
|
||||
//! Thank you! ❤️
|
||||
//!
|
||||
//! For comprehensive info about ADB,
|
||||
//! [see this](https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/master/docs/)
|
||||
|
||||
use adb_client::{ADBDeviceExt, server::ADBServer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write as _;
|
||||
use std::io::Cursor;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
|
@ -53,25 +65,83 @@ pub fn to_trimmed_utf8(v: &[u8]) -> String {
|
|||
String::from_utf8_lossy(v).trim_end().to_string()
|
||||
}
|
||||
|
||||
/// ADB backend selection.
|
||||
///
|
||||
/// - **Builtin**: Uses the `adb_client` crate (pure Rust, no external dependencies)
|
||||
/// - **System**: Uses the system-installed `adb` binary
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub enum AdbBackend {
|
||||
/// Built-in ADB implementation via `adb_client` crate.
|
||||
/// The application can communicate with devices without needing `adb` installed.
|
||||
Builtin,
|
||||
/// Uses the system-installed `adb` binary.
|
||||
/// This is the default to preserve existing behavior.
|
||||
/// Requires `adb` to be available in PATH.
|
||||
/// Useful if you prefer using your own ADB installation or need specific ADB features.
|
||||
#[default]
|
||||
System,
|
||||
}
|
||||
|
||||
impl AdbBackend {
|
||||
/// Returns all available backend variants for UI enumeration
|
||||
pub const ALL: [Self; 2] = [Self::Builtin, Self::System];
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AdbBackend {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Builtin => write!(f, "Builtin"),
|
||||
Self::System => write!(f, "System (adb)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[must_use]
|
||||
fn is_version_triple(s: &str) -> bool {
|
||||
let mut components = s.split('.');
|
||||
for _ in 0..3 {
|
||||
if !components
|
||||
.next()
|
||||
.is_some_and(|comp| comp.as_bytes().iter().all(u8::is_ascii_digit))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if components.next().is_some() {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
let mut parts = s.split('.');
|
||||
let is_digits = |c: &str| !c.is_empty() && c.bytes().all(|b| b.is_ascii_digit());
|
||||
parts.next().is_some_and(is_digits)
|
||||
&& parts.next().is_some_and(is_digits)
|
||||
&& parts.next().is_some_and(is_digits)
|
||||
&& parts.next().is_none()
|
||||
}
|
||||
|
||||
/// Builder object for an Android Debug Bridge CLI command,
|
||||
#[cfg(debug_assertions)]
|
||||
fn assert_adb_version_output_format(out: &str) {
|
||||
const ADBV: &str = "Android Debug Bridge version ";
|
||||
const V: &str = "Version ";
|
||||
|
||||
let mut lns = out.lines();
|
||||
|
||||
assert!(
|
||||
lns.next()
|
||||
.is_some_and(|ln| ln.starts_with(ADBV) && is_version_triple(&ln[ADBV.len()..]))
|
||||
);
|
||||
assert!(
|
||||
lns.next().is_some_and(|ln| ln.starts_with(V)
|
||||
&& is_version_triple(&ln[V.len()..ln.find('-').unwrap_or(ln.len())]))
|
||||
);
|
||||
// missing test for valid path
|
||||
assert!(
|
||||
lns.next().is_some_and(|ln| ln.starts_with("Installed as ")
|
||||
&& (ln.ends_with("adb") || ln.ends_with("adb.exe")))
|
||||
);
|
||||
// missing test for x86/ARM (both 64b)
|
||||
assert!(lns.next().is_some_and(|ln| ln.starts_with("Running on ")));
|
||||
if lns.next().is_some() {
|
||||
unreachable!("Expected < 5 lines")
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal state for `ACommand` - tracks the device serial and backend to use
|
||||
#[derive(Debug)]
|
||||
struct ACommandState {
|
||||
device_serial: Option<String>,
|
||||
backend: AdbBackend,
|
||||
}
|
||||
|
||||
/// Builder object for an Android Debug Bridge command,
|
||||
/// using the type-state and new-type patterns.
|
||||
///
|
||||
/// This is not intended to model the entire ADB API.
|
||||
|
|
@ -79,12 +149,22 @@ fn is_version_triple(s: &str) -> bool {
|
|||
///
|
||||
/// [More info here](https://developer.android.com/tools/adb)
|
||||
#[derive(Debug)]
|
||||
pub struct ACommand(std::process::Command);
|
||||
pub struct ACommand(ACommandState);
|
||||
|
||||
impl ACommand {
|
||||
/// `adb` command builder
|
||||
/// `adb` command builder with the default backend.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self(std::process::Command::new("adb"))
|
||||
Self::with_backend(AdbBackend::default())
|
||||
}
|
||||
|
||||
/// `adb` command builder with a specific backend
|
||||
#[must_use]
|
||||
pub fn with_backend(backend: AdbBackend) -> Self {
|
||||
Self(ACommandState {
|
||||
device_serial: None,
|
||||
backend,
|
||||
})
|
||||
}
|
||||
|
||||
/// `shell` sub-command builder.
|
||||
|
|
@ -94,9 +174,8 @@ impl ACommand {
|
|||
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.device_serial = Some(serial.to_string());
|
||||
}
|
||||
self.0.arg("shell");
|
||||
ShellCommand(self)
|
||||
}
|
||||
|
||||
|
|
@ -108,109 +187,189 @@ impl ACommand {
|
|||
/// 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())
|
||||
pub fn devices(self) -> Result<Vec<(String, String)>, String> {
|
||||
match self.0.backend {
|
||||
AdbBackend::Builtin => Self::devices_builtin(),
|
||||
AdbBackend::System => Self::devices_system(),
|
||||
}
|
||||
}
|
||||
|
||||
/// `version` sub-command
|
||||
/// Returns version information from the ADB server/binary.
|
||||
///
|
||||
/// ## Format
|
||||
/// This is just a sample,
|
||||
/// we don't know which guarantees are stable (yet):
|
||||
/// ## Builtin backend
|
||||
/// Returns the ADB server protocol version:
|
||||
/// ```txt
|
||||
/// ADB Server Version: 1.0.41
|
||||
/// ```
|
||||
///
|
||||
/// ## System backend
|
||||
/// Returns the full `adb version` output (format may vary by installation):
|
||||
/// ```txt
|
||||
/// Android Debug Bridge version 1.0.41
|
||||
/// Version 34.0.5-debian
|
||||
/// Installed as /usr/lib/android-sdk/platform-tools/adb
|
||||
/// Running on Linux 6.12.12-amd64 (x86_64)
|
||||
/// Version 35.0.2-android-tools
|
||||
/// Installed as /usr/bin/adb
|
||||
/// Running on Linux 6.18 (x86_64)
|
||||
/// ```
|
||||
///
|
||||
/// The expected format should be like:
|
||||
/// ```txt
|
||||
/// Android Debug Bridge version <num>.<num>.<num>
|
||||
/// Version <num>.<num>.<num>-<no spaces>
|
||||
/// Installed as <ANDROID_SDK_HOME>/platform-tools/adb[.exe]
|
||||
/// Running on <OS/kernel version> (<CPU arch>)
|
||||
/// ```
|
||||
#[expect(clippy::panic_in_result_fn, reason = "Assertions are fine")]
|
||||
pub fn version(mut self) -> Result<String, String> {
|
||||
self.0.arg("version");
|
||||
let out = self.run()?;
|
||||
pub fn version(self) -> Result<String, String> {
|
||||
match self.0.backend {
|
||||
AdbBackend::Builtin => Self::version_builtin(),
|
||||
AdbBackend::System => Self::version_system(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
const ADBV: &str = "Android Debug Bridge version ";
|
||||
const V: &str = "Version ";
|
||||
// ========== Builtin backend implementation (adb_client) ==========
|
||||
|
||||
let mut lns = out.lines();
|
||||
|
||||
assert!(
|
||||
lns.next()
|
||||
.is_some_and(|ln| ln.starts_with(ADBV) && is_version_triple(&ln[ADBV.len()..]))
|
||||
);
|
||||
assert!(lns.next().is_some_and(|ln| ln.starts_with(V)
|
||||
&& is_version_triple(&ln[V.len()..ln.find('-').unwrap_or(ln.len())])));
|
||||
// missing test for valid path
|
||||
assert!(lns.next().is_some_and(|ln| ln.starts_with("Installed as ")
|
||||
&& (ln.ends_with("adb") || ln.ends_with("adb.exe"))));
|
||||
// missing test for x86/ARM (both 64b)
|
||||
assert!(lns.next().is_some_and(|ln| ln.starts_with("Running on ")));
|
||||
if lns.next().is_some() {
|
||||
unreachable!("Expected < 5 lines")
|
||||
/// Get ADB server version using the builtin `adb_client`
|
||||
fn version_builtin() -> Result<String, String> {
|
||||
let mut server = ADBServer::default();
|
||||
match server.version() {
|
||||
Ok(version) => Ok(format!("ADB Server Version: {version}")),
|
||||
Err(e) => {
|
||||
error!("Failed to get ADB server version: {e}");
|
||||
Err(format!("Cannot get ADB server version: {e}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// List devices using the builtin `adb_client`
|
||||
fn devices_builtin() -> Result<Vec<(String, String)>, String> {
|
||||
let mut server = ADBServer::default();
|
||||
server
|
||||
.devices()
|
||||
.map(|device_list| {
|
||||
device_list
|
||||
.into_iter()
|
||||
.map(|dev| (dev.identifier, dev.state.to_string()))
|
||||
.collect()
|
||||
})
|
||||
.map_err(|e| {
|
||||
error!("ADB: {e}");
|
||||
format!("Cannot connect to ADB server: {e}")
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute a shell command via `adb_client` (builtin backend)
|
||||
fn run_shell_command_builtin(&self, shell_command: &str) -> Result<String, String> {
|
||||
let mut server = ADBServer::default();
|
||||
|
||||
// Validate device availability and serial
|
||||
if let Some(ref serial) = self.0.device_serial {
|
||||
let device_list = server
|
||||
.devices()
|
||||
.map_err(|e| format!("Cannot get device list: {e}"))?;
|
||||
|
||||
if !device_list.iter().any(|d| d.identifier == *serial) {
|
||||
let available = device_list
|
||||
.iter()
|
||||
.map(|d| d.identifier.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
return Err(format!(
|
||||
"Device '{serial}' not found. Available: {available}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut device = server
|
||||
.get_device()
|
||||
.map_err(|e| format!("Cannot connect to device: {e}"))?;
|
||||
|
||||
if shell_command.trim().is_empty() {
|
||||
return Err("Empty shell command".into());
|
||||
}
|
||||
|
||||
info!("Ran command: adb shell {shell_command}");
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
let _exit_code = device
|
||||
.shell_command(&shell_command, Some(&mut Cursor::new(&mut buffer)), None)
|
||||
.map_err(|e| {
|
||||
error!("ADB shell command failed: {e}");
|
||||
format!("Shell command failed: {e}")
|
||||
})?;
|
||||
|
||||
Ok(String::from_utf8_lossy(&buffer).trim_end().to_string())
|
||||
}
|
||||
|
||||
// ========== System backend implementation (adb binary) ==========
|
||||
|
||||
/// Get ADB version using the system `adb` binary
|
||||
fn version_system() -> Result<String, String> {
|
||||
let mut cmd = std::process::Command::new("adb");
|
||||
cmd.arg("version");
|
||||
let out = Self::run_system_command(cmd)?;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
assert_adb_version_output_format(&out);
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// General executor
|
||||
fn run(self) -> Result<String, String> {
|
||||
let mut cmd = self.0;
|
||||
/// List devices using the system `adb` binary
|
||||
fn devices_system() -> Result<Vec<(String, String)>, String> {
|
||||
let mut cmd = std::process::Command::new("adb");
|
||||
cmd.arg("devices");
|
||||
Ok(Self::run_system_command(cmd)?
|
||||
.lines()
|
||||
.skip(1) // header
|
||||
.filter_map(|line| {
|
||||
let (serial, status) = line.split_once('\t')?;
|
||||
Some((serial.to_string(), status.to_string()))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Execute a shell command via system `adb` binary
|
||||
fn run_shell_command_system(&self, shell_command: &str) -> Result<String, String> {
|
||||
let mut cmd = std::process::Command::new("adb");
|
||||
|
||||
if let Some(ref serial) = self.0.device_serial {
|
||||
cmd.args(["-s", serial]);
|
||||
}
|
||||
|
||||
cmd.arg("shell");
|
||||
cmd.arg(shell_command);
|
||||
|
||||
info!("Ran command: adb shell {}", shell_command);
|
||||
Self::run_system_command(cmd)
|
||||
}
|
||||
|
||||
/// General system command executor for adb binary
|
||||
fn run_system_command(mut cmd: std::process::Command) -> Result<String, String> {
|
||||
#[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!()))
|
||||
.filter_map(|s| s.to_str())
|
||||
.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)
|
||||
}
|
||||
}
|
||||
|
||||
let output = cmd.output().map_err(|e| {
|
||||
error!("ADB: {e}");
|
||||
"Cannot run ADB, likely not found".to_string()
|
||||
})?;
|
||||
|
||||
let stdout = to_trimmed_utf8(&output.stdout);
|
||||
if output.status.success() {
|
||||
Ok(stdout)
|
||||
} else {
|
||||
// ADB sometimes outputs errors to stdout instead of stderr
|
||||
Err(if stdout.is_empty() {
|
||||
to_trimmed_utf8(&output.stderr)
|
||||
} else {
|
||||
stdout
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a shell command using the configured backend
|
||||
fn run_shell_command(&self, shell_command: &str) -> Result<String, String> {
|
||||
match self.0.backend {
|
||||
AdbBackend::Builtin => self.run_shell_command_builtin(shell_command),
|
||||
AdbBackend::System => self.run_shell_command_system(shell_command),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -227,12 +386,13 @@ impl Default for ACommand {
|
|||
/// [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");
|
||||
pub fn pm(self) -> PmCommand {
|
||||
PmCommand(self)
|
||||
}
|
||||
|
||||
/// Query a device property value, by its key.
|
||||
/// These can be of any type:
|
||||
/// - `boolean`
|
||||
|
|
@ -241,36 +401,26 @@ impl ShellCommand {
|
|||
/// - 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()
|
||||
pub fn getprop(self, key: &str) -> Result<String, String> {
|
||||
self.0.run_shell_command(&format!("getprop {key}"))
|
||||
}
|
||||
|
||||
/// Reboots device
|
||||
pub fn reboot(mut self) -> Result<String, String> {
|
||||
self.0.0.arg("reboot");
|
||||
self.0.run()
|
||||
pub fn reboot(self) -> Result<String, String> {
|
||||
self.0.run_shell_command("reboot")
|
||||
}
|
||||
|
||||
/// Execute an arbitrary shell action string on the device's default shell.
|
||||
/// The action string is passed as a single argument to `adb shell` and
|
||||
/// interpreted by the remote shell (which splits on spaces).
|
||||
pub fn raw(mut self, action: &str) -> Result<String, String> {
|
||||
self.0.0.arg(action);
|
||||
self.0.run()
|
||||
pub fn raw(self, action: &str) -> Result<String, String> {
|
||||
self.0.run_shell_command(action)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn is_pkg_component(s: &[u8]) -> bool {
|
||||
if s.is_empty() {
|
||||
return false;
|
||||
}
|
||||
s[0].is_ascii_alphabetic()
|
||||
&& if s.len() > 1 {
|
||||
is_all_w_c(s.split_at(1).1)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
!s.is_empty() && s[0].is_ascii_alphabetic() && (s.len() == 1 || is_all_w_c(s.split_at(1).1))
|
||||
}
|
||||
|
||||
/// String with the invariant of being a valid package-name.
|
||||
|
|
@ -314,8 +464,7 @@ pub enum PmListPacksFlag {
|
|||
OnlyDisabled,
|
||||
}
|
||||
impl PmListPacksFlag {
|
||||
// is there a trait for this?
|
||||
fn to_str(self) -> &'static str {
|
||||
const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::IncludeUninstalled => "-u",
|
||||
Self::OnlyEnabled => "-e",
|
||||
|
|
@ -323,10 +472,10 @@ impl PmListPacksFlag {
|
|||
}
|
||||
}
|
||||
}
|
||||
#[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()
|
||||
|
||||
impl std::fmt::Display for PmListPacksFlag {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -348,29 +497,25 @@ impl PmCommand {
|
|||
/// - isn't sorted
|
||||
/// - duplicates never _seem_ to happen, but don't assume uniqueness
|
||||
pub fn list_packages_sys(
|
||||
mut self,
|
||||
f: Option<PmListPacksFlag>,
|
||||
self,
|
||||
flag: 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());
|
||||
let mut command = String::from("pm list packages -s");
|
||||
if let Some(f) = flag {
|
||||
write!(&mut command, " {f}").ok();
|
||||
}
|
||||
if let Some(u) = user_id {
|
||||
cmd.arg("--user");
|
||||
cmd.arg(u.to_string());
|
||||
if let Some(uid) = user_id {
|
||||
write!(&mut command, " --user {uid}").ok();
|
||||
}
|
||||
|
||||
self.0.0.run().map(|pack_ls| {
|
||||
pack_ls
|
||||
self.0.raw(&command).map(|output| {
|
||||
output
|
||||
.lines()
|
||||
.map(|p_ln| {
|
||||
debug_assert!(p_ln.starts_with(PACK_PREFIX));
|
||||
let p = &p_ln[PACK_PREFIX.len()..];
|
||||
debug_assert!(PackageId::new(p).is_some());
|
||||
String::from(p)
|
||||
.filter_map(|line| {
|
||||
let pkg = line.strip_prefix(PACK_PREFIX)?;
|
||||
debug_assert!(PackageId::new(pkg).is_some());
|
||||
Some(pkg.to_string())
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
|
|
@ -380,56 +525,22 @@ impl PmCommand {
|
|||
///
|
||||
/// - <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<Box<[UserInfo]>, String> {
|
||||
self.0.0.0.args(["list", "users"]);
|
||||
pub fn list_users(self) -> Result<Box<[UserInfo]>, String> {
|
||||
// Expected shape: "UserInfo{<id>:<name>:<flags>}[ running]"
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/content/pm/UserInfo.java
|
||||
Ok(self
|
||||
.0
|
||||
.0
|
||||
.run()?
|
||||
.raw("pm list users")?
|
||||
.lines()
|
||||
.skip(1) // omit header
|
||||
.map(|ln| {
|
||||
// this could be optimized by making more API-stability assumptions
|
||||
let ln = ln.trim_ascii_start();
|
||||
let ln = ln.strip_prefix("UserInfo").unwrap_or(ln).trim_ascii_start();
|
||||
let ln = ln.strip_prefix('{').unwrap_or(ln).trim_ascii();
|
||||
//let run;
|
||||
let ln = if let Some(l) = ln.strip_suffix("running") {
|
||||
//run = true;
|
||||
l.trim_ascii_end()
|
||||
} else {
|
||||
//run = false;
|
||||
ln
|
||||
};
|
||||
let ln = ln.strip_suffix('}').unwrap_or(ln).trim_ascii_end();
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/content/pm/UserInfo.java
|
||||
// The format looks stable today, but google may change it in future Android versions
|
||||
// (and very old Androids might differ). Keep parsing defensive.
|
||||
// Expected shape: "UserInfo{<id>:<name>:<flags>}[ running]"
|
||||
|
||||
let mut comps = ln.split(':');
|
||||
|
||||
let id = comps
|
||||
.next()
|
||||
.expect("There must be at least 1 ':'-separated component")
|
||||
.parse()
|
||||
.expect("string assumed to be UID numeral");
|
||||
//let name = comps
|
||||
// .next()
|
||||
// .expect("There must be at least 2 ':'-separated components. 2nd is user-name");
|
||||
//let flags = u32::from_str_radix(
|
||||
// comps.next().expect(
|
||||
// "There must be at least 3 ':'-separated components. 3rd is user bit-flags",
|
||||
// ),
|
||||
// 16,
|
||||
//)
|
||||
//.expect("string assumed to be hexadecimal bit-flags");
|
||||
UserInfo {
|
||||
id,
|
||||
//name: name.into(),
|
||||
//flags,
|
||||
//running: run,
|
||||
}
|
||||
.filter_map(|line| {
|
||||
// Defensive parsing: strip known prefixes/suffixes, extract ID
|
||||
let s = line.trim_ascii();
|
||||
let s = s.strip_prefix("UserInfo{").unwrap_or(s);
|
||||
let s = s.strip_suffix("running").unwrap_or(s).trim_ascii_end();
|
||||
let s = s.strip_suffix('}').unwrap_or(s);
|
||||
let id = s.split(':').next()?.parse().ok()?;
|
||||
Some(UserInfo { id })
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
|
@ -496,4 +607,15 @@ mod tests {
|
|||
assert_ne!(PackageId::new(p_id), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backend_display() {
|
||||
assert_eq!(AdbBackend::Builtin.to_string(), "Builtin");
|
||||
assert_eq!(AdbBackend::System.to_string(), "System (adb)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backend_default_is_system() {
|
||||
assert_eq!(AdbBackend::default(), AdbBackend::System);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::CACHE_DIR;
|
||||
use crate::CONFIG_DIR;
|
||||
use crate::adb::AdbBackend;
|
||||
use crate::sync::User;
|
||||
use crate::utils::DisplayablePath;
|
||||
use log::error;
|
||||
|
|
@ -22,6 +23,9 @@ pub struct GeneralSettings {
|
|||
pub theme: String,
|
||||
pub expert_mode: bool,
|
||||
pub backup_folder: PathBuf,
|
||||
/// ADB backend to use: Builtin (no external dependencies) or System (uses installed adb)
|
||||
#[serde(default)]
|
||||
pub adb_backend: AdbBackend,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
|
|
@ -49,6 +53,7 @@ impl Default for GeneralSettings {
|
|||
theme: DEFAULT_THEME.to_string(),
|
||||
expert_mode: false,
|
||||
backup_folder: CACHE_DIR.join("backups"),
|
||||
adb_backend: AdbBackend::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
adb::{ACommand as AdbCommand, PM_CLEAR_PACK, PackageId},
|
||||
adb::{ACommand as AdbCommand, AdbBackend, PM_CLEAR_PACK, PackageId},
|
||||
uad_lists::PackageState,
|
||||
};
|
||||
use log::{error, info};
|
||||
|
|
@ -61,13 +61,25 @@ pub enum AdbError {
|
|||
/// This replaces the deprecated `adb_shell_command`.
|
||||
///
|
||||
/// If `serial` is empty, it lets ADB choose the default device.
|
||||
/// Uses the default ADB backend.
|
||||
pub fn run_adb_shell_action<S: AsRef<str>>(
|
||||
device_serial: S,
|
||||
action: &str,
|
||||
) -> Result<String, AdbError> {
|
||||
run_adb_shell_action_with_backend(device_serial, action, AdbBackend::default())
|
||||
}
|
||||
|
||||
/// Run an arbitrary shell action via the typed ADB wrapper with a specific backend.
|
||||
///
|
||||
/// If `serial` is empty, it lets ADB choose the default device.
|
||||
pub fn run_adb_shell_action_with_backend<S: AsRef<str>>(
|
||||
device_serial: S,
|
||||
action: &str,
|
||||
backend: AdbBackend,
|
||||
) -> Result<String, AdbError> {
|
||||
let serial = device_serial.as_ref();
|
||||
|
||||
match AdbCommand::new().shell(serial).raw(action) {
|
||||
match AdbCommand::with_backend(backend).shell(serial).raw(action) {
|
||||
Ok(o) => {
|
||||
if ["Error", "Failure"].iter().any(|&e| o.contains(e)) {
|
||||
let friendly_msg = make_friendly_error_message(&o, action);
|
||||
|
|
@ -213,7 +225,7 @@ pub fn request_builder(commands: &[&str], package: &str, user: Option<User>) ->
|
|||
// guarantee local to the sink instead of relying on each caller to sanitise.
|
||||
// Fail closed: emit no command for a malformed name rather than an injectable
|
||||
// device-shell string.
|
||||
if PackageId::new(package).is_some() {
|
||||
if PackageId::new(package).is_none() {
|
||||
error!("request_builder: refusing invalid package name: {package:?}");
|
||||
return Vec::new();
|
||||
}
|
||||
|
|
@ -224,23 +236,48 @@ pub fn request_builder(commands: &[&str], package: &str, user: Option<User>) ->
|
|||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn sanitize_device_prop(raw: &str) -> String {
|
||||
raw.chars()
|
||||
.filter(|c| !c.is_control())
|
||||
.collect::<String>()
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn parse_sdk_version(raw: &str) -> Option<u8> {
|
||||
let sanitized = sanitize_device_prop(raw);
|
||||
let digits: String = sanitized
|
||||
.chars()
|
||||
.skip_while(|c| !c.is_ascii_digit())
|
||||
.take_while(char::is_ascii_digit)
|
||||
.collect();
|
||||
|
||||
if digits.is_empty() {
|
||||
None
|
||||
} else {
|
||||
digits.parse().ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the model by querying the `ro.product.model` property.
|
||||
///
|
||||
/// If `serial` is empty, it lets ADB choose the default device.
|
||||
#[must_use]
|
||||
pub fn get_device_model(serial: &str) -> String {
|
||||
AdbCommand::new()
|
||||
.shell(serial)
|
||||
.getprop("ro.product.model")
|
||||
.unwrap_or_else(|err| {
|
||||
match AdbCommand::new().shell(serial).getprop("ro.product.model") {
|
||||
Ok(model) => sanitize_device_prop(&model),
|
||||
Err(err) => {
|
||||
eprintln!("ERROR: {err}");
|
||||
error!("{err}");
|
||||
if err.contains("adb: no devices/emulators found") {
|
||||
"no devices/emulators found".to_string()
|
||||
} else {
|
||||
err
|
||||
sanitize_device_prop(&err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the brand by querying the `ro.product.brand` property.
|
||||
|
|
@ -251,8 +288,7 @@ pub fn get_device_brand(serial: &str) -> String {
|
|||
AdbCommand::new()
|
||||
.shell(serial)
|
||||
.getprop("ro.product.brand")
|
||||
// `trim` is just-in-case
|
||||
.map(|s| s.trim().to_string())
|
||||
.map(|s| sanitize_device_prop(&s))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
|
@ -266,7 +302,14 @@ pub fn get_android_sdk(device_serial: &str) -> u8 {
|
|||
.shell(device_serial)
|
||||
.getprop("ro.build.version.sdk")
|
||||
.map_or(0, |sdk| {
|
||||
sdk.parse().expect("SDK version numeral must be valid")
|
||||
if let Some(v) = parse_sdk_version(&sdk) {
|
||||
v
|
||||
} else {
|
||||
error!(
|
||||
"Invalid SDK version {sdk:?} for device '{device_serial}'. Falling back to 0"
|
||||
);
|
||||
0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -452,31 +495,39 @@ pub fn list_users_idx_prot(device_serial: &str) -> Vec<User> {
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// This matches serials (`getprop ro.serialno`)
|
||||
/// that are authorized by the user.
|
||||
/// Get list of connected devices using a specific ADB backend.
|
||||
#[must_use]
|
||||
pub fn get_devices_list() -> Vec<Phone> {
|
||||
pub fn get_devices_list(backend: AdbBackend) -> Vec<Phone> {
|
||||
retry(
|
||||
Fixed::from_millis(500).take(if cfg!(debug_assertions) { 3 } else { 10 }),
|
||||
|| match AdbCommand::new().devices() {
|
||||
|| match AdbCommand::with_backend(backend).devices() {
|
||||
Ok(devices) => {
|
||||
let mut device_list: Vec<Phone> = vec![];
|
||||
if devices.iter().all(|(_, stat)| stat != "device") {
|
||||
return OperationResult::Retry(vec![]);
|
||||
}
|
||||
for device in devices {
|
||||
let serial = &device.0;
|
||||
for (serial, status) in devices {
|
||||
if status != "device" {
|
||||
continue;
|
||||
}
|
||||
device_list.push(Phone {
|
||||
model: format!("{} {}", get_device_brand(serial), get_device_model(serial)),
|
||||
android_sdk: get_android_sdk(serial),
|
||||
user_list: list_users_idx_prot(serial),
|
||||
adb_id: serial.clone(),
|
||||
model: format!(
|
||||
"{} {}",
|
||||
get_device_brand(&serial),
|
||||
get_device_model(&serial)
|
||||
),
|
||||
android_sdk: get_android_sdk(&serial),
|
||||
user_list: list_users_idx_prot(&serial),
|
||||
adb_id: serial,
|
||||
});
|
||||
}
|
||||
if device_list.is_empty() {
|
||||
return OperationResult::Retry(vec![]);
|
||||
}
|
||||
OperationResult::Ok(device_list)
|
||||
}
|
||||
Err(err) => {
|
||||
error!("get_devices_list() -> {err}");
|
||||
error!("get_devices_list(backend) -> {err}");
|
||||
let test: Vec<Phone> = vec![];
|
||||
OperationResult::Retry(test)
|
||||
}
|
||||
|
|
@ -486,8 +537,8 @@ pub fn get_devices_list() -> Vec<Phone> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn initial_load() -> bool {
|
||||
match AdbCommand::new().devices() {
|
||||
pub fn initial_load(backend: AdbBackend) -> bool {
|
||||
match AdbCommand::with_backend(backend).devices() {
|
||||
Ok(_devices) => true,
|
||||
Err(_err) => false,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,21 +83,27 @@ impl UadGui {
|
|||
reason = "required by iced's Application trait interface"
|
||||
)]
|
||||
fn title(&self) -> String {
|
||||
self.selected_device.as_ref().map_or_else(
|
||||
|| FULL_NAME.to_string(),
|
||||
|device| format!("{FULL_NAME} - {}", device.model),
|
||||
)
|
||||
if self.selected_device.is_some() {
|
||||
format!("{FULL_NAME} - device connected")
|
||||
} else {
|
||||
FULL_NAME.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn new() -> (Self, Task<Message>) {
|
||||
let app = Self::default();
|
||||
let backend = app.settings_view.general.adb_backend;
|
||||
(
|
||||
Self::default(),
|
||||
app,
|
||||
Task::batch([
|
||||
// Used in crate::widgets::navigation_menu::ICONS. Name is `icomoon`.
|
||||
font::load(include_bytes!("../../../resources/assets/icons.ttf").as_slice())
|
||||
.map(Message::FontLoaded),
|
||||
Task::perform(async { initial_load() }, Message::ADBSatisfied),
|
||||
Task::perform(async { get_devices_list() }, Message::LoadDevices),
|
||||
Task::perform(async move { initial_load(backend) }, Message::ADBSatisfied),
|
||||
Task::perform(
|
||||
async move { get_devices_list(backend) },
|
||||
Message::LoadDevices,
|
||||
),
|
||||
Task::perform(
|
||||
async move { get_latest_release() },
|
||||
Message::GetLatestRelease,
|
||||
|
|
@ -196,16 +202,21 @@ impl UadGui {
|
|||
}
|
||||
Message::RefreshButtonPressed => {
|
||||
self.apps_view = AppsView::default();
|
||||
let backend = self.settings_view.general.adb_backend;
|
||||
#[expect(unused_must_use, reason = "side-effect")]
|
||||
{
|
||||
self.update(Message::AppsAction(AppsMessage::ADBSatisfied(
|
||||
self.adb_satisfied,
|
||||
)));
|
||||
}
|
||||
Task::perform(async { get_devices_list() }, Message::LoadDevices)
|
||||
Task::perform(
|
||||
async move { get_devices_list(backend) },
|
||||
Message::LoadDevices,
|
||||
)
|
||||
}
|
||||
Message::RebootButtonPressed => {
|
||||
self.apps_view = AppsView::default();
|
||||
let backend = self.settings_view.general.adb_backend;
|
||||
let serial = match &self.selected_device {
|
||||
Some(d) => d.adb_id.clone(),
|
||||
_ => String::default(),
|
||||
|
|
@ -213,7 +224,7 @@ impl UadGui {
|
|||
self.selected_device = None;
|
||||
self.devices_list = vec![];
|
||||
Task::perform(
|
||||
async { adb::ACommand::new().shell(serial).reboot() },
|
||||
async move { adb::ACommand::with_backend(backend).shell(serial).reboot() },
|
||||
|_| Message::Nothing,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@ use crate::theme::Theme;
|
|||
use crate::{gui::UpdateState, style, widgets::text};
|
||||
use iced::widget::{Space, column, container, row};
|
||||
use iced::{Alignment, Element, Length, Renderer};
|
||||
use log::error;
|
||||
use std::path::PathBuf;
|
||||
use uad_core::CACHE_DIR;
|
||||
use uad_core::adb;
|
||||
use uad_core::uad_lists::LIST_FNAME;
|
||||
use uad_core::utils::{FULL_NAME, NAME, last_modified_date, open_url};
|
||||
|
||||
|
|
@ -95,38 +93,10 @@ impl About {
|
|||
.spacing(10)
|
||||
.width(550);
|
||||
|
||||
/*
|
||||
There's no need to fetch this info every time the view is updated,
|
||||
we could cache it in a `static` `LazyLock`.
|
||||
|
||||
But what if the system updates ADB while the app is running?
|
||||
the numbers will be out of sync!
|
||||
|
||||
However, the server will still be the "old" version
|
||||
until it's killed
|
||||
*/
|
||||
let adb_version_text = text(match adb::ACommand::new().version() {
|
||||
Ok(s) => s
|
||||
.lines()
|
||||
.nth(0)
|
||||
.unwrap_or_else(|| unreachable!())
|
||||
// This allocation is good.
|
||||
// If it was a ref, the app would hold the entire string
|
||||
// instead of the relevant slice.
|
||||
.to_string(),
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
"Couldn't fetch ADB version. Is it installed?".into()
|
||||
// satisfy `match` by inferring the type of the `Ok` arm
|
||||
}
|
||||
})
|
||||
.width(250);
|
||||
let adb_version_row = row![adb_version_text].align_y(Alignment::Center).width(550);
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
let update_column = column![uad_list_row, self_update_row, adb_version_row];
|
||||
let update_column = column![uad_list_row, self_update_row];
|
||||
#[cfg(not(feature = "self-update"))]
|
||||
let update_column = column![uad_list_row, adb_version_row];
|
||||
let update_column = column![uad_list_row];
|
||||
|
||||
let update_column = update_column.align_x(Alignment::Center).spacing(10);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::widgets::navigation_menu::ICONS;
|
|||
use log::{error, info, warn};
|
||||
use std::path::PathBuf;
|
||||
use uad_core::config::DeviceSettings;
|
||||
use uad_core::sync::{AdbError, Phone, User, apply_pkg_state_commands};
|
||||
use uad_core::sync::{AdbError, CorePackage, Phone, User, apply_pkg_state_commands};
|
||||
use uad_core::uad_lists::{
|
||||
Opposite, PackageHashMap, PackageState, Removal, UadList, UadListState, load_debloat_lists,
|
||||
};
|
||||
|
|
@ -130,6 +130,33 @@ impl From<Removal> for SummaryEntry {
|
|||
}
|
||||
|
||||
impl List {
|
||||
#[inline]
|
||||
fn current_user_index(&self) -> usize {
|
||||
self.selected_user.unwrap_or_default().index
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn refilter(&mut self) -> Task<Message> {
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn map_core_to_row(uad_list: &PackageHashMap, core: &CorePackage) -> PackageRow {
|
||||
let list = uad_list
|
||||
.get(&core.name)
|
||||
.map_or(UadList::Unlisted, |p| p.list);
|
||||
PackageRow::new(
|
||||
&core.name,
|
||||
&core.description,
|
||||
core.removal,
|
||||
core.state,
|
||||
list,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
settings: &mut Settings,
|
||||
|
|
@ -216,7 +243,7 @@ impl List {
|
|||
waiting_view("Finding connected devices...", None, style::Text::Default)
|
||||
} else {
|
||||
waiting_view(
|
||||
"ADB is not installed on your system, install ADB and relaunch application.",
|
||||
"No device connection detected. Connect a device with USB debugging enabled and authorized, then relaunch.",
|
||||
Some(button("Read on how to get started.")
|
||||
.on_press(Message::GoToUrl(PathBuf::from(
|
||||
"https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/wiki/Getting-started",
|
||||
|
|
@ -329,12 +356,13 @@ impl List {
|
|||
settings: &Settings,
|
||||
selected_device: &Phone,
|
||||
) -> Element<'_, Message, Theme, Renderer> {
|
||||
let i_user = self.current_user_index();
|
||||
let packages = self
|
||||
.filtered_packages
|
||||
.iter()
|
||||
.fold(column![].spacing(6), |col, &i| {
|
||||
col.push(
|
||||
self.phone_packages[self.selected_user.unwrap_or_default().index][i]
|
||||
self.phone_packages[i_user][i]
|
||||
.view(settings, selected_device)
|
||||
.map(move |msg| Message::List(i, msg)),
|
||||
)
|
||||
|
|
@ -748,8 +776,7 @@ impl List {
|
|||
.selected_removal
|
||||
.expect("removal recommendation must be selected");
|
||||
|
||||
self.filtered_packages = self.phone_packages
|
||||
[self.selected_user.expect("User must be selected").index]
|
||||
self.filtered_packages = self.phone_packages[self.current_user_index()]
|
||||
.iter()
|
||||
// we must filter the indices associated with pack-rows,
|
||||
// that's why `enumerate` is before `filter`.
|
||||
|
|
@ -776,7 +803,7 @@ impl List {
|
|||
vec![
|
||||
fetch_packages(&uad_list, serial, None)
|
||||
.into_iter()
|
||||
.map(PackageRow::from)
|
||||
.map(|core| Self::map_core_to_row(&uad_list, &core))
|
||||
.collect(),
|
||||
]
|
||||
} else {
|
||||
|
|
@ -785,7 +812,7 @@ impl List {
|
|||
.map(|user| {
|
||||
fetch_packages(&uad_list, serial, Some(user.id))
|
||||
.into_iter()
|
||||
.map(PackageRow::from)
|
||||
.map(|core| Self::map_core_to_row(&uad_list, &core))
|
||||
.collect()
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -841,7 +868,7 @@ impl List {
|
|||
}
|
||||
|
||||
fn on_restoring_device(&mut self, output: Result<PackageInfo, AdbError>) -> Task<Message> {
|
||||
let i_user = self.selected_user.unwrap_or_default().index;
|
||||
let i_user = self.current_user_index();
|
||||
if let Ok(p) = output {
|
||||
self.loading_state =
|
||||
LoadingState::RestoringDevice(self.phone_packages[i_user][p.index].name.clone());
|
||||
|
|
@ -886,7 +913,7 @@ impl List {
|
|||
}
|
||||
|
||||
fn on_apply_filters(&mut self, packages: Vec<Vec<PackageRow>>) -> Task<Message> {
|
||||
let i_user = self.selected_user.unwrap_or_default().index;
|
||||
let i_user = self.current_user_index();
|
||||
self.phone_packages = packages;
|
||||
self.filtered_packages = (0..self.phone_packages[i_user].len()).collect();
|
||||
self.selected_removal = Some(Removal::Recommended);
|
||||
|
|
@ -906,7 +933,7 @@ impl List {
|
|||
selected_device: &mut Phone,
|
||||
list_update_state: &mut UadListState,
|
||||
) -> Task<Message> {
|
||||
let i_user = self.selected_user.unwrap_or_default().index;
|
||||
let i_user = self.current_user_index();
|
||||
for i in self.filtered_packages.clone() {
|
||||
if self.phone_packages[i_user][i].selected != selected {
|
||||
#[expect(unused_must_use, reason = "side-effect")]
|
||||
|
|
@ -924,26 +951,22 @@ impl List {
|
|||
|
||||
fn on_search_input_changed(&mut self, letter: String) -> Task<Message> {
|
||||
self.input_value = letter;
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
self.refilter()
|
||||
}
|
||||
|
||||
fn on_list_selected(&mut self, list: UadList) -> Task<Message> {
|
||||
self.selected_list = Some(list);
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
self.refilter()
|
||||
}
|
||||
|
||||
fn on_package_state_selected(&mut self, package_state: PackageState) -> Task<Message> {
|
||||
self.selected_package_state = Some(package_state);
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
self.refilter()
|
||||
}
|
||||
|
||||
fn on_removal_selected(&mut self, removal: Removal) -> Task<Message> {
|
||||
self.selected_removal = Some(removal);
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
self.refilter()
|
||||
}
|
||||
|
||||
fn on_list_row(
|
||||
|
|
@ -953,7 +976,7 @@ impl List {
|
|||
settings: &Settings,
|
||||
selected_device: &mut Phone,
|
||||
) -> Task<Message> {
|
||||
let i_user = self.selected_user.unwrap_or_default().index;
|
||||
let i_user = self.current_user_index();
|
||||
#[expect(unused_must_use, reason = "side-effect")]
|
||||
{
|
||||
self.phone_packages[i_user][i_package]
|
||||
|
|
@ -1031,8 +1054,7 @@ impl List {
|
|||
self.selected_user = Some(user);
|
||||
self.fallback_notifications.clear();
|
||||
self.filtered_packages = (0..self.phone_packages[user.index].len()).collect();
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
self.refilter()
|
||||
}
|
||||
|
||||
#[allow(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use iced::{Alignment, Element, Length, Renderer, Task, alignment};
|
|||
use log::{debug, error, info};
|
||||
use std::path::PathBuf;
|
||||
use uad_core::{
|
||||
adb::{ACommand, AdbBackend},
|
||||
config::{BackupSettings, Config, DeviceSettings, GeneralSettings},
|
||||
save::{backup_phone, list_available_backup_user, list_available_backups, restore_backup},
|
||||
sync::{
|
||||
|
|
@ -35,15 +36,23 @@ pub struct Settings {
|
|||
pub device: DeviceSettings,
|
||||
is_loading: bool,
|
||||
modal: Option<PopUpModal>,
|
||||
/// Cached ADB version string for display
|
||||
adb_version: String,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
let general = Config::load_configuration_file().general;
|
||||
// Fetch initial ADB version
|
||||
let adb_version = ACommand::with_backend(general.adb_backend)
|
||||
.version()
|
||||
.unwrap_or_else(|e| format!("Error: {e}"));
|
||||
Self {
|
||||
general: Config::load_configuration_file().general,
|
||||
general,
|
||||
device: DeviceSettings::default(),
|
||||
is_loading: false,
|
||||
modal: None,
|
||||
adb_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +64,8 @@ pub enum Message {
|
|||
DisableMode(bool),
|
||||
MultiUserMode(bool),
|
||||
ApplyTheme(Theme),
|
||||
ApplyAdbBackend(AdbBackend),
|
||||
AdbVersionFetched(Result<String, String>),
|
||||
UrlPressed(PathBuf),
|
||||
BackupSelected(DisplayablePath),
|
||||
BackupDevice,
|
||||
|
|
@ -83,6 +94,8 @@ impl Settings {
|
|||
Message::DisableMode(toggled) => self.handle_disable_mode(phone, toggled),
|
||||
Message::MultiUserMode(toggled) => self.handle_multi_user_mode(phone, toggled),
|
||||
Message::ApplyTheme(theme) => self.handle_apply_theme(phone, theme),
|
||||
Message::ApplyAdbBackend(backend) => self.handle_apply_adb_backend(backend),
|
||||
Message::AdbVersionFetched(result) => self.handle_adb_version_fetched(result),
|
||||
Message::UrlPressed(url) => Self::handle_url_pressed(url),
|
||||
Message::LoadDeviceSettings => self.handle_load_device_settings(phone),
|
||||
Message::BackupSelected(d_path) => self.handle_backup_selected(d_path),
|
||||
|
|
@ -138,6 +151,28 @@ impl Settings {
|
|||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_apply_adb_backend(&mut self, backend: AdbBackend) -> Task<Message> {
|
||||
self.general.adb_backend = backend;
|
||||
self.adb_version = "Fetching...".to_string();
|
||||
debug!("Config change: {self:?}");
|
||||
let mut config = Config::load_configuration_file();
|
||||
config.save_device_settings(self.device.clone(), self.general.clone());
|
||||
info!("ADB backend changed to: {backend}");
|
||||
// Fetch version asynchronously
|
||||
Task::perform(
|
||||
async move { ACommand::with_backend(backend).version() },
|
||||
Message::AdbVersionFetched,
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_adb_version_fetched(&mut self, result: Result<String, String>) -> Task<Message> {
|
||||
self.adb_version = match result {
|
||||
Ok(version) => version,
|
||||
Err(e) => format!("Error: {e}"),
|
||||
};
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_url_pressed(url: PathBuf) -> Task<Message> {
|
||||
open_url(url);
|
||||
Task::none()
|
||||
|
|
@ -360,6 +395,8 @@ impl Settings {
|
|||
self.theme_container(),
|
||||
text("General").size(26),
|
||||
self.general_container(),
|
||||
text("ADB").size(26),
|
||||
self.adb_container(),
|
||||
text("Current device").size(26),
|
||||
Self::no_device_container(),
|
||||
text("Backup / Restore").size(26),
|
||||
|
|
@ -380,6 +417,8 @@ impl Settings {
|
|||
self.theme_container(),
|
||||
text("General").size(26),
|
||||
self.general_container(),
|
||||
text("ADB").size(26),
|
||||
self.adb_container(),
|
||||
text("Current device").size(26),
|
||||
Self::warning_container(phone),
|
||||
self.device_specific_container(phone),
|
||||
|
|
@ -459,6 +498,49 @@ impl Settings {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn adb_container(&self) -> Element<'_, Message, Theme, Renderer> {
|
||||
let backend_label = text("Backend:").size(16);
|
||||
let radio_btn_backend =
|
||||
AdbBackend::ALL
|
||||
.iter()
|
||||
.fold(row![].spacing(10), |row_content, option| {
|
||||
row_content.push(
|
||||
radio(
|
||||
format!("{option}"),
|
||||
*option,
|
||||
Some(self.general.adb_backend),
|
||||
Message::ApplyAdbBackend,
|
||||
)
|
||||
.size(20),
|
||||
)
|
||||
});
|
||||
|
||||
let backend_row = row![backend_label, radio_btn_backend]
|
||||
.spacing(10)
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
let backend_descr = text(
|
||||
"Builtin: Uses embedded ADB (no external dependencies). System: Uses your installed adb binary.",
|
||||
)
|
||||
.style(style::Text::Commentary);
|
||||
|
||||
let version_row = row![
|
||||
text("Version: ").size(14),
|
||||
text(&self.adb_version)
|
||||
.size(14)
|
||||
.style(style::Text::Commentary),
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
container(column![backend_row, backend_descr, version_row,].spacing(10))
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Shrink)
|
||||
.style(style::Container::Frame)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn warning_container(phone: &Phone) -> Element<'static, Message, Theme, Renderer> {
|
||||
container(
|
||||
row![
|
||||
|
|
|
|||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1776949667,
|
||||
"narHash": "sha256-GMSVw35Q+294GlrTUKlx087E31z7KurReQ1YHSKp5iw=",
|
||||
"lastModified": 1781607440,
|
||||
"narHash": "sha256-rxO+uc/KFbSJp+pgyXRuAX6QlG9hJdnt0BXpEQRXY+U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "01fbdeef22b76df85ea168fbfe1bfd9e63681b30",
|
||||
"rev": "3e41b24abd260e8f71dbe2f5737d24122f972158",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue