diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index 8b92a2b0..cf20da5f 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -712,3 +712,48 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) { return sc_adb_parse_device_ip(buf); } + +char * +sc_adb_get_installed_apk_path(struct sc_intr *intr, const char *serial, + unsigned flags) { + assert(serial); + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "shell", "pm", "list", "package", "-f", + SC_ANDROID_PACKAGE); + + sc_pipe pout; + sc_pid pid = sc_adb_execute_p(argv, flags, &pout); + if (pid == SC_PROCESS_NONE) { + LOGD("Could not execute \"pm list packages\""); + return NULL; + } + + // "pm list packages -f " output should contain only one line, so + // the output should be short + char buf[1024]; + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1); + sc_pipe_close(pout); + + bool ok = process_check_success_intr(intr, pid, "pm list packages", flags); + if (!ok) { + return NULL; + } + + if (r == -1) { + return NULL; + } + + assert((size_t) r < sizeof(buf)); + if (r == sizeof(buf) - 1) { + // The implementation assumes that the output of "pm list packages" + // fits in the buffer in a single pass + LOGW("Result of \"pm list package\" does not fit in 1Kb. " + "Please report an issue."); + return NULL; + } + + // It is parsed as a NUL-terminated string + buf[r] = '\0'; + + return sc_adb_parse_installed_apk_path(buf); +} diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index ffd532ea..9319ee42 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -15,6 +15,8 @@ #define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR) +#define SC_ANDROID_PACKAGE "com.genymobile.scrcpy" + const char * sc_adb_get_executable(void); @@ -114,4 +116,11 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop, char * sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags); +/** + * Return the path of the installed APK for com.genymobile.scrcpy (if any) + */ +char * +sc_adb_get_installed_apk_path(struct sc_intr *intr, const char *serial, + unsigned flags); + #endif diff --git a/app/src/adb/adb_parser.c b/app/src/adb/adb_parser.c index ab121347..e7782579 100644 --- a/app/src/adb/adb_parser.c +++ b/app/src/adb/adb_parser.c @@ -225,3 +225,31 @@ sc_adb_parse_device_ip(char *str) { return NULL; } + +char * +sc_adb_parse_installed_apk_path(char *str) { + // str is expected to look like: + // "package:/data/app/.../base.apk=com.genymobile.scrcpy" + // ^^^^^^^^^^^^^^^^^^^^^^ + // We want to extract the path (which may contain '=', even in practice) + + if (strncmp(str, "package:", 8)) { + // Does not start with "package:" + return NULL; + } + + char *s = str + 8; + size_t len = strcspn(s, " \r\n"); + s[len] = '\0'; + + char *p = strrchr(s, '='); + if (!p) { + // No '=' found + return NULL; + } + + // Truncate at the last '=' + *p = '\0'; + + return strdup(s); +} diff --git a/app/src/adb/adb_parser.h b/app/src/adb/adb_parser.h index f20349f6..d642af46 100644 --- a/app/src/adb/adb_parser.h +++ b/app/src/adb/adb_parser.h @@ -27,4 +27,15 @@ sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec); char * sc_adb_parse_device_ip(char *str); +/** + * Parse the package path from the output of + * `adb shell pm list packages -f ` + * + * The parameter must be a NUL-terminated string. + * + * Warning: this function modifies the buffer for optimization purposes. + */ +char * +sc_adb_parse_installed_apk_path(char *str); + #endif diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index d95e7ef2..f2562c3f 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -241,6 +241,28 @@ static void test_get_ip_truncated(void) { assert(!ip); } +static void test_apk_path(void) { + char str[] = "package:/data/app/~~71mguyc6p-kNjQdNaNkToA==/com.genymobile." + "scrcpy-l6fiqqUSU7Ok7QLg-rIyJA==/base.apk=com.genymobile." + "scrcpy\n"; + + const char *expected = "/data/app/~~71mguyc6p-kNjQdNaNkToA==/com.genymobile" + ".scrcpy-l6fiqqUSU7Ok7QLg-rIyJA==/base.apk"; + char *path = sc_adb_parse_installed_apk_path(str); + assert(!strcmp(path, expected)); + free(path); +} + +static void test_apk_path_invalid(void) { + // Does not start with "package:" + char str[] = "garbage:/data/app/~~71mguyc6p-kNjQdNaNkToA==/com.genymobile." + "scrcpy-l6fiqqUSU7Ok7QLg-rIyJA==/base.apk=com.genymobile." + "scrcpy\n"; + + char *path = sc_adb_parse_installed_apk_path(str); + assert(!path); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -263,5 +285,8 @@ int main(int argc, char *argv[]) { test_get_ip_no_wlan_without_eol(); test_get_ip_truncated(); + test_apk_path(); + test_apk_path_invalid(); + return 0; }