Merge "SurfaceComposerClient: Replace sync transactions with commit callbacks"
diff --git a/include/ftl/details/match.h b/include/ftl/details/match.h
new file mode 100644
index 0000000..51b99d2
--- /dev/null
+++ b/include/ftl/details/match.h
@@ -0,0 +1,59 @@
+/*
+ * 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 <type_traits>
+#include <variant>
+
+namespace android::ftl::details {
+
+template <typename... Ms>
+struct Matcher : Ms... {
+  using Ms::operator()...;
+};
+
+// Deduction guide.
+template <typename... Ms>
+Matcher(Ms...) -> Matcher<Ms...>;
+
+template <typename Matcher, typename... Ts>
+constexpr bool is_exhaustive_match_v = (std::is_invocable_v<Matcher, Ts> && ...);
+
+template <typename...>
+struct Match;
+
+template <typename T, typename U, typename... Ts>
+struct Match<T, U, Ts...> {
+  template <typename Variant, typename Matcher>
+  static decltype(auto) match(Variant& variant, const Matcher& matcher) {
+    if (auto* const ptr = std::get_if<T>(&variant)) {
+      return matcher(*ptr);
+    } else {
+      return Match<U, Ts...>::match(variant, matcher);
+    }
+  }
+};
+
+template <typename T>
+struct Match<T> {
+  template <typename Variant, typename Matcher>
+  static decltype(auto) match(Variant& variant, const Matcher& matcher) {
+    return matcher(std::get<T>(variant));
+  }
+};
+
+}  // namespace android::ftl::details
diff --git a/include/ftl/match.h b/include/ftl/match.h
new file mode 100644
index 0000000..7318c45
--- /dev/null
+++ b/include/ftl/match.h
@@ -0,0 +1,62 @@
+/*
+ * 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 <utility>
+#include <variant>
+
+#include <ftl/details/match.h>
+
+namespace android::ftl {
+
+// Concise alternative to std::visit that compiles to branches rather than a dispatch table. For
+// std::variant<T0, ..., TN> where N is small, this is slightly faster since the branches can be
+// inlined unlike the function pointers.
+//
+//   using namespace std::chrono;
+//   std::variant<seconds, minutes, hours> duration = 119min;
+//
+//   // Mutable match.
+//   ftl::match(duration, [](auto& d) { ++d; });
+//
+//   // Immutable match. Exhaustive due to minutes being convertible to seconds.
+//   assert("2 hours"s ==
+//          ftl::match(duration,
+//                     [](const seconds& s) {
+//                       const auto h = duration_cast<hours>(s);
+//                       return std::to_string(h.count()) + " hours"s;
+//                     },
+//                     [](const hours& h) { return std::to_string(h.count() / 24) + " days"s; }));
+//
+template <typename... Ts, typename... Ms>
+decltype(auto) match(std::variant<Ts...>& variant, Ms&&... matchers) {
+  const auto matcher = details::Matcher{std::forward<Ms>(matchers)...};
+  static_assert(details::is_exhaustive_match_v<decltype(matcher), Ts&...>, "Non-exhaustive match");
+
+  return details::Match<Ts...>::match(variant, matcher);
+}
+
+template <typename... Ts, typename... Ms>
+decltype(auto) match(const std::variant<Ts...>& variant, Ms&&... matchers) {
+  const auto matcher = details::Matcher{std::forward<Ms>(matchers)...};
+  static_assert(details::is_exhaustive_match_v<decltype(matcher), const Ts&...>,
+                "Non-exhaustive match");
+
+  return details::Match<Ts...>::match(variant, matcher);
+}
+
+}  // namespace android::ftl
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index bfcf39a..11c8e5d 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -39,7 +39,6 @@
 #include <sys/resource.h>
 #include <unistd.h>
 
-#include "Static.h"
 #include "binder_module.h"
 
 #if LOG_NDEBUG
@@ -124,46 +123,43 @@
         return "unknown";
 }
 
-static const void* printBinderTransactionData(TextOutput& out, const void* data)
-{
+static const void* printBinderTransactionData(std::ostream& out, const void* data) {
     const binder_transaction_data* btd =
         (const binder_transaction_data*)data;
     if (btd->target.handle < 1024) {
         /* want to print descriptors in decimal; guess based on value */
-        out << "target.desc=" << btd->target.handle;
+        out << "\ttarget.desc=" << btd->target.handle;
     } else {
-        out << "target.ptr=" << btd->target.ptr;
+        out << "\ttarget.ptr=" << btd->target.ptr;
     }
-    out << " (cookie " << btd->cookie << ")" << endl
-        << "code=" << TypeCode(btd->code) << ", flags=" << (void*)(uint64_t)btd->flags << endl
-        << "data=" << btd->data.ptr.buffer << " (" << (void*)btd->data_size
-        << " bytes)" << endl
-        << "offsets=" << btd->data.ptr.offsets << " (" << (void*)btd->offsets_size
-        << " bytes)";
+    out << "\t (cookie " << btd->cookie << ")"
+        << "\n"
+        << "\tcode=" << TypeCode(btd->code) << ", flags=" << (void*)(uint64_t)btd->flags << "\n"
+        << "\tdata=" << btd->data.ptr.buffer << " (" << (void*)btd->data_size << " bytes)"
+        << "\n"
+        << "\toffsets=" << btd->data.ptr.offsets << " (" << (void*)btd->offsets_size << " bytes)";
     return btd+1;
 }
 
-static const void* printReturnCommand(TextOutput& out, const void* _cmd)
-{
+static const void* printReturnCommand(std::ostream& out, const void* _cmd) {
     static const size_t N = sizeof(kReturnStrings)/sizeof(kReturnStrings[0]);
     const int32_t* cmd = (const int32_t*)_cmd;
     uint32_t code = (uint32_t)*cmd++;
     size_t cmdIndex = code & 0xff;
     if (code == BR_ERROR) {
-        out << "BR_ERROR: " << (void*)(uint64_t)(*cmd++) << endl;
+        out << "\tBR_ERROR: " << (void*)(uint64_t)(*cmd++) << "\n";
         return cmd;
     } else if (cmdIndex >= N) {
-        out << "Unknown reply: " << code << endl;
+        out << "\tUnknown reply: " << code << "\n";
         return cmd;
     }
-    out << kReturnStrings[cmdIndex];
+    out << "\t" << kReturnStrings[cmdIndex];
 
     switch (code) {
         case BR_TRANSACTION:
         case BR_REPLY: {
-            out << ": " << indent;
-            cmd = (const int32_t *)printBinderTransactionData(out, cmd);
-            out << dedent;
+            out << ": ";
+            cmd = (const int32_t*)printBinderTransactionData(out, cmd);
         } break;
 
         case BR_ACQUIRE_RESULT: {
@@ -200,19 +196,18 @@
             break;
     }
 
-    out << endl;
+    out << "\n";
     return cmd;
 }
 
-static const void* printCommand(TextOutput& out, const void* _cmd)
-{
+static const void* printCommand(std::ostream& out, const void* _cmd) {
     static const size_t N = sizeof(kCommandStrings)/sizeof(kCommandStrings[0]);
     const int32_t* cmd = (const int32_t*)_cmd;
     uint32_t code = (uint32_t)*cmd++;
     size_t cmdIndex = code & 0xff;
 
     if (cmdIndex >= N) {
-        out << "Unknown command: " << code << endl;
+        out << "Unknown command: " << code << "\n";
         return cmd;
     }
     out << kCommandStrings[cmdIndex];
@@ -220,9 +215,8 @@
     switch (code) {
         case BC_TRANSACTION:
         case BC_REPLY: {
-            out << ": " << indent;
-            cmd = (const int32_t *)printBinderTransactionData(out, cmd);
-            out << dedent;
+            out << ": ";
+            cmd = (const int32_t*)printBinderTransactionData(out, cmd);
         } break;
 
         case BC_ACQUIRE_RESULT: {
@@ -274,7 +268,7 @@
             break;
     }
 
-    out << endl;
+    out << "\n";
     return cmd;
 }
 
@@ -548,8 +542,10 @@
         if (IN < sizeof(int32_t)) return result;
         cmd = mIn.readInt32();
         IF_LOG_COMMANDS() {
-            alog << "Processing top-level Command: "
-                 << getReturnString(cmd) << endl;
+            std::ostringstream logStream;
+            logStream << "Processing top-level Command: " << getReturnString(cmd) << "\n";
+            std::string message = logStream.str();
+            ALOGI("%s", message.c_str());
         }
 
         pthread_mutex_lock(&mProcess->mThreadCountLock);
@@ -726,10 +722,11 @@
     flags |= TF_ACCEPT_FDS;
 
     IF_LOG_TRANSACTIONS() {
-        TextOutput::Bundle _b(alog);
-        alog << "BC_TRANSACTION thr " << (void*)pthread_self() << " / hand "
-            << handle << " / code " << TypeCode(code) << ": "
-            << indent << data << dedent << endl;
+        std::ostringstream logStream;
+        logStream << "BC_TRANSACTION thr " << (void*)pthread_self() << " / hand " << handle
+                  << " / code " << TypeCode(code) << ": \t" << data << "\n";
+        std::string message = logStream.str();
+        ALOGI("%s", message.c_str());
     }
 
     LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),
@@ -774,11 +771,15 @@
         #endif
 
         IF_LOG_TRANSACTIONS() {
-            TextOutput::Bundle _b(alog);
-            alog << "BR_REPLY thr " << (void*)pthread_self() << " / hand "
-                << handle << ": ";
-            if (reply) alog << indent << *reply << dedent << endl;
-            else alog << "(none requested)" << endl;
+            std::ostringstream logStream;
+            logStream << "BR_REPLY thr " << (void*)pthread_self() << " / hand " << handle << ": ";
+            if (reply)
+                logStream << "\t" << *reply << "\n";
+            else
+                logStream << "(none requested)"
+                          << "\n";
+            std::string message = logStream.str();
+            ALOGI("%s", message.c_str());
         }
     } else {
         err = waitForResponse(nullptr, nullptr);
@@ -920,8 +921,10 @@
         cmd = (uint32_t)mIn.readInt32();
 
         IF_LOG_COMMANDS() {
-            alog << "Processing waitForResponse Command: "
-                << getReturnString(cmd) << endl;
+            std::ostringstream logStream;
+            logStream << "Processing waitForResponse Command: " << getReturnString(cmd) << "\n";
+            std::string message = logStream.str();
+            ALOGI("%s", message.c_str());
         }
 
         switch (cmd) {
@@ -1033,17 +1036,19 @@
     }
 
     IF_LOG_COMMANDS() {
-        TextOutput::Bundle _b(alog);
+        std::ostringstream logStream;
         if (outAvail != 0) {
-            alog << "Sending commands to driver: " << indent;
+            logStream << "Sending commands to driver: ";
             const void* cmds = (const void*)bwr.write_buffer;
-            const void* end = ((const uint8_t*)cmds)+bwr.write_size;
-            alog << HexDump(cmds, bwr.write_size) << endl;
-            while (cmds < end) cmds = printCommand(alog, cmds);
-            alog << dedent;
+            const void* end = ((const uint8_t*)cmds) + bwr.write_size;
+            logStream << "\t" << HexDump(cmds, bwr.write_size) << "\n";
+            while (cmds < end) cmds = printCommand(logStream, cmds);
         }
-        alog << "Size of receive buffer: " << bwr.read_size
-            << ", needRead: " << needRead << ", doReceive: " << doReceive << endl;
+        logStream << "Size of receive buffer: " << bwr.read_size << ", needRead: " << needRead
+                  << ", doReceive: " << doReceive << "\n";
+
+        std::string message = logStream.str();
+        ALOGI("%s", message.c_str());
     }
 
     // Return immediately if there is nothing to do.
@@ -1054,7 +1059,10 @@
     status_t err;
     do {
         IF_LOG_COMMANDS() {
-            alog << "About to read/write, write size = " << mOut.dataSize() << endl;
+            std::ostringstream logStream;
+            logStream << "About to read/write, write size = " << mOut.dataSize() << "\n";
+            std::string message = logStream.str();
+            ALOGI("%s", message.c_str());
         }
 #if defined(__ANDROID__)
         if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
@@ -1068,14 +1076,20 @@
             err = -EBADF;
         }
         IF_LOG_COMMANDS() {
-            alog << "Finished read/write, write size = " << mOut.dataSize() << endl;
+            std::ostringstream logStream;
+            logStream << "Finished read/write, write size = " << mOut.dataSize() << "\n";
+            std::string message = logStream.str();
+            ALOGI("%s", message.c_str());
         }
     } while (err == -EINTR);
 
     IF_LOG_COMMANDS() {
-        alog << "Our err: " << (void*)(intptr_t)err << ", write consumed: "
-            << bwr.write_consumed << " (of " << mOut.dataSize()
-                        << "), read consumed: " << bwr.read_consumed << endl;
+        std::ostringstream logStream;
+        logStream << "Our err: " << (void*)(intptr_t)err
+                  << ", write consumed: " << bwr.write_consumed << " (of " << mOut.dataSize()
+                  << "), read consumed: " << bwr.read_consumed << "\n";
+        std::string message = logStream.str();
+        ALOGI("%s", message.c_str());
     }
 
     if (err >= NO_ERROR) {
@@ -1096,14 +1110,15 @@
             mIn.setDataPosition(0);
         }
         IF_LOG_COMMANDS() {
-            TextOutput::Bundle _b(alog);
-            alog << "Remaining data size: " << mOut.dataSize() << endl;
-            alog << "Received commands from driver: " << indent;
+            std::ostringstream logStream;
+            logStream << "Remaining data size: " << mOut.dataSize() << "\n";
+            logStream << "Received commands from driver: ";
             const void* cmds = mIn.data();
             const void* end = mIn.data() + mIn.dataSize();
-            alog << HexDump(cmds, mIn.dataSize()) << endl;
-            while (cmds < end) cmds = printReturnCommand(alog, cmds);
-            alog << dedent;
+            logStream << "\t" << HexDump(cmds, mIn.dataSize()) << "\n";
+            while (cmds < end) cmds = printReturnCommand(logStream, cmds);
+            std::string message = logStream.str();
+            ALOGI("%s", message.c_str());
         }
         return NO_ERROR;
     }
@@ -1285,15 +1300,15 @@
             Parcel reply;
             status_t error;
             IF_LOG_TRANSACTIONS() {
-                TextOutput::Bundle _b(alog);
-                alog << "BR_TRANSACTION thr " << (void*)pthread_self()
-                    << " / obj " << tr.target.ptr << " / code "
-                    << TypeCode(tr.code) << ": " << indent << buffer
-                    << dedent << endl
-                    << "Data addr = "
-                    << reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer)
-                    << ", offsets addr="
-                    << reinterpret_cast<const size_t*>(tr.data.ptr.offsets) << endl;
+                std::ostringstream logStream;
+                logStream << "BR_TRANSACTION thr " << (void*)pthread_self() << " / obj "
+                          << tr.target.ptr << " / code " << TypeCode(tr.code) << ": \t" << buffer
+                          << "\n"
+                          << "Data addr = " << reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer)
+                          << ", offsets addr="
+                          << reinterpret_cast<const size_t*>(tr.data.ptr.offsets) << "\n";
+                std::string message = logStream.str();
+                ALOGI("%s", message.c_str());
             }
             if (tr.target.ptr) {
                 // We only have a weak reference on the target object, so we must first try to
@@ -1329,21 +1344,21 @@
                 sendReply(reply, (tr.flags & kForwardReplyFlags));
             } else {
                 if (error != OK) {
-                    alog << "oneway function results for code " << tr.code
-                         << " on binder at "
-                         << reinterpret_cast<void*>(tr.target.ptr)
-                         << " will be dropped but finished with status "
-                         << statusToString(error);
+                    std::ostringstream logStream;
+                    logStream << "oneway function results for code " << tr.code << " on binder at "
+                              << reinterpret_cast<void*>(tr.target.ptr)
+                              << " will be dropped but finished with status "
+                              << statusToString(error);
 
                     // ideally we could log this even when error == OK, but it
                     // causes too much logspam because some manually-written
                     // interfaces have clients that call methods which always
                     // write results, sometimes as oneway methods.
                     if (reply.dataSize() != 0) {
-                         alog << " and reply parcel size " << reply.dataSize();
+                        logStream << " and reply parcel size " << reply.dataSize();
                     }
-
-                    alog << endl;
+                    std::string message = logStream.str();
+                    ALOGI("%s", message.c_str());
                 }
                 LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
             }
@@ -1358,9 +1373,11 @@
             mPropagateWorkSource = origPropagateWorkSet;
 
             IF_LOG_TRANSACTIONS() {
-                TextOutput::Bundle _b(alog);
-                alog << "BC_REPLY thr " << (void*)pthread_self() << " / obj "
-                    << tr.target.ptr << ": " << indent << reply << dedent << endl;
+                std::ostringstream logStream;
+                logStream << "BC_REPLY thr " << (void*)pthread_self() << " / obj " << tr.target.ptr
+                          << ": \t" << reply << "\n";
+                std::string message = logStream.str();
+                ALOGI("%s", message.c_str());
             }
 
         }
@@ -1481,7 +1498,10 @@
                                 const binder_size_t* /*objects*/, size_t /*objectsSize*/) {
     //ALOGI("Freeing parcel %p", &parcel);
     IF_LOG_COMMANDS() {
-        alog << "Writing BC_FREE_BUFFER for " << data << endl;
+        std::ostringstream logStream;
+        logStream << "Writing BC_FREE_BUFFER for " << data << "\n";
+        std::string message = logStream.str();
+        ALOGI("%s", message.c_str());
     }
     ALOG_ASSERT(data != NULL, "Called with NULL data");
     IPCThreadState* state = self();
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 8887572..8333298 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -2639,8 +2639,7 @@
     return OK;
 }
 
-void Parcel::print(TextOutput& to, uint32_t /*flags*/) const
-{
+void Parcel::print(std::ostream& to, uint32_t /*flags*/) const {
     to << "Parcel(";
 
     if (errorCheck() != NO_ERROR) {
@@ -2648,7 +2647,7 @@
         to << "Error: " << (void*)(intptr_t)err << " \"" << strerror(-err) << "\"";
     } else if (dataSize() > 0) {
         const uint8_t* DATA = data();
-        to << indent << HexDump(DATA, dataSize()) << dedent;
+        to << "\t" << HexDump(DATA, dataSize());
 #ifdef BINDER_WITH_KERNEL_IPC
         if (const auto* kernelFields = maybeKernelFields()) {
             const binder_size_t* OBJS = kernelFields->mObjects;
@@ -2656,8 +2655,7 @@
             for (size_t i = 0; i < N; i++) {
                 const flat_binder_object* flat =
                         reinterpret_cast<const flat_binder_object*>(DATA + OBJS[i]);
-                to << endl
-                   << "Object #" << i << " @ " << (void*)OBJS[i] << ": "
+                to << "Object #" << i << " @ " << (void*)OBJS[i] << ": "
                    << TypeCode(flat->hdr.type & 0x7f7f7f00) << " = " << flat->binder;
             }
         }
diff --git a/libs/binder/TextOutput.cpp b/libs/binder/TextOutput.cpp
index a0ade50..5dd1f90 100644
--- a/libs/binder/TextOutput.cpp
+++ b/libs/binder/TextOutput.cpp
@@ -39,11 +39,10 @@
 
 static void textOutputPrinter(void* cookie, const char* txt)
 {
-    ((TextOutput*)cookie)->print(txt, strlen(txt));
+    ((std::ostream*)cookie)->write(txt, strlen(txt));
 }
 
-TextOutput& operator<<(TextOutput& to, const TypeCode& val)
-{
+std::ostream& operator<<(std::ostream& to, const TypeCode& val) {
     printTypeCode(val.typeCode(), textOutputPrinter, (void*)&to);
     return to;
 }
@@ -61,8 +60,7 @@
     else mAlignment = 1;
 }
 
-TextOutput& operator<<(TextOutput& to, const HexDump& val)
-{
+std::ostream& operator<<(std::ostream& to, const HexDump& val) {
     printHexData(0, val.buffer(), val.size(), val.bytesPerLine(),
         val.singleLineCutoff(), val.alignment(), val.carrayStyle(),
         textOutputPrinter, (void*)&to);
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 5469239..6de6ce8 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -595,7 +595,7 @@
     // uid.
     uid_t               readCallingWorkSourceUid() const;
 
-    void                print(TextOutput& to, uint32_t flags = 0) const;
+    void print(std::ostream& to, uint32_t flags = 0) const;
 
 private:
     // `objects` and `objectsSize` always 0 for RPC Parcels.
@@ -1594,8 +1594,7 @@
 
 // ---------------------------------------------------------------------------
 
-inline TextOutput& operator<<(TextOutput& to, const Parcel& parcel)
-{
+inline std::ostream& operator<<(std::ostream& to, const Parcel& parcel) {
     parcel.print(to);
     return to;
 }
diff --git a/libs/binder/include/binder/TextOutput.h b/libs/binder/include/binder/TextOutput.h
index bf9c92b..eb98042 100644
--- a/libs/binder/include/binder/TextOutput.h
+++ b/libs/binder/include/binder/TextOutput.h
@@ -94,7 +94,7 @@
     uint32_t mCode;
 };
 
-TextOutput& operator<<(TextOutput& to, const TypeCode& val);
+std::ostream& operator<<(std::ostream& to, const TypeCode& val);
 
 class HexDump
 {
@@ -123,7 +123,7 @@
     bool mCArrayStyle;
 };
 
-TextOutput& operator<<(TextOutput& to, const HexDump& val);
+std::ostream& operator<<(std::ostream& to, const HexDump& val);
 inline TextOutput& operator<<(TextOutput& to,
                               decltype(std::endl<char,
                                        std::char_traits<char>>)
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index a25a493..c1945fd 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -21,6 +21,7 @@
         "fake_guard_test.cpp",
         "flags_test.cpp",
         "future_test.cpp",
+        "match_test.cpp",
         "optional_test.cpp",
         "small_map_test.cpp",
         "small_vector_test.cpp",
diff --git a/libs/ftl/match_test.cpp b/libs/ftl/match_test.cpp
new file mode 100644
index 0000000..a6cff2e
--- /dev/null
+++ b/libs/ftl/match_test.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#include <ftl/match.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <string>
+#include <variant>
+
+namespace android::test {
+
+// Keep in sync with example usage in header file.
+TEST(Match, Example) {
+  using namespace std::chrono;
+  using namespace std::chrono_literals;
+  using namespace std::string_literals;
+
+  std::variant<seconds, minutes, hours> duration = 119min;
+
+  // Mutable match.
+  ftl::match(duration, [](auto& d) { ++d; });
+
+  // Immutable match. Exhaustive due to minutes being convertible to seconds.
+  EXPECT_EQ("2 hours"s,
+            ftl::match(
+                duration,
+                [](const seconds& s) {
+                  const auto h = duration_cast<hours>(s);
+                  return std::to_string(h.count()) + " hours"s;
+                },
+                [](const hours& h) { return std::to_string(h.count() / 24) + " days"s; }));
+}
+
+}  // namespace android::test
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 8b00b5c..e48012b 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -223,10 +223,10 @@
     return events;
 }
 
-static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy,
-                                    const std::vector<PlanarMotionEventEntry>& motions,
-                                    int32_t axis, float targetVelocity,
-                                    uint32_t pointerId = DEFAULT_POINTER_ID) {
+static std::optional<float> computePlanarVelocity(
+        const VelocityTracker::Strategy strategy,
+        const std::vector<PlanarMotionEventEntry>& motions, int32_t axis,
+        uint32_t pointerId = DEFAULT_POINTER_ID) {
     VelocityTracker vt(strategy);
 
     std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
@@ -234,21 +234,45 @@
         vt.addMovement(&event);
     }
 
-    checkVelocity(vt.getVelocity(axis, pointerId).value_or(0), targetVelocity);
+    return vt.getVelocity(axis, pointerId);
+}
+
+static std::vector<MotionEvent> createMotionEventStream(
+        int32_t axis, const std::vector<std::pair<std::chrono::nanoseconds, float>>& motion) {
+    switch (axis) {
+        case AMOTION_EVENT_AXIS_SCROLL:
+            return createAxisScrollMotionEventStream(motion);
+        default:
+            ADD_FAILURE() << "Axis " << axis << " is not supported";
+            return {};
+    }
+}
+
+static std::optional<float> computeVelocity(
+        const VelocityTracker::Strategy strategy,
+        const std::vector<std::pair<std::chrono::nanoseconds, float>>& motions, int32_t axis) {
+    VelocityTracker vt(strategy);
+
+    for (const MotionEvent& event : createMotionEventStream(axis, motions)) {
+        vt.addMovement(&event);
+    }
+
+    return vt.getVelocity(axis, DEFAULT_POINTER_ID);
+}
+
+static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy,
+                                    const std::vector<PlanarMotionEventEntry>& motions,
+                                    int32_t axis, float targetVelocity,
+                                    uint32_t pointerId = DEFAULT_POINTER_ID) {
+    checkVelocity(computePlanarVelocity(strategy, motions, axis, pointerId).value_or(0),
+                  targetVelocity);
 }
 
 static void computeAndCheckAxisScrollVelocity(
         const VelocityTracker::Strategy strategy,
         const std::vector<std::pair<std::chrono::nanoseconds, float>>& motions,
         std::optional<float> targetVelocity) {
-    VelocityTracker vt(strategy);
-
-    std::vector<MotionEvent> events = createAxisScrollMotionEventStream(motions);
-    for (const MotionEvent& event : events) {
-        vt.addMovement(&event);
-    }
-
-    std::optional<float> velocity = vt.getVelocity(AMOTION_EVENT_AXIS_SCROLL, DEFAULT_POINTER_ID);
+    std::optional<float> velocity = computeVelocity(strategy, motions, AMOTION_EVENT_AXIS_SCROLL);
     if (velocity && !targetVelocity) {
         FAIL() << "Expected no velocity, but found " << *velocity;
     }
@@ -280,6 +304,20 @@
 /*
  * ================== VelocityTracker tests generated manually =====================================
  */
+TEST_F(VelocityTrackerTest, TestDefaultStrategiesForPlanarAxes) {
+    std::vector<PlanarMotionEventEntry> motions = {{10ms, {{2, 4}}},
+                                                   {20ms, {{4, 12}}},
+                                                   {30ms, {{6, 20}}},
+                                                   {40ms, {{10, 30}}}};
+
+    EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X),
+              computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions,
+                                    AMOTION_EVENT_AXIS_X));
+    EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y),
+              computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions,
+                                    AMOTION_EVENT_AXIS_Y));
+}
+
 TEST_F(VelocityTrackerTest, TestComputedVelocity) {
     VelocityTracker::ComputedVelocity computedVelocity;
 
@@ -1227,6 +1265,20 @@
 }
 
 // ------------------------------- Hand generated test cases ---------------------------------------
+TEST_F(VelocityTrackerTest, TestDefaultStrategyForAxisScroll) {
+    std::vector<std::pair<std::chrono::nanoseconds, float>> motions = {
+            {10ms, 20},
+            {20ms, 25},
+            {30ms, 50},
+            {40ms, 100},
+    };
+
+    EXPECT_EQ(computeVelocity(VelocityTracker::Strategy::IMPULSE, motions,
+                              AMOTION_EVENT_AXIS_SCROLL),
+              computeVelocity(VelocityTracker::Strategy::DEFAULT, motions,
+                              AMOTION_EVENT_AXIS_SCROLL));
+}
+
 TEST_F(VelocityTrackerTest, AxisScrollVelocity_SimilarDifferentialValues) {
     std::vector<std::pair<std::chrono::nanoseconds, float>> motions = {{1ns, 2.12},  {3ns, 2.12},
                                                                        {7ns, 2.12},  {8ns, 2.12},
diff --git a/libs/ui/DeviceProductInfo.cpp b/libs/ui/DeviceProductInfo.cpp
index 3306012..6ae27de 100644
--- a/libs/ui/DeviceProductInfo.cpp
+++ b/libs/ui/DeviceProductInfo.cpp
@@ -14,44 +14,43 @@
  * limitations under the License.
  */
 
+#include <ftl/match.h>
 #include <ui/DeviceProductInfo.h>
 
 #include <android-base/stringprintf.h>
-#include <utils/Log.h>
-
-#define RETURN_IF_ERROR(op) \
-    if (const status_t status = (op); status != OK) return status;
 
 namespace android {
 
-using base::StringAppendF;
+std::string to_string(const DeviceProductInfo& info) {
+    using base::StringAppendF;
 
-void DeviceProductInfo::dump(std::string& result) const {
-    StringAppendF(&result, "{name=\"%s\", ", name.c_str());
-    StringAppendF(&result, "manufacturerPnpId=%s, ", manufacturerPnpId.data());
-    StringAppendF(&result, "productId=%s, ", productId.c_str());
+    std::string result;
+    StringAppendF(&result, "{name=\"%s\", ", info.name.c_str());
+    StringAppendF(&result, "manufacturerPnpId=%s, ", info.manufacturerPnpId.data());
+    StringAppendF(&result, "productId=%s, ", info.productId.c_str());
 
-    if (const auto* model = std::get_if<ModelYear>(&manufactureOrModelDate)) {
-        StringAppendF(&result, "modelYear=%u, ", model->year);
-    } else if (const auto* manufactureWeekAndYear =
-                       std::get_if<ManufactureWeekAndYear>(&manufactureOrModelDate)) {
-        StringAppendF(&result, "manufactureWeek=%u, ", manufactureWeekAndYear->week);
-        StringAppendF(&result, "manufactureYear=%d, ", manufactureWeekAndYear->year);
-    } else if (const auto* manufactureYear =
-                       std::get_if<ManufactureYear>(&manufactureOrModelDate)) {
-        StringAppendF(&result, "manufactureYear=%d, ", manufactureYear->year);
-    } else {
-        ALOGE("Unknown alternative for variant DeviceProductInfo::ManufactureOrModelDate");
-    }
+    ftl::match(
+            info.manufactureOrModelDate,
+            [&](DeviceProductInfo::ModelYear model) {
+                StringAppendF(&result, "modelYear=%u, ", model.year);
+            },
+            [&](DeviceProductInfo::ManufactureWeekAndYear manufacture) {
+                StringAppendF(&result, "manufactureWeek=%u, ", manufacture.week);
+                StringAppendF(&result, "manufactureYear=%d, ", manufacture.year);
+            },
+            [&](DeviceProductInfo::ManufactureYear manufacture) {
+                StringAppendF(&result, "manufactureYear=%d, ", manufacture.year);
+            });
 
     result.append("relativeAddress=[");
-    for (size_t i = 0; i < relativeAddress.size(); i++) {
+    for (size_t i = 0; i < info.relativeAddress.size(); i++) {
         if (i != 0) {
             result.append(", ");
         }
-        StringAppendF(&result, "%u", relativeAddress[i]);
+        StringAppendF(&result, "%u", info.relativeAddress[i]);
     }
     result.append("]}");
+    return result;
 }
 
 } // namespace android
diff --git a/libs/ui/include/ui/DeviceProductInfo.h b/libs/ui/include/ui/DeviceProductInfo.h
index 879e46f..4229cf1 100644
--- a/libs/ui/include/ui/DeviceProductInfo.h
+++ b/libs/ui/include/ui/DeviceProductInfo.h
@@ -61,8 +61,8 @@
     // address is unavailable.
     // For example, for HDMI connected device this will be the physical address.
     std::vector<uint8_t> relativeAddress;
-
-    void dump(std::string& result) const;
 };
 
+std::string to_string(const DeviceProductInfo&);
+
 } // namespace android
diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp
index d38f2ef..f1122fd 100644
--- a/opengl/libs/EGL/egl_angle_platform.cpp
+++ b/opengl/libs/EGL/egl_angle_platform.cpp
@@ -68,9 +68,9 @@
 
 static TraceEventHandle addTraceEvent(
         PlatformMethods* /**platform*/, char phase, const unsigned char* /*category_group_enabled*/,
-        const char* name, unsigned long long /*id*/, double /*timestamp*/, int /*num_args*/,
-        const char** /*arg_names*/, const unsigned char* /*arg_types*/,
-        const unsigned long long* /*arg_values*/, unsigned char /*flags*/) {
+        const char* name, unsigned long long /*id*/, double /*timestamp*/, int num_args,
+        const char** arg_names, const unsigned char* /*arg_types*/,
+        const unsigned long long* arg_values, unsigned char /*flags*/) {
     switch (phase) {
         case 'B': {
             ATRACE_BEGIN(name);
@@ -84,6 +84,13 @@
             ATRACE_NAME(name);
             break;
         }
+       case 'C': {
+             for(int i=0; i<num_args ; i++)
+             {
+                 ATRACE_INT(arg_names[i],arg_values[i]);
+             }
+             break;
+        }
         default:
             // Could handle other event types here
             break;
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 f8115eb..a57af09 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -328,17 +328,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 510df81..e155ca1 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 {
 
@@ -246,7 +247,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 7d3fc98..950d42a 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>
@@ -5101,18 +5102,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