use std::ffi::OsString;
use std::fmt::{Display, Formatter};
use std::vec::IntoIter;
use cmd_lib::AsOsStr;
use lazy_static::lazy_static;
use regex::Regex;
use simple_cmd::CommandBuilder;
use crate::error::Error;
use crate::prelude::CommandBuilderExt;
use crate::traits::{AsArg, AsArgs};
use crate::types::{
Adb, AdbDevice, AdbInstallOptions, ChmodMode, ChmodModeSpecs, ChmodOptions, Client,
ConnectionType, Extra, FFPlayOptions, InputSource, InstallLocationOption, InstallOptions,
InstallPermission, Intent, KeyCode, KeyEventType, ListPackageDisplayOptions, ListPackageFilter,
LogcatLevel, LogcatOptions, LogcatTag, MemoryStatus, MotionEvent, Package, PackageFlags,
PropType, Property, RebootType, Reconnect, RuntimePermission, SELinuxType, ScreenRecordOptions,
UninstallOptions, UserOption, Wakefulness,
};
lazy_static! {
static ref RE_PROP_TYPE_ENUM: Regex = Regex::new("^enum\\s((?:[\\w_]+\\s?)+)$").unwrap();
}
impl TryFrom<&str> for Wakefulness {
type Error = crate::error::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().as_str() {
"awake" => Ok(Wakefulness::Awake),
"asleep" => Ok(Wakefulness::Asleep),
"dreaming" => Ok(Wakefulness::Dreaming),
_ => Err(std::io::Error::from(std::io::ErrorKind::InvalidInput).into()),
}
}
}
impl Display for AdbDevice {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let connected = if self.connected { "device" } else { "offline" };
write!(
f,
"{} {connected} product:{} model:{}, device:{} addr:{}",
self.name, self.product, self.model, self.device, self.addr
)
}
}
impl Display for Reconnect {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Reconnect::Device => write!(f, "device"),
Reconnect::Offline => write!(f, "offline"),
}
}
}
impl AsArgs<OsString> for UserOption {
fn as_args(&self) -> Vec<OsString> {
match self {
UserOption::UserId(user_id) => vec!["--user".as_os_str(), user_id.as_os_str()],
UserOption::All => vec!["all".as_os_str()],
UserOption::Current => vec!["current".as_os_str()],
UserOption::None => vec![],
}
}
}
impl IntoIterator for UninstallOptions {
type Item = OsString;
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.as_args().into_iter()
}
}
impl AsArgs<OsString> for UninstallOptions {
fn as_args(&self) -> Vec<OsString> {
let mut args: Vec<OsString> = vec![];
if self.keep_data {
args.push("-k".into());
}
match self.user.as_ref() {
None => {}
Some(s) => {
args.push("--user".into());
args.push(s.into());
}
}
match self.version_code.as_ref() {
None => {}
Some(s) => {
args.push("--versionCode".into());
args.push(format!("{:}", s).into());
}
}
args
}
}
impl Display for MemoryStatus {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
MemoryStatus::Hidden => write!(f, "HIDDEN"),
MemoryStatus::RunningModerate => write!(f, "RUNNING_MODERATE"),
MemoryStatus::Background => write!(f, "BACKGROUND"),
MemoryStatus::RunningLow => write!(f, "RUNNING_LOW"),
MemoryStatus::Moderate => write!(f, "MODERATE"),
MemoryStatus::RunningCritical => write!(f, "RUNNING_CRITICAL"),
MemoryStatus::Complete => write!(f, "COMPLETE"),
}
}
}
impl Into<OsString> for InputSource {
fn into(self) -> OsString {
let string: &str = self.into();
string.into()
}
}
impl Into<OsString> for MotionEvent {
fn into(self) -> OsString {
let string: &str = self.into();
string.into()
}
}
impl Into<OsString> for KeyEventType {
fn into(self) -> OsString {
self.as_arg()
}
}
impl AsArg<OsString> for KeyEventType {
fn as_arg(&self) -> OsString {
match self {
KeyEventType::LongPress => "--longpress".into(),
KeyEventType::DoubleTap => "--doubletap".into(),
}
}
}
impl Into<OsString> for KeyCode {
fn into(self) -> OsString {
let string: &str = self.into();
string.into()
}
}
impl Display for Package {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.package_name).unwrap();
if let Some(version_code) = self.version_code {
write!(f, " version:{}", version_code).unwrap();
}
if let Some(uid) = self.uid {
write!(f, " uid:{}", uid).unwrap();
}
if let Some(file_name) = &self.file_name {
write!(f, " file_name:{}", file_name).unwrap();
}
Ok(())
}
}
impl Display for RuntimePermission {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} granted={} flags={}",
self.name,
self.granted,
self.flags.join(",")
)
}
}
impl Display for InstallPermission {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} granted={}", self.name, self.granted)
}
}
impl TryFrom<&str> for PackageFlags {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"SYSTEM" => Ok(PackageFlags::System),
"HAS_CODE" => Ok(PackageFlags::HasCode),
"ALLOW_CLEAR_USER_DATA" => Ok(PackageFlags::AllowClearUserData),
"UPDATED_SYSTEM_APP" => Ok(PackageFlags::UpdatedSystemApp),
"ALLOW_BACKUP" => Ok(PackageFlags::AllowBackup),
_ => Err(Error::NameNotFoundError(value.to_string())),
}
}
}
impl IntoIterator for InstallOptions {
type Item = OsString;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut args = vec![];
match self.user.as_ref() {
None => {}
Some(user) => args.push(format!("--user {:}", user).into()),
}
match self.package_name.as_ref() {
None => {}
Some(user) => args.push(format!("--pkg {:}", user).into()),
}
match self.install_location.as_ref() {
None => {}
Some(s) => args.push(format!("--install-location {:}", s).into()),
}
if self.dont_kill {
args.push("--dont-kill".into());
}
if self.restrict_permissions {
args.push("--restrict-permissions".into());
}
if self.grant_permissions {
args.push("-g".into());
}
if self.force {
args.push("-f".into());
}
if self.replace_existing_application {
args.push("-r".into());
}
if self.allow_version_downgrade {
args.push("-d".into());
}
args.into_iter()
}
}
impl Display for InstallOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let args = self.clone().into_iter().collect::<Vec<_>>();
write!(
f,
"{}",
args.iter()
.filter_map(|s| s.to_str())
.collect::<Vec<_>>()
.join(" ")
)
}
}
impl Default for InstallLocationOption {
fn default() -> Self {
InstallLocationOption::Auto
}
}
impl Display for InstallLocationOption {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
InstallLocationOption::Auto => write!(f, "0"),
InstallLocationOption::InternalOnly => write!(f, "1"),
InstallLocationOption::PreferExternal => write!(f, "2"),
}
}
}
impl IntoIterator for ListPackageDisplayOptions {
type Item = OsString;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut args: Vec<OsString> = vec![];
if self.show_uid {
args.push("-U".into());
}
if self.show_version_code {
args.push("--show-versioncode".into());
}
if self.include_uninstalled {
args.push("-u".into());
}
if self.show_apk_file {
args.push("-f".into());
}
args.into_iter()
}
}
impl Display for ListPackageDisplayOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let args = self.clone().into_iter().collect::<Vec<_>>();
write!(
f,
"{}",
args.iter()
.filter_map(|s| s.to_str())
.collect::<Vec<_>>()
.join(" ")
)
}
}
impl Default for ListPackageDisplayOptions {
fn default() -> Self {
ListPackageDisplayOptions {
show_uid: true,
show_version_code: true,
include_uninstalled: false,
show_apk_file: true,
}
}
}
impl IntoIterator for ListPackageFilter {
type Item = OsString;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut args: Vec<OsString> = vec![];
if self.show_only_disabled {
args.push("-d".into());
}
if self.show_only_enabed {
args.push("-e".into());
}
if self.show_only_system {
args.push("-s".into());
}
if self.show_only3rd_party {
args.push("-3".into());
}
if self.apex_only {
args.push("--apex-only".into());
}
match self.uid.as_ref() {
None => {}
Some(s) => args.push(format!("--uid {:}", s).into()),
}
match self.user.as_ref() {
None => {}
Some(s) => args.push(format!("--user {:}", s).into()),
}
args.into_iter()
}
}
impl Display for ListPackageFilter {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let args = self.clone().into_iter().collect::<Vec<_>>();
write!(
f,
"{}",
args.iter()
.filter_map(|s| s.to_str())
.collect::<Vec<_>>()
.join(" ")
)
}
}
impl Display for RebootType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
RebootType::Bootloader => write!(f, "bootloader"),
RebootType::Recovery => write!(f, "recovery"),
RebootType::Sideload => write!(f, "sideload"),
RebootType::SideloadAutoReboot => write!(f, "sideload-auto-reboot"),
RebootType::Dra => write!(f, "dra"),
}
}
}
impl IntoIterator for LogcatOptions {
type Item = OsString;
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut args = vec![];
if let Some(expr) = self.expr {
args.extend(["-e".into(), expr.as_str().into()]);
}
if self.dump {
args.push("-d".into());
}
if let Some(filename) = self.filename {
args.extend(["-f".into(), filename.into()]);
}
if let Some(format) = self.format {
args.extend(["-v".into(), format.into()]);
}
if let Some(pid) = self.pid {
args.extend(["--pid".into(), format!("{}", pid).into()]);
}
if let Some(since) = self.since {
args.extend([
"-T".into(),
since.format("%m-%d %H:%M:%S.%3f").to_string().into(),
]);
}
if let Some(tags) = self.tags {
if !tags.is_empty() {
for tag in tags {
args.push(format!("{:}", tag).into());
}
args.push("*:S".into());
}
}
args.into_iter()
}
}
impl Default for LogcatOptions {
fn default() -> Self {
LogcatOptions {
expr: None,
dump: false,
filename: None,
format: None,
pid: None,
since: None,
tags: None,
timeout: None,
}
}
}
impl Display for LogcatLevel {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
LogcatLevel::Verbose => write!(f, "V"),
LogcatLevel::Debug => write!(f, "D"),
LogcatLevel::Info => write!(f, "I"),
LogcatLevel::Warn => write!(f, "W"),
LogcatLevel::Error => write!(f, "E"),
}
}
}
impl Display for LogcatTag {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.name, self.level)
}
}
impl Default for FFPlayOptions {
fn default() -> Self {
FFPlayOptions {
framerate: Some(30),
size: Some((1440, 800)),
probesize: Some(300),
}
}
}
impl IntoIterator for FFPlayOptions {
type Item = OsString;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut args = vec![];
if let Some(framerate) = self.framerate {
args.push("-framerate".into());
args.push(framerate.to_string().into());
}
if let Some(probesize) = self.probesize {
args.push("-probesize".into());
args.push(probesize.to_string().into());
}
if let Some(size) = self.size {
args.push("-vf".into());
args.push(format!("scale={:}:{:}", size.0, size.1).into());
}
args.into_iter()
}
}
impl Display for Property {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} = {}", self.key, self.value)
}
}
impl TryFrom<Vec<u8>> for PropType {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Ok(PropType::from(rustix::path::Arg::as_str(&value)?.trim()))
}
}
impl From<&str> for PropType {
fn from(value: &str) -> Self {
return match value {
"string" => PropType::String,
"bool" => PropType::Bool,
"int" => PropType::Int,
_ => {
if let Some(captures) = RE_PROP_TYPE_ENUM.captures(value) {
return if captures.len() == 2 {
let strings = captures.get(1).unwrap().as_str();
let s = strings
.split(' ')
.map(|s| s.to_string())
.collect::<Vec<String>>();
PropType::Enum(s)
} else {
PropType::Unknown(value.to_string())
};
}
return PropType::Unknown(value.to_string());
}
};
}
}
impl ToString for PropType {
fn to_string(&self) -> String {
match self {
PropType::String => "String".to_string(),
PropType::Bool => "Bool".to_string(),
PropType::Int => "Int".to_string(),
PropType::Enum(_) => "Enum".to_string(),
PropType::Unknown(_) => "Unknown".to_string(),
}
}
}
impl Default for ScreenRecordOptions {
fn default() -> Self {
Self::new()
}
}
impl Display for ScreenRecordOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let args = self.clone().into_iter().collect::<Vec<_>>();
write!(
f,
"{}",
args.iter()
.filter_map(|s| s.to_str())
.collect::<Vec<_>>()
.join(" ")
)
}
}
impl IntoIterator for ScreenRecordOptions {
type Item = OsString;
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut args: Vec<OsString> = vec![];
if let Some(bitrate) = self.bitrate {
args.push("--bit-rate".into());
args.push(format!("{:}", bitrate).into());
}
if let Some(timelimit) = self.timelimit {
args.push("--time-limit".into());
args.push(format!("{:}", timelimit.as_secs()).into());
}
if self.rotate.unwrap_or(false) {
args.push("--rotate".into())
}
if self.bug_report.unwrap_or(false) {
args.push("--bugreport".into())
}
if self.verbose {
args.push("--verbose".into())
}
if let Some(size) = self.size {
args.push("--size".into());
args.push(format!("{:}x{:}", size.0, size.1).into());
}
args.into_iter()
}
}
impl ScreenRecordOptions {
pub fn new() -> Self {
ScreenRecordOptions {
bitrate: Some(4_000_000),
timelimit: Some(core::time::Duration::from_secs(10)),
rotate: None,
bug_report: None,
size: None,
verbose: false,
}
}
}
impl Display for SELinuxType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SELinuxType::Enforcing => write!(f, "Enforcing"),
SELinuxType::Permissive => write!(f, "Permissive"),
}
}
}
impl TryFrom<Vec<u8>> for SELinuxType {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let opt_string = rustix::path::Arg::as_str(&value)?;
opt_string.try_into()
}
}
impl TryFrom<&str> for SELinuxType {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.trim() {
"Enforcing" => Ok(SELinuxType::Enforcing),
"Permissive" => Ok(SELinuxType::Permissive),
_ => Err(Error::ParseInputError),
}
}
}
impl AsArg<OsString> for SELinuxType {
fn as_arg(&self) -> OsString {
match self {
SELinuxType::Enforcing => "1".into(),
SELinuxType::Permissive => "0".into(),
}
}
}
impl Intent {
pub fn new() -> Intent {
Intent::default()
}
pub fn from_action(action: &str) -> Intent {
let mut intent = Intent::new();
intent.action = Some(action.to_string());
intent
}
}
impl Display for Intent {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut args: Vec<String> = vec![];
if let Some(action) = self.action.as_ref() {
args.push(format!("-a {:}", action));
}
if let Some(data) = self.data.as_ref() {
args.push(format!("-d {:}", data));
}
if let Some(mime_type) = self.mime_type.as_ref() {
args.push(format!("-t {:}", mime_type));
}
if let Some(category) = self.category.as_ref() {
args.push(format!("-c {:}", category));
}
if let Some(component) = self.component.as_ref() {
args.push(format!("-n {:}", component));
}
if let Some(package) = self.package.as_ref() {
args.push(format!("-p {:}", package));
}
if let Some(user_id) = self.user_id.as_ref() {
args.push(format!("--user {:}", user_id));
}
if self.receiver_foreground {
args.push("--receiver-foreground".to_string());
}
if self.wait {
args.push("-W".to_string());
}
args.push(format!("{:}", self.extra));
write!(f, "{:}", args.join(" "))
}
}
impl Extra {
pub fn put_string_extra(&mut self, name: &str, value: &str) -> &mut Self {
self.es.insert(name.to_string(), value.to_string());
self
}
pub fn put_bool_extra(&mut self, name: &str, value: bool) -> &mut Self {
self.ez.insert(name.to_string(), value);
self
}
pub fn put_int_extra(&mut self, name: &str, value: i32) -> &mut Self {
self.ei.insert(name.to_string(), value);
self
}
pub fn put_long_extra(&mut self, name: &str, value: i64) -> &mut Self {
self.el.insert(name.to_string(), value);
self
}
pub fn put_string_array_extra(&mut self, name: &str, value: Vec<String>) -> &mut Self {
self.esa.insert(name.to_string(), value);
self
}
}
impl Display for Extra {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut output: Vec<String> = Vec::new();
if !self.es.is_empty() {
self.es.iter().for_each(|entry| {
output.push(format!("--es {:} {:}", entry.0, entry.1));
});
}
if !self.ez.is_empty() {
self.ez.iter().for_each(|entry| {
output.push(format!("--ez {:} {:}", entry.0, entry.1));
});
}
if !self.ei.is_empty() {
self.ei.iter().for_each(|entry| {
output.push(format!("--ei {:} {:}", entry.0, entry.1));
});
}
if !self.el.is_empty() {
self.el.iter().for_each(|entry| {
output.push(format!("--el {:} {:}", entry.0, entry.1));
});
}
if !self.ef.is_empty() {
self.ef.iter().for_each(|entry| {
output.push(format!("--ef {:} {:}", entry.0, entry.1));
});
}
if !self.eu.is_empty() {
self.eu.iter().for_each(|entry| {
output.push(format!("--eu {:} {:}", entry.0, entry.1));
});
}
if !self.ecn.is_empty() {
self.ecn.iter().for_each(|entry| {
output.push(format!("--ecn {:} {:}", entry.0, entry.1));
});
}
if !self.eia.is_empty() {
self.eia.iter().for_each(|entry| {
output.push(format!(
"--eia {:} {:}",
entry.0,
entry
.1
.iter()
.map(|f| f.to_string())
.collect::<Vec<_>>()
.join(",")
));
});
}
if !self.ela.is_empty() {
self.ela.iter().for_each(|entry| {
output.push(format!(
"--ela {:} {:}",
entry.0,
entry
.1
.iter()
.map(|f| f.to_string())
.collect::<Vec<_>>()
.join(",")
));
});
}
if !self.efa.is_empty() {
self.efa.iter().for_each(|entry| {
output.push(format!(
"--efa {:} {:}",
entry.0,
entry
.1
.iter()
.map(|f| f.to_string())
.collect::<Vec<_>>()
.join(",")
));
});
}
if !self.esa.is_empty() {
self.esa.iter().for_each(|entry| {
output.push(format!("--efa {:} {:}", entry.0, entry.1.join(",")));
});
}
if self.grant_read_uri_permission {
output.push("--grant-read-uri-permission".to_string());
}
if self.grant_write_uri_permission {
output.push("--grant-write-uri-permission".to_string());
}
if self.exclude_stopped_packages {
output.push("--exclude-stopped-packages".to_string());
}
if self.include_stopped_packages {
output.push("--include-stopped-packages".to_string());
}
write!(f, "{:}", output.join(" "))
}
}
impl IntoIterator for AdbInstallOptions {
type Item = OsString;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut args = vec![];
if self.allow_version_downgrade {
args.push("-d".into());
}
if self.allow_test_package {
args.push("-t".into());
}
if self.replace {
args.push("-r".into());
}
if self.forward_lock {
args.push("-l".into());
}
if self.install_external {
args.push("-s".into());
}
if self.grant_permissions {
args.push("-g".into());
}
if self.instant {
args.push("--instant".into());
}
args.into_iter()
}
}
impl Display for AdbInstallOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let args = self.clone().into_iter().collect::<Vec<_>>();
write!(
f,
"{}",
args.iter()
.filter_map(|s| s.to_str())
.collect::<Vec<_>>()
.join(" ")
)
}
}
impl TryFrom<ConnectionType> for Client {
type Error = crate::error::Error;
fn try_from(value: ConnectionType) -> Result<Self, Self::Error> {
let adb = Adb::new()?;
Ok(Client::new(adb, value, false))
}
}
impl TryFrom<AdbDevice> for Client {
type Error = crate::error::Error;
fn try_from(value: AdbDevice) -> Result<Self, Self::Error> {
value.addr.try_into()
}
}
impl TryFrom<&AdbDevice> for Client {
type Error = crate::error::Error;
fn try_from(value: &AdbDevice) -> Result<Self, Self::Error> {
value.addr.try_into()
}
}
impl From<&Client> for CommandBuilder {
fn from(value: &Client) -> Self {
CommandBuilder::adb(&value.adb)
.addr(value.addr)
.with_debug(value.debug)
}
}
impl Display for Client {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.addr.fmt(f)
}
}
impl AsArg<OsString> for ChmodMode {
fn as_arg(&self) -> OsString {
let value: i32 = self.into();
value.to_string().into()
}
}
impl Into<i32> for &ChmodMode {
fn into(self) -> i32 {
let mut value = 0;
if self.read {
value |= 0o4;
}
if self.write {
value |= 0o2;
}
if self.execute {
value |= 0o1;
}
value
}
}
impl AsArg<OsString> for ChmodModeSpecs {
fn as_arg(&self) -> OsString {
let owner = self.owner.as_arg();
let group = self.group.as_arg();
let other = self.other.as_arg();
format!(
"{}{}{}",
owner.to_string_lossy(),
group.to_string_lossy(),
other.to_string_lossy()
)
.into()
}
}
impl Display for ChmodModeSpecs {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}{}",
self.owner.as_arg().to_string_lossy(),
self.group.as_arg().to_string_lossy(),
self.other.as_arg().to_string_lossy()
)
}
}
impl IntoIterator for ChmodOptions {
type Item = OsString;
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut args: Vec<OsString> = vec![];
if self.recursive {
args.push("-R".into());
}
if self.changes {
args.push("-c".into());
}
if self.quiet {
args.push("-f".into());
}
args.push(self.mode.as_arg());
args.into_iter()
}
}