libbinder_ndk: support dump

At the NDK layer:
AIBinder_Class_setOnDump (to implement dump)
AIBinder_dump (to call dump)

At the AIDL layer:
ICInterface::dump(...) to implement and call dump

Bug: 125733384
Test: atest android.binder.cts
Change-Id: I0873ad574bb7a8b9d645c7301fdfa0797e62c834
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index f9c8c8a..bdbc0d3 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -31,6 +31,7 @@
 using ::android::sp;
 using ::android::status_t;
 using ::android::String16;
+using ::android::String8;
 using ::android::wp;
 
 namespace ABBinderTag {
@@ -67,8 +68,6 @@
 AIBinder::~AIBinder() {}
 
 bool AIBinder::associateClass(const AIBinder_Class* clazz) {
-    using ::android::String8;
-
     if (clazz == nullptr) return false;
     if (mClazz == clazz) return true;
 
@@ -119,6 +118,33 @@
     return getClass()->getInterfaceDescriptor();
 }
 
+status_t ABBinder::dump(int fd, const ::android::Vector<String16>& args) {
+    AIBinder_onDump onDump = getClass()->onDump;
+
+    if (onDump == nullptr) {
+        return STATUS_OK;
+    }
+
+    // technically UINT32_MAX would be okay here, but INT32_MAX is expected since this may be
+    // null in Java
+    if (args.size() > INT32_MAX) {
+        LOG(ERROR) << "ABBinder::dump received too many arguments: " << args.size();
+        return STATUS_BAD_VALUE;
+    }
+
+    std::vector<String8> utf8Args;  // owns memory of utf8s
+    utf8Args.reserve(args.size());
+    std::vector<const char*> utf8Pointers;  // what can be passed over NDK API
+    utf8Pointers.reserve(args.size());
+
+    for (size_t i = 0; i < args.size(); i++) {
+        utf8Args.push_back(String8(args[i]));
+        utf8Pointers.push_back(utf8Args[i].c_str());
+    }
+
+    return onDump(this, fd, utf8Pointers.data(), utf8Pointers.size());
+}
+
 status_t ABBinder::onTransact(transaction_code_t code, const Parcel& data, Parcel* reply,
                               binder_flags_t flags) {
     if (isUserCommand(code)) {
@@ -232,6 +258,13 @@
     return new AIBinder_Class(interfaceDescriptor, onCreate, onDestroy, onTransact);
 }
 
+void AIBinder_Class_setOnDump(AIBinder_Class* clazz, AIBinder_onDump onDump) {
+    CHECK(clazz != nullptr) << "setOnDump requires non-null clazz";
+
+    // this is required to be called before instances are instantiated
+    clazz->onDump = onDump;
+}
+
 void AIBinder_DeathRecipient::TransferDeathRecipient::binderDied(const wp<IBinder>& who) {
     CHECK(who == mWho);
 
@@ -325,6 +358,30 @@
     return PruneStatusT(binder->getBinder()->pingBinder());
 }
 
+binder_status_t AIBinder_dump(AIBinder* binder, int fd, const char** args, uint32_t numArgs) {
+    if (binder == nullptr) {
+        return STATUS_UNEXPECTED_NULL;
+    }
+
+    ABBinder* bBinder = binder->asABBinder();
+    if (bBinder != nullptr) {
+        AIBinder_onDump onDump = binder->getClass()->onDump;
+        if (onDump == nullptr) {
+            return STATUS_OK;
+        }
+        return PruneStatusT(onDump(bBinder, fd, args, numArgs));
+    }
+
+    ::android::Vector<String16> utf16Args;
+    utf16Args.setCapacity(numArgs);
+    for (uint32_t i = 0; i < numArgs; i++) {
+        utf16Args.push(String16(String8(args[i])));
+    }
+
+    status_t status = binder->getBinder()->dump(fd, utf16Args);
+    return PruneStatusT(status);
+}
+
 binder_status_t AIBinder_linkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
                                      void* cookie) {
     if (binder == nullptr || recipient == nullptr) {
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index 7852298..0dd795a 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -25,6 +25,7 @@
 
 #include <binder/Binder.h>
 #include <binder/IBinder.h>
+#include <utils/Vector.h>
 
 inline bool isUserCommand(transaction_code_t code) {
     return code >= FIRST_CALL_TRANSACTION && code <= LAST_CALL_TRANSACTION;
@@ -66,6 +67,7 @@
     ABBinder* asABBinder() override { return this; }
 
     const ::android::String16& getInterfaceDescriptor() const override;
+    ::android::status_t dump(int fd, const ::android::Vector<::android::String16>& args) override;
     ::android::status_t onTransact(uint32_t code, const ::android::Parcel& data,
                                    ::android::Parcel* reply, binder_flags_t flags) override;
 
@@ -106,10 +108,14 @@
 
     const ::android::String16& getInterfaceDescriptor() const { return mInterfaceDescriptor; }
 
+    // required to be non-null, implemented for every class
     const AIBinder_Class_onCreate onCreate;
     const AIBinder_Class_onDestroy onDestroy;
     const AIBinder_Class_onTransact onTransact;
 
+    // optional methods for a class
+    AIBinder_onDump onDump;
+
    private:
     // This must be a String16 since BBinder virtual getInterfaceDescriptor returns a reference to
     // one.
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 9c6c55e..bddc10d 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -179,6 +179,31 @@
         __INTRODUCED_IN(29);
 
 /**
+ * Dump information about an AIBinder (usually for debugging).
+ *
+ * When no arguments are provided, a brief overview of the interview should be given.
+ *
+ * \param binder interface being dumped
+ * \param fd file descriptor to be dumped to, should be flushed, ownership is not passed.
+ * \param args array of null-terminated strings for dump (may be null if numArgs is 0)
+ * \param numArgs number of args to be sent
+ *
+ * \return binder_status_t result of transaction (if remote, for instance)
+ */
+typedef binder_status_t (*AIBinder_onDump)(AIBinder* binder, int fd, const char** args,
+                                           uint32_t numArgs);
+
+/**
+ * This sets the implementation of the dump method for a class.
+ *
+ * If this isn't set, nothing will be dumped when dump is called (for instance with
+ * android.os.Binder#dump). Must be called before any instance of the class is created.
+ *
+ * \param dump function to call when an instance of this binder class is being dumped.
+ */
+void AIBinder_Class_setOnDump(AIBinder_Class* clazz, AIBinder_onDump onDump) __INTRODUCED_IN(29);
+
+/**
  * Creates a new binder object of the appropriate class.
  *
  * Ownership of args is passed to this object. The lifecycle is implemented with AIBinder_incStrong
@@ -237,6 +262,21 @@
 binder_status_t AIBinder_ping(AIBinder* binder) __INTRODUCED_IN(29);
 
 /**
+ * Built-in transaction for all binder objects. This dumps information about a given binder.
+ *
+ * See also AIBinder_Class_setOnDump, AIBinder_onDump
+ *
+ * \param binder the binder to dump information about
+ * \param fd where information should be dumped to
+ * \param args null-terminated arguments to pass (may be null if numArgs is 0)
+ * \param numArgs number of args to send
+ *
+ * \return STATUS_OK if dump succeeds (or if there is nothing to dump)
+ */
+binder_status_t AIBinder_dump(AIBinder* binder, int fd, const char** args, uint32_t numArgs)
+        __INTRODUCED_IN(29);
+
+/**
  * Registers for notifications that the associated binder is dead. The same death recipient may be
  * associated with multiple different binders. If the binder is local, then no death recipient will
  * be given (since if the local process dies, then no recipient will exist to recieve a
diff --git a/libs/binder/ndk/include_ndk/android/binder_interface_utils.h b/libs/binder/ndk/include_ndk/android/binder_interface_utils.h
index a42c60b..b82141c 100644
--- a/libs/binder/ndk/include_ndk/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_ndk/android/binder_interface_utils.h
@@ -104,6 +104,30 @@
      * this will be checked using AIBinder_isRemote.
      */
     virtual bool isRemote() = 0;
+
+    /**
+     * Dumps information about the interface.
+     */
+    virtual binder_status_t dump(int /*fd*/, const char** /*args*/, uint32_t /*numArgs*/) {
+        return STATUS_OK;
+    }
+
+    /**
+     * Helper method to create a class
+     */
+    static AIBinder_Class* defineClass(const char* interfaceDescriptor,
+                                       AIBinder_Class_onCreate onCreate,
+                                       AIBinder_Class_onDestroy onDestroy,
+                                       AIBinder_Class_onTransact onTransact,
+                                       AIBinder_onDump onDump = nullptr) {
+        AIBinder_Class* clazz =
+                AIBinder_Class_define(interfaceDescriptor, onCreate, onDestroy, onTransact);
+        if (clazz == nullptr) {
+            return nullptr;
+        }
+        AIBinder_Class_setOnDump(clazz, onDump);
+        return clazz;
+    }
 };
 
 /**
@@ -144,6 +168,10 @@
 
     bool isRemote() override { return AIBinder_isRemote(mBinder.get()); }
 
+    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override {
+        return AIBinder_dump(asBinder().get(), fd, args, numArgs);
+    }
+
    private:
     SpAIBinder mBinder;
 };
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 655f4d5..7e65817 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -2,10 +2,12 @@
   global:
     AIBinder_associateClass;
     AIBinder_Class_define;
+    AIBinder_Class_setOnDump;
     AIBinder_DeathRecipient_delete;
     AIBinder_DeathRecipient_new;
     AIBinder_debugGetRefCount;
     AIBinder_decStrong;
+    AIBinder_dump;
     AIBinder_fromJavaBinder;
     AIBinder_getCallingPid;
     AIBinder_getCallingUid;