SF: Add utility class for dumpsys formatting

The Dumper class automates stringifying (a minimal set of types for now)
and indenting.

Bug: 241285876
Test: dumpsys SurfaceFlinger --displays
Change-Id: I5581cd9cb4235e2c710e303b6ab634847554cc30
diff --git a/services/surfaceflinger/Display/DisplaySnapshot.cpp b/services/surfaceflinger/Display/DisplaySnapshot.cpp
index b4f104a..fca3a3d 100644
--- a/services/surfaceflinger/Display/DisplaySnapshot.cpp
+++ b/services/surfaceflinger/Display/DisplaySnapshot.cpp
@@ -41,18 +41,11 @@
             .transform(&ftl::to_key<DisplayModes>);
 }
 
-void DisplaySnapshot::dump(std::string& out) const {
-    using namespace std::string_literals;
+void DisplaySnapshot::dump(utils::Dumper& dumper) const {
+    using namespace std::string_view_literals;
 
-    out += "   connectionType="s;
-    out += ftl::enum_string(mConnectionType);
-
-    out += "\n   deviceProductInfo="s;
-    if (mDeviceProductInfo) {
-        mDeviceProductInfo->dump(out);
-    } else {
-        out += "{}"s;
-    }
+    dumper.dump("connectionType"sv, ftl::enum_string(mConnectionType));
+    dumper.dump("deviceProductInfo"sv, mDeviceProductInfo);
 }
 
 } // namespace android::display
diff --git a/services/surfaceflinger/Display/DisplaySnapshot.h b/services/surfaceflinger/Display/DisplaySnapshot.h
index 0279220..3f34e39 100644
--- a/services/surfaceflinger/Display/DisplaySnapshot.h
+++ b/services/surfaceflinger/Display/DisplaySnapshot.h
@@ -17,12 +17,12 @@
 #pragma once
 
 #include <optional>
-#include <string>
 
 #include <ui/DisplayId.h>
 #include <ui/StaticDisplayInfo.h>
 
-#include "../DisplayHardware/DisplayMode.h"
+#include "DisplayHardware/DisplayMode.h"
+#include "Utils/Dumper.h"
 
 namespace android::display {
 
@@ -43,7 +43,7 @@
     const auto& displayModes() const { return mDisplayModes; }
     const auto& deviceProductInfo() const { return mDeviceProductInfo; }
 
-    void dump(std::string&) const;
+    void dump(utils::Dumper&) const;
 
 private:
     const PhysicalDisplayId mDisplayId;
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index a25296c..eeab0cd 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -338,17 +338,16 @@
     return name + ", \""s + mDisplayName + "\")"s;
 }
 
-void DisplayDevice::dump(std::string& result) const {
-    using namespace std::string_literals;
+void DisplayDevice::dump(utils::Dumper& dumper) const {
+    using namespace std::string_view_literals;
 
-    result += getDebugName();
+    dumper.dump({}, getDebugName());
 
-    result += "\n   powerMode="s;
-    result += mPowerMode.has_value() ? to_string(mPowerMode.value()) : "OFF(reset)";
-    result += '\n';
+    utils::Dumper::Indent indent(dumper);
+    dumper.dump("powerMode"sv, mPowerMode);
 
     if (mRefreshRateConfigs) {
-        mRefreshRateConfigs->dump(result);
+        mRefreshRateConfigs->dump(dumper);
     }
 }
 
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 0f52aff..1de8147 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -47,6 +47,7 @@
 #include "Scheduler/RefreshRateConfigs.h"
 #include "ThreadContext.h"
 #include "TracedOrdinal.h"
+#include "Utils/Dumper.h"
 
 namespace android {
 
@@ -248,7 +249,7 @@
      * Debugging
      */
     std::string getDebugName() const;
-    void dump(std::string& result) const;
+    void dump(utils::Dumper&) const;
 
 private:
     const sp<SurfaceFlinger> mFlinger;
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 4737034..33a7bca 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -177,6 +177,9 @@
     return to_string(static_cast<hardware::graphics::composer::hal::V2_4::Error>(error));
 }
 
+// For utils::Dumper ADL.
+namespace hardware::graphics::composer::V2_2 {
+
 inline std::string to_string(hardware::graphics::composer::hal::PowerMode mode) {
     switch (mode) {
         case hardware::graphics::composer::hal::PowerMode::OFF:
@@ -194,6 +197,8 @@
     }
 }
 
+} // namespace hardware::graphics::composer::V2_2
+
 inline std::string to_string(hardware::graphics::composer::hal::Vsync vsync) {
     switch (vsync) {
         case hardware::graphics::composer::hal::Vsync::ENABLE:
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 3cb052c..fb50588 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -1037,47 +1037,45 @@
             isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef));
 }
 
-void RefreshRateConfigs::dump(std::string& result) const {
-    using namespace std::string_literals;
+void RefreshRateConfigs::dump(utils::Dumper& dumper) const {
+    using namespace std::string_view_literals;
 
     std::lock_guard lock(mLock);
 
     const auto activeModeId = getActiveModeItLocked()->first;
-    result += "   activeModeId="s;
-    result += std::to_string(activeModeId.value());
+    dumper.dump("activeModeId"sv, std::to_string(activeModeId.value()));
 
-    result += "\n   displayModes=\n"s;
-    for (const auto& [id, mode] : mDisplayModes) {
-        result += "      "s;
-        result += to_string(*mode);
-        result += '\n';
+    dumper.dump("displayModes"sv);
+    {
+        utils::Dumper::Indent indent(dumper);
+        for (const auto& [id, mode] : mDisplayModes) {
+            dumper.dump({}, to_string(*mode));
+        }
     }
 
-    base::StringAppendF(&result, "   displayManagerPolicy=%s\n",
-                        mDisplayManagerPolicy.toString().c_str());
+    dumper.dump("displayManagerPolicy"sv, mDisplayManagerPolicy.toString());
 
     if (const Policy& currentPolicy = *getCurrentPolicyLocked();
         mOverridePolicy && currentPolicy != mDisplayManagerPolicy) {
-        base::StringAppendF(&result, "   overridePolicy=%s\n", currentPolicy.toString().c_str());
+        dumper.dump("overridePolicy"sv, currentPolicy.toString());
     }
 
-    base::StringAppendF(&result, "   supportsFrameRateOverrideByContent=%s\n",
-                        mSupportsFrameRateOverrideByContent ? "true" : "false");
+    dumper.dump("supportsFrameRateOverrideByContent"sv, mSupportsFrameRateOverrideByContent);
 
-    result += "   idleTimer="s;
+    std::string idleTimer;
     if (mIdleTimer) {
-        result += mIdleTimer->dump();
+        idleTimer = mIdleTimer->dump();
     } else {
-        result += "off"s;
+        idleTimer = "off"sv;
     }
 
     if (const auto controller = mConfig.kernelIdleTimerController) {
-        base::StringAppendF(&result, " (kernel via %s)", ftl::enum_string(*controller).c_str());
+        base::StringAppendF(&idleTimer, " (kernel via %s)", ftl::enum_string(*controller).c_str());
     } else {
-        result += " (platform)"s;
+        idleTimer += " (platform)"sv;
     }
 
-    result += '\n';
+    dumper.dump("idleTimer"sv, idleTimer);
 }
 
 std::chrono::milliseconds RefreshRateConfigs::getIdleTimerTimeout() {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 0642fcb..8b89104 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -32,6 +32,7 @@
 #include "Scheduler/OneShotTimer.h"
 #include "Scheduler/StrongTyping.h"
 #include "ThreadContext.h"
+#include "Utils/Dumper.h"
 
 namespace android::scheduler {
 
@@ -336,7 +337,7 @@
         mIdleTimer->reset();
     }
 
-    void dump(std::string& result) const EXCLUDES(mLock);
+    void dump(utils::Dumper&) const EXCLUDES(mLock);
 
     std::chrono::milliseconds getIdleTimerTimeout();
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f3551ae..896eebf 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -142,6 +142,7 @@
 #include "SurfaceInterceptor.h"
 #include "TimeStats/TimeStats.h"
 #include "TunnelModeEnabledReporter.h"
+#include "Utils/Dumper.h"
 #include "WindowInfosListenerInvoker.h"
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
@@ -5077,18 +5078,22 @@
 }
 
 void SurfaceFlinger::dumpDisplays(std::string& result) const {
+    utils::Dumper dumper{result};
+
     for (const auto& [id, display] : mPhysicalDisplays) {
         if (const auto device = getDisplayDeviceLocked(id)) {
-            device->dump(result);
+            device->dump(dumper);
         }
-        display.snapshot().dump(result);
-        result += '\n';
+
+        utils::Dumper::Indent indent(dumper);
+        display.snapshot().dump(dumper);
+        dumper.eol();
     }
 
     for (const auto& [token, display] : mDisplays) {
         if (display->isVirtual()) {
-            display->dump(result);
-            result += '\n';
+            display->dump(dumper);
+            dumper.eol();
         }
     }
 }
diff --git a/services/surfaceflinger/Utils/Dumper.h b/services/surfaceflinger/Utils/Dumper.h
new file mode 100644
index 0000000..3761f9e
--- /dev/null
+++ b/services/surfaceflinger/Utils/Dumper.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2022 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 <optional>
+#include <string>
+#include <string_view>
+
+namespace android::utils {
+
+// Dumps variables by appending their name and value to the output string. A variable is formatted
+// as "name=value". If the name or value is empty, the format is "value" or "name=", respectively.
+// A value of user-defined type T is stringified via `std::string to_string(const T&)`, which must
+// be defined in the same namespace as T per the rules of ADL (argument-dependent lookup).
+//
+// TODO(b/249828573): Consolidate with <compositionengine/impl/DumpHelpers.h>
+class Dumper {
+public:
+    explicit Dumper(std::string& out) : mOut(out) {}
+
+    void eol() { mOut += '\n'; }
+
+    void dump(std::string_view name, std::string_view value = {}) {
+        using namespace std::string_view_literals;
+
+        for (int i = mIndent; i-- > 0;) mOut += "    "sv;
+        mOut += name;
+        if (!name.empty()) mOut += '=';
+        mOut += value;
+        eol();
+    }
+
+    void dump(std::string_view name, bool value) {
+        using namespace std::string_view_literals;
+        dump(name, value ? "true"sv : "false"sv);
+    }
+
+    template <typename T>
+    void dump(std::string_view name, const std::optional<T>& value) {
+        using namespace std::string_view_literals;
+        using std::to_string;
+        dump(name, value ? to_string(*value) : "nullopt"sv);
+    }
+
+    struct Indent {
+        explicit Indent(Dumper& dumper) : dumper(dumper) { dumper.mIndent++; }
+        ~Indent() { dumper.mIndent--; }
+
+        Dumper& dumper;
+    };
+
+private:
+    std::string& mOut;
+    int mIndent = 0;
+};
+
+} // namespace android::utils