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