Appinfo: Make adb the app debug source of truth am: 15335e0159 am: c2ef2ee608
Original change: https://android-review.googlesource.com/c/platform/packages/modules/adb/+/3038432
Change-Id: I4df3899347d1481f944454f210059c90cd2490b8
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 6809cfe..967bcfd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -916,6 +916,7 @@
name: "adb_integration_test_device",
main: "test_device.py",
srcs: [
+ "proto/app_processes.proto",
"proto/devices.proto",
"test_device.py",
],
@@ -931,6 +932,10 @@
test_options: {
unit_test: false,
},
+ data: [
+ ":adb_test_app1",
+ ":adb_test_app2",
+ ],
}
// Note: using pipe for xxd to control the variable name generated
diff --git a/client/commandline.cpp b/client/commandline.cpp
index bd94f53..97034d8 100644
--- a/client/commandline.cpp
+++ b/client/commandline.cpp
@@ -2094,7 +2094,17 @@
error_exit("track-app is not supported by the device");
}
TrackAppStreamsCallback callback;
- return adb_connect_command("track-app", nullptr, &callback);
+ if (argc == 1) {
+ return adb_connect_command("track-app", nullptr, &callback);
+ } else if (argc == 2) {
+ if (!strcmp(argv[1], "--proto-binary")) {
+ return adb_connect_command("track-app");
+ } else if (!strcmp(argv[1], "--proto-text")) {
+ return adb_connect_command("track-app", nullptr, &callback);
+ }
+ } else {
+ error_exit("usage: adb track-app [--proto-binary][--proto-text]");
+ }
} else if (!strcmp(argv[0], "track-devices")) {
const char* listopt;
if (argc < 2) {
diff --git a/daemon/jdwp_service.cpp b/daemon/jdwp_service.cpp
index 05c8c71..3aeade0 100644
--- a/daemon/jdwp_service.cpp
+++ b/daemon/jdwp_service.cpp
@@ -33,7 +33,6 @@
#include <thread>
#include <vector>
-#include <adbconnection/process_info.h>
#include <adbconnection/server.h>
#include <android-base/cmsg.h>
#include <android-base/unique_fd.h>
@@ -153,6 +152,7 @@
this->socket = socket;
this->process = process;
this->fde = fdevent_create(socket.release(), jdwp_process_event, this);
+ fdevent_set(this->fde, FDE_READ);
if (!this->fde) {
LOG(FATAL) << "could not create fdevent for new JDWP process";
@@ -218,10 +218,7 @@
for (auto& proc : _jdwp_list) {
if (!proc->process.debuggable && !proc->process.profileable) continue;
auto* entry = temp.add_process();
- entry->set_pid(proc->process.pid);
- entry->set_debuggable(proc->process.debuggable);
- entry->set_profileable(proc->process.profileable);
- entry->set_architecture(proc->process.arch_name, proc->process.arch_name_length);
+ *entry = std::move(proc->process.toProtobuf());
temp.SerializeToString(&serialized_message);
if (serialized_message.size() > bufferlen) {
D("truncating app process list (max len = %zu)", bufferlen);
@@ -264,9 +261,16 @@
CHECK_EQ(socket, proc->socket.get());
if (events & FDE_READ) {
- // We already have the PID, if we can read from the socket, we've probably hit EOF.
- D("terminating JDWP connection %" PRId64, proc->process.pid);
- goto CloseProcess;
+ auto process_info = readProcessInfoFromSocket(socket);
+
+ // Unable to get a process info, the remote app process either died or errored
+ if (!process_info) {
+ goto CloseProcess;
+ }
+
+ proc->process = std::move(*process_info);
+ jdwp_process_list_updated();
+ app_process_list_updated();
}
if (events & FDE_WRITE) {
diff --git a/libs/adbconnection/Android.bp b/libs/adbconnection/Android.bp
index caef23d..45238ad 100644
--- a/libs/adbconnection/Android.bp
+++ b/libs/adbconnection/Android.bp
@@ -13,15 +13,25 @@
cc_library {
name: "libadbconnection_server",
- srcs: ["adbconnection_server.cpp", "common.cpp"],
+ srcs: [
+ "adbconnection_server.cpp",
+ "common.cpp",
+ ],
export_include_dirs: ["include"],
stl: "libc++_static",
shared_libs: ["liblog"],
- static_libs: ["libbase"],
+ static_libs: [
+ "libbase",
+ "libapp_processes_protos_lite",
+ "libprotobuf-cpp-lite",
+ ],
- defaults: ["adbd_defaults", "host_adbd_supported"],
+ defaults: [
+ "adbd_defaults",
+ "host_adbd_supported",
+ ],
// Avoid getting duplicate symbol of android::build::GetBuildNumber().
use_version_lib: false,
@@ -38,13 +48,20 @@
cc_library {
name: "libadbconnection_client",
- srcs: ["adbconnection_client.cpp", "common.cpp"],
+ srcs: [
+ "adbconnection_client.cpp",
+ "common.cpp",
+ ],
export_include_dirs: ["include"],
stl: "libc++_static",
shared_libs: ["liblog"],
- static_libs: ["libbase"],
+ static_libs: [
+ "libbase",
+ "libapp_processes_protos_lite",
+ "libprotobuf-cpp-lite",
+ ],
defaults: ["adbd_defaults"],
visibility: [
@@ -64,7 +81,9 @@
linux: {
version_script: "libadbconnection_client.map.txt",
},
- darwin: { enabled: false },
+ darwin: {
+ enabled: false,
+ },
},
stubs: {
symbol_file: "libadbconnection_client.map.txt",
@@ -74,3 +93,25 @@
host_supported: true,
compile_multilib: "both",
}
+
+cc_test_host {
+ name: "libadbconnection_test",
+ srcs: ["tests.cc"],
+ static_libs: [
+ "libandroidfw",
+ "libbase",
+ "libadbconnection_client",
+ "libadbconnection_server",
+ "libapp_processes_protos_lite",
+ "libprotobuf-cpp-lite",
+ "liblog",
+ ],
+ target: {
+ windows: {
+ enabled: false,
+ },
+ darwin: {
+ enabled: false,
+ },
+ }
+}
diff --git a/libs/adbconnection/adbconnection_client.cpp b/libs/adbconnection/adbconnection_client.cpp
index eb3ef83..1d06a97 100644
--- a/libs/adbconnection/adbconnection_client.cpp
+++ b/libs/adbconnection/adbconnection_client.cpp
@@ -23,23 +23,43 @@
#include <sys/un.h>
#include <memory>
+#include <mutex>
#include <optional>
+#include <string>
+#include <vector>
#include <android-base/cmsg.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
+#include <android-base/thread_annotations.h>
#include <android-base/unique_fd.h>
#include "adbconnection/common.h"
-#include "adbconnection/process_info.h"
using android::base::unique_fd;
+struct AppInfo {
+ std::mutex mutex;
+
+ // The state of the app process
+ ProcessInfo process GUARDED_BY(mutex);
+
+ // True if any of the ProcessInfo fields have been modified since we last sent an update to the
+ // server.
+ bool has_pending_update GUARDED_BY(mutex) = false;
+};
+
+static auto& app_info = *new AppInfo();
+
struct AdbConnectionClientContext {
unique_fd control_socket_;
};
bool SocketPeerIsTrusted(int fd) {
+ // Allow unit tests to pass on Linux host
+#if !defined(__ANDROID__)
+ return true;
+#endif
ucred cr;
socklen_t cr_length = sizeof(cr);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cr_length) != 0) {
@@ -56,6 +76,40 @@
return true;
}
+static void send_app_info(const AdbConnectionClientContext* ctx) {
+ std::lock_guard<std::mutex> lock(app_info.mutex);
+ if (!ctx) {
+ LOG(WARNING) << "Can't send app_info: No connection to adbd";
+ return;
+ }
+
+ if (!app_info.has_pending_update) {
+ LOG(WARNING) << "adbconnection_client: No pending updates";
+ return;
+ }
+
+ auto protobufProcess = app_info.process.toProtobuf();
+ std::string serialized_message;
+ if (!protobufProcess.SerializeToString(&serialized_message)) {
+ LOG(ERROR) << "Unable to build ART -> adbd message";
+ return;
+ }
+
+ if (serialized_message.size() > MAX_APP_MESSAGE_LENGTH) {
+ LOG(ERROR) << "adbd appinfo message too big (> " << MAX_APP_MESSAGE_LENGTH << ")";
+ return;
+ }
+
+ ssize_t message_size = serialized_message.size();
+ ssize_t rc = TEMP_FAILURE_RETRY(
+ write(ctx->control_socket_.get(), serialized_message.data(), serialized_message.size()));
+ if (rc != message_size) {
+ PLOG(ERROR) << "failed to send app info to adbd";
+ return;
+ }
+ app_info.has_pending_update = false;
+}
+
AdbConnectionClientContext* adbconnection_client_new(
const AdbConnectionClientInfo* const* info_elems, size_t info_count) {
auto ctx = std::make_unique<AdbConnectionClientContext>();
@@ -168,15 +222,63 @@
return nullptr;
}
- ProcessInfo process(*pid, *debuggable, *profileable, *architecture);
- rc = TEMP_FAILURE_RETRY(write(ctx->control_socket_.get(), &process, sizeof(process)));
- if (rc != sizeof(process)) {
- PLOG(ERROR) << "failed to send JDWP process info to adbd";
+ {
+ std::lock_guard<std::mutex> lock(app_info.mutex);
+ app_info.process.pid = *pid;
+ app_info.process.debuggable = *debuggable;
+ if (profileable) {
+ app_info.process.profileable = *profileable;
+ }
+ if (architecture) {
+ app_info.process.architecture = *architecture;
+ }
+ app_info.process.uid = getuid();
+ app_info.has_pending_update = true;
}
+ send_app_info(ctx.get());
return ctx.release();
}
+void adbconnection_client_set_current_process_name(const char* process_name) {
+ std::lock_guard<std::mutex> lock(app_info.mutex);
+ app_info.process.process_name = process_name;
+ app_info.has_pending_update = true;
+}
+
+void adbconnection_client_add_application(const char* package_name) {
+ std::lock_guard<std::mutex> lock(app_info.mutex);
+ app_info.process.package_names.insert(package_name);
+ app_info.has_pending_update = true;
+}
+
+void adbconnection_client_remove_application(const char* package_name) {
+ std::lock_guard<std::mutex> lock(app_info.mutex);
+ app_info.process.package_names.erase(package_name);
+ app_info.has_pending_update = true;
+}
+
+void adbconnection_client_set_waiting_for_debugger(bool waiting) {
+ std::lock_guard<std::mutex> lock(app_info.mutex);
+ app_info.process.waiting_for_debugger = waiting;
+ app_info.has_pending_update = true;
+}
+
+bool adbconnection_client_has_pending_update() {
+ std::lock_guard<std::mutex> lock(app_info.mutex);
+ return app_info.has_pending_update;
+}
+
+void adbconnection_client_set_user_id(int user_id) {
+ std::lock_guard<std::mutex> lock(app_info.mutex);
+ app_info.process.user_id = user_id;
+ app_info.has_pending_update = true;
+}
+
+void adbconnection_client_send_update(const AdbConnectionClientContext* ctx) {
+ send_app_info(ctx);
+}
+
void adbconnection_client_destroy(AdbConnectionClientContext* ctx) {
delete ctx;
}
@@ -193,4 +295,4 @@
return rc;
}
return jdwp_fd.release();
-}
+}
\ No newline at end of file
diff --git a/libs/adbconnection/adbconnection_server.cpp b/libs/adbconnection/adbconnection_server.cpp
index 5c8871f..2413ee6 100644
--- a/libs/adbconnection/adbconnection_server.cpp
+++ b/libs/adbconnection/adbconnection_server.cpp
@@ -29,10 +29,48 @@
#include <android-base/unique_fd.h>
#include "adbconnection/common.h"
-#include "adbconnection/process_info.h"
+#include "app_processes.pb.h"
using android::base::unique_fd;
+std::optional<ProcessInfo> readProcessInfoFromSocket(int socket) {
+ std::string proto;
+ proto.resize(MAX_APP_MESSAGE_LENGTH);
+ ssize_t rc = TEMP_FAILURE_RETRY(recv(socket, proto.data(), proto.length(), MSG_PEEK));
+
+ if (rc == 0) {
+ LOG(INFO) << "Remote process closed the socket (on MSG_PEEK)";
+ return {};
+ }
+
+ if (rc == -1) {
+ PLOG(ERROR) << "adbconnection_server: Unable to MSG_PEEK ProcessInfo recv";
+ return {};
+ }
+
+ ssize_t message_size = rc;
+ proto.resize(message_size);
+ rc = TEMP_FAILURE_RETRY(recv(socket, proto.data(), message_size, 0));
+
+ if (rc == 0) {
+ LOG(INFO) << "Remote process closed the socket (on recv)";
+ return {};
+ }
+
+ if (rc == -1) {
+ PLOG(ERROR) << "adbconnection_server: Unable to recv ProcessInfo " << message_size << " bytes";
+ return {};
+ }
+
+ if (rc != message_size) {
+ LOG(ERROR) << "adbconnection_server: Unexpected ProcessInfo size " << message_size
+ << " bytes but got " << rc;
+ return {};
+ }
+
+ return ProcessInfo::parseProtobufString(proto);
+}
+
// Listen for incoming jdwp clients forever.
void adbconnection_listen(void (*callback)(int fd, ProcessInfo process)) {
unique_fd s(socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
@@ -99,13 +137,11 @@
<< ") in pending connections";
}
- ProcessInfo process;
- int rc = TEMP_FAILURE_RETRY(recv(it->get(), &process, sizeof(process), MSG_DONTWAIT));
- if (rc != sizeof(process)) {
- LOG(ERROR) << "received data of incorrect size from JDWP client: read " << rc
- << ", expected " << sizeof(process);
+ auto process_info = readProcessInfoFromSocket(it->get());
+ if (process_info) {
+ callback(it->release(), *process_info);
} else {
- callback(it->release(), process);
+ LOG(ERROR) << "Unable to read ProcessInfo from app startup";
}
if (epoll_ctl(epfd.get(), EPOLL_CTL_DEL, event.data.fd, nullptr) != 0) {
diff --git a/libs/adbconnection/common.cpp b/libs/adbconnection/common.cpp
index b279702..66aca45 100644
--- a/libs/adbconnection/common.cpp
+++ b/libs/adbconnection/common.cpp
@@ -33,4 +33,41 @@
socklen_t addrlen = offsetof(sockaddr_un, sun_path) + kJdwpControlName.size();
return {addr, addrlen};
+}
+
+adb::proto::ProcessEntry ProcessInfo::toProtobuf() const {
+ adb::proto::ProcessEntry process;
+ process.set_pid(pid);
+ process.set_user_id(user_id);
+ process.set_debuggable(debuggable);
+ process.set_profileable(profileable);
+ process.set_architecture(architecture);
+ process.set_process_name(process_name);
+ for (std::string package_name : package_names) {
+ process.add_package_names(package_name);
+ }
+ process.set_waiting_for_debugger(waiting_for_debugger);
+ process.set_uid(uid);
+ return process;
+}
+
+std::optional<ProcessInfo> ProcessInfo::parseProtobufString(const std::string& proto) {
+ adb::proto::ProcessEntry process_entry_proto;
+ if (!process_entry_proto.ParseFromString(proto)) {
+ return {};
+ }
+
+ ProcessInfo process_info;
+ process_info.pid = process_entry_proto.pid();
+ process_info.user_id = process_entry_proto.user_id();
+ process_info.debuggable = process_entry_proto.debuggable();
+ process_info.profileable = process_entry_proto.profileable();
+ process_info.architecture = process_entry_proto.architecture();
+ process_info.process_name = process_entry_proto.process_name();
+ for (int i = 0; i < process_entry_proto.package_names_size(); i++) {
+ process_info.package_names.insert(process_entry_proto.package_names(i));
+ }
+ process_info.waiting_for_debugger = process_entry_proto.waiting_for_debugger();
+ process_info.uid = process_entry_proto.uid();
+ return process_info;
}
\ No newline at end of file
diff --git a/libs/adbconnection/include/adbconnection/client.h b/libs/adbconnection/include/adbconnection/client.h
index a74cd36..1cfc134 100644
--- a/libs/adbconnection/include/adbconnection/client.h
+++ b/libs/adbconnection/include/adbconnection/client.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@
#include <android-base/unique_fd.h>
+// Allow tests to build on host
+#if !defined(__INTRODUCED_IN)
+#define __INTRODUCED_IN(__api_level) /* nothing */
+#endif
+
extern "C" {
struct AdbConnectionClientContext;
@@ -51,6 +56,40 @@
AdbConnectionClientContext* adbconnection_client_new(
const AdbConnectionClientInfo* const* info_elems, size_t info_count);
+// Update the apex client with the new name of the process. Nothing is transferred to the server.
+// You need to call adbconnection_client_send_update to transmit the latest state to adbd.
+void adbconnection_client_set_current_process_name(const char* process_name) __INTRODUCED_IN(36);
+
+// Update the apex client when a package name is added to the current process. Nothing is
+// transferred to the server. You need to call adbconnection_client_send_update to transmit the
+// latest state to adbd.
+void adbconnection_client_add_application(const char* package_name) __INTRODUCED_IN(36);
+
+// Update the apex client when a package name is removed from the current process. Nothing is
+// transferred to the server. You need to call adbconnection_client_send_update to transmit the
+// latest state to adbd.
+void adbconnection_client_remove_application(const char* package_name) __INTRODUCED_IN(36);
+
+// Update the apex client when the app is waiting for debugger (or not). Nothing is
+// transferred to the server. You need to call adbconnection_client_send_update to transmit the
+// latest state to adbd.
+void adbconnection_client_set_waiting_for_debugger(bool waiting) __INTRODUCED_IN(36);
+
+// Update the apex client when app process uid is known. This is not the value from getuid() but
+// the UserID profile (e.g.: A device with a single user will have one UserID=0 but a device with
+// an additional work profile will have a second UserID). The origin of this value is the unix UID
+// massaged in Framework via PER_USER_RANGE and USER_SYSTEM. Nothing is transferred to the server.
+// You need to call adbconnection_client_send_update to transmit the latest state to adbd.
+void adbconnection_client_set_user_id(int uid) __INTRODUCED_IN(36);
+
+// Check if the client has something to send to the server (we don't want to be woken by a
+// writable socket if we have nothing to write). If it does, adbconnection_client_send_update
+// should be called.
+bool adbconnection_client_has_pending_update() __INTRODUCED_IN(36);
+
+// Write the latest appinfo state so adbd receives it.
+void adbconnection_client_send_update(const AdbConnectionClientContext* ctx) __INTRODUCED_IN(36);
+
void adbconnection_client_destroy(AdbConnectionClientContext* ctx);
// Get an fd which can be polled upon to detect when a jdwp socket is available.
diff --git a/libs/adbconnection/include/adbconnection/common.h b/libs/adbconnection/include/adbconnection/common.h
index 5397848..46eba03 100644
--- a/libs/adbconnection/include/adbconnection/common.h
+++ b/libs/adbconnection/include/adbconnection/common.h
@@ -16,9 +16,30 @@
#pragma once
+#include "app_processes.pb.h"
+
#include <sys/socket.h>
#include <sys/un.h>
+#include <optional>
#include <tuple>
+#include <unordered_set>
+
+struct ProcessInfo {
+ uint64_t pid;
+ bool debuggable;
+ bool profileable;
+ std::string architecture;
+ bool waiting_for_debugger = false;
+ uint64_t user_id = 0;
+ std::string process_name = "";
+ std::unordered_set<std::string> package_names;
+ int uid;
+
+ adb::proto::ProcessEntry toProtobuf() const;
+ static std::optional<ProcessInfo> parseProtobufString(const std::string& proto);
+};
+
+#define MAX_APP_MESSAGE_LENGTH 4096
std::tuple<sockaddr_un, socklen_t> get_control_socket_addr();
\ No newline at end of file
diff --git a/libs/adbconnection/include/adbconnection/process_info.h b/libs/adbconnection/include/adbconnection/process_info.h
deleted file mode 100644
index d226699..0000000
--- a/libs/adbconnection/include/adbconnection/process_info.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <stdint.h>
-#include <string.h>
-#include <string>
-
-struct ProcessInfo {
- static constexpr size_t kMaxArchNameLength = 16;
-
- uint64_t pid;
- bool debuggable;
- bool profileable;
- int32_t arch_name_length; // length of architecture name in bytes
- char arch_name[kMaxArchNameLength]; // ISA name, e.g., "arm64"
-
- ProcessInfo() : pid(0), debuggable(false), profileable(false), arch_name_length(0) {}
-
- ProcessInfo(uint64_t pid, bool dbg, bool prof, const std::string& arch)
- : pid(pid), debuggable(dbg), profileable(prof) {
- arch_name_length = std::min(arch.size(), kMaxArchNameLength);
- memcpy(arch_name, arch.data(), arch_name_length);
- }
-};
diff --git a/libs/adbconnection/include/adbconnection/server.h b/libs/adbconnection/include/adbconnection/server.h
index b1059ba..65102c3 100644
--- a/libs/adbconnection/include/adbconnection/server.h
+++ b/libs/adbconnection/include/adbconnection/server.h
@@ -18,9 +18,14 @@
#include <sys/types.h>
+#include "common.h"
+
#include <android-base/unique_fd.h>
-#include "adbconnection/process_info.h"
+#include <stdint.h>
+#include <optional>
// Note this is NOT an apex interface as it's linked only into adbd.
void adbconnection_listen(void (*callback)(int fd, ProcessInfo process));
+
+std::optional<ProcessInfo> readProcessInfoFromSocket(int socket);
diff --git a/libs/adbconnection/libadbconnection_client.map.txt b/libs/adbconnection/libadbconnection_client.map.txt
index a5e126d..0cd8f06 100644
--- a/libs/adbconnection/libadbconnection_client.map.txt
+++ b/libs/adbconnection/libadbconnection_client.map.txt
@@ -23,3 +23,16 @@
local:
*;
};
+
+LIBADBCONNECTION_CLIENT_36 { # introduced=36
+ global:
+ adbconnection_client_set_current_process_name; # apex
+ adbconnection_client_add_application; # apex
+ adbconnection_client_remove_application; # apex
+ adbconnection_client_set_waiting_for_debugger; # apex
+ adbconnection_client_has_pending_update; # apex
+ adbconnection_client_set_user_id; # apex
+ adbconnection_client_send_update; # apex
+ local:
+ *;
+};
diff --git a/libs/adbconnection/tests.cc b/libs/adbconnection/tests.cc
new file mode 100644
index 0000000..f44550a
--- /dev/null
+++ b/libs/adbconnection/tests.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <chrono>
+#include <iostream>
+#include <mutex>
+#include <thread>
+
+#include "adbconnection/client.h"
+#include "adbconnection/common.h"
+#include "adbconnection/server.h"
+
+#include <gtest/gtest.h>
+
+// How this test works. Normally, the client lives in ART process and the server live in adbd.
+// They communicate over UDS "@jdwp-control", each using a poll system (we don't have here).
+//
+// We spawn the client and the server in the same process. They still use the same UDS to
+// communicate. The fdevent from adbd system is mocked with a single loop (server_callback). The
+// conditional variable is used to pace the test (send then assert).
+
+ProcessInfo info = {};
+
+// The client synchronize with the server with mx and cv
+std::mutex mx;
+std::condition_variable cv;
+void onUpdateReceived() {
+ std::lock_guard<std::mutex> lock(mx);
+ cv.notify_one();
+}
+void waitForUpdateReceived() {
+ std::unique_lock<std::mutex> lock(mx);
+ cv.wait(lock);
+}
+
+void server_callback(int fd, ProcessInfo process) {
+ info = process;
+ onUpdateReceived();
+ // After the first processinfo update is received, jdwp_service in adbd takes over reading
+ // from the fd leveraging fdevent system. We emulate it by reading on a regular basis.
+ while (true) {
+ auto optional = readProcessInfoFromSocket(fd);
+ if (optional) {
+ info = *optional;
+ onUpdateReceived();
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ }
+}
+
+// This test mimics an app starting up and sending data as zygote is specializing
+TEST(LibAdbConnectionTest, TestComm) {
+ // Start a fake server
+ std::thread([]() { adbconnection_listen(server_callback); }).detach();
+ // Let the server start
+ sleep(1);
+
+ const char* isa = "arch_foo";
+ const AdbConnectionClientInfo infos[] = {
+ {.type = AdbConnectionClientInfoType::pid, .data.pid = 666},
+ {.type = AdbConnectionClientInfoType::debuggable, .data.debuggable = true},
+ {.type = AdbConnectionClientInfoType::profileable, .data.profileable = true},
+ {.type = AdbConnectionClientInfoType::architecture,
+ .data.architecture.name = isa,
+ .data.architecture.size = strlen(isa)},
+ };
+
+ // Send the first batch of data (mimic the app starting up)
+ const AdbConnectionClientInfo* info_ptrs[] = {&infos[0], &infos[1], &infos[2], &infos[3]};
+ auto ctx = adbconnection_client_new(info_ptrs, std::size(infos));
+ EXPECT_TRUE(ctx != nullptr);
+ EXPECT_FALSE(adbconnection_client_has_pending_update());
+
+ waitForUpdateReceived();
+ EXPECT_EQ(info.pid, infos[0].data.pid);
+ EXPECT_TRUE(info.debuggable);
+ EXPECT_TRUE(info.profileable);
+ EXPECT_EQ(info.architecture, isa);
+ EXPECT_FALSE(adbconnection_client_has_pending_update());
+
+ adbconnection_client_set_current_process_name("my_process_name");
+ adbconnection_client_add_application("my_package_name");
+ adbconnection_client_add_application("my_package_name2");
+ adbconnection_client_remove_application("my_package_name2");
+ adbconnection_client_set_user_id(888);
+ adbconnection_client_set_waiting_for_debugger(true);
+
+ EXPECT_TRUE(adbconnection_client_has_pending_update());
+
+ // Send an update
+ adbconnection_client_send_update(ctx);
+ EXPECT_FALSE(adbconnection_client_has_pending_update());
+
+ waitForUpdateReceived();
+ EXPECT_EQ(info.package_names.size(), 1);
+ EXPECT_EQ(info.package_names.count("my_package_name"), 1);
+ EXPECT_EQ(info.process_name, "my_process_name");
+ EXPECT_EQ(info.user_id, 888);
+ EXPECT_TRUE(info.waiting_for_debugger);
+}
\ No newline at end of file
diff --git a/proto/app_processes.proto b/proto/app_processes.proto
index 1183645..5c1d665 100644
--- a/proto/app_processes.proto
+++ b/proto/app_processes.proto
@@ -25,7 +25,12 @@
int64 pid = 1;
bool debuggable = 2;
bool profileable = 3;
- string architecture = 4;
+ string architecture = 4; // ISA name, e.g., "arm64"
+ optional int64 user_id = 5;
+ optional string process_name = 6;
+ repeated string package_names = 7;
+ optional bool waiting_for_debugger = 8;
+ optional int64 uid = 9;
}
message AppProcesses {
diff --git a/test_device.py b/test_device.py
index d741594..46a5545 100755
--- a/test_device.py
+++ b/test_device.py
@@ -37,6 +37,7 @@
import unittest
import proto.devices_pb2 as proto_devices
+import proto.app_processes_pb2 as proto_track_app
from datetime import datetime
@@ -1844,6 +1845,42 @@
proc.terminate()
+class DevicesListing(DeviceTest):
+
+ serial = subprocess.check_output(['adb', 'get-serialno']).strip().decode("utf-8")
+
+ def test_track_app_appinfo(self):
+ return # Disabled until b/301491148 is fixed.
+ # (Exported FeatureFlags cannot be read-only)
+ subprocess.check_output(['adb', 'install', '-t', 'adb1.apk']).strip().decode("utf-8")
+ subprocess.check_output(['adb', 'install', '-t', 'adb2.apk']).strip().decode("utf-8")
+ subprocess.check_output(['adb', 'shell', 'am', 'start', '-W', 'adb.test.app1/.MainActivity']).strip().decode("utf-8")
+ subprocess.check_output(['adb', 'shell', 'am', 'start', '-W', 'adb.test.app2/.MainActivity']).strip().decode("utf-8")
+ subprocess.check_output(['adb', 'shell', 'am', 'start', '-W', 'adb.test.app1/.OwnProcessActivity']).strip().decode("utf-8")
+ with subprocess.Popen(['adb', 'track-app', '--proto-binary'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
+ output_size = int(proc.stdout.read(4).decode("utf-8"), 16)
+ proto = proc.stdout.read(output_size)
+
+ apps = proto_track_app.AppProcesses()
+ apps.ParseFromString(proto)
+
+ foundAdbAppDefProc = False
+ foundAdbAppOwnProc = False
+ for app in apps.process:
+ if (app.process_name == "adb.test.process.name"):
+ foundAdbAppDefProc = True
+ self.assertTrue(app.debuggable)
+ self.assertTrue("adb.test.app1" in app.package_names)
+ self.assertTrue("adb.test.app2" in app.package_names)
+
+ if (app.process_name == "adb.test.own.process"):
+ foundAdbAppOwnProc = True
+ self.assertTrue(app.debuggable)
+ self.assertTrue("adb.test.app1" in app.package_names)
+
+ self.assertTrue(foundAdbAppDefProc)
+ self.assertTrue(foundAdbAppOwnProc)
+ proc.terminate()
if __name__ == '__main__':
random.seed(0)
diff --git a/test_device_apks/Android.bp b/test_device_apks/Android.bp
new file mode 100644
index 0000000..90a296d
--- /dev/null
+++ b/test_device_apks/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "adb_test_app1",
+ srcs: [
+ "test_app1/src/**/*.java",
+ ],
+ manifest: "test_app1/AndroidManifest.xml",
+ certificate: "platform",
+ sdk_version: "current",
+}
+
+android_test {
+ name: "adb_test_app2",
+ srcs: [
+ "test_app2/src/**/*.java",
+ ],
+ manifest: "test_app2/AndroidManifest.xml",
+ certificate: "platform",
+ sdk_version: "current",
+}
diff --git a/test_device_apks/test_app1/AndroidManifest.xml b/test_device_apks/test_app1/AndroidManifest.xml
new file mode 100644
index 0000000..9ed7b6d
--- /dev/null
+++ b/test_device_apks/test_app1/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="adb.test.app1"
+ android:sharedUserId="adb.shared.process">
+ <uses-sdk android:minSdkVersion="28"/>
+ <application
+ android:label="Adb Test 1"
+ android:debuggable="true"
+ android:process="adb.test.process.name">
+ <activity
+ android:name="MainActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name="OwnProcessActivity"
+ android:exported="true"
+ android:process="adb.test.own.process"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/test_device_apks/test_app1/src/adb/test/app1/MainActivity.java b/test_device_apks/test_app1/src/adb/test/app1/MainActivity.java
new file mode 100644
index 0000000..8b2b535
--- /dev/null
+++ b/test_device_apks/test_app1/src/adb/test/app1/MainActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package adb.test.app1;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class MainActivity extends Activity
+{
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ TextView label = new TextView(this);
+ label.setText("I am MainActivity!");
+
+ setContentView(label);
+ }
+}
diff --git a/test_device_apks/test_app1/src/adb/test/app1/OwnProcessActivity.java b/test_device_apks/test_app1/src/adb/test/app1/OwnProcessActivity.java
new file mode 100644
index 0000000..7741b23
--- /dev/null
+++ b/test_device_apks/test_app1/src/adb/test/app1/OwnProcessActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package adb.test.app1;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class OwnProcessActivity extends Activity
+{
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ TextView label = new TextView(this);
+ label.setText("I am OwnProcessActivity!");
+
+ setContentView(label);
+ }
+}
diff --git a/test_device_apks/test_app2/AndroidManifest.xml b/test_device_apks/test_app2/AndroidManifest.xml
new file mode 100644
index 0000000..3b47cb9
--- /dev/null
+++ b/test_device_apks/test_app2/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="adb.test.app2"
+ android:sharedUserId="adb.shared.process">
+ <uses-sdk android:minSdkVersion="28"/>
+ <application
+ android:label="Adb Test 2"
+ android:debuggable="true"
+ android:process="adb.test.process.name">
+ <activity
+ android:name="MainActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/test_device_apks/test_app2/src/adb/test/app2/MainActivity.java b/test_device_apks/test_app2/src/adb/test/app2/MainActivity.java
new file mode 100644
index 0000000..ff48fdc
--- /dev/null
+++ b/test_device_apks/test_app2/src/adb/test/app2/MainActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package adb.test.app2;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class MainActivity extends Activity
+{
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ TextView label = new TextView(this);
+ label.setText("I am MainActivity!");
+
+ setContentView(label);
+ }
+}
diff --git a/transport.cpp b/transport.cpp
index 9c27fd5..dd644f2 100644
--- a/transport.cpp
+++ b/transport.cpp
@@ -99,6 +99,7 @@
const char* const kFeatureOpenscreenMdns = "openscreen_mdns";
const char* const kFeatureDeviceTrackerProtoFormat = "devicetracker_proto_format";
const char* const kFeatureDevRaw = "devraw";
+const char* const kFeatureAppInfo = "app_info"; // Add information to track-app (package name, ...)
namespace {
@@ -1208,6 +1209,7 @@
kFeatureOpenscreenMdns,
kFeatureDeviceTrackerProtoFormat,
kFeatureDevRaw,
+ kFeatureAppInfo,
};
// clang-format on