diff --git a/app/src/adb/adb.c b/app/src/adb/adb.c index cf20da5f..d8de263f 100644 --- a/app/src/adb/adb.c +++ b/app/src/adb/adb.c @@ -434,6 +434,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags, "Please report an issue."); return false; } +#undef BUFSIZE // It is parsed as a NUL-terminated string buf[r] = '\0'; @@ -757,3 +758,50 @@ sc_adb_get_installed_apk_path(struct sc_intr *intr, const char *serial, return sc_adb_parse_installed_apk_path(buf); } + +char * +sc_adb_get_installed_apk_version(struct sc_intr *intr, const char *serial, + unsigned flags) { + assert(serial); + const char *const argv[] = + SC_ADB_COMMAND("-s", serial, "shell", "dumpsys", "package", + 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 \"dumpsys package\""); + return NULL; + } + + // "dumpsys package" output can be huge (e.g. 16k), but versionName is at + // the beginning, typically in the first 1024 bytes (64k should be enough + // for the whole output anyway) +#define BUFSIZE 65536 + char *buf = malloc(BUFSIZE); + if (!buf) { + return false; + } + ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, BUFSIZE - 1); + sc_pipe_close(pout); + + bool ok = process_check_success_intr(intr, pid, "dumpsys package", flags); + if (!ok) { + return NULL; + } + + if (r == -1) { + return NULL; + } + + assert((size_t) r < BUFSIZE); +#undef BUFSIZE + // if r == sizeof(buf), then the output is truncated, but we don't care, + // versionName is at the beginning in practice, and is unlikely to be + // truncated at 64k + + // It is parsed as a NUL-terminated string + buf[r] = '\0'; + + return sc_adb_parse_installed_apk_version(buf); +} diff --git a/app/src/adb/adb.h b/app/src/adb/adb.h index 9319ee42..c26d7ebb 100644 --- a/app/src/adb/adb.h +++ b/app/src/adb/adb.h @@ -123,4 +123,11 @@ char * sc_adb_get_installed_apk_path(struct sc_intr *intr, const char *serial, unsigned flags); +/** + * Return the version of the installed APK for com.genymobile.scrcpy (if any) + */ +char * +sc_adb_get_installed_apk_version(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 e7782579..ec5077c5 100644 --- a/app/src/adb/adb_parser.c +++ b/app/src/adb/adb_parser.c @@ -253,3 +253,34 @@ sc_adb_parse_installed_apk_path(char *str) { return strdup(s); } + +char * +sc_adb_parse_installed_apk_version(const char *str) { + // str is the (beginning of the) output of `dumpsys package` + // We want to extract the version string from a line starting with 4 spaces + // then `versionName=` then the version string. + +#define VERSION_NAME_PREFIX "\n versionName=" + char *s = strstr(str, VERSION_NAME_PREFIX); + if (!s) { + // Not found + return NULL; + } + + s+= sizeof(VERSION_NAME_PREFIX) - 1; + + size_t len = strspn(s, "0123456789."); + if (!len) { + LOGW("Unexpected version name with no value"); + return NULL; + } + + char *version = malloc(len + 1); + if (!version) { + return NULL; + } + + memcpy(version, s, len); + version[len] = '\0'; + return version; +} diff --git a/app/src/adb/adb_parser.h b/app/src/adb/adb_parser.h index d642af46..68661458 100644 --- a/app/src/adb/adb_parser.h +++ b/app/src/adb/adb_parser.h @@ -38,4 +38,13 @@ sc_adb_parse_device_ip(char *str); char * sc_adb_parse_installed_apk_path(char *str); +/** + * Parse the package version from the output of + * `adb shell dumpsys package ` + * + * The parameter must be a NUL-terminated string. + */ +char * +sc_adb_parse_installed_apk_version(const char *str); + #endif diff --git a/app/src/server.c b/app/src/server.c index 663ef18b..e2b5f667 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -160,6 +160,8 @@ execute_server(struct sc_server *server, const char *serial = server->serial; assert(serial); + fprintf(stderr, "=== [%s]\n", sc_adb_get_installed_apk_version(&server->intr, serial, 0)); + const char *cmd[128]; unsigned count = 0; cmd[count++] = sc_adb_get_executable(); diff --git a/app/tests/test_adb_parser.c b/app/tests/test_adb_parser.c index f2562c3f..4e71fcea 100644 --- a/app/tests/test_adb_parser.c +++ b/app/tests/test_adb_parser.c @@ -263,6 +263,32 @@ static void test_apk_path_invalid(void) { assert(!path); } +static void test_apk_version(void) { + char str[] = + "Key Set Manager:\n" + " [com.genymobile.scrcpy]\n" + " Signing KeySets: 128\n" + "\n" + "Packages:\n" + " Package [com.genymobile.scrcpy] (89abcdef):\n" + " userId=12345\n" + " pkg=Package{012345 com.genymobile.scrcpy}\n" + " codePath=/data/app/~~abcdef==/com.genymobile.scrcpy-012345==\n" + " resourcePath=/data/app/~~abcdef==/com.genymobile.scrcpy-013245==\n" + " primaryCpuAbi=null\n" + " secondaryCpuAbi=null\n" + " versionCode=12400 minSdk=21 targetSdk=31\n" + " versionName=1.24\n" + " splits=[base]\n" + " apkSigningVersion=2\n" + " applicationInfo=ApplicationInfo{012345 com.genymobile.scrcpy}\n"; + + const char *expected = "1.24"; + char *version = sc_adb_parse_installed_apk_version(str); + assert(!strcmp(version, expected)); + free(version); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -287,6 +313,7 @@ int main(int argc, char *argv[]) { test_apk_path(); test_apk_path_invalid(); + test_apk_version(); return 0; }