Skip to content

Commit

Permalink
ndk-build: Use uid to limit logcat to the current application
Browse files Browse the repository at this point in the history
This is a port of:
rust-mobile/xbuild#131
rust-mobile/xbuild#135

Having never really understood how Android Studio does it, I just
stumbled upon this very new [stackoverflow answer] that has a rather
beatiful solution to the current problems with `pidof`, without
drawbacks.  Pidof has always been flaky as it relies on the app to be
running, which may either take some time or never happen if the app
crashed before `pidof` is first run.  This results in silly workarounds
such as loops that induce extra delay and need to have an upper bound.
And this `pid` changes every time the app is restarted, making it a
tedious process that also doesn't react to manual app restarts on the
device.  Retrieving the `uid` via `pm list packages -U` on the other
hand, and passing that to `logcat --uid` has the following advantages:

- Always available immediately after the app has been installed, no need
  to check it in a loop (no extra delay);
- Doesn't change after the app is (re!)installed, unless the user fully
  deletes and installs the app again;
- Is resilient against app crashes because of that, and allows the user
  to see any error/crash related messages straight away;
- Still includes logs printed by other system components that run or are
  invoked within an app, as before.

The only downside is that `pm list package` possibly returns multiple
packages if there is a substring match; for this reason the code
searches for an explicit match in the output.

[stackoverflow answer]: https://stackoverflow.com/a/76551835
  • Loading branch information
MarijnS95 committed Sep 11, 2023
1 parent 8ce097b commit 5df5274
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 29 deletions.
7 changes: 4 additions & 3 deletions cargo-apk/src/apk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,16 +313,17 @@ impl<'a> ApkBuilder<'a> {
let apk = self.build(artifact)?;
apk.reverse_port_forwarding(self.device_serial.as_deref())?;
apk.install(self.device_serial.as_deref())?;
let pid = apk.start(self.device_serial.as_deref())?;
apk.start(self.device_serial.as_deref())?;
let uid = apk.uidof(self.device_serial.as_deref())?;

if !no_logcat {
self.ndk
.adb(self.device_serial.as_deref())?
.arg("logcat")
.arg("-v")
.arg("color")
.arg("--pid")
.arg(pid.to_string())
.arg("--uid")
.arg(uid.to_string())
.status()?;
}

Expand Down
3 changes: 2 additions & 1 deletion ndk-build/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Unreleased

- Add `android:extractNativeLibs`, `android:usesCleartextTraffic` attributes to the manifest's `Application` element, and `android:alwaysRetainTaskState` to the `Activity` element. ([#15](https://github.com/rust-mobile/cargo-apk/pull/15))
- Enable building from `android` host ([#29](https://github.com/rust-mobile/cargo-apk/pull/29))
- Enable building from `android` host. ([#29](https://github.com/rust-mobile/cargo-apk/pull/29))
- Use app `uid` instead of `pid` to limit `logcat` output to the current app. ([#33](https://github.com/rust-mobile/cargo-apk/pull/33))

# 0.9.0 (2022-11-23)

Expand Down
61 changes: 38 additions & 23 deletions ndk-build/src/apk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,37 +292,52 @@ impl Apk {
Ok(())
}

pub fn start(&self, device_serial: Option<&str>) -> Result<u32, NdkError> {
let mut am_start = self.ndk.adb(device_serial)?;
am_start
.arg("shell")
pub fn start(&self, device_serial: Option<&str>) -> Result<(), NdkError> {
let mut adb = self.ndk.adb(device_serial)?;
adb.arg("shell")
.arg("am")
.arg("start")
.arg("-W")
.arg("-a")
.arg("android.intent.action.MAIN")
.arg("-n")
.arg(format!("{}/android.app.NativeActivity", &self.package_name));
if !am_start.status()?.success() {
return Err(NdkError::CmdFailed(am_start));
}
.arg(format!("{}/android.app.NativeActivity", self.package_name));

let pid_vec = self
.ndk
.adb(device_serial)?
.arg("shell")
.arg("pidof")
.arg(&self.package_name)
.output()?
.stdout;
if !adb.status()?.success() {
return Err(NdkError::CmdFailed(adb));
}

let pid = std::str::from_utf8(&pid_vec).unwrap().trim();
let pid: u32 = pid
.parse()
.map_err(|e| NdkError::NotAPid(e, pid.to_owned()))?;
Ok(())
}

println!("Launched with PID {}", pid);
pub fn uidof(&self, device_serial: Option<&str>) -> Result<u32, NdkError> {
let mut adb = self.ndk.adb(device_serial)?;
adb.arg("shell")
.arg("pm")
.arg("list")
.arg("package")
.arg("-U")
.arg(&self.package_name);
let output = adb.output()?;

if !output.status.success() {
return Err(NdkError::CmdFailed(adb));
}

Ok(pid)
let output = std::str::from_utf8(&output.stdout).unwrap();
let (_package, uid) = output
.lines()
.filter_map(|line| line.split_once(' '))
// `pm list package` uses the id as a substring filter; make sure
// we select the right package in case it returns multiple matches:
.find(|(package, _uid)| package.strip_prefix("package:") == Some(&self.package_name))
.ok_or(NdkError::PackageNotInOutput {
package: self.package_name.clone(),
output: output.to_owned(),
})?;
let uid = uid
.strip_prefix("uid:")
.ok_or(NdkError::UidNotInOutput(output.to_owned()))?;
uid.parse()
.map_err(|e| NdkError::NotAUid(e, uid.to_owned()))
}
}
8 changes: 6 additions & 2 deletions ndk-build/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ pub enum NdkError {
CmdFailed(Command),
#[error(transparent)]
Serialize(#[from] quick_xml::de::DeError),
#[error("String `{1}` is not a PID")]
NotAPid(#[source] ParseIntError, String),
#[error("String `{1}` is not a UID")]
NotAUid(#[source] ParseIntError, String),
#[error("Could not find `package:{package}` in output `{output}`")]
PackageNotInOutput { package: String, output: String },
#[error("Could not find `uid:` in output `{0}`")]
UidNotInOutput(String),
}

0 comments on commit 5df5274

Please sign in to comment.