Snap for 11216811 from 123fe0d6e21c1d80781dc525220016afb55a4690 to 24Q1-release

Change-Id: I500cfef72ccc114e78e6424c4dfc16f42bfceef5
diff --git a/Android.bp b/Android.bp
index 3427079..2a0a6a8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -310,6 +310,7 @@
         "libcrypto_utils",
         "libcutils",
         "libdiagnose_usb",
+        "libdevices_protos",
         "liblog",
         "libmdnssd",
         "libopenscreen-discovery",
@@ -379,12 +380,13 @@
         "libcrypto",
         "libcrypto_utils",
         "libcutils",
+        "libdevices_protos",
         "libdiagnose_usb",
         "liblog",
         "libmdnssd",
         "libopenscreen-discovery",
         "libopenscreen-platform-impl",
-        "libprotobuf-cpp-lite",
+        "libprotobuf-cpp-full",
         "libssl",
         "libusb",
     ],
@@ -447,6 +449,7 @@
         "libcrypto_utils",
         "libcutils",
         "libdiagnose_usb",
+        "libdevices_protos",
         "libfastdeploy_host",
         "liblog",
         "liblog",
@@ -889,10 +892,15 @@
     name: "adb_integration_test_device",
     main: "test_device.py",
     srcs: [
+        "proto/devices.proto",
         "test_device.py",
     ],
+    proto: {
+        canonical_path_from_root: false,
+    },
     libs: [
         "adb_py",
+        "libprotobuf-python",
     ],
     test_config: "adb_integration_test_device.xml",
     test_suites: ["general-tests"],
@@ -974,13 +982,14 @@
         "libcrypto",
         "libcrypto_utils",
         "libcutils",
+        "libdevices_protos",
         "libdiagnose_usb",
         "libfastdeploy_host",
         "liblog",
         "libmdnssd",
         "libopenscreen-discovery",
         "libopenscreen-platform-impl",
-        "libprotobuf-cpp-lite",
+        "libprotobuf-cpp-full",
         "libssl",
         "libusb",
         "libutils",
diff --git a/SERVICES.TXT b/SERVICES.TXT
index c8d06a3..380f2cf 100644
--- a/SERVICES.TXT
+++ b/SERVICES.TXT
@@ -21,12 +21,14 @@
     the connection is closed
 
 host:track-devices
-    This is a variant of host:devices which doesn't close the
+host:track-devices-proto-binary
+host:track-devices-proto-text
+    These are variants of host:devices which doesn't close the
     connection. Instead, a new device list description is sent
     each time a device is added/removed or the state of a given
-    device changes (hex4 + content). This allows tools like DDMS
-    to track the state of connected devices in real-time without
-    polling the server repeatedly.
+    device changes.
+    Variant [-proto-binary] is binary protobuf format.
+    Variant [-proto-text] is text protobuf format.
 
 host:emulator:<port>
     This is a special query that is sent to the ADB server when a
diff --git a/adb.cpp b/adb.cpp
index bb077a0..f2cf8fc 100644
--- a/adb.cpp
+++ b/adb.cpp
@@ -1326,9 +1326,14 @@
 
     // return a list of all connected devices
     if (service == "devices" || service == "devices-l") {
-        bool long_listing = service == "devices-l";
+        TrackerOutputType output_type;
+        if (service == "devices-l") {
+            output_type = LONG_TEXT;
+        } else {
+            output_type = SHORT_TEXT;
+        }
         D("Getting device list...");
-        std::string device_list = list_transports(long_listing);
+        std::string device_list = list_transports(output_type);
         D("Sending device list...");
         SendOkay(reply_fd, device_list);
         return HostRequestResult::Handled;
diff --git a/client/commandline.cpp b/client/commandline.cpp
index 8d3af7a..781c6d5 100644
--- a/client/commandline.cpp
+++ b/client/commandline.cpp
@@ -2068,10 +2068,22 @@
         TrackAppStreamsCallback callback;
         return adb_connect_command("track-app", nullptr, &callback);
     } else if (!strcmp(argv[0], "track-devices")) {
-        if (argc > 2 || (argc == 2 && strcmp(argv[1], "-l"))) {
-            error_exit("usage: adb track-devices [-l]");
+        const char* listopt;
+        if (argc < 2) {
+            listopt = "";
+        } else {
+            if (!strcmp(argv[1], "-l")) {
+                listopt = argv[1];
+            } else if (!strcmp(argv[1], "--proto-text")) {
+                listopt = "-proto-text";
+            } else if (!strcmp(argv[1], "--proto-binary")) {
+                listopt = "-proto-binary";
+            } else {
+                error_exit("usage: adb track-devices [-l][--proto-text][--proto-binary]");
+            }
         }
-        return adb_connect_command(argc == 2 ? "host:track-devices-l" : "host:track-devices");
+        std::string query = android::base::StringPrintf("host:track-devices%s", listopt);
+        return adb_connect_command(query);
     } else if (!strcmp(argv[0], "raw")) {
         if (argc != 2) {
             error_exit("usage: adb raw SERVICE");
diff --git a/client/usb_libusb.cpp b/client/usb_libusb.cpp
index 2f3a426..4244225 100644
--- a/client/usb_libusb.cpp
+++ b/client/usb_libusb.cpp
@@ -490,6 +490,74 @@
         return serial;
     }
 
+    // libusb gives us an int which is a value from 'enum libusb_speed'
+    static ConnectionSpeed ToConnectionSpeed(int speed) {
+        switch (speed) {
+            case LIBUSB_SPEED_LOW:
+                return USB1_0;
+            case LIBUSB_SPEED_FULL:
+                return USB2_0_FULL;
+            case LIBUSB_SPEED_HIGH:
+                return USB2_0_HIGH;
+            case LIBUSB_SPEED_SUPER:
+                return USB3_0;
+            case LIBUSB_SPEED_SUPER_PLUS:
+                return USB3_1;
+            case LIBUSB_SPEED_UNKNOWN:
+            default:
+                return UNKNOWN;
+        }
+    }
+
+    // libusb gives us a bitfield made of 'enum libusb_supported_speed' values
+    static ConnectionSpeed ExtractMaxSpeed(uint16_t wSpeedSupported) {
+        if (wSpeedSupported == 0) {
+            return UNKNOWN;
+        }
+
+        int msb = 0;
+        while (wSpeedSupported >>= 1) {
+            msb++;
+        }
+
+        switch (1 << msb) {
+            case LIBUSB_LOW_SPEED_OPERATION:
+                return USB1_0;
+            case LIBUSB_FULL_SPEED_OPERATION:
+                return USB2_0_FULL;
+            case LIBUSB_HIGH_SPEED_OPERATION:
+                return USB2_0_HIGH;
+            case LIBUSB_SUPER_SPEED_OPERATION:
+                return USB3_0;
+            default:
+                return UNKNOWN;
+        }
+    }
+
+    void RetrieveSpeeds() {
+        negotiated_speed_ = ToConnectionSpeed(libusb_get_device_speed(device_.get()));
+
+        // The set of supported speed is in a SuperSpeed capability
+        struct libusb_bos_descriptor* bos = nullptr;
+        if (!libusb_get_bos_descriptor(device_handle_.get(), &bos)) {
+            for (int i = 0; i < bos->bNumDeviceCaps; i++) {
+                if (bos->dev_capability[i]->bDevCapabilityType !=
+                    LIBUSB_BT_SS_USB_DEVICE_CAPABILITY) {
+                    continue;
+                }
+
+                libusb_ss_usb_device_capability_descriptor* ss_usb_device_cap = nullptr;
+                int r = libusb_get_ss_usb_device_capability_descriptor(
+                        nullptr, bos->dev_capability[i], &ss_usb_device_cap);
+                if (!r) {
+                    max_speed_ = ExtractMaxSpeed(ss_usb_device_cap->wSpeedSupported);
+                    libusb_free_ss_usb_device_capability_descriptor(ss_usb_device_cap);
+                }
+            }
+            libusb_free_bos_descriptor(bos);
+        }
+    }
+
     bool OpenDevice(std::string* error) {
         if (device_handle_) {
             LOG_ERR(error, "device already open");
@@ -545,6 +613,7 @@
             }
         }
 
+        RetrieveSpeeds();
         return true;
     }
 
@@ -763,6 +832,10 @@
         return connection;
     }
 
+    virtual ConnectionSpeed MaxSpeedMbps() override final { return max_speed_; }
+
+    virtual ConnectionSpeed NegotiatedSpeedMbps() override final { return negotiated_speed_; }
+
     unique_device device_;
     unique_device_handle device_handle_;
     std::string device_address_;
@@ -788,6 +861,9 @@
     std::condition_variable destruction_cv_;
 
     size_t zero_mask_ = 0;
+
+    ConnectionSpeed negotiated_speed_ = UNKNOWN;
+    ConnectionSpeed max_speed_ = UNKNOWN;
 };
 
 static std::mutex usb_handles_mutex [[clang::no_destroy]];
diff --git a/proto/Android.bp b/proto/Android.bp
index fae321f..383e4a7 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -143,3 +143,47 @@
         type: "full",
     },
 }
+
+cc_defaults {
+    name: "libdevices_protos_defaults",
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Wthread-safety",
+        "-Werror",
+    ],
+
+    compile_multilib: "both",
+
+    srcs: [
+        "devices.proto",
+    ],
+    target: {
+        windows: {
+            compile_multilib: "first",
+            enabled: true,
+        },
+    },
+
+    visibility: [
+        "//packages/modules/adb:__subpackages__",
+    ],
+
+    stl: "libc++_static",
+
+    apex_available: [
+        "com.android.adbd",
+        "test_com.android.adbd",
+    ],
+}
+
+cc_library_host_static {
+    name: "libdevices_protos",
+    defaults: ["libdevices_protos_defaults"],
+
+    proto: {
+        export_proto_headers: true,
+        type: "full",
+    },
+}
+
diff --git a/proto/devices.proto b/proto/devices.proto
new file mode 100644
index 0000000..48ebadf
--- /dev/null
+++ b/proto/devices.proto
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+syntax = "proto3";
+
+option java_package = "com.android.server.adb.protos";
+option java_outer_classname = "DevicesProto";
+
+package adb.proto;
+
+// This mirrors adb.h's "enum ConnectionState"
+enum ConnectionState {
+    ANY = 0;
+    CONNECTING = 1;
+    AUTHORIZING = 2;
+    UNAUTHORIZED = 3;
+    NOPERMISSION = 4;
+    DETACHED = 5;
+    OFFLINE = 6;
+    BOOTLOADER = 7;
+    DEVICE = 8;
+    HOST = 9;
+    RECOVERY = 10;
+    SIDELOAD = 11;
+    RESCUE = 12;
+}
+
+enum ConnectionType {
+    UNKNOWN = 0;
+    USB = 1;
+    SOCKET = 2;
+}
+
+message Device {
+    string serial = 1;
+    ConnectionState state = 2;
+    string bus_address = 3;
+    string product = 4;
+    string model = 5;
+    string device = 6;
+    ConnectionType connection_type = 7;
+    int64 negotiated_speed = 8;
+    int64 max_speed = 9;
+}
+
+message Devices {
+    repeated Device device = 1;
+}
diff --git a/services.cpp b/services.cpp
index 0c3d061..2e9499f 100644
--- a/services.cpp
+++ b/services.cpp
@@ -250,9 +250,13 @@
 asocket* host_service_to_socket(std::string_view name, std::string_view serial,
                                 TransportId transport_id) {
     if (name == "track-devices") {
-        return create_device_tracker(false);
+        return create_device_tracker(SHORT_TEXT);
     } else if (name == "track-devices-l") {
-        return create_device_tracker(true);
+        return create_device_tracker(LONG_TEXT);
+    } else if (name == "track-devices-proto-binary") {
+        return create_device_tracker(PROTOBUF);
+    } else if (name == "track-devices-proto-text") {
+        return create_device_tracker(TEXT_PROTOBUF);
     } else if (android::base::ConsumePrefix(&name, "wait-for-")) {
         std::string spec(name);
         unique_fd fd =
diff --git a/sockets.cpp b/sockets.cpp
index 87905cd..e10f710 100644
--- a/sockets.cpp
+++ b/sockets.cpp
@@ -879,7 +879,8 @@
         s2 = host_service_to_socket(service, serial, transport_id);
         if (s2 == nullptr) {
             LOG(VERBOSE) << "SS(" << s->id << "): couldn't create host service '" << service << "'";
-            SendFail(s->peer->fd, "unknown host service");
+            std::string msg = std::string("unknown host service '") + std::string(service) + "'";
+            SendFail(s->peer->fd, msg);
             goto fail;
         }
 
diff --git a/test_device.py b/test_device.py
index c606adf..20cd98d 100755
--- a/test_device.py
+++ b/test_device.py
@@ -19,6 +19,7 @@
 
 import contextlib
 import hashlib
+import io
 import os
 import posixpath
 import random
@@ -35,6 +36,8 @@
 import time
 import unittest
 
+import proto.devices_pb2 as proto_devices
+
 from datetime import datetime
 
 import adb
@@ -1761,15 +1764,107 @@
                 console_output = read_screen(screen)
                 self.assertEqual(unicode_string, console_output)
 
+class DevicesListing(DeviceTest):
+
+    serial = subprocess.check_output(['adb', 'get-serialno']).strip().decode("utf-8")
+    # def get_serial(self):
+    #     return subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip().decode("utf-8")
+
+    def test_devices(self):
+        proc = subprocess.Popen(['adb', 'devices'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+        lines = list(map(lambda b: b.decode("utf-8"), proc.stdout.readlines()))
+        self.assertEqual(len(lines), 3)
+        line = lines[1]
+        self.assertTrue(self.serial in line)
+        self.assertFalse("{" in line)
+        self.assertFalse("}" in line)
+        self.assertTrue("device" in line)
+        self.assertFalse("product" in line)
+        self.assertFalse("transport" in line)
+
+    def test_devices_l(self):
+        proc = subprocess.Popen(['adb', 'devices', '-l'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+        lines = list(map(lambda b: b.decode("utf-8"), proc.stdout.readlines()))
+        self.assertEqual(len(lines), 3)
+        line = lines[1]
+        self.assertTrue(self.serial in line)
+        self.assertFalse("{" in line)
+        self.assertFalse("}" in line)
+        self.assertTrue("device" in line)
+        self.assertTrue("product" in line)
+        self.assertTrue("transport" in line)
+
+    def test_track_devices(self):
+        proc = subprocess.Popen(['adb', 'track-devices'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+        reader = io.TextIOWrapper(proc.stdout, encoding='utf8')
+        output_size = int(reader.read(4), 16)
+        output = reader.read(output_size)
+        self.assertFalse("{" in output)
+        self.assertFalse("}" in output)
+        self.assertTrue(self.serial in output)
+        self.assertTrue("device" in output)
+        self.assertFalse("product" in output)
+        self.assertFalse("transport" in output)
+
+    def test_track_devices_l(self):
+        proc = subprocess.Popen(['adb', 'track-devices', '-l'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+        reader = io.TextIOWrapper(proc.stdout, encoding='utf8')
+        output_size = int(reader.read(4), 16)
+        output = reader.read(output_size)
+        self.assertFalse("{" in output)
+        self.assertFalse("}" in output)
+        self.assertTrue(self.serial in output)
+        self.assertTrue("device" in output)
+        self.assertTrue("product" in output)
+        self.assertTrue("transport" in output)
+
+    def test_track_devices_proto_text(self):
+        proc = subprocess.Popen(['adb', 'track-devices', '--proto-text'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+        reader = io.TextIOWrapper(proc.stdout, encoding='utf8')
+        output_size = int(reader.read(4), 16)
+        output = reader.read(output_size)
+        self.assertTrue("{" in output)
+        self.assertTrue("}" in output)
+        self.assertTrue(self.serial in output)
+        self.assertTrue("device" in output)
+        self.assertTrue("product" in output)
+        self.assertTrue("connection_type" in output)
+
+    def test_track_devices_proto_binary(self):
+        proc = subprocess.Popen(['adb', 'track-devices', '--proto-binary'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+
+        output_size = int(proc.stdout.read(4).decode("utf-8"), 16)
+        proto = proc.stdout.read(output_size)
+
+        devices = proto_devices.Devices()
+        devices.ParseFromString(proto)
+
+        device = devices.device[0]
+        self.assertTrue(device.serial == self.serial)
+        self.assertFalse(device.bus_address == "")
+        self.assertFalse(device.product == "")
+        self.assertFalse(device.model == "")
+        self.assertFalse(device.device == "")
+        self.assertTrue(device.negotiated_speed == int(device.negotiated_speed))
+        self.assertTrue(device.max_speed == int(device.max_speed))
 
 def main():
     random.seed(0)
-    if len(adb.get_devices()) > 0:
-        suite = unittest.TestLoader().loadTestsFromName(__name__)
-        unittest.TextTestRunner(verbosity=3).run(suite)
-    else:
+    if len(adb.get_devices()) == 0:
         print('Test suite must be run with attached devices')
+        return
+
+    # Run only specific test if given on command-line e.g:
+    # ./test_device.py ForwardReverseTest
+    # ./test_device.py ForwardReverseTest.test_forward_no_rebind
+    if len(sys.argv) == 2:
+        test_name = "." + sys.argv[1]
+    else:
+        test_name = ""
+
+    suite = unittest.TestLoader().loadTestsFromName("__main__" + test_name)
+    unittest.TextTestRunner(verbosity=3).run(suite)
 
 
 if __name__ == '__main__':
-    main()
+    main()
\ No newline at end of file
diff --git a/transport.cpp b/transport.cpp
index 5f06f29..71756d3 100644
--- a/transport.cpp
+++ b/transport.cpp
@@ -56,7 +56,9 @@
 #include "sysdeps/chrono.h"
 
 #if ADB_HOST
+#include <google/protobuf/text_format.h>
 #include "client/usb.h"
+#include "devices.pb.h"
 #endif
 
 using namespace adb::crypto;
@@ -95,6 +97,7 @@
 const char* const kFeatureDelayedAck = "delayed_ack";
 // TODO(joshuaduong): Bump to v2 when openscreen discovery is enabled by default
 const char* const kFeatureOpenscreenMdns = "openscreen_mdns";
+const char* const kFeatureDeviceTrackerProtoFormat = "devicetracker_proto_format";
 
 namespace {
 
@@ -602,7 +605,7 @@
 struct device_tracker {
     asocket socket;
     bool update_needed = false;
-    bool long_output = false;
+    TrackerOutputType output_type = SHORT_TEXT;
     device_tracker* next = nullptr;
 };
 
@@ -662,11 +665,11 @@
     // for the first time, even if no update occurred.
     if (tracker->update_needed) {
         tracker->update_needed = false;
-        device_tracker_send(tracker, list_transports(tracker->long_output));
+        device_tracker_send(tracker, list_transports(tracker->output_type));
     }
 }
 
-asocket* create_device_tracker(bool long_output) {
+asocket* create_device_tracker(TrackerOutputType output_type) {
     device_tracker* tracker = new device_tracker();
     if (tracker == nullptr) LOG(FATAL) << "cannot allocate device tracker";
 
@@ -676,7 +679,7 @@
     tracker->socket.ready = device_tracker_ready;
     tracker->socket.close = device_tracker_close;
     tracker->update_needed = true;
-    tracker->long_output = long_output;
+    tracker->output_type = output_type;
 
     tracker->next = device_tracker_list;
     device_tracker_list = tracker;
@@ -709,7 +712,7 @@
     while (tracker != nullptr) {
         device_tracker* next = tracker->next;
         // This may destroy the tracker if the connection is closed.
-        device_tracker_send(tracker, list_transports(tracker->long_output));
+        device_tracker_send(tracker, list_transports(tracker->output_type));
         tracker = next;
     }
 }
@@ -1202,6 +1205,7 @@
             kFeatureSendRecv2Zstd,
             kFeatureSendRecv2DryRunSend,
             kFeatureOpenscreenMdns,
+            kFeatureDeviceTrackerProtoFormat,
         };
         // clang-format on
 
@@ -1316,6 +1320,63 @@
     return str;
 }
 
+static adb::proto::ConnectionState adbStateFromProto(ConnectionState state) {
+    switch (state) {
+        case kCsConnecting:
+            return adb::proto::ConnectionState::CONNECTING;
+        case kCsAuthorizing:
+            return adb::proto::ConnectionState::AUTHORIZING;
+        case kCsUnauthorized:
+            return adb::proto::ConnectionState::UNAUTHORIZED;
+        case kCsNoPerm:
+            return adb::proto::ConnectionState::NOPERMISSION;
+        case kCsDetached:
+            return adb::proto::ConnectionState::DETACHED;
+        case kCsOffline:
+            return adb::proto::ConnectionState::OFFLINE;
+        case kCsBootloader:
+            return adb::proto::ConnectionState::BOOTLOADER;
+        case kCsDevice:
+            return adb::proto::ConnectionState::DEVICE;
+        case kCsHost:
+            return adb::proto::ConnectionState::HOST;
+        case kCsRecovery:
+            return adb::proto::ConnectionState::RECOVERY;
+        case kCsSideload:
+            return adb::proto::ConnectionState::SIDELOAD;
+        case kCsRescue:
+            return adb::proto::ConnectionState::RESCUE;
+        case kCsAny:
+            return adb::proto::ConnectionState::ANY;
+    }
+}
+
+static std::string transportListToProto(const std::list<atransport*>& sorted_transport_list,
+                                        bool text_version) {
+    adb::proto::Devices devices;
+    for (const auto& t : sorted_transport_list) {
+        auto* device = devices.add_device();
+        device->set_serial(t->serial.c_str());
+        device->set_connection_type(t->type == kTransportUsb ? adb::proto::ConnectionType::USB
+                                                             : adb::proto::ConnectionType::SOCKET);
+        device->set_state(adbStateFromProto(t->GetConnectionState()));
+        device->set_bus_address(sanitize(t->devpath, false));
+        device->set_product(sanitize(t->product, false));
+        device->set_model(sanitize(t->model, true));
+        device->set_device(sanitize(t->device, false));
+        device->set_max_speed(t->connection()->MaxSpeedMbps());
+        device->set_negotiated_speed(t->connection()->NegotiatedSpeedMbps());
+    }
+
+    std::string proto;
+    if (text_version) {
+        google::protobuf::TextFormat::PrintToString(devices, &proto);
+    } else {
+        devices.SerializeToString(&proto);
+    }
+    return proto;
+}
+
 static void append_transport_info(std::string* result, const char* key, const std::string& value,
                                   bool alphanumeric) {
     if (value.empty()) {
@@ -1354,7 +1415,16 @@
     *result += '\n';
 }
 
-std::string list_transports(bool long_listing) {
+static std::string transportListToText(const std::list<atransport*>& sorted_transport_list,
+                                       bool long_listing) {
+    std::string result;
+    for (const auto& t : sorted_transport_list) {
+        append_transport(t, &result, long_listing);
+    }
+    return result;
+}
+
+std::string list_transports(TrackerOutputType outputType) {
     std::lock_guard<std::recursive_mutex> lock(transport_lock);
 
     auto sorted_transport_list = transport_list;
@@ -1365,11 +1435,16 @@
         return x->serial < y->serial;
     });
 
-    std::string result;
-    for (const auto& t : sorted_transport_list) {
-        append_transport(t, &result, long_listing);
+    switch (outputType) {
+        case SHORT_TEXT:
+        case LONG_TEXT: {
+            return transportListToText(sorted_transport_list, outputType == LONG_TEXT);
+        }
+        case PROTOBUF:
+        case TEXT_PROTOBUF: {
+            return transportListToProto(sorted_transport_list, outputType == TEXT_PROTOBUF);
+        }
     }
-    return result;
 }
 
 void close_usb_devices(std::function<bool(const atransport*)> predicate, bool reset) {
diff --git a/transport.h b/transport.h
index eb634ed..97391a8 100644
--- a/transport.h
+++ b/transport.h
@@ -136,6 +136,20 @@
     atransport* transport_ = nullptr;
 
     static std::unique_ptr<Connection> FromFd(unique_fd fd);
+
+    enum ConnectionSpeed {
+        UNKNOWN = 0,
+        USB1_0 = 1,
+        USB2_0_FULL = 12,
+        USB2_0_HIGH = 480,
+        USB3_0 = 5000,
+        USB3_1 = 10000,
+        USB3_2 = 20000,
+        USB4_0 = 40000,
+    };
+
+    virtual ConnectionSpeed NegotiatedSpeedMbps() { return UNKNOWN; }
+    virtual ConnectionSpeed MaxSpeedMbps() { return UNKNOWN; }
 };
 
 // Abstraction for a blocking packet transport.
@@ -474,7 +488,6 @@
 
 void init_reconnect_handler(void);
 void init_mdns_transport_discovery(void);
-std::string list_transports(bool long_listing);
 
 #if ADB_HOST
 atransport* find_transport(const char* serial);
@@ -519,7 +532,11 @@
 
 void send_packet(apacket* p, atransport* t);
 
-asocket* create_device_tracker(bool long_output);
+#if ADB_HOST
+enum TrackerOutputType { SHORT_TEXT, LONG_TEXT, PROTOBUF, TEXT_PROTOBUF };
+asocket* create_device_tracker(TrackerOutputType type);
+std::string list_transports(TrackerOutputType type);
+#endif
 
 #if !ADB_HOST
 unique_fd adb_listen(std::string_view addr, std::string* error);