Merge "Convert Gatekeeper from HIDL to AIDL"
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 15b5813..5da1b40 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -98,6 +98,7 @@
         "libbase_headers",
         "libdebuggerd_common_headers",
         "bionic_libc_platform_headers",
+        "gwp_asan_headers",
     ],
 
     whole_static_libs: [
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 70f333e..cbb1181 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -265,10 +265,12 @@
 }
 
 static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
-                          std::unique_ptr<unwindstack::Regs>* regs, ProcessInfo* process_info) {
+                          std::unique_ptr<unwindstack::Regs>* regs, ProcessInfo* process_info,
+                          bool* recoverable_gwp_asan_crash) {
   std::aligned_storage<sizeof(CrashInfo) + 1, alignof(CrashInfo)>::type buf;
   CrashInfo* crash_info = reinterpret_cast<CrashInfo*>(&buf);
   ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), &buf, sizeof(buf)));
+  *recoverable_gwp_asan_crash = false;
   if (rc == -1) {
     PLOG(FATAL) << "failed to read target ucontext";
   } else {
@@ -304,6 +306,7 @@
       process_info->scudo_region_info = crash_info->data.d.scudo_region_info;
       process_info->scudo_ring_buffer = crash_info->data.d.scudo_ring_buffer;
       process_info->scudo_ring_buffer_size = crash_info->data.d.scudo_ring_buffer_size;
+      *recoverable_gwp_asan_crash = crash_info->data.d.recoverable_gwp_asan_crash;
       FALLTHROUGH_INTENDED;
     case 1:
     case 2:
@@ -468,6 +471,7 @@
   std::map<pid_t, ThreadInfo> thread_info;
   siginfo_t siginfo;
   std::string error;
+  bool recoverable_gwp_asan_crash = false;
 
   {
     ATRACE_NAME("ptrace");
@@ -519,7 +523,8 @@
 
       if (thread == g_target_thread) {
         // Read the thread's registers along with the rest of the crash info out of the pipe.
-        ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info);
+        ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info,
+                      &recoverable_gwp_asan_crash);
         info.siginfo = &siginfo;
         info.signo = info.siginfo->si_signo;
 
@@ -646,7 +651,7 @@
     }
   }
 
-  if (fatal_signal) {
+  if (fatal_signal && !recoverable_gwp_asan_crash) {
     // Don't try to notify ActivityManager if it just crashed, or we might hang until timeout.
     if (thread_info[target_process].thread_name != "system_server") {
       activity_manager_notify(target_process, signo, amfd_data);
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 9c1b136..895c111 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -36,6 +36,7 @@
 #include <string>
 #include <thread>
 
+#include <android/dlext.h>
 #include <android/fdsan.h>
 #include <android/set_abort_message.h>
 #include <bionic/malloc.h>
@@ -64,6 +65,7 @@
 
 #include "crash_test.h"
 #include "debuggerd/handler.h"
+#include "gtest/gtest.h"
 #include "libdebuggerd/utility.h"
 #include "protocol.h"
 #include "tombstoned/tombstoned.h"
@@ -110,19 +112,6 @@
   ASSERT_MATCH(result,                             \
                R"(#\d\d pc [0-9a-f]+\s+ \S+ (\(offset 0x[0-9a-f]+\) )?\()" frame_name R"(\+)");
 
-// Enable GWP-ASan at the start of this process. GWP-ASan is enabled using
-// process sampling, so we need to ensure we force GWP-ASan on.
-__attribute__((constructor)) static void enable_gwp_asan() {
-  android_mallopt_gwp_asan_options_t opts;
-  // No, we're not an app, but let's turn ourselves on without sampling.
-  // Technically, if someone's using the *.default_app sysprops, they'll adjust
-  // our settings, but I don't think this will be common on a device that's
-  // running debuggerd_tests.
-  opts.desire = android_mallopt_gwp_asan_options_t::Action::TURN_ON_FOR_APP;
-  opts.program_name = "";
-  android_mallopt(M_INITIALIZE_GWP_ASAN, &opts, sizeof(android_mallopt_gwp_asan_options_t));
-}
-
 static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd,
                                  InterceptStatus* status, DebuggerdDumpType intercept_type) {
   intercept_fd->reset(socket_local_client(kTombstonedInterceptSocketName,
@@ -468,76 +457,6 @@
 }
 #endif
 
-// Number of iterations required to reliably guarantee a GWP-ASan crash.
-// GWP-ASan's sample rate is not truly nondeterministic, it initialises a
-// thread-local counter at 2*SampleRate, and decrements on each malloc(). Once
-// the counter reaches zero, we provide a sampled allocation. Then, double that
-// figure to allow for left/right allocation alignment, as this is done randomly
-// without bias.
-#define GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH (0x20000)
-
-struct GwpAsanTestParameters {
-  size_t alloc_size;
-  bool free_before_access;
-  int access_offset;
-  std::string cause_needle; // Needle to be found in the "Cause: [GWP-ASan]" line.
-};
-
-struct GwpAsanCrasherTest : CrasherTest, testing::WithParamInterface<GwpAsanTestParameters> {};
-
-GwpAsanTestParameters gwp_asan_tests[] = {
-  {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 0, "Use After Free, 0 bytes into a 7-byte allocation"},
-  {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 1, "Use After Free, 1 byte into a 7-byte allocation"},
-  {/* alloc_size */ 7, /* free_before_access */ false, /* access_offset */ 16, "Buffer Overflow, 9 bytes right of a 7-byte allocation"},
-  {/* alloc_size */ 16, /* free_before_access */ false, /* access_offset */ -1, "Buffer Underflow, 1 byte left of a 16-byte allocation"},
-};
-
-INSTANTIATE_TEST_SUITE_P(GwpAsanTests, GwpAsanCrasherTest, testing::ValuesIn(gwp_asan_tests));
-
-TEST_P(GwpAsanCrasherTest, gwp_asan_uaf) {
-  if (mte_supported()) {
-    // Skip this test on MTE hardware, as MTE will reliably catch these errors
-    // instead of GWP-ASan.
-    GTEST_SKIP() << "Skipped on MTE.";
-  }
-  // Skip this test on HWASan, which will reliably catch test errors as well.
-  SKIP_WITH_HWASAN;
-
-  GwpAsanTestParameters params = GetParam();
-  LogcatCollector logcat_collector;
-
-  int intercept_result;
-  unique_fd output_fd;
-  StartProcess([&params]() {
-    for (unsigned i = 0; i < GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH; ++i) {
-      volatile char* p = reinterpret_cast<volatile char*>(malloc(params.alloc_size));
-      if (params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
-      p[params.access_offset] = 42;
-      if (!params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
-    }
-  });
-
-  StartIntercept(&output_fd);
-  FinishCrasher();
-  AssertDeath(SIGSEGV);
-  FinishIntercept(&intercept_result);
-
-  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
-
-  std::vector<std::string> log_sources(2);
-  ConsumeFd(std::move(output_fd), &log_sources[0]);
-  logcat_collector.Collect(&log_sources[1]);
-
-  for (const auto& result : log_sources) {
-    ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
-    ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
-    if (params.free_before_access) {
-      ASSERT_MATCH(result, R"(deallocated by thread .*\n.*#00 pc)");
-    }
-    ASSERT_MATCH(result, R"((^|\s)allocated by thread .*\n.*#00 pc)");
-  }
-}
-
 struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {};
 
 INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(0, 16, 131072));
@@ -1278,7 +1197,11 @@
 static const char* const kDebuggerdSeccompPolicy =
     "/system/etc/seccomp_policy/crash_dump." ABI_STRING ".policy";
 
-static pid_t seccomp_fork_impl(void (*prejail)()) {
+static void setup_jail(minijail* jail) {
+  if (!jail) {
+    LOG(FATAL) << "failed to create minijail";
+  }
+
   std::string policy;
   if (!android::base::ReadFileToString(kDebuggerdSeccompPolicy, &policy)) {
     PLOG(FATAL) << "failed to read policy file";
@@ -1305,15 +1228,15 @@
     PLOG(FATAL) << "failed to seek tmp_fd";
   }
 
-  ScopedMinijail jail{minijail_new()};
-  if (!jail) {
-    LOG(FATAL) << "failed to create minijail";
-  }
+  minijail_no_new_privs(jail);
+  minijail_log_seccomp_filter_failures(jail);
+  minijail_use_seccomp_filter(jail);
+  minijail_parse_seccomp_filters_from_fd(jail, tmp_fd.release());
+}
 
-  minijail_no_new_privs(jail.get());
-  minijail_log_seccomp_filter_failures(jail.get());
-  minijail_use_seccomp_filter(jail.get());
-  minijail_parse_seccomp_filters_from_fd(jail.get(), tmp_fd.release());
+static pid_t seccomp_fork_impl(void (*prejail)()) {
+  ScopedMinijail jail{minijail_new()};
+  setup_jail(jail.get());
 
   pid_t result = fork();
   if (result == -1) {
@@ -1627,6 +1550,138 @@
   AssertDeath(SIGABRT);
 }
 
+struct GwpAsanTestParameters {
+  size_t alloc_size;
+  bool free_before_access;
+  int access_offset;
+  std::string cause_needle;  // Needle to be found in the "Cause: [GWP-ASan]" line.
+};
+
+struct GwpAsanCrasherTest
+    : CrasherTest,
+      testing::WithParamInterface<
+          std::tuple<GwpAsanTestParameters, /* recoverable */ bool, /* seccomp */ bool>> {};
+
+GwpAsanTestParameters gwp_asan_tests[] = {
+    {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 0,
+     "Use After Free, 0 bytes into a 7-byte allocation"},
+    {/* alloc_size */ 15, /* free_before_access */ true, /* access_offset */ 1,
+     "Use After Free, 1 byte into a 15-byte allocation"},
+    {/* alloc_size */ 4096, /* free_before_access */ false, /* access_offset */ 4098,
+     "Buffer Overflow, 2 bytes right of a 4096-byte allocation"},
+    {/* alloc_size */ 4096, /* free_before_access */ false, /* access_offset */ -1,
+     "Buffer Underflow, 1 byte left of a 4096-byte allocation"},
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    GwpAsanTests, GwpAsanCrasherTest,
+    testing::Combine(testing::ValuesIn(gwp_asan_tests),
+                     /* recoverable */ testing::Bool(),
+                     /* seccomp */ testing::Bool()),
+    [](const testing::TestParamInfo<
+        std::tuple<GwpAsanTestParameters, /* recoverable */ bool, /* seccomp */ bool>>& info) {
+      const GwpAsanTestParameters& params = std::get<0>(info.param);
+      std::string name = params.free_before_access ? "UseAfterFree" : "Overflow";
+      name += testing::PrintToString(params.alloc_size);
+      name += "Alloc";
+      if (params.access_offset < 0) {
+        name += "Left";
+        name += testing::PrintToString(params.access_offset * -1);
+      } else {
+        name += "Right";
+        name += testing::PrintToString(params.access_offset);
+      }
+      name += "Bytes";
+      if (std::get<1>(info.param)) name += "Recoverable";
+      if (std::get<2>(info.param)) name += "Seccomp";
+      return name;
+    });
+
+TEST_P(GwpAsanCrasherTest, run_gwp_asan_test) {
+  if (mte_supported()) {
+    // Skip this test on MTE hardware, as MTE will reliably catch these errors
+    // instead of GWP-ASan.
+    GTEST_SKIP() << "Skipped on MTE.";
+  }
+  // Skip this test on HWASan, which will reliably catch test errors as well.
+  SKIP_WITH_HWASAN;
+
+  GwpAsanTestParameters params = std::get<0>(GetParam());
+  bool recoverable = std::get<1>(GetParam());
+  LogcatCollector logcat_collector;
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&recoverable]() {
+    const char* env[] = {"GWP_ASAN_SAMPLE_RATE=1", "GWP_ASAN_PROCESS_SAMPLING=1",
+                         "GWP_ASAN_MAX_ALLOCS=40000", nullptr, nullptr};
+    if (recoverable) {
+      env[3] = "GWP_ASAN_RECOVERABLE=true";
+    }
+    std::string test_name = ::testing::UnitTest::GetInstance()->current_test_info()->name();
+    test_name = std::regex_replace(test_name, std::regex("run_gwp_asan_test"),
+                                   "DISABLED_run_gwp_asan_test");
+    std::string test_filter = "--gtest_filter=*";
+    test_filter += test_name;
+    std::string this_binary = android::base::GetExecutablePath();
+    const char* args[] = {this_binary.c_str(), "--gtest_also_run_disabled_tests",
+                          test_filter.c_str(), nullptr};
+    // We check the crash report from a debuggerd handler and from logcat. The
+    // echo from stdout/stderr of the subprocess trips up atest, because it
+    // doesn't like that two tests started in a row without the first one
+    // finishing (even though the second one is in a subprocess).
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+    execve(this_binary.c_str(), const_cast<char**>(args), const_cast<char**>(env));
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  if (recoverable) {
+    AssertDeath(0);
+  } else {
+    AssertDeath(SIGSEGV);
+  }
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::vector<std::string> log_sources(2);
+  ConsumeFd(std::move(output_fd), &log_sources[0]);
+  logcat_collector.Collect(&log_sources[1]);
+
+  // seccomp forces the fallback handler, which doesn't print GWP-ASan debugging
+  // information. Make sure the recovery still works, but the report won't be
+  // hugely useful, it looks like a regular SEGV.
+  bool seccomp = std::get<2>(GetParam());
+  if (!seccomp) {
+    for (const auto& result : log_sources) {
+      ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
+      ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
+      if (params.free_before_access) {
+        ASSERT_MATCH(result, R"(deallocated by thread .*\n.*#00 pc)");
+      }
+      ASSERT_MATCH(result, R"((^|\s)allocated by thread .*\n.*#00 pc)");
+    }
+  }
+}
+
+TEST_P(GwpAsanCrasherTest, DISABLED_run_gwp_asan_test) {
+  GwpAsanTestParameters params = std::get<0>(GetParam());
+  bool seccomp = std::get<2>(GetParam());
+  if (seccomp) {
+    ScopedMinijail jail{minijail_new()};
+    setup_jail(jail.get());
+    minijail_enter(jail.get());
+  }
+
+  // Use 'volatile' to prevent a very clever compiler eliminating the store.
+  char* volatile p = reinterpret_cast<char* volatile>(malloc(params.alloc_size));
+  if (params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
+  p[params.access_offset] = 42;
+  if (!params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
+}
+
 TEST_F(CrasherTest, fdsan_warning_abort_message) {
   int intercept_result;
   unique_fd output_fd;
@@ -1909,7 +1964,7 @@
   ASSERT_MATCH(result, R"(Cause: stack pointer[^\n]*stack overflow.\n)");
 }
 
-static bool CopySharedLibrary(const char* tmp_dir, std::string* tmp_so_name) {
+static std::string GetTestLibraryPath() {
   std::string test_lib(testing::internal::GetArgvs()[0]);
   auto const value = test_lib.find_last_of('/');
   if (value == std::string::npos) {
@@ -1917,7 +1972,62 @@
   } else {
     test_lib = test_lib.substr(0, value + 1) + "./";
   }
-  test_lib += "libcrash_test.so";
+  return test_lib + "libcrash_test.so";
+}
+
+static void CreateEmbeddedLibrary(int out_fd) {
+  std::string test_lib(GetTestLibraryPath());
+  android::base::unique_fd fd(open(test_lib.c_str(), O_RDONLY | O_CLOEXEC));
+  ASSERT_NE(fd.get(), -1);
+  off_t file_size = lseek(fd, 0, SEEK_END);
+  ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
+  std::vector<uint8_t> contents(file_size);
+  ASSERT_TRUE(android::base::ReadFully(fd, contents.data(), contents.size()));
+
+  // Put the shared library data at a pagesize() offset.
+  ASSERT_EQ(lseek(out_fd, 4 * getpagesize(), SEEK_CUR), 4 * getpagesize());
+  ASSERT_EQ(static_cast<size_t>(write(out_fd, contents.data(), contents.size())), contents.size());
+}
+
+TEST_F(CrasherTest, non_zero_offset_in_library) {
+  int intercept_result;
+  unique_fd output_fd;
+  TemporaryFile tf;
+  CreateEmbeddedLibrary(tf.fd);
+  StartProcess([&tf]() {
+    android_dlextinfo extinfo{};
+    extinfo.flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET;
+    extinfo.library_fd = tf.fd;
+    extinfo.library_fd_offset = 4 * getpagesize();
+    void* handle = android_dlopen_ext(tf.path, RTLD_NOW, &extinfo);
+    if (handle == nullptr) {
+      _exit(1);
+    }
+    void (*crash_func)() = reinterpret_cast<void (*)()>(dlsym(handle, "crash"));
+    if (crash_func == nullptr) {
+      _exit(1);
+    }
+    crash_func();
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  // Verify the crash includes an offset value in the backtrace.
+  std::string match_str = android::base::StringPrintf("%s\\!libcrash_test.so \\(offset 0x%x\\)",
+                                                      tf.path, 4 * getpagesize());
+  ASSERT_MATCH(result, match_str);
+}
+
+static bool CopySharedLibrary(const char* tmp_dir, std::string* tmp_so_name) {
+  std::string test_lib(GetTestLibraryPath());
 
   *tmp_so_name = std::string(tmp_dir) + "/libcrash_test.so";
   std::string cp_cmd = android::base::StringPrintf("cp %s %s", test_lib.c_str(), tmp_dir);
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 7120d73..baa5bfb 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -395,6 +395,7 @@
     ASSERT_SAME_OFFSET(scudo_region_info, scudo_region_info);
     ASSERT_SAME_OFFSET(scudo_ring_buffer, scudo_ring_buffer);
     ASSERT_SAME_OFFSET(scudo_ring_buffer_size, scudo_ring_buffer_size);
+    ASSERT_SAME_OFFSET(recoverable_gwp_asan_crash, recoverable_gwp_asan_crash);
 #undef ASSERT_SAME_OFFSET
 
     iovs[3] = {.iov_base = &thread_info->process_info,
@@ -565,17 +566,37 @@
     process_info = g_callbacks.get_process_info();
   }
 
+  // GWP-ASan catches use-after-free and heap-buffer-overflow by using PROT_NONE
+  // guard pages, which lead to SEGV. Normally, debuggerd prints a bug report
+  // and the process terminates, but in some cases, we actually want to print
+  // the bug report and let the signal handler return, and restart the process.
+  // In order to do that, we need to disable GWP-ASan's guard pages. The
+  // following callbacks handle this case.
+  gwp_asan_callbacks_t gwp_asan_callbacks = g_callbacks.get_gwp_asan_callbacks();
+  if (signal_number == SIGSEGV && signal_has_si_addr(info) &&
+      gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery &&
+      gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report &&
+      gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report &&
+      gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery(info->si_addr)) {
+    gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report(info->si_addr);
+    process_info.recoverable_gwp_asan_crash = true;
+  }
+
   // If sival_int is ~0, it means that the fallback handler has been called
   // once before and this function is being called again to dump the stack
   // of a specific thread. It is possible that the prctl call might return 1,
   // then return 0 in subsequent calls, so check the sival_int to determine if
   // the fallback handler should be called first.
-  if (si_val == kDebuggerdFallbackSivalUintptrRequestDump ||
-      prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1) {
+  bool no_new_privs = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1;
+  if (si_val == kDebuggerdFallbackSivalUintptrRequestDump || no_new_privs) {
     // This check might be racy if another thread sets NO_NEW_PRIVS, but this should be unlikely,
     // you can only set NO_NEW_PRIVS to 1, and the effect should be at worst a single missing
     // ANR trace.
     debuggerd_fallback_handler(info, ucontext, process_info.abort_msg);
+    if (no_new_privs && process_info.recoverable_gwp_asan_crash) {
+      gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr);
+      return;
+    }
     resend_signal(info);
     return;
   }
@@ -649,6 +670,9 @@
     // If the signal is fatal, don't unlock the mutex to prevent other crashing threads from
     // starting to dump right before our death.
     pthread_mutex_unlock(&crash_mutex);
+  } else if (process_info.recoverable_gwp_asan_crash) {
+    gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr);
+    pthread_mutex_unlock(&crash_mutex);
   }
 #ifdef __aarch64__
   else if (info->si_signo == SIGSEGV &&
@@ -727,3 +751,52 @@
 
   debuggerd_register_handlers(&action);
 }
+
+// When debuggerd's signal handler is the first handler called, it's great at
+// handling the recoverable GWP-ASan mode. For apps, sigchain (from libart) is
+// always the first signal handler, and so the following function is what
+// sigchain must call before processing the signal. This allows for processing
+// of a potentially recoverable GWP-ASan crash. If the signal requires GWP-ASan
+// recovery, then dump a report (via the regular debuggerd hanndler), and patch
+// up the allocator, and allow the process to continue (indicated by returning
+// 'true'). If the crash has nothing to do with GWP-ASan, or recovery isn't
+// possible, return 'false'.
+bool debuggerd_handle_signal(int signal_number, siginfo_t* info, void* context) {
+  if (signal_number != SIGSEGV || !signal_has_si_addr(info)) return false;
+
+  gwp_asan_callbacks_t gwp_asan_callbacks = g_callbacks.get_gwp_asan_callbacks();
+  if (gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery == nullptr ||
+      gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report == nullptr ||
+      gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report == nullptr ||
+      !gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery(info->si_addr)) {
+    return false;
+  }
+
+  // Only dump a crash report for the first GWP-ASan crash. ActivityManager
+  // doesn't like it when an app crashes multiple times, and is even more strict
+  // about an app crashing multiple times in a short time period. While the app
+  // won't crash fully when we do GWP-ASan recovery, ActivityManager still gets
+  // the information about the crash through the DropBoxManager service. If an
+  // app has multiple back-to-back GWP-ASan crashes, this would lead to the app
+  // being killed, which defeats the purpose of having the recoverable mode. To
+  // mitigate against this, only generate a debuggerd crash report for the first
+  // GWP-ASan crash encountered. We still need to do the patching up of the
+  // allocator though, so do that.
+  static pthread_mutex_t first_crash_mutex = PTHREAD_MUTEX_INITIALIZER;
+  pthread_mutex_lock(&first_crash_mutex);
+  static bool first_crash = true;
+
+  if (first_crash) {
+    // `debuggerd_signal_handler` will call
+    // `debuggerd_gwp_asan_(pre|post)_crash_report`, so no need to manually call
+    // them here.
+    debuggerd_signal_handler(signal_number, info, context);
+    first_crash = false;
+  } else {
+    gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report(info->si_addr);
+    gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr);
+  }
+
+  pthread_mutex_unlock(&first_crash_mutex);
+  return true;
+}
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index 1f9f4e2..ebb5372 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -35,7 +35,7 @@
 
 // When updating this data structure, CrashInfoDataDynamic and the code in
 // ReadCrashInfo() must also be updated.
-struct debugger_process_info {
+struct __attribute__((packed)) debugger_process_info {
   void* abort_msg;
   void* fdsan_table;
   const gwp_asan::AllocatorState* gwp_asan_state;
@@ -44,16 +44,28 @@
   const char* scudo_region_info;
   const char* scudo_ring_buffer;
   size_t scudo_ring_buffer_size;
+  bool recoverable_gwp_asan_crash;
 };
 
+// GWP-ASan calbacks to support the recoverable mode. Separate from the
+// debuggerd_callbacks_t because these values aren't available at debuggerd_init
+// time, and have to be synthesized on request.
+typedef struct {
+  bool (*debuggerd_needs_gwp_asan_recovery)(void* fault_addr);
+  void (*debuggerd_gwp_asan_pre_crash_report)(void* fault_addr);
+  void (*debuggerd_gwp_asan_post_crash_report)(void* fault_addr);
+} gwp_asan_callbacks_t;
+
 // These callbacks are called in a signal handler, and thus must be async signal safe.
 // If null, the callbacks will not be called.
 typedef struct {
   debugger_process_info (*get_process_info)();
+  gwp_asan_callbacks_t (*get_gwp_asan_callbacks)();
   void (*post_dump)();
 } debuggerd_callbacks_t;
 
 void debuggerd_init(debuggerd_callbacks_t* callbacks);
+bool debuggerd_handle_signal(int signal_number, siginfo_t* info, void* context);
 
 // DEBUGGER_ACTION_DUMP_TOMBSTONE and DEBUGGER_ACTION_DUMP_BACKTRACE are both
 // triggered via BIONIC_SIGNAL_DEBUGGER. The debugger_action_t is sent via si_value
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 3ff82bc..e4d68f8 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -176,8 +176,14 @@
       build_id = StringPrintf(" (BuildId: %s)", frame.build_id().c_str());
     }
 
-    CB(should_log, "      #%02d pc %0*" PRIx64 "  %s%s%s", index++, pointer_width(tombstone) * 2,
-       frame.rel_pc(), frame.file_name().c_str(), function.c_str(), build_id.c_str());
+    std::string line =
+        StringPrintf("      #%02d pc %0*" PRIx64 "  %s", index++, pointer_width(tombstone) * 2,
+                     frame.rel_pc(), frame.file_name().c_str());
+    if (frame.file_map_offset() != 0) {
+      line += StringPrintf(" (offset 0x%" PRIx64 ")", frame.file_map_offset());
+    }
+    line += function + build_id;
+    CB(should_log, "%s", line.c_str());
   }
 }
 
@@ -303,95 +309,8 @@
   }
 }
 
-static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
-                              const Thread& thread) {
+static void print_memory_maps(CallbackType callback, const Tombstone& tombstone) {
   int word_size = pointer_width(tombstone);
-  print_thread_header(callback, tombstone, thread, true);
-
-  const Signal& signal_info = tombstone.signal_info();
-  std::string sender_desc;
-
-  if (signal_info.has_sender()) {
-    sender_desc =
-        StringPrintf(" from pid %d, uid %d", signal_info.sender_pid(), signal_info.sender_uid());
-  }
-
-  bool is_async_mte_crash = false;
-  if (!tombstone.has_signal_info()) {
-    CBL("signal information missing");
-  } else {
-    std::string fault_addr_desc;
-    if (signal_info.has_fault_address()) {
-      fault_addr_desc = StringPrintf("0x%0*" PRIx64, 2 * word_size, signal_info.fault_address());
-    } else {
-      fault_addr_desc = "--------";
-    }
-
-    CBL("signal %d (%s), code %d (%s%s), fault addr %s", signal_info.number(),
-        signal_info.name().c_str(), signal_info.code(), signal_info.code_name().c_str(),
-        sender_desc.c_str(), fault_addr_desc.c_str());
-#ifdef SEGV_MTEAERR
-    is_async_mte_crash = signal_info.number() == SIGSEGV && signal_info.code() == SEGV_MTEAERR;
-#endif
-  }
-
-  if (tombstone.causes_size() == 1) {
-    CBL("Cause: %s", tombstone.causes(0).human_readable().c_str());
-  }
-
-  if (!tombstone.abort_message().empty()) {
-    CBL("Abort message: '%s'", tombstone.abort_message().c_str());
-  }
-
-  print_thread_registers(callback, tombstone, thread, true);
-  if (is_async_mte_crash) {
-    CBL("Note: This crash is a delayed async MTE crash. Memory corruption has occurred");
-    CBL("      in this process. The stack trace below is the first system call or context");
-    CBL("      switch that was executed after the memory corruption happened.");
-  }
-  print_thread_backtrace(callback, tombstone, thread, true);
-
-  if (tombstone.causes_size() > 1) {
-    CBS("");
-    CBL("Note: multiple potential causes for this crash were detected, listing them in decreasing "
-        "order of likelihood.");
-  }
-
-  for (const Cause& cause : tombstone.causes()) {
-    if (tombstone.causes_size() > 1) {
-      CBS("");
-      CBL("Cause: %s", cause.human_readable().c_str());
-    }
-
-    if (cause.has_memory_error() && cause.memory_error().has_heap()) {
-      const HeapObject& heap_object = cause.memory_error().heap();
-
-      if (heap_object.deallocation_backtrace_size() != 0) {
-        CBS("");
-        CBL("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
-        print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), true);
-      }
-
-      if (heap_object.allocation_backtrace_size() != 0) {
-        CBS("");
-        CBL("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
-        print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), true);
-      }
-    }
-  }
-
-  print_tag_dump(callback, tombstone);
-
-  print_thread_memory_dump(callback, tombstone, thread);
-
-  CBS("");
-
-  // No memory maps to print.
-  if (tombstone.memory_mappings().empty()) {
-    CBS("No memory maps found");
-    return;
-  }
-
   const auto format_pointer = [word_size](uint64_t ptr) -> std::string {
     if (word_size == 8) {
       uint64_t top = ptr >> 32;
@@ -406,6 +325,7 @@
       StringPrintf("memory map (%d %s):", tombstone.memory_mappings().size(),
                    tombstone.memory_mappings().size() == 1 ? "entry" : "entries");
 
+  const Signal& signal_info = tombstone.signal_info();
   bool has_fault_address = signal_info.has_fault_address();
   uint64_t fault_address = untag_address(signal_info.fault_address());
   bool preamble_printed = false;
@@ -465,6 +385,106 @@
   }
 }
 
+static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
+                              const Thread& thread) {
+  print_thread_header(callback, tombstone, thread, true);
+
+  const Signal& signal_info = tombstone.signal_info();
+  std::string sender_desc;
+
+  if (signal_info.has_sender()) {
+    sender_desc =
+        StringPrintf(" from pid %d, uid %d", signal_info.sender_pid(), signal_info.sender_uid());
+  }
+
+  bool is_async_mte_crash = false;
+  bool is_mte_crash = false;
+  if (!tombstone.has_signal_info()) {
+    CBL("signal information missing");
+  } else {
+    std::string fault_addr_desc;
+    if (signal_info.has_fault_address()) {
+      fault_addr_desc =
+          StringPrintf("0x%0*" PRIx64, 2 * pointer_width(tombstone), signal_info.fault_address());
+    } else {
+      fault_addr_desc = "--------";
+    }
+
+    CBL("signal %d (%s), code %d (%s%s), fault addr %s", signal_info.number(),
+        signal_info.name().c_str(), signal_info.code(), signal_info.code_name().c_str(),
+        sender_desc.c_str(), fault_addr_desc.c_str());
+#ifdef SEGV_MTEAERR
+    is_async_mte_crash = signal_info.number() == SIGSEGV && signal_info.code() == SEGV_MTEAERR;
+    is_mte_crash = is_async_mte_crash ||
+                   (signal_info.number() == SIGSEGV && signal_info.code() == SEGV_MTESERR);
+#endif
+  }
+
+  if (tombstone.causes_size() == 1) {
+    CBL("Cause: %s", tombstone.causes(0).human_readable().c_str());
+  }
+
+  if (!tombstone.abort_message().empty()) {
+    CBL("Abort message: '%s'", tombstone.abort_message().c_str());
+  }
+
+  print_thread_registers(callback, tombstone, thread, true);
+  if (is_async_mte_crash) {
+    CBL("Note: This crash is a delayed async MTE crash. Memory corruption has occurred");
+    CBL("      in this process. The stack trace below is the first system call or context");
+    CBL("      switch that was executed after the memory corruption happened.");
+  }
+  print_thread_backtrace(callback, tombstone, thread, true);
+
+  if (tombstone.causes_size() > 1) {
+    CBS("");
+    CBL("Note: multiple potential causes for this crash were detected, listing them in decreasing "
+        "order of likelihood.");
+  }
+
+  for (const Cause& cause : tombstone.causes()) {
+    if (tombstone.causes_size() > 1) {
+      CBS("");
+      CBL("Cause: %s", cause.human_readable().c_str());
+    }
+
+    if (cause.has_memory_error() && cause.memory_error().has_heap()) {
+      const HeapObject& heap_object = cause.memory_error().heap();
+
+      if (heap_object.deallocation_backtrace_size() != 0) {
+        CBS("");
+        CBL("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
+        print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), true);
+      }
+
+      if (heap_object.allocation_backtrace_size() != 0) {
+        CBS("");
+        CBL("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
+        print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), true);
+      }
+    }
+  }
+
+  print_tag_dump(callback, tombstone);
+
+  if (is_mte_crash) {
+    CBS("");
+    CBL("Learn more about MTE reports: "
+        "https://source.android.com/docs/security/test/memory-safety/mte-reports");
+  }
+
+  print_thread_memory_dump(callback, tombstone, thread);
+
+  CBS("");
+
+  // No memory maps to print.
+  if (!tombstone.memory_mappings().empty()) {
+    print_memory_maps(callback, tombstone);
+  } else {
+    CBS("No memory maps found");
+  }
+}
+
 void print_logs(CallbackType callback, const Tombstone& tombstone, int tail) {
   for (const auto& buffer : tombstone.log_buffers()) {
     if (tail) {
diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h
index e7cb218..b60cf5b 100644
--- a/debuggerd/protocol.h
+++ b/debuggerd/protocol.h
@@ -99,6 +99,7 @@
   uintptr_t scudo_region_info;
   uintptr_t scudo_ring_buffer;
   size_t scudo_ring_buffer_size;
+  bool recoverable_gwp_asan_crash;
 };
 
 struct __attribute__((__packed__)) CrashInfo {
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 765174b..3b786e8 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -170,7 +170,7 @@
         "android.hardware.fastboot@1.1",
         "android.hardware.fastboot-V1-ndk",
         "android.hardware.health@2.0",
-        "android.hardware.health-V1-ndk",
+        "android.hardware.health-V2-ndk",
         "libasyncio",
         "libbase",
         "libbinder_ndk",
@@ -283,14 +283,18 @@
 
     srcs: [
         "bootimg_utils.cpp",
+        "fastboot_driver.cpp",
         "fastboot.cpp",
+        "filesystem.cpp",
         "fs.cpp",
         "socket.cpp",
+        "storage.cpp",
+        "super_flash_helper.cpp",
         "tcp.cpp",
         "udp.cpp",
         "util.cpp",
         "vendor_boot_img_utils.cpp",
-        "fastboot_driver.cpp",
+        "task.cpp",
     ],
 
     // Only version the final binaries
@@ -372,14 +376,19 @@
     defaults: ["fastboot_host_defaults"],
 
     srcs: [
+        "fastboot_driver_test.cpp",
         "fastboot_test.cpp",
         "socket_mock.cpp",
         "socket_test.cpp",
+        "super_flash_helper_test.cpp",
         "tcp_test.cpp",
         "udp_test.cpp",
     ],
 
-    static_libs: ["libfastboot"],
+    static_libs: [
+        "libfastboot",
+        "libgmock",
+    ],
 
     target: {
         windows: {
@@ -390,6 +399,14 @@
             enabled: false,
         },
     },
+
+    test_suites: ["general-tests"],
+
+    data: [
+        "testdata/super.img",
+        "testdata/super_empty.img",
+        "testdata/system.img",
+    ],
 }
 
 cc_test_host {
diff --git a/fastboot/TEST_MAPPING b/fastboot/TEST_MAPPING
new file mode 100644
index 0000000..10f3d82
--- /dev/null
+++ b/fastboot/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "fastboot_test"
+    }
+  ]
+}
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 5afeb4f..6b6a982 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -70,11 +70,14 @@
     using HidlFastboot = android::hardware::fastboot::V1_1::IFastboot;
     using aidl::android::hardware::fastboot::FastbootShim;
     auto service_name = IFastboot::descriptor + "/default"s;
-    ndk::SpAIBinder binder(AServiceManager_getService(service_name.c_str()));
-    std::shared_ptr<IFastboot> fastboot = IFastboot::fromBinder(binder);
-    if (fastboot != nullptr) {
-        LOG(INFO) << "Using AIDL fastboot service";
-        return fastboot;
+    if (AServiceManager_isDeclared(service_name.c_str())) {
+        ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str()));
+        std::shared_ptr<IFastboot> fastboot = IFastboot::fromBinder(binder);
+        if (fastboot != nullptr) {
+            LOG(INFO) << "Found and using AIDL fastboot service";
+            return fastboot;
+        }
+        LOG(WARNING) << "AIDL fastboot service is declared, but it cannot be retrieved.";
     }
     LOG(INFO) << "Unable to get AIDL fastboot service, trying HIDL...";
     android::sp<HidlFastboot> hidl_fastboot = HidlFastboot::getService();
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 9676f87..c9cb228 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -44,8 +44,12 @@
 #include <unistd.h>
 
 #include <chrono>
+#include <fstream>
 #include <functional>
+#include <iostream>
+#include <memory>
 #include <regex>
+#include <sstream>
 #include <string>
 #include <thread>
 #include <utility>
@@ -53,6 +57,7 @@
 
 #include <android-base/endian.h>
 #include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/macros.h>
 #include <android-base/parseint.h>
 #include <android-base/parsenetaddress.h>
@@ -62,6 +67,7 @@
 #include <build/version.h>
 #include <libavb/libavb.h>
 #include <liblp/liblp.h>
+#include <liblp/super_layout_builder.h>
 #include <platform_tools_version.h>
 #include <sparse/sparse.h>
 #include <ziparchive/zip_archive.h>
@@ -71,6 +77,9 @@
 #include "diagnose_usb.h"
 #include "fastboot_driver.h"
 #include "fs.h"
+#include "storage.h"
+#include "super_flash_helper.h"
+#include "task.h"
 #include "tcp.h"
 #include "transport.h"
 #include "udp.h"
@@ -113,7 +122,7 @@
 
 struct fastboot_buffer {
     enum fb_buffer_type type;
-    void* data;
+    std::vector<SparsePtr> files;
     int64_t sz;
     unique_fd fd;
     int64_t image_size;
@@ -211,7 +220,7 @@
     return std::string(dir) + "/" + img_name;
 }
 
-static std::string find_item(const std::string& item) {
+std::string find_item(const std::string& item) {
     for (size_t i = 0; i < images.size(); ++i) {
         if (!images[i].nickname.empty() && item == images[i].nickname) {
             return find_item_given_name(images[i].img_name);
@@ -246,14 +255,6 @@
     fprintf(stderr, "(bootloader) %s\n", info.c_str());
 }
 
-static int64_t get_file_size(borrowed_fd fd) {
-    struct stat sb;
-    if (fstat(fd.get(), &sb) == -1) {
-        die("could not get file size");
-    }
-    return sb.st_size;
-}
-
 bool ReadFileToVector(const std::string& file, std::vector<char>* out) {
     out->clear();
 
@@ -279,8 +280,36 @@
     return 0;
 }
 
-static int match_fastboot(usb_ifc_info* info) {
-    return match_fastboot_with_serial(info, serial);
+static ifc_match_func match_fastboot(const char* local_serial = serial) {
+    return [local_serial](usb_ifc_info* info) -> int {
+        return match_fastboot_with_serial(info, local_serial);
+    };
+}
+
+// output compatible with "adb devices"
+static void PrintDevice(const char* local_serial, const char* status = nullptr,
+                        const char* details = nullptr) {
+    if (local_serial == nullptr || strlen(local_serial) == 0) {
+        return;
+    }
+
+    if (g_long_listing) {
+        printf("%-22s", local_serial);
+    } else {
+        printf("%s\t", local_serial);
+    }
+
+    if (status != nullptr && strlen(status) > 0) {
+        printf(" %s", status);
+    }
+
+    if (g_long_listing) {
+        if (details != nullptr && strlen(details) > 0) {
+            printf(" %s", details);
+        }
+    }
+
+    putchar('\n');
 }
 
 static int list_devices_callback(usb_ifc_info* info) {
@@ -296,91 +325,232 @@
         if (!serial[0]) {
             serial = "????????????";
         }
-        // output compatible with "adb devices"
-        if (!g_long_listing) {
-            printf("%s\t%s", serial.c_str(), interface.c_str());
-        } else {
-            printf("%-22s %s", serial.c_str(), interface.c_str());
-            if (strlen(info->device_path) > 0) printf(" %s", info->device_path);
-        }
-        putchar('\n');
+
+        PrintDevice(serial.c_str(), interface.c_str(), info->device_path);
     }
 
     return -1;
 }
 
-// Opens a new Transport connected to a device. If |serial| is non-null it will be used to identify
-// a specific device, otherwise the first USB device found will be used.
+struct NetworkSerial {
+    Socket::Protocol protocol;
+    std::string address;
+    int port;
+};
+
+static Result<NetworkSerial> ParseNetworkSerial(const std::string& serial) {
+    const auto serial_parsed = android::base::Tokenize(serial, ":");
+    const auto parsed_segments_count = serial_parsed.size();
+    if (parsed_segments_count != 2 && parsed_segments_count != 3) {
+        return Error() << "invalid network address: " << serial << ". Expected format:\n"
+                       << "<protocol>:<address>:<port> (tcp:localhost:5554)";
+    }
+
+    Socket::Protocol protocol;
+    if (serial_parsed[0] == "tcp") {
+        protocol = Socket::Protocol::kTcp;
+    } else if (serial_parsed[0] == "udp") {
+        protocol = Socket::Protocol::kUdp;
+    } else {
+        return Error() << "invalid network address: " << serial << ". Expected format:\n"
+                       << "<protocol>:<address>:<port> (tcp:localhost:5554)";
+    }
+
+    int port = 5554;
+    if (parsed_segments_count == 3) {
+        android::base::ParseInt(serial_parsed[2], &port, 5554);
+    }
+
+    return NetworkSerial{protocol, serial_parsed[1], port};
+}
+
+// Opens a new Transport connected to the particular device.
+// arguments:
 //
-// If |serial| is non-null but invalid, this exits.
-// Otherwise it blocks until the target is available.
+// local_serial - device to connect (can be a network or usb serial name)
+// wait_for_device - flag indicates whether we need to wait for device
+// announce - flag indicates whether we need to print error to stdout in case
+// we cannot connect to the device
 //
 // The returned Transport is a singleton, so multiple calls to this function will return the same
 // object, and the caller should not attempt to delete the returned Transport.
-static Transport* open_device() {
-    bool announce = true;
-
-    Socket::Protocol protocol = Socket::Protocol::kTcp;
-    std::string host;
-    int port = 0;
-    if (serial != nullptr) {
-        const char* net_address = nullptr;
-
-        if (android::base::StartsWith(serial, "tcp:")) {
-            protocol = Socket::Protocol::kTcp;
-            port = tcp::kDefaultPort;
-            net_address = serial + strlen("tcp:");
-        } else if (android::base::StartsWith(serial, "udp:")) {
-            protocol = Socket::Protocol::kUdp;
-            port = udp::kDefaultPort;
-            net_address = serial + strlen("udp:");
-        }
-
-        if (net_address != nullptr) {
-            std::string error;
-            if (!android::base::ParseNetAddress(net_address, &host, &port, nullptr, &error)) {
-                die("invalid network address '%s': %s\n", net_address, error.c_str());
-            }
-        }
-    }
+static Transport* open_device(const char* local_serial, bool wait_for_device = true,
+                              bool announce = true) {
+    const Result<NetworkSerial> network_serial = ParseNetworkSerial(local_serial);
 
     Transport* transport = nullptr;
     while (true) {
-        if (!host.empty()) {
+        if (network_serial.ok()) {
             std::string error;
-            if (protocol == Socket::Protocol::kTcp) {
-                transport = tcp::Connect(host, port, &error).release();
-            } else if (protocol == Socket::Protocol::kUdp) {
-                transport = udp::Connect(host, port, &error).release();
+            if (network_serial->protocol == Socket::Protocol::kTcp) {
+                transport = tcp::Connect(network_serial->address, network_serial->port, &error)
+                                    .release();
+            } else if (network_serial->protocol == Socket::Protocol::kUdp) {
+                transport = udp::Connect(network_serial->address, network_serial->port, &error)
+                                    .release();
             }
 
             if (transport == nullptr && announce) {
-                fprintf(stderr, "error: %s\n", error.c_str());
+                LOG(ERROR) << "error: " << error;
             }
         } else {
-            transport = usb_open(match_fastboot);
+            transport = usb_open(match_fastboot(local_serial));
         }
 
         if (transport != nullptr) {
             return transport;
         }
 
+        if (!wait_for_device) {
+            return nullptr;
+        }
+
         if (announce) {
             announce = false;
-            fprintf(stderr, "< waiting for %s >\n", serial ? serial : "any device");
+            LOG(ERROR) << "< waiting for " << local_serial << ">";
         }
         std::this_thread::sleep_for(std::chrono::milliseconds(1));
     }
 }
 
+static Transport* NetworkDeviceConnected(bool print = false) {
+    Transport* transport = nullptr;
+    Transport* result = nullptr;
+
+    ConnectedDevicesStorage storage;
+    std::set<std::string> devices;
+    {
+        FileLock lock = storage.Lock();
+        devices = storage.ReadDevices(lock);
+    }
+
+    for (const std::string& device : devices) {
+        transport = open_device(device.c_str(), false, false);
+
+        if (print) {
+            PrintDevice(device.c_str(), transport == nullptr ? "offline" : "device");
+        }
+
+        if (transport != nullptr) {
+            result = transport;
+        }
+    }
+
+    return result;
+}
+
+// Detects the fastboot connected device to open a new Transport.
+// Detecting logic:
+//
+// if serial is provided - try to connect to this particular usb/network device
+// othervise:
+// 1. Check connected usb devices and return the last connected one
+// 2. Check connected network devices and return the last connected one
+// 2. If nothing is connected - wait for any device by repeating p. 1 and 2
+//
+// The returned Transport is a singleton, so multiple calls to this function will return the same
+// object, and the caller should not attempt to delete the returned Transport.
+static Transport* open_device() {
+    if (serial != nullptr) {
+        return open_device(serial);
+    }
+
+    bool announce = true;
+    Transport* transport = nullptr;
+    while (true) {
+        transport = usb_open(match_fastboot(nullptr));
+        if (transport != nullptr) {
+            return transport;
+        }
+
+        transport = NetworkDeviceConnected();
+        if (transport != nullptr) {
+            return transport;
+        }
+
+        if (announce) {
+            announce = false;
+            LOG(ERROR) << "< waiting for any device >";
+        }
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    }
+}
+
+static int Connect(int argc, char* argv[]) {
+    if (argc != 1) {
+        LOG(FATAL) << "connect command requires to receive only 1 argument. Usage:" << std::endl
+                   << "fastboot connect [tcp:|udp:host:port]";
+    }
+
+    const char* local_serial = *argv;
+    EXPECT(ParseNetworkSerial(local_serial));
+
+    const Transport* transport = open_device(local_serial, false);
+    if (transport == nullptr) {
+        return 1;
+    }
+
+    ConnectedDevicesStorage storage;
+    {
+        FileLock lock = storage.Lock();
+        std::set<std::string> devices = storage.ReadDevices(lock);
+        devices.insert(local_serial);
+        storage.WriteDevices(lock, devices);
+    }
+
+    return 0;
+}
+
+static int Disconnect(const char* local_serial) {
+    EXPECT(ParseNetworkSerial(local_serial));
+
+    ConnectedDevicesStorage storage;
+    {
+        FileLock lock = storage.Lock();
+        std::set<std::string> devices = storage.ReadDevices(lock);
+        devices.erase(local_serial);
+        storage.WriteDevices(lock, devices);
+    }
+
+    return 0;
+}
+
+static int Disconnect() {
+    ConnectedDevicesStorage storage;
+    {
+        FileLock lock = storage.Lock();
+        storage.Clear(lock);
+    }
+
+    return 0;
+}
+
+static int Disconnect(int argc, char* argv[]) {
+    switch (argc) {
+        case 0: {
+            return Disconnect();
+        }
+        case 1: {
+            return Disconnect(*argv);
+        }
+        default:
+            LOG(FATAL) << "disconnect command can receive only 0 or 1 arguments. Usage:"
+                       << std::endl
+                       << "fastboot disconnect # disconnect all devices" << std::endl
+                       << "fastboot disconnect [tcp:|udp:host:port] # disconnect device";
+    }
+
+    return 0;
+}
+
 static void list_devices() {
     // We don't actually open a USB device here,
     // just getting our callback called so we can
     // list all the connected devices.
     usb_open(list_devices_callback);
+    NetworkDeviceConnected(/* print */ true);
 }
-
-static void syntax_error(const char* fmt, ...) {
+void syntax_error(const char* fmt, ...) {
     fprintf(stderr, "fastboot: usage: ");
 
     va_list ap;
@@ -824,27 +994,32 @@
     fprintf(stderr, "--------------------------------------------\n");
 }
 
-static struct sparse_file** load_sparse_files(int fd, int64_t max_size) {
-    struct sparse_file* s = sparse_file_import_auto(fd, false, true);
-    if (!s) die("cannot sparse read file");
-
+static std::vector<SparsePtr> resparse_file(sparse_file* s, int64_t max_size) {
     if (max_size <= 0 || max_size > std::numeric_limits<uint32_t>::max()) {
         die("invalid max size %" PRId64, max_size);
     }
 
-    int files = sparse_file_resparse(s, max_size, nullptr, 0);
+    const int files = sparse_file_resparse(s, max_size, nullptr, 0);
     if (files < 0) die("Failed to resparse");
 
-    sparse_file** out_s =
-            reinterpret_cast<sparse_file**>(calloc(sizeof(struct sparse_file*), files + 1));
-    if (!out_s) die("Failed to allocate sparse file array");
+    auto temp = std::make_unique<sparse_file*[]>(files);
+    const int rv = sparse_file_resparse(s, max_size, temp.get(), files);
+    if (rv < 0) die("Failed to resparse");
 
-    files = sparse_file_resparse(s, max_size, out_s, files);
-    if (files < 0) die("Failed to resparse");
-
+    std::vector<SparsePtr> out_s;
+    for (int i = 0; i < files; i++) {
+        out_s.emplace_back(temp[i], sparse_file_destroy);
+    }
     return out_s;
 }
 
+static std::vector<SparsePtr> load_sparse_files(int fd, int64_t max_size) {
+    SparsePtr s(sparse_file_import_auto(fd, false, true), sparse_file_destroy);
+    if (!s) die("cannot sparse read file");
+
+    return resparse_file(s.get(), max_size);
+}
+
 static uint64_t get_uint_var(const char* var_name) {
     std::string value_str;
     if (fb->GetVar(var_name, &value_str) != fastboot::SUCCESS || value_str.empty()) {
@@ -903,15 +1078,13 @@
     int64_t limit = get_sparse_limit(sz);
     buf->fd = std::move(fd);
     if (limit) {
-        sparse_file** s = load_sparse_files(buf->fd.get(), limit);
-        if (s == nullptr) {
+        buf->files = load_sparse_files(buf->fd.get(), limit);
+        if (buf->files.empty()) {
             return false;
         }
         buf->type = FB_BUFFER_SPARSE;
-        buf->data = s;
     } else {
         buf->type = FB_BUFFER_FD;
-        buf->data = nullptr;
         buf->sz = sz;
     }
 
@@ -996,6 +1169,8 @@
            fb->GetVar("partition-type:vbmeta_b", &partition_type) == fastboot::SUCCESS;
 }
 
+// Note: this only works in userspace fastboot. In the bootloader, use
+// should_flash_in_userspace().
 static bool is_logical(const std::string& partition) {
     std::string value;
     return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
@@ -1078,9 +1253,16 @@
     lseek(buf->fd.get(), 0, SEEK_SET);
 }
 
-static void flash_buf(const std::string& partition, struct fastboot_buffer* buf) {
-    sparse_file** s;
+static void flash_partition_files(const std::string& partition,
+                                  const std::vector<SparsePtr>& files) {
+    for (size_t i = 0; i < files.size(); i++) {
+        sparse_file* s = files[i].get();
+        int64_t sz = sparse_file_len(s, true, false);
+        fb->FlashPartition(partition, s, sz, i + 1, files.size());
+    }
+}
 
+static void flash_buf(const std::string& partition, struct fastboot_buffer* buf) {
     if (partition == "boot" || partition == "boot_a" || partition == "boot_b" ||
         partition == "init_boot" || partition == "init_boot_a" || partition == "init_boot_b" ||
         partition == "recovery" || partition == "recovery_a" || partition == "recovery_b") {
@@ -1103,18 +1285,7 @@
 
     switch (buf->type) {
         case FB_BUFFER_SPARSE: {
-            std::vector<std::pair<sparse_file*, int64_t>> sparse_files;
-            s = reinterpret_cast<sparse_file**>(buf->data);
-            while (*s) {
-                int64_t sz = sparse_file_len(*s, true, false);
-                sparse_files.emplace_back(*s, sz);
-                ++s;
-            }
-
-            for (size_t i = 0; i < sparse_files.size(); ++i) {
-                const auto& pair = sparse_files[i];
-                fb->FlashPartition(partition, pair.first, pair.second, i + 1, sparse_files.size());
-            }
+            flash_partition_files(partition, buf->files);
             break;
         }
         case FB_BUFFER_FD:
@@ -1242,9 +1413,8 @@
  * partition names. If force_slot is true, it will fail if a slot is specified, and the given
  * partition does not support slots.
  */
-static void do_for_partitions(const std::string& part, const std::string& slot,
-                              const std::function<void(const std::string&)>& func,
-                              bool force_slot) {
+void do_for_partitions(const std::string& part, const std::string& slot,
+                       const std::function<void(const std::string&)>& func, bool force_slot) {
     std::string has_slot;
     // |part| can be vendor_boot:default. Query has-slot on the first token only.
     auto part_tokens = android::base::Split(part, ":");
@@ -1340,7 +1510,7 @@
     return partition;
 }
 
-static void do_flash(const char* pname, const char* fname) {
+void do_flash(const char* pname, const char* fname) {
     verbose("Do flash %s %s", pname, fname);
     struct fastboot_buffer buf;
 
@@ -1369,12 +1539,12 @@
     }
 }
 
-static bool is_userspace_fastboot() {
+bool is_userspace_fastboot() {
     std::string value;
     return fb->GetVar("is-userspace", &value) == fastboot::SUCCESS && value == "yes";
 }
 
-static void reboot_to_userspace_fastboot() {
+void reboot_to_userspace_fastboot() {
     fb->RebootTo("fastboot");
 
     auto* old_transport = fb->set_transport(nullptr);
@@ -1402,13 +1572,6 @@
     }
 }
 
-class ImageSource {
-  public:
-    virtual ~ImageSource(){};
-    virtual bool ReadFile(const std::string& name, std::vector<char>* out) const = 0;
-    virtual unique_fd OpenFile(const std::string& name) const = 0;
-};
-
 class FlashAllTool {
   public:
     FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary,
@@ -1418,20 +1581,30 @@
 
   private:
     void CheckRequirements();
-    void DetermineSecondarySlot();
+    void DetermineSlot();
     void CollectImages();
     void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
     void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
     void UpdateSuperPartition();
+    bool OptimizedFlashSuper();
+
+    // If the image uses the default slot, or the user specified "all", then
+    // the paired string will be empty. If the image requests a specific slot
+    // (for example, system_other) it is specified instead.
+    using ImageEntry = std::pair<const Image*, std::string>;
+
+    std::string GetPartitionName(const ImageEntry& entry);
 
     const ImageSource& source_;
     std::string slot_override_;
     bool skip_secondary_;
     bool wipe_;
     bool force_flash_;
+    std::string current_slot_;
     std::string secondary_slot_;
-    std::vector<std::pair<const Image*, std::string>> boot_images_;
-    std::vector<std::pair<const Image*, std::string>> os_images_;
+
+    std::vector<ImageEntry> boot_images_;
+    std::vector<ImageEntry> os_images_;
 };
 
 FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override,
@@ -1454,7 +1627,7 @@
         set_active(slot_override_);
     }
 
-    DetermineSecondarySlot();
+    DetermineSlot();
     CollectImages();
 
     CancelSnapshotIfNeeded();
@@ -1463,24 +1636,92 @@
     // or in bootloader fastboot.
     FlashImages(boot_images_);
 
-    // Sync the super partition. This will reboot to userspace fastboot if needed.
-    UpdateSuperPartition();
+    if (!OptimizedFlashSuper()) {
+        // Sync the super partition. This will reboot to userspace fastboot if needed.
+        UpdateSuperPartition();
 
-    // Resize any logical partition to 0, so each partition is reset to 0
-    // extents, and will achieve more optimal allocation.
-    for (const auto& [image, slot] : os_images_) {
-        auto resize_partition = [](const std::string& partition) -> void {
-            if (is_logical(partition)) {
-                fb->ResizePartition(partition, "0");
-            }
-        };
-        do_for_partitions(image->part_name, slot, resize_partition, false);
+        // Resize any logical partition to 0, so each partition is reset to 0
+        // extents, and will achieve more optimal allocation.
+        for (const auto& [image, slot] : os_images_) {
+            auto resize_partition = [](const std::string& partition) -> void {
+                if (is_logical(partition)) {
+                    fb->ResizePartition(partition, "0");
+                }
+            };
+            do_for_partitions(image->part_name, slot, resize_partition, false);
+        }
     }
 
     // Flash OS images, resizing logical partitions as needed.
     FlashImages(os_images_);
 }
 
+bool FlashAllTool::OptimizedFlashSuper() {
+    if (!supports_AB()) {
+        LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
+        return false;
+    }
+    if (slot_override_ == "all") {
+        LOG(VERBOSE) << "Cannot optimize flashing super for all slots";
+        return false;
+    }
+
+    // Does this device use dynamic partitions at all?
+    unique_fd fd = source_.OpenFile("super_empty.img");
+    if (fd < 0) {
+        LOG(VERBOSE) << "could not open super_empty.img";
+        return false;
+    }
+
+    // Try to find whether there is a super partition.
+    std::string super_name;
+    if (fb->GetVar("super-partition-name", &super_name) != fastboot::SUCCESS) {
+        super_name = "super";
+    }
+    std::string partition_size_str;
+    if (fb->GetVar("partition-size:" + super_name, &partition_size_str) != fastboot::SUCCESS) {
+        LOG(VERBOSE) << "Cannot optimize super flashing: could not determine super partition";
+        return false;
+    }
+
+    SuperFlashHelper helper(source_);
+    if (!helper.Open(fd)) {
+        return false;
+    }
+
+    for (const auto& entry : os_images_) {
+        auto partition = GetPartitionName(entry);
+        auto image = entry.first;
+
+        if (!helper.AddPartition(partition, image->img_name, image->optional_if_no_image)) {
+            return false;
+        }
+    }
+
+    auto s = helper.GetSparseLayout();
+    if (!s) {
+        return false;
+    }
+
+    std::vector<SparsePtr> files;
+    if (int limit = get_sparse_limit(sparse_file_len(s.get(), false, false))) {
+        files = resparse_file(s.get(), limit);
+    } else {
+        files.emplace_back(std::move(s));
+    }
+
+    // Send the data to the device.
+    flash_partition_files(super_name, files);
+
+    // Remove images that we already flashed, just in case we have non-dynamic OS images.
+    auto remove_if_callback = [&, this](const ImageEntry& entry) -> bool {
+        return helper.WillFlash(GetPartitionName(entry));
+    };
+    os_images_.erase(std::remove_if(os_images_.begin(), os_images_.end(), remove_if_callback),
+                     os_images_.end());
+    return true;
+}
+
 void FlashAllTool::CheckRequirements() {
     std::vector<char> contents;
     if (!source_.ReadFile("android-info.txt", &contents)) {
@@ -1489,7 +1730,13 @@
     ::CheckRequirements({contents.data(), contents.size()}, force_flash_);
 }
 
-void FlashAllTool::DetermineSecondarySlot() {
+void FlashAllTool::DetermineSlot() {
+    if (slot_override_.empty()) {
+        current_slot_ = get_current_slot();
+    } else {
+        current_slot_ = slot_override_;
+    }
+
     if (skip_secondary_) {
         return;
     }
@@ -1588,6 +1835,20 @@
     }
 }
 
+std::string FlashAllTool::GetPartitionName(const ImageEntry& entry) {
+    auto slot = entry.second;
+    if (slot.empty()) {
+        slot = current_slot_;
+    }
+    if (slot.empty()) {
+        return entry.first->part_name;
+    }
+    if (slot == "all") {
+        LOG(FATAL) << "Cannot retrieve a singular name when using all slots";
+    }
+    return entry.first->part_name + "_" + slot;
+}
+
 class ZipImageSource final : public ImageSource {
   public:
     explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
@@ -1770,7 +2031,7 @@
     }
 }
 
-static bool should_flash_in_userspace(const std::string& partition_name) {
+bool should_flash_in_userspace(const std::string& partition_name) {
     if (!get_android_product_out()) {
         return false;
     }
@@ -1782,20 +2043,7 @@
     if (!metadata) {
         return false;
     }
-    for (const auto& partition : metadata->partitions) {
-        auto candidate = android::fs_mgr::GetPartitionName(partition);
-        if (partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED) {
-            // On retrofit devices, we don't know if, or whether, the A or B
-            // slot has been flashed for dynamic partitions. Instead we add
-            // both names to the list as a conservative guess.
-            if (candidate + "_a" == partition_name || candidate + "_b" == partition_name) {
-                return true;
-            }
-        } else if (candidate == partition_name) {
-            return true;
-        }
-    }
-    return false;
+    return should_flash_in_userspace(*metadata.get(), partition_name);
 }
 
 static bool wipe_super(const android::fs_mgr::LpMetadata& metadata, const std::string& slot,
@@ -1868,7 +2116,28 @@
     }
 }
 
+static void FastbootLogger(android::base::LogId /* id */, android::base::LogSeverity severity,
+                           const char* /* tag */, const char* /* file */, unsigned int /* line */,
+                           const char* message) {
+    switch (severity) {
+        case android::base::INFO:
+            fprintf(stdout, "%s\n", message);
+            break;
+        case android::base::ERROR:
+            fprintf(stderr, "%s\n", message);
+            break;
+        default:
+            verbose("%s\n", message);
+    }
+}
+
+static void FastbootAborter(const char* message) {
+    die("%s", message);
+}
+
 int FastBootTool::Main(int argc, char* argv[]) {
+    android::base::InitLogging(argv, FastbootLogger, FastbootAborter);
+
     bool wants_wipe = false;
     bool wants_reboot = false;
     bool wants_reboot_bootloader = false;
@@ -2012,6 +2281,18 @@
         return 0;
     }
 
+    if (argc > 0 && !strcmp(*argv, "connect")) {
+        argc -= optind;
+        argv += optind;
+        return Connect(argc, argv);
+    }
+
+    if (argc > 0 && !strcmp(*argv, "disconnect")) {
+        argc -= optind;
+        argv += optind;
+        return Disconnect(argc, argv);
+    }
+
     if (argc > 0 && !strcmp(*argv, "help")) {
         return show_help();
     }
@@ -2137,7 +2418,6 @@
             fb->Boot();
         } else if (command == FB_CMD_FLASH) {
             std::string pname = next_arg(&args);
-
             std::string fname;
             if (!args.empty()) {
                 fname = next_arg(&args);
@@ -2145,21 +2425,8 @@
                 fname = find_item(pname);
             }
             if (fname.empty()) die("cannot determine image filename for '%s'", pname.c_str());
-
-            auto flash = [&](const std::string& partition) {
-                if (should_flash_in_userspace(partition) && !is_userspace_fastboot() &&
-                    !force_flash) {
-                    die("The partition you are trying to flash is dynamic, and "
-                        "should be flashed via fastbootd. Please run:\n"
-                        "\n"
-                        "    fastboot reboot fastboot\n"
-                        "\n"
-                        "And try again. If you are intentionally trying to "
-                        "overwrite a fixed partition, use --force.");
-                }
-                do_flash(partition.c_str(), fname.c_str());
-            };
-            do_for_partitions(pname, slot_override, flash, true);
+            FlashTask task(slot_override, force_flash, pname, fname);
+            task.Run();
         } else if (command == "flash:raw") {
             std::string partition = next_arg(&args);
             std::string kernel = next_arg(&args);
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index c23793a..b5fb8c0 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -25,6 +25,9 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#pragma once
+
+#include <string>
 
 #include <bootimg.h>
 
@@ -36,3 +39,12 @@
     void ParseOsVersion(boot_img_hdr_v1*, const char*);
     unsigned ParseFsOption(const char*);
 };
+
+bool should_flash_in_userspace(const std::string& partition_name);
+bool is_userspace_fastboot();
+void do_flash(const char* pname, const char* fname);
+void do_for_partitions(const std::string& part, const std::string& slot,
+                       const std::function<void(const std::string&)>& func, bool force_slot);
+std::string find_item(const std::string& item);
+void reboot_to_userspace_fastboot();
+void syntax_error(const char* fmt, ...);
diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h
index bccd668..b422c91 100644
--- a/fastboot/fastboot_driver.h
+++ b/fastboot/fastboot_driver.h
@@ -32,6 +32,7 @@
 #include <string>
 #include <vector>
 
+#include <android-base/endian.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
diff --git a/fastboot/fastboot_driver_test.cpp b/fastboot/fastboot_driver_test.cpp
new file mode 100644
index 0000000..e874c3a
--- /dev/null
+++ b/fastboot/fastboot_driver_test.cpp
@@ -0,0 +1,60 @@
+//
+// 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.
+//
+
+#include "fastboot_driver.h"
+
+#include <optional>
+
+#include <gtest/gtest.h>
+#include "mock_transport.h"
+
+using namespace ::testing;
+using namespace fastboot;
+
+class DriverTest : public ::testing::Test {
+  protected:
+    InSequence s_;
+};
+
+TEST_F(DriverTest, GetVar) {
+    MockTransport transport;
+    FastBootDriver driver(&transport);
+
+    EXPECT_CALL(transport, Write(_, _))
+            .With(AllArgs(RawData("getvar:version")))
+            .WillOnce(ReturnArg<1>());
+    EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY0.4")));
+
+    std::string output;
+    ASSERT_EQ(driver.GetVar("version", &output), SUCCESS) << driver.Error();
+    ASSERT_EQ(output, "0.4");
+}
+
+TEST_F(DriverTest, InfoMessage) {
+    MockTransport transport;
+    FastBootDriver driver(&transport);
+
+    EXPECT_CALL(transport, Write(_, _))
+            .With(AllArgs(RawData("oem dmesg")))
+            .WillOnce(ReturnArg<1>());
+    EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("INFOthis is an info line")));
+    EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY")));
+
+    std::vector<std::string> info;
+    ASSERT_EQ(driver.RawCommand("oem dmesg", "", nullptr, &info), SUCCESS) << driver.Error();
+    ASSERT_EQ(info.size(), size_t(1));
+    ASSERT_EQ(info[0], "this is an info line");
+}
diff --git a/fastboot/fastboot_test.cpp b/fastboot/fastboot_test.cpp
index 9c3ab6e..79f37fd 100644
--- a/fastboot/fastboot_test.cpp
+++ b/fastboot/fastboot_test.cpp
@@ -16,6 +16,7 @@
 
 #include "fastboot.h"
 
+#include <android-base/logging.h>
 #include <gtest/gtest.h>
 
 TEST(FastBoot, ParseOsPatchLevel) {
@@ -201,3 +202,11 @@
     // No spaces allowed before between require-for-product and :.
     ParseRequirementLineTestMalformed("require-for-product :");
 }
+
+int main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    android::base::InitLogging(argv);
+    android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+
+    return RUN_ALL_TESTS();
+}
diff --git a/fastboot/filesystem.cpp b/fastboot/filesystem.cpp
new file mode 100644
index 0000000..94fde8e
--- /dev/null
+++ b/fastboot/filesystem.cpp
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+#ifdef _WIN32
+#include <android-base/utf8.h>
+#include <direct.h>
+#include <shlobj.h>
+#else
+#include <pwd.h>
+#endif
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "filesystem.h"
+
+namespace {
+
+int LockFile(int fd) {
+#ifdef _WIN32
+    HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
+    OVERLAPPED overlapped = {};
+    const BOOL locked =
+            LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &overlapped);
+    return locked ? 0 : -1;
+#else
+    return flock(fd, LOCK_EX);
+#endif
+}
+
+}  // namespace
+
+// inspired by adb implementation:
+// cs.android.com/android/platform/superproject/+/master:packages/modules/adb/adb_utils.cpp;l=275
+std::string GetHomeDirPath() {
+#ifdef _WIN32
+    WCHAR path[MAX_PATH];
+    const HRESULT hr = SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, path);
+    if (FAILED(hr)) {
+        return {};
+    }
+    std::string home_str;
+    if (!android::base::WideToUTF8(path, &home_str)) {
+        return {};
+    }
+    return home_str;
+#else
+    if (const char* const home = getenv("HOME")) {
+        return home;
+    }
+
+    struct passwd pwent;
+    struct passwd* result;
+    int pwent_max = sysconf(_SC_GETPW_R_SIZE_MAX);
+    if (pwent_max == -1) {
+        pwent_max = 16384;
+    }
+    std::vector<char> buf(pwent_max);
+    int rc = getpwuid_r(getuid(), &pwent, buf.data(), buf.size(), &result);
+    if (rc == 0 && result) {
+        return result->pw_dir;
+    }
+#endif
+
+    return {};
+}
+
+bool FileExists(const std::string& path) {
+    return access(path.c_str(), F_OK) == 0;
+}
+
+bool EnsureDirectoryExists(const std::string& directory_path) {
+    const int result =
+#ifdef _WIN32
+            _mkdir(directory_path.c_str());
+#else
+            mkdir(directory_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+#endif
+
+    return result == 0 || errno == EEXIST;
+}
+
+FileLock::FileLock(const std::string& path) : fd_(open(path.c_str(), O_CREAT | O_WRONLY, 0644)) {
+    if (LockFile(fd_.get()) != 0) {
+        LOG(FATAL) << "Failed to acquire a lock on " << path;
+    }
+}
\ No newline at end of file
diff --git a/fastboot/filesystem.h b/fastboot/filesystem.h
new file mode 100644
index 0000000..5f41fbc
--- /dev/null
+++ b/fastboot/filesystem.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/unique_fd.h>
+
+#include <string>
+
+using android::base::unique_fd;
+
+// TODO(b/175635923): remove after enabling libc++fs for windows
+const char kPathSeparator =
+#ifdef _WIN32
+        '\\';
+#else
+        '/';
+#endif
+
+std::string GetHomeDirPath();
+bool EnsureDirectoryExists(const std::string& directory_path);
+
+class FileLock {
+  public:
+    FileLock() = delete;
+    FileLock(const std::string& path);
+
+  private:
+    unique_fd fd_;
+};
\ No newline at end of file
diff --git a/fastboot/mock_transport.h b/fastboot/mock_transport.h
new file mode 100644
index 0000000..cc3840c
--- /dev/null
+++ b/fastboot/mock_transport.h
@@ -0,0 +1,67 @@
+//
+// 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.
+//
+
+#pragma once
+
+#include <string.h>
+
+#include <algorithm>
+#include <string_view>
+
+#include <gmock/gmock.h>
+#include "transport.h"
+
+class MockTransport : public Transport {
+  public:
+    MOCK_METHOD(ssize_t, Read, (void* data, size_t len), (override));
+    MOCK_METHOD(ssize_t, Write, (const void* data, size_t len), (override));
+    MOCK_METHOD(int, Close, (), (override));
+    MOCK_METHOD(int, Reset, (), (override));
+};
+
+class RawDataMatcher {
+  public:
+    explicit RawDataMatcher(const char* data) : data_(data) {}
+    explicit RawDataMatcher(std::string_view data) : data_(data) {}
+
+    bool MatchAndExplain(std::tuple<const void*, size_t> args,
+                         ::testing::MatchResultListener*) const {
+        const void* expected_data = std::get<0>(args);
+        size_t expected_len = std::get<1>(args);
+        if (expected_len != data_.size()) {
+            return false;
+        }
+        return memcmp(expected_data, data_.data(), expected_len) == 0;
+    }
+    void DescribeTo(std::ostream* os) const { *os << "raw data is"; }
+    void DescribeNegationTo(std::ostream* os) const { *os << "raw data is not"; }
+
+  private:
+    std::string_view data_;
+};
+
+template <typename T>
+static inline ::testing::PolymorphicMatcher<RawDataMatcher> RawData(T data) {
+    return ::testing::MakePolymorphicMatcher(RawDataMatcher(data));
+}
+
+static inline auto CopyData(const char* source) {
+    return [source](void* buffer, size_t size) -> ssize_t {
+        size_t to_copy = std::min(size, strlen(source));
+        memcpy(buffer, source, to_copy);
+        return to_copy;
+    };
+};
diff --git a/fastboot/storage.cpp b/fastboot/storage.cpp
new file mode 100644
index 0000000..d6e00cf
--- /dev/null
+++ b/fastboot/storage.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+#include <fstream>
+
+#include "storage.h"
+#include "util.h"
+
+ConnectedDevicesStorage::ConnectedDevicesStorage() {
+    const std::string home_path = GetHomeDirPath();
+    if (home_path.empty()) {
+        return;
+    }
+
+    const std::string home_fastboot_path = home_path + kPathSeparator + ".fastboot";
+
+    if (!EnsureDirectoryExists(home_fastboot_path)) {
+        LOG(FATAL) << "Cannot create directory: " << home_fastboot_path;
+    }
+
+    // We're using a separate file for locking because the Windows LockFileEx does not
+    // permit opening a file stream for the locked file, even within the same process. So,
+    // we have to use fd or handle API to manipulate the storage files, which makes it
+    // nearly impossible to fully rewrite a file content without having to recreate it.
+    // Unfortunately, this is not an option during holding a lock.
+    devices_path_ = home_fastboot_path + kPathSeparator + "devices";
+    devices_lock_path_ = home_fastboot_path + kPathSeparator + "devices.lock";
+}
+
+void ConnectedDevicesStorage::WriteDevices(const FileLock&, const std::set<std::string>& devices) {
+    std::ofstream devices_stream(devices_path_);
+    std::copy(devices.begin(), devices.end(),
+              std::ostream_iterator<std::string>(devices_stream, "\n"));
+}
+
+std::set<std::string> ConnectedDevicesStorage::ReadDevices(const FileLock&) {
+    std::ifstream devices_stream(devices_path_);
+    std::istream_iterator<std::string> start(devices_stream), end;
+    std::set<std::string> devices(start, end);
+    return devices;
+}
+
+void ConnectedDevicesStorage::Clear(const FileLock&) {
+    if (!android::base::RemoveFileIfExists(devices_path_)) {
+        LOG(FATAL) << "Failed to clear connected device list: " << devices_path_;
+    }
+}
+
+FileLock ConnectedDevicesStorage::Lock() const {
+    return FileLock(devices_lock_path_);
+}
\ No newline at end of file
diff --git a/fastboot/storage.h b/fastboot/storage.h
new file mode 100644
index 0000000..0cc3d86
--- /dev/null
+++ b/fastboot/storage.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <set>
+#include <string>
+
+#include "filesystem.h"
+
+class ConnectedDevicesStorage {
+  public:
+    ConnectedDevicesStorage();
+    void WriteDevices(const FileLock&, const std::set<std::string>& devices);
+    std::set<std::string> ReadDevices(const FileLock&);
+    void Clear(const FileLock&);
+
+    FileLock Lock() const;
+
+  private:
+    std::string devices_path_;
+    std::string devices_lock_path_;
+};
\ No newline at end of file
diff --git a/fastboot/super_flash_helper.cpp b/fastboot/super_flash_helper.cpp
new file mode 100644
index 0000000..b617ce8
--- /dev/null
+++ b/fastboot/super_flash_helper.cpp
@@ -0,0 +1,125 @@
+//
+// 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.
+//
+
+#include "super_flash_helper.h"
+
+#include <android-base/logging.h>
+
+#include "util.h"
+
+using android::base::borrowed_fd;
+using android::base::unique_fd;
+using android::fs_mgr::SuperImageExtent;
+
+SuperFlashHelper::SuperFlashHelper(const ImageSource& source) : source_(source) {}
+
+bool SuperFlashHelper::Open(borrowed_fd fd) {
+    if (!builder_.Open(fd)) {
+        LOG(VERBOSE) << "device does not support optimized super flashing";
+        return false;
+    }
+
+    base_metadata_ = builder_.Export();
+    return !!base_metadata_;
+}
+
+bool SuperFlashHelper::IncludeInSuper(const std::string& partition) {
+    return should_flash_in_userspace(*base_metadata_.get(), partition);
+}
+
+bool SuperFlashHelper::AddPartition(const std::string& partition, const std::string& image_name,
+                                    bool optional) {
+    if (!IncludeInSuper(partition)) {
+        return true;
+    }
+    auto iter = image_fds_.find(image_name);
+    if (iter == image_fds_.end()) {
+        unique_fd fd = source_.OpenFile(image_name);
+        if (fd < 0) {
+            if (!optional) {
+                LOG(VERBOSE) << "could not find partition image: " << image_name;
+                return false;
+            }
+            return true;
+        }
+        if (is_sparse_file(fd)) {
+            LOG(VERBOSE) << "cannot optimize dynamic partitions with sparse images";
+            return false;
+        }
+        iter = image_fds_.emplace(image_name, std::move(fd)).first;
+    }
+
+    if (!builder_.AddPartition(partition, image_name, get_file_size(iter->second))) {
+        return false;
+    }
+
+    will_flash_.emplace(partition);
+    return true;
+}
+
+SparsePtr SuperFlashHelper::GetSparseLayout() {
+    // Cache extents since the sparse ptr depends on data pointers.
+    if (extents_.empty()) {
+        extents_ = builder_.GetImageLayout();
+        if (extents_.empty()) {
+            LOG(VERBOSE) << "device does not support optimized super flashing";
+            return {nullptr, nullptr};
+        }
+    }
+
+    unsigned int block_size = base_metadata_->geometry.logical_block_size;
+    int64_t flashed_size = extents_.back().offset + extents_.back().size;
+    SparsePtr s(sparse_file_new(block_size, flashed_size), sparse_file_destroy);
+
+    for (const auto& extent : extents_) {
+        if (extent.offset / block_size > UINT_MAX) {
+            // Super image is too big to send via sparse files (>8TB).
+            LOG(VERBOSE) << "super image is too big to flash";
+            return {nullptr, nullptr};
+        }
+        unsigned int block = extent.offset / block_size;
+
+        int rv = 0;
+        switch (extent.type) {
+            case SuperImageExtent::Type::DONTCARE:
+                break;
+            case SuperImageExtent::Type::ZERO:
+                rv = sparse_file_add_fill(s.get(), 0, extent.size, block);
+                break;
+            case SuperImageExtent::Type::DATA:
+                rv = sparse_file_add_data(s.get(), extent.blob->data(), extent.size, block);
+                break;
+            case SuperImageExtent::Type::PARTITION: {
+                auto iter = image_fds_.find(extent.image_name);
+                if (iter == image_fds_.end()) {
+                    LOG(FATAL) << "image added but not found: " << extent.image_name;
+                    return {nullptr, nullptr};
+                }
+                rv = sparse_file_add_fd(s.get(), iter->second.get(), extent.image_offset,
+                                        extent.size, block);
+                break;
+            }
+            default:
+                LOG(VERBOSE) << "unrecognized extent type in super image layout";
+                return {nullptr, nullptr};
+        }
+        if (rv) {
+            LOG(VERBOSE) << "sparse failure building super image layout";
+            return {nullptr, nullptr};
+        }
+    }
+    return s;
+}
diff --git a/fastboot/super_flash_helper.h b/fastboot/super_flash_helper.h
new file mode 100644
index 0000000..29c15d0
--- /dev/null
+++ b/fastboot/super_flash_helper.h
@@ -0,0 +1,56 @@
+//
+// 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.
+//
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <android-base/unique_fd.h>
+#include <liblp/liblp.h>
+#include <liblp/super_layout_builder.h>
+
+#include "util.h"
+
+class SuperFlashHelper final {
+  public:
+    explicit SuperFlashHelper(const ImageSource& source);
+
+    bool Open(android::base::borrowed_fd fd);
+    bool IncludeInSuper(const std::string& partition);
+    bool AddPartition(const std::string& partition, const std::string& image_name, bool optional);
+
+    // Note: the SparsePtr if non-null should not outlive SuperFlashHelper, since
+    // it depends on open fds and data pointers.
+    SparsePtr GetSparseLayout();
+
+    bool WillFlash(const std::string& partition) const {
+        return will_flash_.find(partition) != will_flash_.end();
+    }
+
+  private:
+    const ImageSource& source_;
+    android::fs_mgr::SuperLayoutBuilder builder_;
+    std::unique_ptr<android::fs_mgr::LpMetadata> base_metadata_;
+    std::vector<android::fs_mgr::SuperImageExtent> extents_;
+
+    // Cache open image fds. This keeps them alive while we flash the sparse
+    // file.
+    std::unordered_map<std::string, android::base::unique_fd> image_fds_;
+    std::unordered_set<std::string> will_flash_;
+};
diff --git a/fastboot/super_flash_helper_test.cpp b/fastboot/super_flash_helper_test.cpp
new file mode 100644
index 0000000..82b8aa5
--- /dev/null
+++ b/fastboot/super_flash_helper_test.cpp
@@ -0,0 +1,88 @@
+//
+// 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.
+//
+
+#include "super_flash_helper.h"
+
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <sparse/sparse.h>
+
+using android::base::unique_fd;
+
+unique_fd OpenTestFile(const std::string& file, int flags) {
+    std::string path = "testdata/" + file;
+
+    unique_fd fd(open(path.c_str(), flags));
+    if (fd >= 0) {
+        return fd;
+    }
+
+    path = android::base::GetExecutableDirectory() + "/" + path;
+    return unique_fd{open(path.c_str(), flags)};
+}
+
+class TestImageSource final : public ImageSource {
+  public:
+    bool ReadFile(const std::string&, std::vector<char>*) const override {
+        // Not used here.
+        return false;
+    }
+    unique_fd OpenFile(const std::string& name) const override {
+        return OpenTestFile(name, O_RDONLY | O_CLOEXEC);
+    }
+};
+
+TEST(SuperFlashHelper, ImageEquality) {
+    auto super_empty_fd = OpenTestFile("super_empty.img", O_RDONLY);
+    ASSERT_GE(super_empty_fd, 0);
+
+    TestImageSource source;
+    SuperFlashHelper helper(source);
+    ASSERT_TRUE(helper.Open(super_empty_fd));
+    ASSERT_TRUE(helper.AddPartition("system_a", "system.img", false));
+
+    auto sparse_file = helper.GetSparseLayout();
+    ASSERT_NE(sparse_file, nullptr);
+
+    TemporaryFile fb_super;
+    ASSERT_GE(fb_super.fd, 0);
+    ASSERT_EQ(sparse_file_write(sparse_file.get(), fb_super.fd, false, false, false), 0);
+
+    auto real_super_fd = OpenTestFile("super.img", O_RDONLY);
+    ASSERT_GE(real_super_fd, 0);
+
+    std::string expected(get_file_size(real_super_fd), '\0');
+    ASSERT_FALSE(expected.empty());
+    ASSERT_TRUE(android::base::ReadFully(real_super_fd, expected.data(), expected.size()));
+
+    std::string actual(get_file_size(fb_super.fd), '\0');
+    ASSERT_FALSE(actual.empty());
+    ASSERT_EQ(lseek(fb_super.fd, 0, SEEK_SET), 0);
+    ASSERT_TRUE(android::base::ReadFully(fb_super.fd, actual.data(), actual.size()));
+
+    // The helper doesn't add any extra zeroes to the image, whereas lpmake does, to
+    // pad to the entire super size.
+    ASSERT_LE(actual.size(), expected.size());
+    for (size_t i = 0; i < actual.size(); i++) {
+        ASSERT_EQ(actual[i], expected[i]) << "byte mismatch at position " << i;
+    }
+    for (size_t i = actual.size(); i < expected.size(); i++) {
+        ASSERT_EQ(expected[i], 0) << "byte mismatch at position " << i;
+    }
+}
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
new file mode 100644
index 0000000..3f33c76
--- /dev/null
+++ b/fastboot/task.cpp
@@ -0,0 +1,46 @@
+//
+// 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.
+//
+#include "task.h"
+
+#include "fastboot.h"
+#include "util.h"
+
+FlashTask::FlashTask(const std::string& _slot) : slot_(_slot){};
+FlashTask::FlashTask(const std::string& _slot, bool _force_flash)
+    : slot_(_slot), force_flash_(_force_flash) {}
+FlashTask::FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname)
+    : pname_(_pname), fname_(find_item(_pname)), slot_(_slot), force_flash_(_force_flash) {
+    if (fname_.empty()) die("cannot determine image filename for '%s'", pname_.c_str());
+}
+FlashTask::FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname,
+                     const std::string& _fname)
+    : pname_(_pname), fname_(_fname), slot_(_slot), force_flash_(_force_flash) {}
+
+void FlashTask::Run() {
+    auto flash = [&](const std::string& partition) {
+        if (should_flash_in_userspace(partition) && !is_userspace_fastboot() && !force_flash_) {
+            die("The partition you are trying to flash is dynamic, and "
+                "should be flashed via fastbootd. Please run:\n"
+                "\n"
+                "    fastboot reboot fastboot\n"
+                "\n"
+                "And try again. If you are intentionally trying to "
+                "overwrite a fixed partition, use --force.");
+        }
+        do_flash(partition.c_str(), fname_.c_str());
+    };
+    do_for_partitions(pname_, slot_, flash, true);
+}
diff --git a/fastboot/task.h b/fastboot/task.h
new file mode 100644
index 0000000..216e658
--- /dev/null
+++ b/fastboot/task.h
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#pragma once
+
+#include <sstream>
+#include <string>
+
+#include "fastboot.h"
+#include "fastboot_driver.h"
+#include "util.h"
+
+class Task {
+  public:
+    Task() = default;
+    virtual void Run() = 0;
+    virtual ~Task() = default;
+};
+
+class FlashTask : public Task {
+  public:
+    FlashTask(const std::string& _slot);
+    FlashTask(const std::string& _slot, bool _force_flash);
+    FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname);
+    FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname,
+              const std::string& _fname);
+
+    void Run() override;
+    ~FlashTask() {}
+
+  private:
+    const std::string pname_;
+    const std::string fname_;
+    const std::string slot_;
+    bool force_flash_ = false;
+};
diff --git a/fastboot/testdata/make_super_images.sh b/fastboot/testdata/make_super_images.sh
new file mode 100644
index 0000000..71c54ee
--- /dev/null
+++ b/fastboot/testdata/make_super_images.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e
+set -x
+
+lpmake \
+    --device-size=auto \
+    --metadata-size=4096 \
+    --metadata-slots=3 \
+    --partition=system_a:readonly:0 \
+    --alignment=16384 \
+    --output=super_empty.img
+
+lpmake \
+    --device-size=auto \
+    --metadata-size=4096 \
+    --metadata-slots=3 \
+    --partition=system_a:readonly:0 \
+    --alignment=16384 \
+    --output=super.img \
+    --image=system_a=system.img
diff --git a/fastboot/testdata/super.img b/fastboot/testdata/super.img
new file mode 100644
index 0000000..be13d36
--- /dev/null
+++ b/fastboot/testdata/super.img
Binary files differ
diff --git a/fastboot/testdata/super_empty.img b/fastboot/testdata/super_empty.img
new file mode 100644
index 0000000..4b25869
--- /dev/null
+++ b/fastboot/testdata/super_empty.img
Binary files differ
diff --git a/fastboot/testdata/system.img b/fastboot/testdata/system.img
new file mode 100644
index 0000000..b360610
--- /dev/null
+++ b/fastboot/testdata/system.img
Binary files differ
diff --git a/fastboot/usb.h b/fastboot/usb.h
index e5f56e2..69581ab 100644
--- a/fastboot/usb.h
+++ b/fastboot/usb.h
@@ -28,6 +28,8 @@
 
 #pragma once
 
+#include <functional>
+
 #include "transport.h"
 
 struct usb_ifc_info {
@@ -61,7 +63,7 @@
     virtual int Reset() = 0;
 };
 
-typedef int (*ifc_match_func)(usb_ifc_info *ifc);
+typedef std::function<int(usb_ifc_info*)> ifc_match_func;
 
 // 0 is non blocking
 UsbTransport* usb_open(ifc_match_func callback, uint32_t timeout_ms = 0);
diff --git a/fastboot/util.cpp b/fastboot/util.cpp
index 900d6ea..ded54a5 100644
--- a/fastboot/util.cpp
+++ b/fastboot/util.cpp
@@ -30,11 +30,13 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-
+#include <sys/stat.h>
 #include <sys/time.h>
 
 #include "util.h"
 
+using android::base::borrowed_fd;
+
 static bool g_verbose = false;
 
 double now() {
@@ -73,3 +75,34 @@
     }
     fprintf(stderr, "\n");
 }
+
+bool should_flash_in_userspace(const android::fs_mgr::LpMetadata& metadata,
+                               const std::string& partition_name) {
+    for (const auto& partition : metadata.partitions) {
+        auto candidate = android::fs_mgr::GetPartitionName(partition);
+        if (partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED) {
+            // On retrofit devices, we don't know if, or whether, the A or B
+            // slot has been flashed for dynamic partitions. Instead we add
+            // both names to the list as a conservative guess.
+            if (candidate + "_a" == partition_name || candidate + "_b" == partition_name) {
+                return true;
+            }
+        } else if (candidate == partition_name) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool is_sparse_file(borrowed_fd fd) {
+    SparsePtr s(sparse_file_import(fd.get(), false, false), sparse_file_destroy);
+    return !!s;
+}
+
+int64_t get_file_size(borrowed_fd fd) {
+    struct stat sb;
+    if (fstat(fd.get(), &sb) == -1) {
+        die("could not get file size");
+    }
+    return sb.st_size;
+}
diff --git a/fastboot/util.h b/fastboot/util.h
index c719df2..bc01473 100644
--- a/fastboot/util.h
+++ b/fastboot/util.h
@@ -4,8 +4,24 @@
 #include <stdlib.h>
 
 #include <string>
+#include <vector>
 
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
 #include <bootimg.h>
+#include <liblp/liblp.h>
+#include <sparse/sparse.h>
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::ResultError;
+
+#define EXPECT(result) \
+    (result.ok() ? result.value() : (LOG(FATAL) << result.error().message(), result.value()))
+
+using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>;
 
 /* util stuff */
 double now();
@@ -19,3 +35,15 @@
 void verbose(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2)));
 
 void die(const std::string& str) __attribute__((__noreturn__));
+
+bool should_flash_in_userspace(const android::fs_mgr::LpMetadata& metadata,
+                               const std::string& partition_name);
+bool is_sparse_file(android::base::borrowed_fd fd);
+int64_t get_file_size(android::base::borrowed_fd fd);
+
+class ImageSource {
+  public:
+    virtual ~ImageSource(){};
+    virtual bool ReadFile(const std::string& name, std::vector<char>* out) const = 0;
+    virtual android::base::unique_fd OpenFile(const std::string& name) const = 0;
+};
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index 2b5e337..b6710d5 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -22,6 +22,12 @@
       "name": "vts_libsnapshot_test"
     },
     {
+      "name": "vab_legacy_tests"
+    },
+    {
+      "name": "vabc_legacy_tests"
+    },
+    {
       "name": "libsnapshot_fuzzer_test"
     },
     {
@@ -37,6 +43,12 @@
     },
     {
       "name": "vts_libsnapshot_test"
+    },
+    {
+      "name": "vab_legacy_tests"
+    },
+    {
+      "name": "vabc_legacy_tests"
     }
   ]
 }
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 1c1ab48..742cdfa 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -1176,6 +1176,10 @@
                     return false;
                 }
 
+                // dm-bow will not load if size is not a multiple of 4096
+                // rounding down does not hurt, since ext4 will only use full blocks
+                size &= ~7;
+
                 android::dm::DmTable table;
                 auto bowTarget =
                         std::make_unique<android::dm::DmTargetBow>(0, size, entry->blk_device);
diff --git a/fs_mgr/fs_mgr_priv.h b/fs_mgr/fs_mgr_priv.h
index 46f54cc..46cdb62 100644
--- a/fs_mgr/fs_mgr_priv.h
+++ b/fs_mgr/fs_mgr_priv.h
@@ -33,7 +33,7 @@
  */
 #define FS_MGR_CHECK(x) CHECK(x) << "in libfs_mgr "
 
-#define FS_MGR_TAG "[libfs_mgr]"
+#define FS_MGR_TAG "[libfs_mgr] "
 
 // Logs a message to kernel
 #define LINFO    LOG(INFO) << FS_MGR_TAG
diff --git a/fs_mgr/libdm/Android.bp b/fs_mgr/libdm/Android.bp
index 2bb9035..5cc0346 100644
--- a/fs_mgr/libdm/Android.bp
+++ b/fs_mgr/libdm/Android.bp
@@ -90,19 +90,3 @@
         min_shipping_api_level: 29,
     },
 }
-
-cc_fuzz {
-    name: "dm_linear_table_fuzzer",
-    defaults: ["fs_mgr_defaults"],
-    srcs: [
-        "dm_linear_fuzzer.cpp",
-        "test_util.cpp",
-    ],
-    static_libs: [
-        "libdm",
-        "libbase",
-        "libext2_uuid",
-        "libfs_mgr",
-        "liblog",
-    ],
-}
diff --git a/fs_mgr/libdm/dm_linear_fuzzer.cpp b/fs_mgr/libdm/dm_linear_fuzzer.cpp
deleted file mode 100644
index 8462901..0000000
--- a/fs_mgr/libdm/dm_linear_fuzzer.cpp
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stddef.h>
-#include <stdint.h>
-#include <string.h>
-
-#include <chrono>
-
-#include <android-base/file.h>
-#include <android-base/unique_fd.h>
-#include <libdm/dm_table.h>
-#include <libdm/loop_control.h>
-
-#include "test_util.h"
-
-using namespace android;
-using namespace android::base;
-using namespace android::dm;
-using namespace std;
-using namespace std::chrono_literals;
-
-/*
- * This test aims at making the library crash, so these functions are not
- * really useful.
- * Keeping them here for future use.
- */
-template <class T, class C>
-void ASSERT_EQ(const T& /*a*/, const C& /*b*/) {
-    // if (a != b) {}
-}
-
-template <class T>
-void ASSERT_FALSE(const T& /*a*/) {
-    // if (a) {}
-}
-
-template <class T, class C>
-void ASSERT_GE(const T& /*a*/, const C& /*b*/) {
-    // if (a < b) {}
-}
-
-template <class T, class C>
-void ASSERT_NE(const T& /*a*/, const C& /*b*/) {
-    // if (a == b) {}
-}
-
-template <class T>
-void ASSERT_TRUE(const T& /*a*/) {
-    // if (!a) {}
-}
-
-template <class T, class C>
-void EXPECT_EQ(const T& a, const C& b) {
-    ASSERT_EQ(a, b);
-}
-
-template <class T>
-void EXPECT_TRUE(const T& a) {
-    ASSERT_TRUE(a);
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    uint64_t val[6];
-
-    if (size != sizeof(val)) {
-        return 0;
-    }
-
-    memcpy(&val, &data[0], sizeof(*val));
-
-    unique_fd tmp1(CreateTempFile("file_1", 4096));
-    ASSERT_GE(tmp1, 0);
-    unique_fd tmp2(CreateTempFile("file_2", 4096));
-    ASSERT_GE(tmp2, 0);
-
-    LoopDevice loop_a(tmp1, 10s);
-    ASSERT_TRUE(loop_a.valid());
-    LoopDevice loop_b(tmp2, 10s);
-    ASSERT_TRUE(loop_b.valid());
-
-    // Define a 2-sector device, with each sector mapping to the first sector
-    // of one of our loop devices.
-    DmTable table;
-    ASSERT_TRUE(table.Emplace<DmTargetLinear>(val[0], val[1], loop_a.device(), val[2]));
-    ASSERT_TRUE(table.Emplace<DmTargetLinear>(val[3], val[4], loop_b.device(), val[5]));
-    ASSERT_TRUE(table.valid());
-    ASSERT_EQ(2u, table.num_sectors());
-
-    TempDevice dev("libdm-test-dm-linear", table);
-    ASSERT_TRUE(dev.valid());
-    ASSERT_FALSE(dev.path().empty());
-
-    auto& dm = DeviceMapper::Instance();
-
-    dev_t dev_number;
-    ASSERT_TRUE(dm.GetDeviceNumber(dev.name(), &dev_number));
-    ASSERT_NE(dev_number, 0);
-
-    std::string dev_string;
-    ASSERT_TRUE(dm.GetDeviceString(dev.name(), &dev_string));
-    ASSERT_FALSE(dev_string.empty());
-
-    // Test GetTableStatus.
-    vector<DeviceMapper::TargetInfo> targets;
-    ASSERT_TRUE(dm.GetTableStatus(dev.name(), &targets));
-    ASSERT_EQ(targets.size(), 2);
-    EXPECT_EQ(strcmp(targets[0].spec.target_type, "linear"), 0);
-    EXPECT_TRUE(targets[0].data.empty());
-    EXPECT_EQ(targets[0].spec.sector_start, 0);
-    EXPECT_EQ(targets[0].spec.length, 1);
-    EXPECT_EQ(strcmp(targets[1].spec.target_type, "linear"), 0);
-    EXPECT_TRUE(targets[1].data.empty());
-    EXPECT_EQ(targets[1].spec.sector_start, 1);
-    EXPECT_EQ(targets[1].spec.length, 1);
-
-    // Test GetTargetType().
-    EXPECT_EQ(DeviceMapper::GetTargetType(targets[0].spec), std::string{"linear"});
-    EXPECT_EQ(DeviceMapper::GetTargetType(targets[1].spec), std::string{"linear"});
-
-    // Normally the TestDevice destructor would delete this, but at least one
-    // test should ensure that device deletion works.
-    ASSERT_TRUE(dev.Destroy());
-
-    return 0;
-}
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index 4448d35..788cf51 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -19,6 +19,7 @@
 #include <stdio.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
+#include <sys/utsname.h>
 #include <time.h>
 #include <unistd.h>
 
@@ -692,7 +693,17 @@
 }
 
 TEST(libdm, UeventAfterLoadTable) {
-    static const char* kDeviceName = "libmd-test-uevent-load-table";
+    static const char* kDeviceName = "libdm-test-uevent-load-table";
+
+    struct utsname u;
+    ASSERT_EQ(uname(&u), 0);
+
+    unsigned int major, minor;
+    ASSERT_EQ(sscanf(u.release, "%u.%u", &major, &minor), 2);
+
+    if (major < 5 || (major == 5 && minor < 15)) {
+        GTEST_SKIP() << "Skipping test on kernel < 5.15";
+    }
 
     DeviceMapper& dm = DeviceMapper::Instance();
     ASSERT_TRUE(dm.CreateEmptyDevice(kDeviceName));
diff --git a/fs_mgr/libfiemap/metadata.cpp b/fs_mgr/libfiemap/metadata.cpp
index b0dfb5c..22b8afb 100644
--- a/fs_mgr/libfiemap/metadata.cpp
+++ b/fs_mgr/libfiemap/metadata.cpp
@@ -30,6 +30,7 @@
 namespace fiemap {
 
 using namespace android::fs_mgr;
+using android::base::unique_fd;
 
 static constexpr uint32_t kMaxMetadataSize = 256 * 1024;
 
@@ -109,10 +110,18 @@
     if (exported->partitions.empty() && android::base::RemoveFileIfExists(metadata_file)) {
         return true;
     }
-    if (!WriteToImageFile(metadata_file, *exported.get())) {
+
+    unique_fd fd(open(metadata_file.c_str(), O_CREAT | O_RDWR | O_TRUNC | O_CLOEXEC | O_BINARY | O_SYNC, 0644));
+    if (fd < 0) {
+        LOG(ERROR) << "open failed: " << metadata_file;
+        return false;
+    }
+
+    if (!WriteToImageFile(fd, *exported.get())) {
         LOG(ERROR) << "Unable to save new metadata";
         return false;
     }
+
     return true;
 }
 
diff --git a/fs_mgr/libfs_avb/avb_ops.cpp b/fs_mgr/libfs_avb/avb_ops.cpp
index 46072bb..a119bfc 100644
--- a/fs_mgr/libfs_avb/avb_ops.cpp
+++ b/fs_mgr/libfs_avb/avb_ops.cpp
@@ -26,8 +26,10 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <linux/fs.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/ioctl.h>
 #include <sys/stat.h>
 
 #include <string>
@@ -96,13 +98,11 @@
     return AVB_IO_RESULT_OK;
 }
 
-static AvbIOResult no_op_get_size_of_partition(AvbOps* ops ATTRIBUTE_UNUSED,
-                                              const char* partition ATTRIBUTE_UNUSED,
-                                              uint64_t* out_size_num_byte) {
-    // The function is for bootloader to load entire content of AVB HASH partitions.
-    // In user-space, returns 0 as we only need to set up AVB HASHTHREE partitions.
-    *out_size_num_byte = 0;
-    return AVB_IO_RESULT_OK;
+static AvbIOResult get_size_of_partition(AvbOps* ops ATTRIBUTE_UNUSED,
+                                         const char* partition ATTRIBUTE_UNUSED,
+                                         uint64_t* out_size_num_byte) {
+    return FsManagerAvbOps::GetInstanceFromAvbOps(ops)->GetSizeOfPartition(partition,
+                                                                           out_size_num_byte);
 }
 
 // Converts a partition name (with ab_suffix) to the corresponding mount point.
@@ -131,7 +131,7 @@
     avb_ops_.validate_vbmeta_public_key = no_op_validate_vbmeta_public_key;
     avb_ops_.read_is_device_unlocked = no_op_read_is_device_unlocked;
     avb_ops_.get_unique_guid_for_partition = no_op_get_unique_guid_for_partition;
-    avb_ops_.get_size_of_partition = no_op_get_size_of_partition;
+    avb_ops_.get_size_of_partition = get_size_of_partition;
 
     // Sets user_data for GetInstanceFromAvbOps() to convert it back to FsManagerAvbOps.
     avb_ops_.user_data = this;
@@ -167,13 +167,8 @@
 
     return "";
 }
-
-AvbIOResult FsManagerAvbOps::ReadFromPartition(const char* partition, int64_t offset,
-                                               size_t num_bytes, void* buffer,
-                                               size_t* out_num_read) {
+std::string FsManagerAvbOps::GetPartitionPath(const char* partition) {
     std::string path = "/dev/block/by-name/"s + partition;
-
-    // Ensures the device path (a symlink created by init) is ready to access.
     if (!WaitForFile(path, 1s)) {
         LERROR << "Device path not found: " << path;
         // Falls back to logical path if the physical path is not found.
@@ -182,8 +177,36 @@
         // the bootloader failed to read a physical partition, it will failed to boot
         // the HLOS and we won't reach the code here.
         path = GetLogicalPath(partition);
-        if (path.empty() || !WaitForFile(path, 1s)) return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
-        LINFO << "Fallback to use logical device path: " << path;
+        if (path.empty() || !WaitForFile(path, 1s)) return "";
+    }
+    return path;
+}
+
+AvbIOResult FsManagerAvbOps::GetSizeOfPartition(const char* partition,
+                                                uint64_t* out_size_num_byte) {
+    const auto path = GetPartitionPath(partition);
+    if (path.empty()) {
+        return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+    }
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
+    if (fd < 0) {
+        PERROR << "Failed to open " << path;
+        return AVB_IO_RESULT_ERROR_IO;
+    }
+    int err = ioctl(fd, BLKGETSIZE64, out_size_num_byte);
+    if (err) {
+        *out_size_num_byte = 0;
+        return AVB_IO_RESULT_ERROR_IO;
+    }
+    return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FsManagerAvbOps::ReadFromPartition(const char* partition, int64_t offset,
+                                               size_t num_bytes, void* buffer,
+                                               size_t* out_num_read) {
+    std::string path = GetPartitionPath(partition);
+    if (path.empty()) {
+        return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
     }
 
     android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
diff --git a/fs_mgr/libfs_avb/avb_ops.h b/fs_mgr/libfs_avb/avb_ops.h
index b39812d..12686a6 100644
--- a/fs_mgr/libfs_avb/avb_ops.h
+++ b/fs_mgr/libfs_avb/avb_ops.h
@@ -56,12 +56,14 @@
 
     AvbIOResult ReadFromPartition(const char* partition, int64_t offset, size_t num_bytes,
                                   void* buffer, size_t* out_num_read);
+    AvbIOResult GetSizeOfPartition(const char* partition, uint64_t* out_size_num_byte);
 
     AvbSlotVerifyResult AvbSlotVerify(const std::string& ab_suffix, AvbSlotVerifyFlags flags,
                                       std::vector<VBMetaData>* out_vbmeta_images);
 
   private:
     std::string GetLogicalPath(const std::string& partition_name);
+    std::string GetPartitionPath(const char* partition_name);
     AvbOps avb_ops_;
     Fstab fstab_;
 };
diff --git a/fs_mgr/libfs_avb/util.h b/fs_mgr/libfs_avb/util.h
index 427ab7c..29d1e9c 100644
--- a/fs_mgr/libfs_avb/util.h
+++ b/fs_mgr/libfs_avb/util.h
@@ -31,7 +31,7 @@
 using android::base::ErrnoError;
 using android::base::Result;
 
-#define FS_AVB_TAG "[libfs_avb]"
+#define FS_AVB_TAG "[libfs_avb] "
 
 // Logs a message to kernel
 #define LINFO LOG(INFO) << FS_AVB_TAG
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index 4b81c2c..996ffd7 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -39,6 +39,7 @@
     ],
     srcs: [
         "builder.cpp",
+        "super_layout_builder.cpp",
         "images.cpp",
         "partition_opener.cpp",
         "property_fetcher.cpp",
@@ -62,17 +63,6 @@
     export_include_dirs: ["include"],
 }
 
-filegroup {
-    name: "liblp_test_srcs",
-    srcs: [
-        "builder_test.cpp",
-        "device_test.cpp",
-        "io_test.cpp",
-        "test_partition_opener.cpp",
-        "utility_test.cpp",
-    ],
-}
-
 cc_defaults {
     name: "liblp_test_defaults",
     defaults: ["fs_mgr_defaults"],
@@ -82,22 +72,39 @@
     static_libs: [
         "libcutils",
         "libgmock",
-        "libfs_mgr",
         "liblp",
         "libcrypto_static",
     ] + liblp_lib_deps,
     header_libs: [
         "libstorage_literals_headers",
     ],
+    target: {
+        android: {
+            srcs: [
+                "device_test.cpp",
+                "io_test.cpp",
+            ],
+            static_libs: [
+                "libfs_mgr",
+            ],
+        }
+    },
     stl: "libc++_static",
-    srcs: [":liblp_test_srcs"],
+    srcs: [
+        "builder_test.cpp",
+        "super_layout_builder_test.cpp",
+        "test_partition_opener.cpp",
+        "utility_test.cpp",
+    ],
 }
 
 cc_test {
     name: "liblp_test",
     defaults: ["liblp_test_defaults"],
-    test_config: "liblp_test.xml",
     test_suites: ["device-tests"],
+    auto_gen_config: true,
+    require_root: true,
+    host_supported: true
 }
 
 cc_test {
diff --git a/fs_mgr/liblp/images.cpp b/fs_mgr/liblp/images.cpp
index 0b1e522..02b64ac 100644
--- a/fs_mgr/liblp/images.cpp
+++ b/fs_mgr/liblp/images.cpp
@@ -312,6 +312,11 @@
 
 bool ImageBuilder::AddPartitionImage(const LpMetadataPartition& partition,
                                      const std::string& file) {
+    if (partition.num_extents == 0) {
+        LERROR << "Partition size is zero: " << GetPartitionName(partition);
+        return false;
+    }
+
     // Track which extent we're processing.
     uint32_t extent_index = partition.first_extent_index;
 
diff --git a/fs_mgr/liblp/include/liblp/super_layout_builder.h b/fs_mgr/liblp/include/liblp/super_layout_builder.h
new file mode 100644
index 0000000..d920855
--- /dev/null
+++ b/fs_mgr/liblp/include/liblp/super_layout_builder.h
@@ -0,0 +1,104 @@
+//
+// 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.
+//
+#pragma once
+
+#include <stdint.h>
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include <android-base/unique_fd.h>
+#include <liblp/builder.h>
+
+namespace android {
+namespace fs_mgr {
+
+struct SuperImageExtent {
+    enum class Type { INVALID, DATA, PARTITION, ZERO, DONTCARE };
+
+    SuperImageExtent(const SuperImageExtent& other) = default;
+    SuperImageExtent(SuperImageExtent&& other) = default;
+    SuperImageExtent(uint64_t offset, uint64_t size, Type type)
+        : offset(offset), size(size), type(type) {}
+
+    SuperImageExtent(uint64_t offset, std::shared_ptr<std::string> blob)
+        : SuperImageExtent(offset, blob->size(), Type::DATA) {
+        this->blob = blob;
+    }
+
+    SuperImageExtent(uint64_t offset, uint64_t size, const std::string& image_name,
+                     uint64_t image_offset)
+        : SuperImageExtent(offset, size, Type::PARTITION) {
+        this->image_name = image_name;
+        this->image_offset = image_offset;
+    }
+
+    SuperImageExtent& operator=(const SuperImageExtent& other) = default;
+    SuperImageExtent& operator=(SuperImageExtent&& other) = default;
+
+    bool operator<(const SuperImageExtent& other) const { return offset < other.offset; }
+    bool operator==(const SuperImageExtent& other) const;
+
+    // Location, size, and type of the extent.
+    uint64_t offset = 0;
+    uint64_t size = 0;
+    Type type = Type::INVALID;
+
+    // If type == DATA, this contains the bytes to write.
+    std::shared_ptr<std::string> blob;
+    // If type == PARTITION, this contains the partition image name and
+    // offset within that file.
+    std::string image_name;
+    uint64_t image_offset = 0;
+};
+
+// The SuperLayoutBuilder allows building a sparse view of a super image. This
+// is useful for efficient flashing, eg to bypass fastbootd and directly flash
+// super without physically building and storing the image.
+class SuperLayoutBuilder final {
+  public:
+    // Open a super_empty.img, return false on failure. This must be called to
+    // initialize the tool. If it returns false, either the image failed to
+    // parse, or the tool is not compatible with how the device is configured
+    // (in which case fastbootd should be preferred).
+    [[nodiscard]] bool Open(android::base::borrowed_fd fd);
+    [[nodiscard]] bool Open(const void* data, size_t bytes);
+    [[nodiscard]] bool Open(const LpMetadata& metadata);
+
+    // Add a partition's image and size to the work list. If false is returned,
+    // there was either a duplicate partition or not enough space in super.
+    bool AddPartition(const std::string& partition_name, const std::string& image_name,
+                      uint64_t partition_size);
+
+    // Return the list of extents describing the super image. If this list is
+    // empty, then there was an unrecoverable error in building the list.
+    std::vector<SuperImageExtent> GetImageLayout();
+
+    // Return the current metadata.
+    std::unique_ptr<LpMetadata> Export() const { return builder_->Export(); }
+
+  private:
+    std::unique_ptr<MetadataBuilder> builder_;
+    std::unordered_map<std::string, std::string> image_map_;
+};
+
+std::ostream& operator<<(std::ostream& stream, const SuperImageExtent& extent);
+
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/fs_mgr/liblp/liblp_test.xml b/fs_mgr/liblp/liblp_test.xml
deleted file mode 100644
index 98414b1..0000000
--- a/fs_mgr/liblp/liblp_test.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for liblp_test">
-    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="liblp_test->/data/local/tmp/liblp_test" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="liblp_test" />
-    </test>
-</configuration>
diff --git a/fs_mgr/liblp/super_layout_builder.cpp b/fs_mgr/liblp/super_layout_builder.cpp
new file mode 100644
index 0000000..37f28e1
--- /dev/null
+++ b/fs_mgr/liblp/super_layout_builder.cpp
@@ -0,0 +1,241 @@
+//
+// 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.
+//
+#include <liblp/super_layout_builder.h>
+
+#include <liblp/liblp.h>
+
+#include "images.h"
+#include "utility.h"
+#include "writer.h"
+
+using android::base::borrowed_fd;
+using android::base::unique_fd;
+
+namespace android {
+namespace fs_mgr {
+
+bool SuperLayoutBuilder::Open(borrowed_fd fd) {
+    auto metadata = ReadFromImageFile(fd.get());
+    if (!metadata) {
+        return false;
+    }
+    return Open(*metadata.get());
+}
+
+bool SuperLayoutBuilder::Open(const void* data, size_t size) {
+    auto metadata = ReadFromImageBlob(data, size);
+    if (!metadata) {
+        return false;
+    }
+    return Open(*metadata.get());
+}
+
+bool SuperLayoutBuilder::Open(const LpMetadata& metadata) {
+    for (const auto& partition : metadata.partitions) {
+        if (partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED) {
+            // Retrofit devices are not supported.
+            return false;
+        }
+        if (!(partition.attributes & LP_PARTITION_ATTR_READONLY)) {
+            // Writable partitions are not supported.
+            return false;
+        }
+    }
+    if (!metadata.extents.empty()) {
+        // Partitions that already have extents are not supported (should
+        // never be true of super_empty.img).
+        return false;
+    }
+    if (metadata.block_devices.size() != 1) {
+        // Only one "super" is supported.
+        return false;
+    }
+
+    builder_ = MetadataBuilder::New(metadata);
+    return !!builder_;
+}
+
+bool SuperLayoutBuilder::AddPartition(const std::string& partition_name,
+                                      const std::string& image_name, uint64_t partition_size) {
+    auto p = builder_->FindPartition(partition_name);
+    if (!p) {
+        return false;
+    }
+    if (!builder_->ResizePartition(p, partition_size)) {
+        return false;
+    }
+    image_map_.emplace(partition_name, image_name);
+    return true;
+}
+
+// Fill the space between each extent, if any, with either a fill or dontcare
+// extent. The caller constructs a sample extent to re-use.
+static bool AddGapExtents(std::vector<SuperImageExtent>* extents, SuperImageExtent::Type gap_type) {
+    std::vector<SuperImageExtent> old = std::move(*extents);
+    std::sort(old.begin(), old.end());
+
+    *extents = {};
+
+    uint64_t current_offset = 0;
+    for (const auto& extent : old) {
+        // Check for overlapping extents - this would be a serious error.
+        if (current_offset > extent.offset) {
+            LOG(INFO) << "Overlapping extents detected; cannot layout temporary super image";
+            return false;
+        }
+
+        if (extent.offset != current_offset) {
+            uint64_t gap_size = extent.offset - current_offset;
+            extents->emplace_back(current_offset, gap_size, gap_type);
+            current_offset = extent.offset;
+        }
+
+        extents->emplace_back(extent);
+        current_offset += extent.size;
+    }
+    return true;
+}
+
+std::vector<SuperImageExtent> SuperLayoutBuilder::GetImageLayout() {
+    auto metadata = builder_->Export();
+    if (!metadata) {
+        return {};
+    }
+
+    std::vector<SuperImageExtent> extents;
+
+    // Write the primary and backup copies of geometry.
+    std::string geometry_bytes = SerializeGeometry(metadata->geometry);
+    auto blob = std::make_shared<std::string>(std::move(geometry_bytes));
+
+    extents.emplace_back(0, GetPrimaryGeometryOffset(), SuperImageExtent::Type::ZERO);
+    extents.emplace_back(GetPrimaryGeometryOffset(), blob);
+    extents.emplace_back(GetBackupGeometryOffset(), blob);
+
+    // Write the primary and backup copies of each metadata slot. When flashing,
+    // all metadata copies are the same, even for different slots.
+    std::string metadata_bytes = SerializeMetadata(*metadata.get());
+
+    // Align metadata size to 4KB. This makes the layout easily compatible with
+    // libsparse.
+    static constexpr size_t kSparseAlignment = 4096;
+    size_t metadata_aligned_bytes;
+    if (!AlignTo(metadata_bytes.size(), kSparseAlignment, &metadata_aligned_bytes)) {
+        LOG(ERROR) << "Unable to align metadata size " << metadata_bytes.size() << " to "
+                   << kSparseAlignment;
+        return {};
+    }
+    metadata_bytes.resize(metadata_aligned_bytes, '\0');
+
+    // However, alignment can cause larger-than-supported metadata blocks. Fall
+    // back to fastbootd/update-super.
+    if (metadata_bytes.size() > metadata->geometry.metadata_max_size) {
+        LOG(VERBOSE) << "Aligned metadata size " << metadata_bytes.size()
+                     << " is larger than maximum metadata size "
+                     << metadata->geometry.metadata_max_size;
+        return {};
+    }
+
+    blob = std::make_shared<std::string>(std::move(metadata_bytes));
+    for (uint32_t i = 0; i < metadata->geometry.metadata_slot_count; i++) {
+        int64_t metadata_primary = GetPrimaryMetadataOffset(metadata->geometry, i);
+        int64_t metadata_backup = GetBackupMetadataOffset(metadata->geometry, i);
+        extents.emplace_back(metadata_primary, blob);
+        extents.emplace_back(metadata_backup, blob);
+    }
+
+    // Add extents for each partition.
+    for (const auto& partition : metadata->partitions) {
+        auto partition_name = GetPartitionName(partition);
+        auto image_name_iter = image_map_.find(partition_name);
+        if (image_name_iter == image_map_.end()) {
+            if (partition.num_extents != 0) {
+                LOG(ERROR) << "Partition " << partition_name
+                           << " has extents but no image filename";
+                return {};
+            }
+            continue;
+        }
+        const auto& image_name = image_name_iter->second;
+
+        uint64_t image_offset = 0;
+        for (uint32_t i = 0; i < partition.num_extents; i++) {
+            const auto& e = metadata->extents[partition.first_extent_index + i];
+
+            if (e.target_type != LP_TARGET_TYPE_LINEAR) {
+                // Any type other than LINEAR isn't understood here. We don't even
+                // bother with ZERO, which is never generated.
+                LOG(INFO) << "Unknown extent type from liblp: " << e.target_type;
+                return {};
+            }
+
+            size_t size = e.num_sectors * LP_SECTOR_SIZE;
+            uint64_t super_offset = e.target_data * LP_SECTOR_SIZE;
+            extents.emplace_back(super_offset, size, image_name, image_offset);
+
+            image_offset += size;
+        }
+    }
+
+    if (!AddGapExtents(&extents, SuperImageExtent::Type::DONTCARE)) {
+        return {};
+    }
+    return extents;
+}
+
+bool SuperImageExtent::operator==(const SuperImageExtent& other) const {
+    if (offset != other.offset) {
+        return false;
+    }
+    if (size != other.size) {
+        return false;
+    }
+    if (type != other.type) {
+        return false;
+    }
+    switch (type) {
+        case Type::DATA:
+            return *blob == *other.blob;
+        case Type::PARTITION:
+            return image_name == other.image_name && image_offset == other.image_offset;
+        default:
+            return true;
+    }
+}
+
+std::ostream& operator<<(std::ostream& stream, const SuperImageExtent& extent) {
+    stream << "extent:" << extent.offset << ":" << extent.size << ":";
+    switch (extent.type) {
+        case SuperImageExtent::Type::DATA:
+            stream << "data";
+            break;
+        case SuperImageExtent::Type::PARTITION:
+            stream << "partition:" << extent.image_name << ":" << extent.image_offset;
+            break;
+        case SuperImageExtent::Type::ZERO:
+            stream << "zero";
+            break;
+        case SuperImageExtent::Type::DONTCARE:
+            stream << "dontcare";
+            break;
+        default:
+            stream << "invalid";
+    }
+    return stream;
+}
+
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/fs_mgr/liblp/super_layout_builder_test.cpp b/fs_mgr/liblp/super_layout_builder_test.cpp
new file mode 100644
index 0000000..714b6b4
--- /dev/null
+++ b/fs_mgr/liblp/super_layout_builder_test.cpp
@@ -0,0 +1,141 @@
+//
+// 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.
+//
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <liblp/builder.h>
+#include <liblp/super_layout_builder.h>
+#include <storage_literals/storage_literals.h>
+
+#include "images.h"
+#include "writer.h"
+
+using namespace android::fs_mgr;
+using namespace android::storage_literals;
+
+TEST(SuperImageTool, Layout) {
+    auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2);
+    ASSERT_NE(builder, nullptr);
+
+    Partition* p = builder->AddPartition("system_a", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(p, nullptr);
+
+    auto metadata = builder->Export();
+    ASSERT_NE(metadata, nullptr);
+
+    SuperLayoutBuilder tool;
+    ASSERT_TRUE(tool.Open(*metadata.get()));
+    ASSERT_TRUE(tool.AddPartition("system_a", "system.img", 16_KiB));
+
+    // Get a copy of the metadata we'd expect if flashing.
+    ASSERT_TRUE(builder->ResizePartition(p, 16_KiB));
+    metadata = builder->Export();
+    ASSERT_NE(metadata, nullptr);
+
+    auto geometry_blob = std::make_shared<std::string>(SerializeGeometry(metadata->geometry));
+    auto metadata_blob = std::make_shared<std::string>(SerializeMetadata(*metadata.get()));
+    metadata_blob->resize(4_KiB, '\0');
+
+    auto extents = tool.GetImageLayout();
+    ASSERT_EQ(extents.size(), 12);
+    EXPECT_EQ(extents[0], SuperImageExtent(0, 4096, SuperImageExtent::Type::ZERO));
+    EXPECT_EQ(extents[1], SuperImageExtent(4096, geometry_blob));
+    EXPECT_EQ(extents[2], SuperImageExtent(8192, geometry_blob));
+    EXPECT_EQ(extents[3], SuperImageExtent(12288, metadata_blob));
+    EXPECT_EQ(extents[4], SuperImageExtent(16384, 4096, SuperImageExtent::Type::DONTCARE));
+    EXPECT_EQ(extents[5], SuperImageExtent(20480, metadata_blob));
+    EXPECT_EQ(extents[6], SuperImageExtent(24576, 4096, SuperImageExtent::Type::DONTCARE));
+    EXPECT_EQ(extents[7], SuperImageExtent(28672, metadata_blob));
+    EXPECT_EQ(extents[8], SuperImageExtent(32768, 4096, SuperImageExtent::Type::DONTCARE));
+    EXPECT_EQ(extents[9], SuperImageExtent(36864, metadata_blob));
+    EXPECT_EQ(extents[10], SuperImageExtent(40960, 4096, SuperImageExtent::Type::DONTCARE));
+    EXPECT_EQ(extents[11], SuperImageExtent(45056, 16384, "system.img", 0));
+}
+
+TEST(SuperImageTool, NoWritablePartitions) {
+    auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2);
+    ASSERT_NE(builder, nullptr);
+
+    Partition* p = builder->AddPartition("system_a", 0);
+    ASSERT_NE(p, nullptr);
+
+    auto metadata = builder->Export();
+    ASSERT_NE(metadata, nullptr);
+
+    SuperLayoutBuilder tool;
+    ASSERT_FALSE(tool.Open(*metadata.get()));
+}
+
+TEST(SuperImageTool, NoRetrofit) {
+    auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2);
+    ASSERT_NE(builder, nullptr);
+
+    Partition* p = builder->AddPartition("system_a", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(p, nullptr);
+
+    auto metadata = builder->Export();
+    ASSERT_NE(metadata, nullptr);
+
+    // Add an extra block device.
+    metadata->block_devices.emplace_back(metadata->block_devices[0]);
+
+    SuperLayoutBuilder tool;
+    ASSERT_FALSE(tool.Open(*metadata.get()));
+}
+
+TEST(SuperImageTool, NoRetrofit2) {
+    auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2);
+    ASSERT_NE(builder, nullptr);
+
+    Partition* p = builder->AddPartition(
+            "system_a", LP_PARTITION_ATTR_READONLY | LP_PARTITION_ATTR_SLOT_SUFFIXED);
+    ASSERT_NE(p, nullptr);
+
+    auto metadata = builder->Export();
+    ASSERT_NE(metadata, nullptr);
+
+    SuperLayoutBuilder tool;
+    ASSERT_FALSE(tool.Open(*metadata.get()));
+}
+
+TEST(SuperImageTool, NoFixedPartitions) {
+    auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2);
+    ASSERT_NE(builder, nullptr);
+
+    Partition* p = builder->AddPartition("system_a", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(p, nullptr);
+    ASSERT_TRUE(builder->ResizePartition(p, 4_KiB));
+
+    auto metadata = builder->Export();
+    ASSERT_NE(metadata, nullptr);
+
+    SuperLayoutBuilder tool;
+    ASSERT_FALSE(tool.Open(*metadata.get()));
+}
+
+TEST(SuperImageTool, LargeAlignedMetadata) {
+    auto builder = MetadataBuilder::New(4_MiB, 512, 2);
+    ASSERT_NE(builder, nullptr);
+
+    auto metadata = builder->Export();
+    ASSERT_NE(metadata, nullptr);
+
+    SuperLayoutBuilder tool;
+    ASSERT_TRUE(tool.Open(*metadata.get()));
+
+    auto extents = tool.GetImageLayout();
+    ASSERT_TRUE(extents.empty());
+}
diff --git a/fs_mgr/liblp/utility.h b/fs_mgr/liblp/utility.h
index aa3a6a0..32a59a5 100644
--- a/fs_mgr/liblp/utility.h
+++ b/fs_mgr/liblp/utility.h
@@ -30,7 +30,7 @@
 
 #include "liblp/liblp.h"
 
-#define LP_TAG "[liblp]"
+#define LP_TAG "[liblp] "
 #define LWARN LOG(WARNING) << LP_TAG
 #define LINFO LOG(INFO) << LP_TAG
 #define LERROR LOG(ERROR) << LP_TAG
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 2165961..c2f435f 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -269,14 +269,19 @@
     defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
 }
 
-sh_test {
-    name: "run_snapshot_tests",
-    src: "run_snapshot_tests.sh",
-    test_suites: [
-        "device-tests",
+cc_test {
+    name: "vab_legacy_tests",
+    defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
+    cppflags: [
+        "-DLIBSNAPSHOT_TEST_VAB_LEGACY",
     ],
-    required: [
-        "vts_libsnapshot_test",
+}
+
+cc_test {
+    name: "vabc_legacy_tests",
+    defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
+    cppflags: [
+        "-DLIBSNAPSHOT_TEST_VABC_LEGACY",
     ],
 }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
index 3932fad..56b48f0 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
@@ -298,13 +298,12 @@
         return false;
     }
 
-    bool ret = OpenForWrite();
-
-    if (ret) {
-        InitWorkers();
+    if (!OpenForWrite()) {
+        return false;
     }
 
-    return ret;
+    InitWorkers();
+    return true;
 }
 
 bool CowWriter::InitializeAppend(android::base::unique_fd&& fd, uint64_t label) {
diff --git a/fs_mgr/libsnapshot/run_snapshot_tests.sh b/fs_mgr/libsnapshot/run_snapshot_tests.sh
deleted file mode 100644
index b03a4e0..0000000
--- a/fs_mgr/libsnapshot/run_snapshot_tests.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/system/bin/sh
-
-# Detect host or AOSP.
-getprop ro.build.version.sdk > /dev/null 2>&1
-if [ $? -eq 0 ]; then
-    cmd_prefix=""
-    local_root=""
-else
-    cmd_prefix="adb shell"
-    local_root="${ANDROID_PRODUCT_OUT}"
-    set -e
-    set -x
-    adb root
-    adb sync data
-    set +x
-    set +e
-fi
-
-testpath64="/data/nativetest64/vts_libsnapshot_test/vts_libsnapshot_test"
-testpath32="/data/nativetest/vts_libsnapshot_test/vts_libsnapshot_test"
-if [ -f "${local_root}/${testpath64}" ]; then
-    testpath="${testpath64}"
-elif [ -f "${local_root}/${testpath32}" ]; then
-    testpath="${testpath32}"
-else
-    echo "ERROR: vts_libsnapshot_test not found." 1>&2
-    echo "Make sure to build vts_libsnapshot_test or snapshot_tests first." 1>&2
-    exit 1
-fi
-
-# Verbose, error on failure.
-set -x
-set -e
-
-time ${cmd_prefix} ${testpath}
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 2c01cf6..13314da 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -53,7 +53,15 @@
 #include <libsnapshot/mock_device_info.h>
 #include <libsnapshot/mock_snapshot.h>
 
-DEFINE_string(force_mode, "",
+#if defined(LIBSNAPSHOT_TEST_VAB_LEGACY)
+#define DEFAULT_MODE "vab-legacy"
+#elif defined(LIBSNAPSHOT_TEST_VABC_LEGACY)
+#define DEFAULT_MODE "vabc-legacy"
+#else
+#define DEFAULT_MODE ""
+#endif
+
+DEFINE_string(force_mode, DEFAULT_MODE,
               "Force testing older modes (vab-legacy, vabc-legacy) ignoring device config.");
 DEFINE_string(force_iouring_disable, "",
               "Force testing mode (iouring_disabled) - disable io_uring");
@@ -2661,44 +2669,46 @@
                                     "Merge"s;
                          });
 
-// Test behavior of ImageManager::Create on low space scenario. These tests assumes image manager
-// uses /data as backup device.
-class ImageManagerTest : public SnapshotTest, public WithParamInterface<uint64_t> {
+class ImageManagerTest : public SnapshotTest {
   protected:
     void SetUp() override {
         SKIP_IF_NON_VIRTUAL_AB();
         SnapshotTest::SetUp();
-        userdata_ = std::make_unique<LowSpaceUserdata>();
-        ASSERT_TRUE(userdata_->Init(GetParam()));
     }
     void TearDown() override {
         RETURN_IF_NON_VIRTUAL_AB();
-
+        CleanUp();
+    }
+    void CleanUp() {
         EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) ||
                     image_manager_->DeleteBackingImage(kImageName));
     }
+
     static constexpr const char* kImageName = "my_image";
-    std::unique_ptr<LowSpaceUserdata> userdata_;
 };
 
-TEST_P(ImageManagerTest, CreateImageNoSpace) {
-    uint64_t to_allocate = userdata_->free_space() + userdata_->bsize();
-    auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
-                                                  IImageManager::CREATE_IMAGE_DEFAULT);
-    ASSERT_FALSE(res) << "Should not be able to create image with size = " << to_allocate
-                      << " bytes because only " << userdata_->free_space() << " bytes are free";
-    ASSERT_EQ(FiemapStatus::ErrorCode::NO_SPACE, res.error_code()) << res.string();
-}
-
-std::vector<uint64_t> ImageManagerTestParams() {
-    std::vector<uint64_t> ret;
+TEST_F(ImageManagerTest, CreateImageNoSpace) {
+    bool at_least_one_failure = false;
     for (uint64_t size = 1_MiB; size <= 512_MiB; size *= 2) {
-        ret.push_back(size);
-    }
-    return ret;
-}
+        auto userdata = std::make_unique<LowSpaceUserdata>();
+        ASSERT_TRUE(userdata->Init(size));
 
-INSTANTIATE_TEST_SUITE_P(ImageManagerTest, ImageManagerTest, ValuesIn(ImageManagerTestParams()));
+        uint64_t to_allocate = userdata->free_space() + userdata->bsize();
+
+        auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
+                                                      IImageManager::CREATE_IMAGE_DEFAULT);
+        if (!res) {
+            at_least_one_failure = true;
+        } else {
+            ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
+        }
+
+        CleanUp();
+    }
+
+    ASSERT_TRUE(at_least_one_failure)
+            << "We should have failed to allocate at least one over-sized image";
+}
 
 bool Mkdir(const std::string& path) {
     if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
diff --git a/fs_mgr/libsnapshot/snapshot_writer_test.cpp b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
index da48eb9..a03632b 100644
--- a/fs_mgr/libsnapshot/snapshot_writer_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
@@ -33,8 +33,8 @@
     TemporaryFile cow_device_file{};
     android::snapshot::CowOptions options{.block_size = BLOCK_SIZE};
     android::snapshot::CompressedSnapshotWriter snapshot_writer{options};
-    snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd});
-    snapshot_writer.Initialize();
+    ASSERT_TRUE(snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd}));
+    ASSERT_TRUE(snapshot_writer.Initialize());
     std::vector<unsigned char> buffer;
     buffer.resize(BLOCK_SIZE);
     std::fill(buffer.begin(), buffer.end(), 123);
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index fb2251e..010beb3 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -47,6 +47,8 @@
     bool ValidateConnection();
     std::string GetDaemonAliveIndicatorPath();
 
+    void WaitForServiceToTerminate(std::chrono::milliseconds timeout_ms);
+
   public:
     explicit SnapuserdClient(android::base::unique_fd&& sockfd);
     SnapuserdClient(){};
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 695b581..3bed3a4 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -94,6 +94,21 @@
     return client;
 }
 
+void SnapuserdClient::WaitForServiceToTerminate(std::chrono::milliseconds timeout_ms) {
+    auto start = std::chrono::steady_clock::now();
+    while (android::base::GetProperty("init.svc.snapuserd", "") == "running") {
+        auto now = std::chrono::steady_clock::now();
+        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
+        if (elapsed >= timeout_ms) {
+            LOG(ERROR) << "Timed out - Snapuserd service did not stop - Forcefully terminating the "
+                          "service";
+            android::base::SetProperty("ctl.stop", "snapuserd");
+            return;
+        }
+        std::this_thread::sleep_for(100ms);
+    }
+}
+
 bool SnapuserdClient::ValidateConnection() {
     if (!Sendmsg("query")) {
         return false;
@@ -238,6 +253,8 @@
         LOG(ERROR) << "Failed to detach snapuserd.";
         return false;
     }
+
+    WaitForServiceToTerminate(3s);
     return true;
 }
 
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index b05123a..9f1d676 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -227,7 +227,7 @@
         return AssertionFailure() << "Temp file allocated to " << big_file_->path << ", not in "
                                   << kUserDataDevice;
     }
-    uint64_t next_consume = std::min(available_space_ - max_free_space,
+    uint64_t next_consume = std::min(std::max(available_space_, max_free_space) - max_free_space,
                                      (uint64_t)std::numeric_limits<off_t>::max());
     off_t allocated = 0;
     while (next_consume > 0 && free_space_ > max_free_space) {
diff --git a/fs_mgr/libvbmeta/utility.h b/fs_mgr/libvbmeta/utility.h
index 91db0ad..ab9828d 100644
--- a/fs_mgr/libvbmeta/utility.h
+++ b/fs_mgr/libvbmeta/utility.h
@@ -19,7 +19,7 @@
 #include <android-base/logging.h>
 #include <android-base/result.h>
 
-#define VBMETA_TAG "[libvbmeta]"
+#define VBMETA_TAG "[libvbmeta] "
 #define LWARN LOG(WARNING) << VBMETA_TAG
 #define LINFO LOG(INFO) << VBMETA_TAG
 #define LERROR LOG(ERROR) << VBMETA_TAG
diff --git a/healthd/Android.bp b/healthd/Android.bp
index 76b6ad0..235303f 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -4,7 +4,6 @@
 
 cc_defaults {
     name: "libbatterymonitor_defaults",
-    srcs: ["BatteryMonitor.cpp"],
     cflags: ["-Wall", "-Werror"],
     vendor_available: true,
     recovery_available: true,
@@ -75,8 +74,9 @@
 cc_library_static {
     name: "libbatterymonitor",
     defaults: ["libbatterymonitor_defaults"],
+    srcs: ["BatteryMonitor.cpp"],
     static_libs: [
-        "android.hardware.health-V1-ndk",
+        "android.hardware.health-V2-ndk",
     ],
     whole_static_libs: [
         // Need to translate HIDL to AIDL to support legacy APIs in
@@ -89,6 +89,7 @@
 cc_library_static {
     name: "libbatterymonitor-V1",
     defaults: ["libbatterymonitor_defaults"],
+    srcs: ["BatteryMonitor_v1.cpp"],
     static_libs: [
         "android.hardware.health-V1-ndk",
     ],
@@ -202,12 +203,12 @@
     defaults: ["libhealthd_charger_ui_defaults"],
 
     static_libs: [
-        "android.hardware.health-V1-ndk",
+        "android.hardware.health-V2-ndk",
         "android.hardware.health-translate-ndk",
     ],
 
     export_static_lib_headers: [
-        "android.hardware.health-V1-ndk",
+        "android.hardware.health-V2-ndk",
     ],
 }
 
@@ -279,7 +280,7 @@
     static_libs: [
         // common
         "android.hardware.health@1.0-convert",
-        "android.hardware.health-V1-ndk",
+        "android.hardware.health-V2-ndk",
         "libbatterymonitor",
         "libcharger_sysprop",
         "libhealthd_charger_nops",
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index a7571a2..b180a58 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -55,7 +55,10 @@
 using HealthInfo_2_0 = android::hardware::health::V2_0::HealthInfo;
 using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
 using aidl::android::hardware::health::BatteryCapacityLevel;
+using aidl::android::hardware::health::BatteryChargingPolicy;
+using aidl::android::hardware::health::BatteryChargingState;
 using aidl::android::hardware::health::BatteryHealth;
+using aidl::android::hardware::health::BatteryHealthData;
 using aidl::android::hardware::health::BatteryStatus;
 using aidl::android::hardware::health::HealthInfo;
 
@@ -135,6 +138,7 @@
       mBatteryDevicePresent(false),
       mBatteryFixedCapacity(0),
       mBatteryFixedTemperature(0),
+      mBatteryHealthStatus(BatteryMonitor::BH_UNKNOWN),
       mHealthInfo(std::make_unique<HealthInfo>()) {
     initHealthInfo(mHealthInfo.get());
 }
@@ -227,6 +231,54 @@
     return *ret;
 }
 
+BatteryHealth getBatteryHealthStatus(int status) {
+    BatteryHealth value;
+
+    if (status == BatteryMonitor::BH_NOMINAL)
+        value = BatteryHealth::GOOD;
+    else if (status == BatteryMonitor::BH_MARGINAL)
+        value = BatteryHealth::FAIR;
+    else if (status == BatteryMonitor::BH_NEEDS_REPLACEMENT)
+        value = BatteryHealth::DEAD;
+    else if (status == BatteryMonitor::BH_FAILED)
+        value = BatteryHealth::UNSPECIFIED_FAILURE;
+    else
+        value = BatteryHealth::UNKNOWN;
+
+    return value;
+}
+
+BatteryChargingPolicy getBatteryChargingPolicy(const char* chargingPolicy) {
+    static SysfsStringEnumMap<BatteryChargingPolicy> batteryChargingPolicyMap[] = {
+            {"0", BatteryChargingPolicy::INVALID},   {"1", BatteryChargingPolicy::DEFAULT},
+            {"2", BatteryChargingPolicy::LONG_LIFE}, {"3", BatteryChargingPolicy::ADAPTIVE},
+            {NULL, BatteryChargingPolicy::DEFAULT},
+    };
+
+    auto ret = mapSysfsString(chargingPolicy, batteryChargingPolicyMap);
+    if (!ret) {
+        *ret = BatteryChargingPolicy::DEFAULT;
+    }
+
+    return *ret;
+}
+
+BatteryChargingState getBatteryChargingState(const char* chargingState) {
+    static SysfsStringEnumMap<BatteryChargingState> batteryChargingStateMap[] = {
+            {"0", BatteryChargingState::INVALID},   {"1", BatteryChargingState::NORMAL},
+            {"2", BatteryChargingState::TOO_COLD},  {"3", BatteryChargingState::TOO_HOT},
+            {"4", BatteryChargingState::LONG_LIFE}, {"5", BatteryChargingState::ADAPTIVE},
+            {NULL, BatteryChargingState::NORMAL},
+    };
+
+    auto ret = mapSysfsString(chargingState, batteryChargingStateMap);
+    if (!ret) {
+        *ret = BatteryChargingState::NORMAL;
+    }
+
+    return *ret;
+}
+
 static int readFromFile(const String8& path, std::string* buf) {
     buf->clear();
     if (android::base::ReadFileToString(path.c_str(), buf)) {
@@ -235,6 +287,10 @@
     return buf->length();
 }
 
+static bool writeToFile(const String8& path, int32_t in_value) {
+    return android::base::WriteStringToFile(std::to_string(in_value), path.c_str());
+}
+
 static BatteryMonitor::PowerSupplyType readPowerSupplyType(const String8& path) {
     static SysfsStringEnumMap<int> supplyTypeMap[] = {
             {"Unknown", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN},
@@ -336,6 +392,20 @@
         mHealthInfo->batteryFullChargeDesignCapacityUah =
                 getIntField(mHealthdConfig->batteryFullChargeDesignCapacityUahPath);
 
+    if (!mHealthdConfig->batteryStateOfHealthPath.isEmpty())
+        mHealthInfo->batteryStateOfHealth = getIntField(mHealthdConfig->batteryStateOfHealthPath);
+
+    if (!mHealthdConfig->batteryHealthStatusPath.isEmpty())
+        mBatteryHealthStatus = getIntField(mHealthdConfig->batteryHealthStatusPath);
+
+    if (!mHealthdConfig->batteryManufacturingDatePath.isEmpty())
+        mHealthInfo->batteryHealthData->batteryManufacturingDateSeconds =
+                getIntField(mHealthdConfig->batteryManufacturingDatePath);
+
+    if (!mHealthdConfig->batteryFirstUsageDatePath.isEmpty())
+        mHealthInfo->batteryHealthData->batteryFirstUsageSeconds =
+                getIntField(mHealthdConfig->batteryFirstUsageDatePath);
+
     mHealthInfo->batteryTemperatureTenthsCelsius =
             mBatteryFixedTemperature ? mBatteryFixedTemperature
                                      : getIntField(mHealthdConfig->batteryTemperaturePath);
@@ -348,12 +418,23 @@
     if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0)
         mHealthInfo->batteryStatus = getBatteryStatus(buf.c_str());
 
-    if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
-        mHealthInfo->batteryHealth = getBatteryHealth(buf.c_str());
+    // Backward compatible with android.hardware.health V1
+    if (mBatteryHealthStatus < BatteryMonitor::BH_MARGINAL) {
+        if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
+            mHealthInfo->batteryHealth = getBatteryHealth(buf.c_str());
+    } else {
+        mHealthInfo->batteryHealth = getBatteryHealthStatus(mBatteryHealthStatus);
+    }
 
     if (readFromFile(mHealthdConfig->batteryTechnologyPath, &buf) > 0)
         mHealthInfo->batteryTechnology = String8(buf.c_str());
 
+    if (readFromFile(mHealthdConfig->chargingPolicyPath, &buf) > 0)
+        mHealthInfo->chargingPolicy = getBatteryChargingPolicy(buf.c_str());
+
+    if (readFromFile(mHealthdConfig->chargingStatePath, &buf) > 0)
+        mHealthInfo->chargingState = getBatteryChargingState(buf.c_str());
+
     double MaxPower = 0;
 
     for (size_t i = 0; i < mChargerNames.size(); i++) {
@@ -476,6 +557,43 @@
     return static_cast<int>(result);
 }
 
+status_t BatteryMonitor::setChargingPolicy(int value) {
+    status_t ret = NAME_NOT_FOUND;
+    bool result;
+    if (!mHealthdConfig->chargingPolicyPath.isEmpty()) {
+        result = writeToFile(mHealthdConfig->chargingPolicyPath, value);
+        if (!result) {
+            KLOG_WARNING(LOG_TAG, "setChargingPolicy fail\n");
+            ret = BAD_VALUE;
+        } else {
+            ret = OK;
+        }
+    }
+    return ret;
+}
+
+int BatteryMonitor::getChargingPolicy() {
+    BatteryChargingPolicy result = BatteryChargingPolicy::DEFAULT;
+    if (!mHealthdConfig->chargingPolicyPath.isEmpty()) {
+        std::string buf;
+        if (readFromFile(mHealthdConfig->chargingPolicyPath, &buf) > 0)
+            result = getBatteryChargingPolicy(buf.c_str());
+    }
+    return static_cast<int>(result);
+}
+
+int BatteryMonitor::getBatteryHealthData(int id) {
+    if (id == BATTERY_PROP_MANUFACTURING_DATE) {
+        if (!mHealthdConfig->batteryManufacturingDatePath.isEmpty())
+            return getIntField(mHealthdConfig->batteryManufacturingDatePath);
+    }
+    if (id == BATTERY_PROP_FIRST_USAGE_DATE) {
+        if (!mHealthdConfig->batteryFirstUsageDatePath.isEmpty())
+            return getIntField(mHealthdConfig->batteryFirstUsageDatePath);
+    }
+    return 0;
+}
+
 status_t BatteryMonitor::getProperty(int id, struct BatteryProperty *val) {
     status_t ret = BAD_VALUE;
     std::string buf;
@@ -536,6 +654,21 @@
         ret = OK;
         break;
 
+    case BATTERY_PROP_CHARGING_POLICY:
+        val->valueInt64 = getChargingPolicy();
+        ret = OK;
+        break;
+
+    case BATTERY_PROP_MANUFACTURING_DATE:
+        val->valueInt64 = getBatteryHealthData(BATTERY_PROP_MANUFACTURING_DATE);
+        ret = OK;
+        break;
+
+    case BATTERY_PROP_FIRST_USAGE_DATE:
+        val->valueInt64 = getBatteryHealthData(BATTERY_PROP_FIRST_USAGE_DATE);
+        ret = OK;
+        break;
+
     default:
         break;
     }
@@ -758,6 +891,50 @@
                         mHealthdConfig->batteryTechnologyPath = path;
                 }
 
+                if (mHealthdConfig->batteryStateOfHealthPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/state_of_health", POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0) {
+                        mHealthdConfig->batteryStateOfHealthPath = path;
+                    } else {
+                        path.clear();
+                        path.appendFormat("%s/%s/health_index", POWER_SUPPLY_SYSFS_PATH, name);
+                        if (access(path, R_OK) == 0)
+                            mHealthdConfig->batteryStateOfHealthPath = path;
+                    }
+                }
+
+                if (mHealthdConfig->batteryHealthStatusPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/health_status", POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0) mHealthdConfig->batteryHealthStatusPath = path;
+                }
+
+                if (mHealthdConfig->batteryManufacturingDatePath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/manufacturing_date", POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryManufacturingDatePath = path;
+                }
+
+                if (mHealthdConfig->batteryFirstUsageDatePath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/first_usage_date", POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0) mHealthdConfig->batteryFirstUsageDatePath = path;
+                }
+
+                if (mHealthdConfig->chargingStatePath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/charging_state", POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0) mHealthdConfig->chargingStatePath = path;
+                }
+
+                if (mHealthdConfig->chargingPolicyPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/charging_policy", POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0) mHealthdConfig->chargingPolicyPath = path;
+                }
+
                 break;
 
             case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN:
@@ -810,6 +987,18 @@
             KLOG_WARNING(LOG_TAG, "batteryChargeTimeToFullNowPath. not found\n");
         if (mHealthdConfig->batteryFullChargeDesignCapacityUahPath.isEmpty())
             KLOG_WARNING(LOG_TAG, "batteryFullChargeDesignCapacityUahPath. not found\n");
+        if (mHealthdConfig->batteryStateOfHealthPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "batteryStateOfHealthPath not found\n");
+        if (mHealthdConfig->batteryHealthStatusPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "batteryHealthStatusPath not found\n");
+        if (mHealthdConfig->batteryManufacturingDatePath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "batteryManufacturingDatePath not found\n");
+        if (mHealthdConfig->batteryFirstUsageDatePath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "batteryFirstUsageDatePath not found\n");
+        if (mHealthdConfig->chargingStatePath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "chargingStatePath not found\n");
+        if (mHealthdConfig->chargingPolicyPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "chargingPolicyPath not found\n");
     }
 
     if (property_get("ro.boot.fake_battery", pval, NULL) > 0
diff --git a/healthd/BatteryMonitor_v1.cpp b/healthd/BatteryMonitor_v1.cpp
new file mode 100644
index 0000000..b87c493
--- /dev/null
+++ b/healthd/BatteryMonitor_v1.cpp
@@ -0,0 +1,822 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#define LOG_TAG "healthd"
+
+#include <healthd/healthd.h>
+#include <healthd/BatteryMonitor_v1.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <optional>
+
+#include <aidl/android/hardware/health/HealthInfo.h>
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android/hardware/health/2.1/types.h>
+#include <android/hardware/health/translate-ndk.h>
+#include <batteryservice/BatteryService.h>
+#include <cutils/klog.h>
+#include <cutils/properties.h>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#define POWER_SUPPLY_SUBSYSTEM "power_supply"
+#define POWER_SUPPLY_SYSFS_PATH "/sys/class/" POWER_SUPPLY_SUBSYSTEM
+#define FAKE_BATTERY_CAPACITY 42
+#define FAKE_BATTERY_TEMPERATURE 424
+#define MILLION 1.0e6
+#define DEFAULT_VBUS_VOLTAGE 5000000
+
+using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
+using HealthInfo_2_0 = android::hardware::health::V2_0::HealthInfo;
+using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
+using aidl::android::hardware::health::BatteryCapacityLevel;
+using aidl::android::hardware::health::BatteryHealth;
+using aidl::android::hardware::health::BatteryStatus;
+using aidl::android::hardware::health::HealthInfo;
+
+namespace {
+
+// Translate from AIDL back to HIDL definition for getHealthInfo_*_* calls.
+// Skips storageInfo and diskStats.
+void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in,
+                     ::android::hardware::health::V1_0::HealthInfo* out) {
+    out->chargerAcOnline = in.chargerAcOnline;
+    out->chargerUsbOnline = in.chargerUsbOnline;
+    out->chargerWirelessOnline = in.chargerWirelessOnline;
+    out->maxChargingCurrent = in.maxChargingCurrentMicroamps;
+    out->maxChargingVoltage = in.maxChargingVoltageMicrovolts;
+    out->batteryStatus =
+            static_cast<::android::hardware::health::V1_0::BatteryStatus>(in.batteryStatus);
+    out->batteryHealth =
+            static_cast<::android::hardware::health::V1_0::BatteryHealth>(in.batteryHealth);
+    out->batteryPresent = in.batteryPresent;
+    out->batteryLevel = in.batteryLevel;
+    out->batteryVoltage = in.batteryVoltageMillivolts;
+    out->batteryTemperature = in.batteryTemperatureTenthsCelsius;
+    out->batteryCurrent = in.batteryCurrentMicroamps;
+    out->batteryCycleCount = in.batteryCycleCount;
+    out->batteryFullCharge = in.batteryFullChargeUah;
+    out->batteryChargeCounter = in.batteryChargeCounterUah;
+    out->batteryTechnology = in.batteryTechnology;
+}
+
+void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in,
+                     ::android::hardware::health::V2_0::HealthInfo* out) {
+    translateToHidl(in, &out->legacy);
+    out->batteryCurrentAverage = in.batteryCurrentAverageMicroamps;
+    // Skip storageInfo and diskStats
+}
+
+void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in,
+                     ::android::hardware::health::V2_1::HealthInfo* out) {
+    translateToHidl(in, &out->legacy);
+    out->batteryCapacityLevel = static_cast<android::hardware::health::V2_1::BatteryCapacityLevel>(
+            in.batteryCapacityLevel);
+    out->batteryChargeTimeToFullNowSeconds = in.batteryChargeTimeToFullNowSeconds;
+    out->batteryFullChargeDesignCapacityUah = in.batteryFullChargeDesignCapacityUah;
+}
+
+}  // namespace
+
+namespace android {
+
+template <typename T>
+struct SysfsStringEnumMap {
+    const char* s;
+    T val;
+};
+
+template <typename T>
+static std::optional<T> mapSysfsString(const char* str, SysfsStringEnumMap<T> map[]) {
+    for (int i = 0; map[i].s; i++)
+        if (!strcmp(str, map[i].s))
+            return map[i].val;
+
+    return std::nullopt;
+}
+
+static void initHealthInfo(HealthInfo* health_info) {
+    *health_info = {
+            .batteryCapacityLevel = BatteryCapacityLevel::UNSUPPORTED,
+            .batteryChargeTimeToFullNowSeconds =
+                    (int64_t)HealthInfo::BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED,
+            .batteryStatus = BatteryStatus::UNKNOWN,
+            .batteryHealth = BatteryHealth::UNKNOWN,
+    };
+}
+
+BatteryMonitor::BatteryMonitor()
+    : mHealthdConfig(nullptr),
+      mBatteryDevicePresent(false),
+      mBatteryFixedCapacity(0),
+      mBatteryFixedTemperature(0),
+      mHealthInfo(std::make_unique<HealthInfo>()) {
+    initHealthInfo(mHealthInfo.get());
+}
+
+BatteryMonitor::~BatteryMonitor() {}
+
+HealthInfo_1_0 BatteryMonitor::getHealthInfo_1_0() const {
+    HealthInfo_1_0 health_info_1_0;
+    translateToHidl(*mHealthInfo, &health_info_1_0);
+    return health_info_1_0;
+}
+
+HealthInfo_2_0 BatteryMonitor::getHealthInfo_2_0() const {
+    HealthInfo_2_0 health_info_2_0;
+    translateToHidl(*mHealthInfo, &health_info_2_0);
+    return health_info_2_0;
+}
+
+HealthInfo_2_1 BatteryMonitor::getHealthInfo_2_1() const {
+    HealthInfo_2_1 health_info_2_1;
+    translateToHidl(*mHealthInfo, &health_info_2_1);
+    return health_info_2_1;
+}
+
+const HealthInfo& BatteryMonitor::getHealthInfo() const {
+    return *mHealthInfo;
+}
+
+BatteryStatus getBatteryStatus(const char* status) {
+    static SysfsStringEnumMap<BatteryStatus> batteryStatusMap[] = {
+            {"Unknown", BatteryStatus::UNKNOWN},
+            {"Charging", BatteryStatus::CHARGING},
+            {"Discharging", BatteryStatus::DISCHARGING},
+            {"Not charging", BatteryStatus::NOT_CHARGING},
+            {"Full", BatteryStatus::FULL},
+            {NULL, BatteryStatus::UNKNOWN},
+    };
+
+    auto ret = mapSysfsString(status, batteryStatusMap);
+    if (!ret) {
+        KLOG_WARNING(LOG_TAG, "Unknown battery status '%s'\n", status);
+        *ret = BatteryStatus::UNKNOWN;
+    }
+
+    return *ret;
+}
+
+BatteryCapacityLevel getBatteryCapacityLevel(const char* capacityLevel) {
+    static SysfsStringEnumMap<BatteryCapacityLevel> batteryCapacityLevelMap[] = {
+            {"Unknown", BatteryCapacityLevel::UNKNOWN},
+            {"Critical", BatteryCapacityLevel::CRITICAL},
+            {"Low", BatteryCapacityLevel::LOW},
+            {"Normal", BatteryCapacityLevel::NORMAL},
+            {"High", BatteryCapacityLevel::HIGH},
+            {"Full", BatteryCapacityLevel::FULL},
+            {NULL, BatteryCapacityLevel::UNSUPPORTED},
+    };
+
+    auto ret = mapSysfsString(capacityLevel, batteryCapacityLevelMap);
+    if (!ret) {
+        KLOG_WARNING(LOG_TAG, "Unsupported battery capacity level '%s'\n", capacityLevel);
+        *ret = BatteryCapacityLevel::UNSUPPORTED;
+    }
+
+    return *ret;
+}
+
+BatteryHealth getBatteryHealth(const char* status) {
+    static SysfsStringEnumMap<BatteryHealth> batteryHealthMap[] = {
+            {"Unknown", BatteryHealth::UNKNOWN},
+            {"Good", BatteryHealth::GOOD},
+            {"Overheat", BatteryHealth::OVERHEAT},
+            {"Dead", BatteryHealth::DEAD},
+            {"Over voltage", BatteryHealth::OVER_VOLTAGE},
+            {"Unspecified failure", BatteryHealth::UNSPECIFIED_FAILURE},
+            {"Cold", BatteryHealth::COLD},
+            // battery health values from JEITA spec
+            {"Warm", BatteryHealth::GOOD},
+            {"Cool", BatteryHealth::GOOD},
+            {"Hot", BatteryHealth::OVERHEAT},
+            {NULL, BatteryHealth::UNKNOWN},
+    };
+
+    auto ret = mapSysfsString(status, batteryHealthMap);
+    if (!ret) {
+        KLOG_WARNING(LOG_TAG, "Unknown battery health '%s'\n", status);
+        *ret = BatteryHealth::UNKNOWN;
+    }
+
+    return *ret;
+}
+
+static int readFromFile(const String8& path, std::string* buf) {
+    buf->clear();
+    if (android::base::ReadFileToString(path.c_str(), buf)) {
+        *buf = android::base::Trim(*buf);
+    }
+    return buf->length();
+}
+
+static BatteryMonitor::PowerSupplyType readPowerSupplyType(const String8& path) {
+    static SysfsStringEnumMap<int> supplyTypeMap[] = {
+            {"Unknown", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN},
+            {"Battery", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_BATTERY},
+            {"UPS", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"Mains", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_USB},
+            {"USB_DCP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_HVDCP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_CDP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_ACA", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_C", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_PD", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC},
+            {"USB_PD_DRP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_USB},
+            {"Wireless", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_WIRELESS},
+            {"Dock", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_DOCK},
+            {NULL, 0},
+    };
+    std::string buf;
+
+    if (readFromFile(path, &buf) <= 0) {
+        return BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
+    }
+
+    auto ret = mapSysfsString(buf.c_str(), supplyTypeMap);
+    if (!ret) {
+        KLOG_WARNING(LOG_TAG, "Unknown power supply type '%s'\n", buf.c_str());
+        *ret = BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
+    }
+
+    return static_cast<BatteryMonitor::PowerSupplyType>(*ret);
+}
+
+static bool getBooleanField(const String8& path) {
+    std::string buf;
+    bool value = false;
+
+    if (readFromFile(path, &buf) > 0)
+        if (buf[0] != '0')
+            value = true;
+
+    return value;
+}
+
+static int getIntField(const String8& path) {
+    std::string buf;
+    int value = 0;
+
+    if (readFromFile(path, &buf) > 0)
+        android::base::ParseInt(buf, &value);
+
+    return value;
+}
+
+static bool isScopedPowerSupply(const char* name) {
+    constexpr char kScopeDevice[] = "Device";
+
+    String8 path;
+    path.appendFormat("%s/%s/scope", POWER_SUPPLY_SYSFS_PATH, name);
+    std::string scope;
+    return (readFromFile(path, &scope) > 0 && scope == kScopeDevice);
+}
+
+void BatteryMonitor::updateValues(void) {
+    initHealthInfo(mHealthInfo.get());
+
+    if (!mHealthdConfig->batteryPresentPath.isEmpty())
+        mHealthInfo->batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
+    else
+        mHealthInfo->batteryPresent = mBatteryDevicePresent;
+
+    mHealthInfo->batteryLevel = mBatteryFixedCapacity
+                                        ? mBatteryFixedCapacity
+                                        : getIntField(mHealthdConfig->batteryCapacityPath);
+    mHealthInfo->batteryVoltageMillivolts = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
+
+    if (!mHealthdConfig->batteryCurrentNowPath.isEmpty())
+        mHealthInfo->batteryCurrentMicroamps = getIntField(mHealthdConfig->batteryCurrentNowPath);
+
+    if (!mHealthdConfig->batteryFullChargePath.isEmpty())
+        mHealthInfo->batteryFullChargeUah = getIntField(mHealthdConfig->batteryFullChargePath);
+
+    if (!mHealthdConfig->batteryCycleCountPath.isEmpty())
+        mHealthInfo->batteryCycleCount = getIntField(mHealthdConfig->batteryCycleCountPath);
+
+    if (!mHealthdConfig->batteryChargeCounterPath.isEmpty())
+        mHealthInfo->batteryChargeCounterUah =
+                getIntField(mHealthdConfig->batteryChargeCounterPath);
+
+    if (!mHealthdConfig->batteryCurrentAvgPath.isEmpty())
+        mHealthInfo->batteryCurrentAverageMicroamps =
+                getIntField(mHealthdConfig->batteryCurrentAvgPath);
+
+    if (!mHealthdConfig->batteryChargeTimeToFullNowPath.isEmpty())
+        mHealthInfo->batteryChargeTimeToFullNowSeconds =
+                getIntField(mHealthdConfig->batteryChargeTimeToFullNowPath);
+
+    if (!mHealthdConfig->batteryFullChargeDesignCapacityUahPath.isEmpty())
+        mHealthInfo->batteryFullChargeDesignCapacityUah =
+                getIntField(mHealthdConfig->batteryFullChargeDesignCapacityUahPath);
+
+    mHealthInfo->batteryTemperatureTenthsCelsius =
+            mBatteryFixedTemperature ? mBatteryFixedTemperature
+                                     : getIntField(mHealthdConfig->batteryTemperaturePath);
+
+    std::string buf;
+
+    if (readFromFile(mHealthdConfig->batteryCapacityLevelPath, &buf) > 0)
+        mHealthInfo->batteryCapacityLevel = getBatteryCapacityLevel(buf.c_str());
+
+    if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0)
+        mHealthInfo->batteryStatus = getBatteryStatus(buf.c_str());
+
+    if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
+        mHealthInfo->batteryHealth = getBatteryHealth(buf.c_str());
+
+    if (readFromFile(mHealthdConfig->batteryTechnologyPath, &buf) > 0)
+        mHealthInfo->batteryTechnology = String8(buf.c_str());
+
+    double MaxPower = 0;
+
+    for (size_t i = 0; i < mChargerNames.size(); i++) {
+        String8 path;
+        path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH,
+                          mChargerNames[i].string());
+        if (getIntField(path)) {
+            path.clear();
+            path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH,
+                              mChargerNames[i].string());
+            switch(readPowerSupplyType(path)) {
+            case ANDROID_POWER_SUPPLY_TYPE_AC:
+                mHealthInfo->chargerAcOnline = true;
+                break;
+            case ANDROID_POWER_SUPPLY_TYPE_USB:
+                mHealthInfo->chargerUsbOnline = true;
+                break;
+            case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
+                mHealthInfo->chargerWirelessOnline = true;
+                break;
+            case ANDROID_POWER_SUPPLY_TYPE_DOCK:
+                mHealthInfo->chargerDockOnline = true;
+                break;
+            default:
+                path.clear();
+                path.appendFormat("%s/%s/is_dock", POWER_SUPPLY_SYSFS_PATH,
+                                  mChargerNames[i].string());
+                if (access(path.string(), R_OK) == 0)
+                    mHealthInfo->chargerDockOnline = true;
+                else
+                    KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
+                                 mChargerNames[i].string());
+            }
+            path.clear();
+            path.appendFormat("%s/%s/current_max", POWER_SUPPLY_SYSFS_PATH,
+                              mChargerNames[i].string());
+            int ChargingCurrent =
+                    (access(path.string(), R_OK) == 0) ? getIntField(path) : 0;
+
+            path.clear();
+            path.appendFormat("%s/%s/voltage_max", POWER_SUPPLY_SYSFS_PATH,
+                              mChargerNames[i].string());
+
+            int ChargingVoltage =
+                (access(path.string(), R_OK) == 0) ? getIntField(path) :
+                DEFAULT_VBUS_VOLTAGE;
+
+            double power = ((double)ChargingCurrent / MILLION) *
+                           ((double)ChargingVoltage / MILLION);
+            if (MaxPower < power) {
+                mHealthInfo->maxChargingCurrentMicroamps = ChargingCurrent;
+                mHealthInfo->maxChargingVoltageMicrovolts = ChargingVoltage;
+                MaxPower = power;
+            }
+        }
+    }
+}
+
+static void doLogValues(const HealthInfo& props, const struct healthd_config& healthd_config) {
+    char dmesgline[256];
+    size_t len;
+    if (props.batteryPresent) {
+        snprintf(dmesgline, sizeof(dmesgline), "battery l=%d v=%d t=%s%d.%d h=%d st=%d",
+                 props.batteryLevel, props.batteryVoltageMillivolts,
+                 props.batteryTemperatureTenthsCelsius < 0 ? "-" : "",
+                 abs(props.batteryTemperatureTenthsCelsius / 10),
+                 abs(props.batteryTemperatureTenthsCelsius % 10), props.batteryHealth,
+                 props.batteryStatus);
+
+        len = strlen(dmesgline);
+        if (!healthd_config.batteryCurrentNowPath.isEmpty()) {
+            len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " c=%d",
+                            props.batteryCurrentMicroamps);
+        }
+
+        if (!healthd_config.batteryFullChargePath.isEmpty()) {
+            len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " fc=%d",
+                            props.batteryFullChargeUah);
+        }
+
+        if (!healthd_config.batteryCycleCountPath.isEmpty()) {
+            len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " cc=%d",
+                            props.batteryCycleCount);
+        }
+    } else {
+        len = snprintf(dmesgline, sizeof(dmesgline), "battery none");
+    }
+
+    snprintf(dmesgline + len, sizeof(dmesgline) - len, " chg=%s%s%s%s",
+             props.chargerAcOnline ? "a" : "", props.chargerUsbOnline ? "u" : "",
+             props.chargerWirelessOnline ? "w" : "", props.chargerDockOnline ? "d" : "");
+
+    KLOG_WARNING(LOG_TAG, "%s\n", dmesgline);
+}
+
+void BatteryMonitor::logValues(const HealthInfo_2_1& health_info,
+                               const struct healthd_config& healthd_config) {
+    HealthInfo aidl_health_info;
+    (void)android::h2a::translate(health_info, &aidl_health_info);
+    doLogValues(aidl_health_info, healthd_config);
+}
+
+void BatteryMonitor::logValues(void) {
+    doLogValues(*mHealthInfo, *mHealthdConfig);
+}
+
+bool BatteryMonitor::isChargerOnline() {
+    const HealthInfo& props = *mHealthInfo;
+    return props.chargerAcOnline | props.chargerUsbOnline | props.chargerWirelessOnline |
+           props.chargerDockOnline;
+}
+
+int BatteryMonitor::getChargeStatus() {
+    BatteryStatus result = BatteryStatus::UNKNOWN;
+    if (!mHealthdConfig->batteryStatusPath.isEmpty()) {
+        std::string buf;
+        if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0)
+            result = getBatteryStatus(buf.c_str());
+    }
+    return static_cast<int>(result);
+}
+
+status_t BatteryMonitor::getProperty(int id, struct BatteryProperty *val) {
+    status_t ret = BAD_VALUE;
+    std::string buf;
+
+    val->valueInt64 = LONG_MIN;
+
+    switch(id) {
+    case BATTERY_PROP_CHARGE_COUNTER:
+        if (!mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
+            val->valueInt64 =
+                getIntField(mHealthdConfig->batteryChargeCounterPath);
+            ret = OK;
+        } else {
+            ret = NAME_NOT_FOUND;
+        }
+        break;
+
+    case BATTERY_PROP_CURRENT_NOW:
+        if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
+            val->valueInt64 =
+                getIntField(mHealthdConfig->batteryCurrentNowPath);
+            ret = OK;
+        } else {
+            ret = NAME_NOT_FOUND;
+        }
+        break;
+
+    case BATTERY_PROP_CURRENT_AVG:
+        if (!mHealthdConfig->batteryCurrentAvgPath.isEmpty()) {
+            val->valueInt64 =
+                getIntField(mHealthdConfig->batteryCurrentAvgPath);
+            ret = OK;
+        } else {
+            ret = NAME_NOT_FOUND;
+        }
+        break;
+
+    case BATTERY_PROP_CAPACITY:
+        if (!mHealthdConfig->batteryCapacityPath.isEmpty()) {
+            val->valueInt64 =
+                getIntField(mHealthdConfig->batteryCapacityPath);
+            ret = OK;
+        } else {
+            ret = NAME_NOT_FOUND;
+        }
+        break;
+
+    case BATTERY_PROP_ENERGY_COUNTER:
+        if (mHealthdConfig->energyCounter) {
+            ret = mHealthdConfig->energyCounter(&val->valueInt64);
+        } else {
+            ret = NAME_NOT_FOUND;
+        }
+        break;
+
+    case BATTERY_PROP_BATTERY_STATUS:
+        val->valueInt64 = getChargeStatus();
+        ret = OK;
+        break;
+
+    default:
+        break;
+    }
+
+    return ret;
+}
+
+void BatteryMonitor::dumpState(int fd) {
+    int v;
+    char vs[128];
+    const HealthInfo& props = *mHealthInfo;
+
+    snprintf(vs, sizeof(vs),
+             "ac: %d usb: %d wireless: %d dock: %d current_max: %d voltage_max: %d\n",
+             props.chargerAcOnline, props.chargerUsbOnline, props.chargerWirelessOnline,
+             props.chargerDockOnline, props.maxChargingCurrentMicroamps,
+             props.maxChargingVoltageMicrovolts);
+    write(fd, vs, strlen(vs));
+    snprintf(vs, sizeof(vs), "status: %d health: %d present: %d\n",
+             props.batteryStatus, props.batteryHealth, props.batteryPresent);
+    write(fd, vs, strlen(vs));
+    snprintf(vs, sizeof(vs), "level: %d voltage: %d temp: %d\n", props.batteryLevel,
+             props.batteryVoltageMillivolts, props.batteryTemperatureTenthsCelsius);
+    write(fd, vs, strlen(vs));
+
+    if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
+        v = getIntField(mHealthdConfig->batteryCurrentNowPath);
+        snprintf(vs, sizeof(vs), "current now: %d\n", v);
+        write(fd, vs, strlen(vs));
+    }
+
+    if (!mHealthdConfig->batteryCurrentAvgPath.isEmpty()) {
+        v = getIntField(mHealthdConfig->batteryCurrentAvgPath);
+        snprintf(vs, sizeof(vs), "current avg: %d\n", v);
+        write(fd, vs, strlen(vs));
+    }
+
+    if (!mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
+        v = getIntField(mHealthdConfig->batteryChargeCounterPath);
+        snprintf(vs, sizeof(vs), "charge counter: %d\n", v);
+        write(fd, vs, strlen(vs));
+    }
+
+    if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
+        snprintf(vs, sizeof(vs), "current now: %d\n", props.batteryCurrentMicroamps);
+        write(fd, vs, strlen(vs));
+    }
+
+    if (!mHealthdConfig->batteryCycleCountPath.isEmpty()) {
+        snprintf(vs, sizeof(vs), "cycle count: %d\n", props.batteryCycleCount);
+        write(fd, vs, strlen(vs));
+    }
+
+    if (!mHealthdConfig->batteryFullChargePath.isEmpty()) {
+        snprintf(vs, sizeof(vs), "Full charge: %d\n", props.batteryFullChargeUah);
+        write(fd, vs, strlen(vs));
+    }
+}
+
+void BatteryMonitor::init(struct healthd_config *hc) {
+    String8 path;
+    char pval[PROPERTY_VALUE_MAX];
+
+    mHealthdConfig = hc;
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(POWER_SUPPLY_SYSFS_PATH), closedir);
+    if (dir == NULL) {
+        KLOG_ERROR(LOG_TAG, "Could not open %s\n", POWER_SUPPLY_SYSFS_PATH);
+    } else {
+        struct dirent* entry;
+
+        while ((entry = readdir(dir.get()))) {
+            const char* name = entry->d_name;
+
+            if (!strcmp(name, ".") || !strcmp(name, ".."))
+                continue;
+
+            std::vector<String8>::iterator itIgnoreName =
+                    find(hc->ignorePowerSupplyNames.begin(), hc->ignorePowerSupplyNames.end(),
+                         String8(name));
+            if (itIgnoreName != hc->ignorePowerSupplyNames.end())
+                continue;
+
+            // Look for "type" file in each subdirectory
+            path.clear();
+            path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, name);
+            switch(readPowerSupplyType(path)) {
+            case ANDROID_POWER_SUPPLY_TYPE_AC:
+            case ANDROID_POWER_SUPPLY_TYPE_USB:
+            case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
+            case ANDROID_POWER_SUPPLY_TYPE_DOCK:
+                path.clear();
+                path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name);
+                if (access(path.string(), R_OK) == 0)
+                    mChargerNames.add(String8(name));
+                break;
+
+            case ANDROID_POWER_SUPPLY_TYPE_BATTERY:
+                // Some devices expose the battery status of sub-component like
+                // stylus. Such a device-scoped battery info needs to be skipped
+                // in BatteryMonitor, which is intended to report the status of
+                // the battery supplying the power to the whole system.
+                if (isScopedPowerSupply(name)) continue;
+                mBatteryDevicePresent = true;
+
+                if (mHealthdConfig->batteryStatusPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH,
+                                      name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryStatusPath = path;
+                }
+
+                if (mHealthdConfig->batteryHealthPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH,
+                                      name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryHealthPath = path;
+                }
+
+                if (mHealthdConfig->batteryPresentPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH,
+                                      name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryPresentPath = path;
+                }
+
+                if (mHealthdConfig->batteryCapacityPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/capacity", POWER_SUPPLY_SYSFS_PATH,
+                                      name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryCapacityPath = path;
+                }
+
+                if (mHealthdConfig->batteryVoltagePath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/voltage_now",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0) {
+                        mHealthdConfig->batteryVoltagePath = path;
+                    }
+                }
+
+                if (mHealthdConfig->batteryFullChargePath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/charge_full",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryFullChargePath = path;
+                }
+
+                if (mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/current_now",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryCurrentNowPath = path;
+                }
+
+                if (mHealthdConfig->batteryCycleCountPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/cycle_count",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryCycleCountPath = path;
+                }
+
+                if (mHealthdConfig->batteryCapacityLevelPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/capacity_level", POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0) mHealthdConfig->batteryCapacityLevelPath = path;
+                }
+
+                if (mHealthdConfig->batteryChargeTimeToFullNowPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/time_to_full_now", POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryChargeTimeToFullNowPath = path;
+                }
+
+                if (mHealthdConfig->batteryFullChargeDesignCapacityUahPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/charge_full_design", POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryFullChargeDesignCapacityUahPath = path;
+                }
+
+                if (mHealthdConfig->batteryCurrentAvgPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/current_avg",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryCurrentAvgPath = path;
+                }
+
+                if (mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/charge_counter",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryChargeCounterPath = path;
+                }
+
+                if (mHealthdConfig->batteryTemperaturePath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/temp", POWER_SUPPLY_SYSFS_PATH,
+                                      name);
+                    if (access(path, R_OK) == 0) {
+                        mHealthdConfig->batteryTemperaturePath = path;
+                    }
+                }
+
+                if (mHealthdConfig->batteryTechnologyPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/technology",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryTechnologyPath = path;
+                }
+
+                break;
+
+            case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN:
+                break;
+            }
+
+            // Look for "is_dock" file
+            path.clear();
+            path.appendFormat("%s/%s/is_dock", POWER_SUPPLY_SYSFS_PATH, name);
+            if (access(path.string(), R_OK) == 0) {
+                path.clear();
+                path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name);
+                if (access(path.string(), R_OK) == 0)
+                    mChargerNames.add(String8(name));
+
+            }
+        }
+    }
+
+    // Typically the case for devices which do not have a battery and
+    // and are always plugged into AC mains.
+    if (!mBatteryDevicePresent) {
+        KLOG_WARNING(LOG_TAG, "No battery devices found\n");
+        hc->periodic_chores_interval_fast = -1;
+        hc->periodic_chores_interval_slow = -1;
+    } else {
+        if (mHealthdConfig->batteryStatusPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "BatteryStatusPath not found\n");
+        if (mHealthdConfig->batteryHealthPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "BatteryHealthPath not found\n");
+        if (mHealthdConfig->batteryPresentPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "BatteryPresentPath not found\n");
+        if (mHealthdConfig->batteryCapacityPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "BatteryCapacityPath not found\n");
+        if (mHealthdConfig->batteryVoltagePath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "BatteryVoltagePath not found\n");
+        if (mHealthdConfig->batteryTemperaturePath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "BatteryTemperaturePath not found\n");
+        if (mHealthdConfig->batteryTechnologyPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "BatteryTechnologyPath not found\n");
+        if (mHealthdConfig->batteryCurrentNowPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "BatteryCurrentNowPath not found\n");
+        if (mHealthdConfig->batteryFullChargePath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "BatteryFullChargePath not found\n");
+        if (mHealthdConfig->batteryCycleCountPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "BatteryCycleCountPath not found\n");
+        if (mHealthdConfig->batteryCapacityLevelPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "batteryCapacityLevelPath not found\n");
+        if (mHealthdConfig->batteryChargeTimeToFullNowPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "batteryChargeTimeToFullNowPath. not found\n");
+        if (mHealthdConfig->batteryFullChargeDesignCapacityUahPath.isEmpty())
+            KLOG_WARNING(LOG_TAG, "batteryFullChargeDesignCapacityUahPath. not found\n");
+    }
+
+    if (property_get("ro.boot.fake_battery", pval, NULL) > 0
+                                               && strtol(pval, NULL, 10) != 0) {
+        mBatteryFixedCapacity = FAKE_BATTERY_CAPACITY;
+        mBatteryFixedTemperature = FAKE_BATTERY_TEMPERATURE;
+    }
+}
+
+}; // namespace android
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index 8cbf5ea..7b4f46c 100644
--- a/healthd/include/healthd/BatteryMonitor.h
+++ b/healthd/include/healthd/BatteryMonitor.h
@@ -56,6 +56,14 @@
         ANDROID_POWER_SUPPLY_TYPE_DOCK
     };
 
+    enum BatteryHealthStatus {
+        BH_UNKNOWN = -1,
+        BH_NOMINAL,
+        BH_MARGINAL,
+        BH_NEEDS_REPLACEMENT,
+        BH_FAILED,
+    };
+
     BatteryMonitor();
     ~BatteryMonitor();
     void init(struct healthd_config *hc);
@@ -72,6 +80,10 @@
     void logValues(void);
     bool isChargerOnline();
 
+    int setChargingPolicy(int value);
+    int getChargingPolicy();
+    int getBatteryHealthData(int id);
+
     static void logValues(const android::hardware::health::V2_1::HealthInfo& health_info,
                           const struct healthd_config& healthd_config);
 
@@ -81,6 +93,7 @@
     bool mBatteryDevicePresent;
     int mBatteryFixedCapacity;
     int mBatteryFixedTemperature;
+    int mBatteryHealthStatus;
     std::unique_ptr<aidl::android::hardware::health::HealthInfo> mHealthInfo;
 };
 
diff --git a/healthd/include/healthd/BatteryMonitor_v1.h b/healthd/include/healthd/BatteryMonitor_v1.h
new file mode 100644
index 0000000..49f6f9d
--- /dev/null
+++ b/healthd/include/healthd/BatteryMonitor_v1.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef HEALTHD_BATTERYMONITOR_V1_H
+#define HEALTHD_BATTERYMONITOR_V1_H
+
+#include <memory>
+
+#include <batteryservice/BatteryService.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <healthd/healthd.h>
+
+namespace aidl::android::hardware::health {
+class HealthInfo;
+}  // namespace aidl::android::hardware::health
+
+namespace android {
+namespace hardware {
+namespace health {
+namespace V1_0 {
+struct HealthInfo;
+}  // namespace V1_0
+namespace V2_0 {
+struct HealthInfo;
+}  // namespace V2_0
+namespace V2_1 {
+struct HealthInfo;
+}  // namespace V2_1
+}  // namespace health
+}  // namespace hardware
+
+class BatteryMonitor {
+  public:
+
+    enum PowerSupplyType {
+        ANDROID_POWER_SUPPLY_TYPE_UNKNOWN = 0,
+        ANDROID_POWER_SUPPLY_TYPE_AC,
+        ANDROID_POWER_SUPPLY_TYPE_USB,
+        ANDROID_POWER_SUPPLY_TYPE_WIRELESS,
+        ANDROID_POWER_SUPPLY_TYPE_BATTERY,
+        ANDROID_POWER_SUPPLY_TYPE_DOCK
+    };
+
+    BatteryMonitor();
+    ~BatteryMonitor();
+    void init(struct healthd_config *hc);
+    int getChargeStatus();
+    status_t getProperty(int id, struct BatteryProperty *val);
+    void dumpState(int fd);
+
+    android::hardware::health::V1_0::HealthInfo getHealthInfo_1_0() const;
+    android::hardware::health::V2_0::HealthInfo getHealthInfo_2_0() const;
+    android::hardware::health::V2_1::HealthInfo getHealthInfo_2_1() const;
+    const aidl::android::hardware::health::HealthInfo& getHealthInfo() const;
+
+    void updateValues(void);
+    void logValues(void);
+    bool isChargerOnline();
+
+    static void logValues(const android::hardware::health::V2_1::HealthInfo& health_info,
+                          const struct healthd_config& healthd_config);
+
+  private:
+    struct healthd_config *mHealthdConfig;
+    Vector<String8> mChargerNames;
+    bool mBatteryDevicePresent;
+    int mBatteryFixedCapacity;
+    int mBatteryFixedTemperature;
+    std::unique_ptr<aidl::android::hardware::health::HealthInfo> mHealthInfo;
+};
+
+}; // namespace android
+
+#endif // HEALTHD_BATTERYMONITOR_V1_H
diff --git a/healthd/include/healthd/healthd.h b/healthd/include/healthd/healthd.h
index 706c332..688e458 100644
--- a/healthd/include/healthd/healthd.h
+++ b/healthd/include/healthd/healthd.h
@@ -72,6 +72,12 @@
     android::String8 batteryCapacityLevelPath;
     android::String8 batteryChargeTimeToFullNowPath;
     android::String8 batteryFullChargeDesignCapacityUahPath;
+    android::String8 batteryStateOfHealthPath;
+    android::String8 batteryHealthStatusPath;
+    android::String8 batteryManufacturingDatePath;
+    android::String8 batteryFirstUsageDatePath;
+    android::String8 chargingStatePath;
+    android::String8 chargingPolicyPath;
 
     int (*energyCounter)(int64_t *);
     int boot_min_cap;
diff --git a/init/OWNERS b/init/OWNERS
index 4604d06..68b9396 100644
--- a/init/OWNERS
+++ b/init/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 1312227
 dvander@google.com
 jiyong@google.com
diff --git a/init/init.cpp b/init/init.cpp
index 4262191..f964c60 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -952,6 +952,8 @@
     InitKernelLogging(argv);
     LOG(INFO) << "init second stage started!";
 
+    SelinuxSetupKernelLogging();
+
     // Update $PATH in the case the second stage init is newer than first stage init, where it is
     // first set.
     if (setenv("PATH", _PATH_DEFPATH, 1) != 0) {
@@ -1012,7 +1014,6 @@
     MountExtraFilesystems();
 
     // Now set up SELinux for second stage.
-    SelinuxSetupKernelLogging();
     SelabelInitialize();
     SelinuxRestoreContext();
 
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 1e69ede..7bb5c90 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -679,6 +679,10 @@
 }
 
 TEST(init, GentleKill) {
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Must be run as root.";
+        return;
+    }
     std::string init_script = R"init(
 service test_gentle_kill /system/bin/sleep 1000
     disabled
diff --git a/init/selinux.cpp b/init/selinux.cpp
index ea308aa..4cc00fe 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -629,7 +629,7 @@
 }
 
 Result<void> SepolicyFsVerityCheck() {
-    return Error() << "TODO implementent support for fsverity SEPolicy.";
+    return Error() << "TODO implement support for fsverity SEPolicy.";
 }
 
 Result<void> SepolicyCheckSignature(const std::string& dir) {
diff --git a/libcutils/qtaguid.cpp b/libcutils/qtaguid.cpp
index a987b85..f847782 100644
--- a/libcutils/qtaguid.cpp
+++ b/libcutils/qtaguid.cpp
@@ -22,76 +22,60 @@
 
 #include <dlfcn.h>
 #include <errno.h>
-#include <fcntl.h>
 #include <inttypes.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
 
 #include <log/log.h>
 
-class netdHandler {
-  public:
+struct netdHandler {
     int (*netdTagSocket)(int, uint32_t, uid_t);
     int (*netdUntagSocket)(int);
 };
 
-int stubTagSocket(int, uint32_t, uid_t) {
+static int stubTagSocket(int, uint32_t, uid_t) {
     return -EREMOTEIO;
 }
 
-int stubUntagSocket(int) {
+static int stubUntagSocket(int) {
     return -EREMOTEIO;
 }
 
-netdHandler initHandler(void) {
-    netdHandler handler = {stubTagSocket, stubUntagSocket};
+static netdHandler initHandler(void) {
+    const netdHandler stubHandler = { stubTagSocket, stubUntagSocket };
 
     void* netdClientHandle = dlopen("libnetd_client.so", RTLD_NOW);
     if (!netdClientHandle) {
         ALOGE("Failed to open libnetd_client.so: %s", dlerror());
-        return handler;
+        return stubHandler;
     }
 
+    netdHandler handler;
     handler.netdTagSocket = (int (*)(int, uint32_t, uid_t))dlsym(netdClientHandle, "tagSocket");
     if (!handler.netdTagSocket) {
         ALOGE("load netdTagSocket handler failed: %s", dlerror());
+        return stubHandler;
     }
 
     handler.netdUntagSocket = (int (*)(int))dlsym(netdClientHandle, "untagSocket");
     if (!handler.netdUntagSocket) {
         ALOGE("load netdUntagSocket handler failed: %s", dlerror());
+        return stubHandler;
     }
 
     return handler;
 }
 
 // The language guarantees that this object will be initialized in a thread-safe way.
-static netdHandler& getHandler() {
-    static netdHandler instance = initHandler();
+static const netdHandler& getHandler() {
+    static const netdHandler instance = initHandler();
     return instance;
 }
 
 int qtaguid_tagSocket(int sockfd, int tag, uid_t uid) {
-    // Check the socket fd passed to us is still valid before we load the netd
-    // client. Pass a already closed socket fd to netd client may let netd open
-    // the unix socket with the same fd number and pass it to server for
-    // tagging.
-    // TODO: move the check into netdTagSocket.
-    int res = fcntl(sockfd, F_GETFD);
-    if (res < 0) return res;
-
     ALOGV("Tagging socket %d with tag %u for uid %d", sockfd, tag, uid);
     return getHandler().netdTagSocket(sockfd, tag, uid);
 }
 
 int qtaguid_untagSocket(int sockfd) {
-    // Similiar to tag socket. We need a check before untag to make sure untag a closed socket fail
-    // as expected.
-    // TODO: move the check into netdTagSocket.
-    int res = fcntl(sockfd, F_GETFD);
-    if (res < 0) return res;
-
     ALOGV("Untagging socket %d", sockfd);
     return getHandler().netdUntagSocket(sockfd);
 }
diff --git a/libcutils/sched_policy_test.cpp b/libcutils/sched_policy_test.cpp
index b9e2832..50bd6d0 100644
--- a/libcutils/sched_policy_test.cpp
+++ b/libcutils/sched_policy_test.cpp
@@ -75,9 +75,11 @@
     }
 
     ASSERT_EQ(0, set_sched_policy(0, SP_BACKGROUND));
+    ASSERT_EQ(0, set_cpuset_policy(0, SP_BACKGROUND));
     AssertPolicy(SP_BACKGROUND);
 
     ASSERT_EQ(0, set_sched_policy(0, SP_FOREGROUND));
+    ASSERT_EQ(0, set_cpuset_policy(0, SP_FOREGROUND));
     AssertPolicy(SP_FOREGROUND);
 }
 
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 468d796..ce7f10b 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -229,12 +229,17 @@
         auto controller_count = ACgroupFile_getControllerCount();
         for (uint32_t i = 0; i < controller_count; ++i) {
             const ACgroupController* controller = ACgroupFile_getController(i);
-            if (ACgroupController_getFlags(controller) &
-                CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
+            const uint32_t flags = ACgroupController_getFlags(controller);
+            if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
                 std::string str("+");
                 str.append(ACgroupController_getName(controller));
                 if (!WriteStringToFile(str, path + "/cgroup.subtree_control")) {
-                    return -errno;
+                    if (flags & CGROUPRC_CONTROLLER_FLAG_OPTIONAL) {
+                        PLOG(WARNING) << "Activation of cgroup controller " << str
+                                      << " failed in path " << path;
+                    } else {
+                        return -errno;
+                    }
                 }
             }
         }
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index 304248a..fbeedf9 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -254,86 +254,64 @@
 // To avoid issues in sdk_mac build
 #if defined(__ANDROID__)
 
-static bool SetupCgroup(const CgroupDescriptor& descriptor) {
+static bool IsOptionalController(const format::CgroupController* controller) {
+    return controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+}
+
+static bool MountV2CgroupController(const CgroupDescriptor& descriptor) {
     const format::CgroupController* controller = descriptor.controller();
 
-    int result;
-    if (controller->version() == 2) {
-        result = 0;
-        if (!strcmp(controller->name(), CGROUPV2_CONTROLLER_NAME)) {
-            // /sys/fs/cgroup is created by cgroup2 with specific selinux permissions,
-            // try to create again in case the mount point is changed
-            if (!Mkdir(controller->path(), 0, "", "")) {
-                LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
-                return false;
-            }
+    // /sys/fs/cgroup is created by cgroup2 with specific selinux permissions,
+    // try to create again in case the mount point is changed
+    if (!Mkdir(controller->path(), 0, "", "")) {
+        LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+        return false;
+    }
 
-            // The memory_recursiveprot mount option has been introduced by kernel commit
-            // 8a931f801340 ("mm: memcontrol: recursive memory.low protection"; v5.7). Try first to
-            // mount with that option enabled. If mounting fails because the kernel is too old,
-            // retry without that mount option.
-            if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
-                      "memory_recursiveprot") < 0) {
-                LOG(INFO) << "Mounting memcg with memory_recursiveprot failed. Retrying without.";
-                if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
-                          nullptr) < 0) {
-                    PLOG(ERROR) << "Failed to mount cgroup v2";
-                }
-            }
-
-            // selinux permissions change after mounting, so it's ok to change mode and owner now
-            if (!ChangeDirModeAndOwner(controller->path(), descriptor.mode(), descriptor.uid(),
-                                       descriptor.gid())) {
-                LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
-                result = -1;
-            }
-        } else {
-            if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
-                LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
-                return false;
-            }
-
-            if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
-                std::string str = std::string("+") + controller->name();
-                std::string path = std::string(controller->path()) + "/cgroup.subtree_control";
-
-                if (!base::WriteStringToFile(str, path)) {
-                    LOG(ERROR) << "Failed to activate controller " << controller->name();
-                    return false;
-                }
-            }
-        }
-    } else {
-        // mkdir <path> [mode] [owner] [group]
-        if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
-            LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
-            return false;
-        }
-
-        // Unfortunately historically cpuset controller was mounted using a mount command
-        // different from all other controllers. This results in controller attributes not
-        // to be prepended with controller name. For example this way instead of
-        // /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what
-        // the system currently expects.
-        if (!strcmp(controller->name(), "cpuset")) {
-            // mount cpuset none /dev/cpuset nodev noexec nosuid
-            result = mount("none", controller->path(), controller->name(),
-                           MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr);
-        } else {
-            // mount cgroup none <path> nodev noexec nosuid <controller>
-            result = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID,
-                           controller->name());
+    // The memory_recursiveprot mount option has been introduced by kernel commit
+    // 8a931f801340 ("mm: memcontrol: recursive memory.low protection"; v5.7). Try first to
+    // mount with that option enabled. If mounting fails because the kernel is too old,
+    // retry without that mount option.
+    if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+              "memory_recursiveprot") < 0) {
+        LOG(INFO) << "Mounting memcg with memory_recursiveprot failed. Retrying without.";
+        if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+                  nullptr) < 0) {
+            PLOG(ERROR) << "Failed to mount cgroup v2";
+            return IsOptionalController(controller);
         }
     }
 
-    if (result < 0) {
-        bool optional = controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+    // selinux permissions change after mounting, so it's ok to change mode and owner now
+    if (!ChangeDirModeAndOwner(controller->path(), descriptor.mode(), descriptor.uid(),
+                               descriptor.gid())) {
+        PLOG(ERROR) << "Change of ownership or mode failed for controller " << controller->name();
+        return IsOptionalController(controller);
+    }
 
-        if (optional && errno == EINVAL) {
-            // Optional controllers are allowed to fail to mount if kernel does not support them
-            LOG(INFO) << "Optional " << controller->name() << " cgroup controller is not mounted";
-        } else {
-            PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
+    return true;
+}
+
+static bool ActivateV2CgroupController(const CgroupDescriptor& descriptor) {
+    const format::CgroupController* controller = descriptor.controller();
+
+    if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
+        LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+        return false;
+    }
+
+    if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
+        std::string str = "+";
+        str += controller->name();
+        std::string path = controller->path();
+        path += "/cgroup.subtree_control";
+
+        if (!base::WriteStringToFile(str, path)) {
+            if (IsOptionalController(controller)) {
+                PLOG(INFO) << "Failed to activate optional controller " << controller->name();
+                return true;
+            }
+            PLOG(ERROR) << "Failed to activate controller " << controller->name();
             return false;
         }
     }
@@ -341,6 +319,55 @@
     return true;
 }
 
+static bool MountV1CgroupController(const CgroupDescriptor& descriptor) {
+    const format::CgroupController* controller = descriptor.controller();
+
+    // mkdir <path> [mode] [owner] [group]
+    if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
+        LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+        return false;
+    }
+
+    // Unfortunately historically cpuset controller was mounted using a mount command
+    // different from all other controllers. This results in controller attributes not
+    // to be prepended with controller name. For example this way instead of
+    // /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what
+    // the system currently expects.
+    int res;
+    if (!strcmp(controller->name(), "cpuset")) {
+        // mount cpuset none /dev/cpuset nodev noexec nosuid
+        res = mount("none", controller->path(), controller->name(),
+                    MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr);
+    } else {
+        // mount cgroup none <path> nodev noexec nosuid <controller>
+        res = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+                    controller->name());
+    }
+    if (res != 0) {
+        if (IsOptionalController(controller)) {
+            PLOG(INFO) << "Failed to mount optional controller " << controller->name();
+            return true;
+        }
+        PLOG(ERROR) << "Failed to mount controller " << controller->name();
+        return false;
+    }
+    return true;
+}
+
+static bool SetupCgroup(const CgroupDescriptor& descriptor) {
+    const format::CgroupController* controller = descriptor.controller();
+
+    if (controller->version() == 2) {
+        if (!strcmp(controller->name(), CGROUPV2_CONTROLLER_NAME)) {
+            return MountV2CgroupController(descriptor);
+        } else {
+            return ActivateV2CgroupController(descriptor);
+        }
+    } else {
+        return MountV1CgroupController(descriptor);
+    }
+}
+
 #else
 
 // Stubs for non-Android targets.
diff --git a/libstats/expresslog/Android.bp b/libstats/expresslog/Android.bp
index 9cdc2c3..004f8b9 100644
--- a/libstats/expresslog/Android.bp
+++ b/libstats/expresslog/Android.bp
@@ -18,11 +18,17 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-cc_library {
-    name: "libexpresslog",
+cc_defaults {
+    name: "expresslog_defaults",
     srcs: [
         "Counter.cpp",
+        "Histogram.cpp",
     ],
+}
+
+cc_library {
+    name: "libexpresslog",
+    defaults: ["expresslog_defaults"],
     cflags: [
         "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
         "-Wall",
@@ -37,6 +43,7 @@
     ],
     shared_libs: [
         "libbase",
+        "liblog",
         "libstatssocket",
     ],
     export_include_dirs: ["include"],
@@ -69,3 +76,38 @@
         "libstatssocket",
     ],
 }
+
+cc_test {
+    name: "expresslog_test",
+    defaults: ["expresslog_defaults"],
+    test_suites: [
+        "general-tests",
+    ],
+    srcs: [
+        "tests/Histogram_test.cpp",
+    ],
+    local_include_dirs: [
+        "include",
+    ],
+    cflags: [
+        "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
+        "-Wall",
+        "-Wextra",
+        "-Wunused",
+        "-Wpedantic",
+        "-Werror",
+    ],
+    header_libs: [
+        "libtextclassifier_hash_headers",
+    ],
+    static_libs: [
+        "libgmock",
+        "libbase",
+        "liblog",
+        "libstatslog_express",
+        "libtextclassifier_hash_static",
+    ],
+    shared_libs: [
+        "libstatssocket",
+    ]
+}
diff --git a/libstats/expresslog/Histogram.cpp b/libstats/expresslog/Histogram.cpp
new file mode 100644
index 0000000..cb29a00
--- /dev/null
+++ b/libstats/expresslog/Histogram.cpp
@@ -0,0 +1,75 @@
+//
+// 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.
+//
+
+#include "include/Histogram.h"
+
+#define LOG_TAG "tex"
+
+#include <log/log.h>
+#include <statslog_express.h>
+#include <string.h>
+#include <utils/hash/farmhash.h>
+
+namespace android {
+namespace expresslog {
+
+std::shared_ptr<Histogram::UniformOptions> Histogram::UniformOptions::create(
+        int binCount, float minValue, float exclusiveMaxValue) {
+    if (binCount < 1) {
+        ALOGE("Bin count should be positive number");
+        return nullptr;
+    }
+
+    if (exclusiveMaxValue <= minValue) {
+        ALOGE("Bins range invalid (maxValue < minValue)");
+        return nullptr;
+    }
+
+    return std::shared_ptr<UniformOptions>(
+            new UniformOptions(binCount, minValue, exclusiveMaxValue));
+}
+
+Histogram::UniformOptions::UniformOptions(int binCount, float minValue, float exclusiveMaxValue)
+    :  // Implicitly add 2 for the extra undeflow & overflow bins
+      mBinCount(binCount + 2),
+      mMinValue(minValue),
+      mExclusiveMaxValue(exclusiveMaxValue),
+      mBinSize((exclusiveMaxValue - minValue) / binCount) {
+}
+
+int Histogram::UniformOptions::getBinForSample(float sample) const {
+    if (sample < mMinValue) {
+        // goes to underflow
+        return 0;
+    } else if (sample >= mExclusiveMaxValue) {
+        // goes to overflow
+        return mBinCount - 1;
+    }
+    return (int)((sample - mMinValue) / mBinSize + 1);
+}
+
+Histogram::Histogram(const char* metricName, std::shared_ptr<BinOptions> binOptions)
+    : mMetricIdHash(farmhash::Fingerprint64(metricName, strlen(metricName))),
+      mBinOptions(std::move(binOptions)) {
+}
+
+void Histogram::logSample(float sample) const {
+    const int binIndex = mBinOptions->getBinForSample(sample);
+    stats_write(EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, /*count*/ 1, binIndex);
+}
+
+}  // namespace expresslog
+}  // namespace android
diff --git a/libstats/expresslog/include/Histogram.h b/libstats/expresslog/include/Histogram.h
new file mode 100644
index 0000000..8fdc1b6
--- /dev/null
+++ b/libstats/expresslog/include/Histogram.h
@@ -0,0 +1,81 @@
+//
+// 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.
+//
+
+#pragma once
+#include <stdint.h>
+
+#include <memory>
+
+namespace android {
+namespace expresslog {
+
+/** Histogram encapsulates StatsD write API calls */
+class Histogram final {
+public:
+    class BinOptions {
+    public:
+        virtual ~BinOptions() = default;
+        /**
+         * Returns bins count to be used by a Histogram
+         *
+         * @return bins count used to initialize Options, including overflow & underflow bins
+         */
+        virtual int getBinsCount() const = 0;
+
+        /**
+         * @return zero based index
+         * Calculates bin index for the input sample value
+         * index == 0 stands for underflow
+         * index == getBinsCount() - 1 stands for overflow
+         */
+        virtual int getBinForSample(float sample) const = 0;
+    };
+
+    /** Used by Histogram to map data sample to corresponding bin for uniform bins */
+    class UniformOptions : public BinOptions {
+    public:
+        static std::shared_ptr<UniformOptions> create(int binCount, float minValue,
+                                                      float exclusiveMaxValue);
+
+        int getBinsCount() const override {
+            return mBinCount;
+        }
+
+        int getBinForSample(float sample) const override;
+
+    private:
+        UniformOptions(int binCount, float minValue, float exclusiveMaxValue);
+
+        const int mBinCount;
+        const float mMinValue;
+        const float mExclusiveMaxValue;
+        const float mBinSize;
+    };
+
+    Histogram(const char* metricName, std::shared_ptr<BinOptions> binOptions);
+
+    /**
+     * Logs increment sample count for automatically calculated bin
+     */
+    void logSample(float sample) const;
+
+private:
+    const int64_t mMetricIdHash;
+    const std::shared_ptr<BinOptions> mBinOptions;
+};
+
+}  // namespace expresslog
+}  // namespace android
diff --git a/libstats/expresslog/tests/Histogram_test.cpp b/libstats/expresslog/tests/Histogram_test.cpp
new file mode 100644
index 0000000..813c997
--- /dev/null
+++ b/libstats/expresslog/tests/Histogram_test.cpp
@@ -0,0 +1,128 @@
+//
+// 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.
+//
+
+#include "Histogram.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace expresslog {
+
+#ifdef __ANDROID__
+TEST(UniformOptions, getBinsCount) {
+    const std::shared_ptr<Histogram::UniformOptions> options1(
+            Histogram::UniformOptions::create(1, 100, 1000));
+    ASSERT_EQ(3, options1->getBinsCount());
+
+    const std::shared_ptr<Histogram::UniformOptions> options10(
+            Histogram::UniformOptions::create(10, 100, 1000));
+    ASSERT_EQ(12, options10->getBinsCount());
+}
+
+TEST(UniformOptions, constructZeroBinsCount) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(0, 100, 1000));
+    ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, constructNegativeBinsCount) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(-1, 100, 1000));
+    ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, constructMaxValueLessThanMinValue) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(10, 1000, 100));
+    ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual1) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(10, 1, 11));
+    for (int i = 0, bins = options->getBinsCount(); i < bins; i++) {
+        ASSERT_EQ(i, options->getBinForSample(i));
+    }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual2) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(10, 1, 21));
+    for (int i = 0, bins = options->getBinsCount(); i < bins; i++) {
+        ASSERT_EQ(i, options->getBinForSample(i * 2));
+        ASSERT_EQ(i, options->getBinForSample(i * 2 - 1));
+    }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual5) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(2, 0, 10));
+    ASSERT_EQ(4, options->getBinsCount());
+    for (int i = 0; i < 2; i++) {
+        for (int sample = 0; sample < 5; sample++) {
+            ASSERT_EQ(i + 1, options->getBinForSample(i * 5 + sample));
+        }
+    }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual10) {
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(10, 1, 101));
+    ASSERT_EQ(0, options->getBinForSample(0));
+    ASSERT_EQ(options->getBinsCount() - 2, options->getBinForSample(100));
+    ASSERT_EQ(options->getBinsCount() - 1, options->getBinForSample(101));
+
+    const float binSize = (101 - 1) / 10.f;
+    for (int i = 1, bins = options->getBinsCount() - 1; i < bins; i++) {
+        ASSERT_EQ(i, options->getBinForSample(i * binSize));
+    }
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual90) {
+    const int binCount = 10;
+    const int minValue = 100;
+    const int maxValue = 100000;
+
+    const std::shared_ptr<Histogram::UniformOptions> options(
+            Histogram::UniformOptions::create(binCount, minValue, maxValue));
+
+    // logging underflow sample
+    ASSERT_EQ(0, options->getBinForSample(minValue - 1));
+
+    // logging overflow sample
+    ASSERT_EQ(binCount + 1, options->getBinForSample(maxValue));
+    ASSERT_EQ(binCount + 1, options->getBinForSample(maxValue + 1));
+
+    // logging min edge sample
+    ASSERT_EQ(1, options->getBinForSample(minValue));
+
+    // logging max edge sample
+    ASSERT_EQ(binCount, options->getBinForSample(maxValue - 1));
+
+    // logging single valid sample per bin
+    const int binSize = (maxValue - minValue) / binCount;
+
+    for (int i = 0; i < binCount; i++) {
+        ASSERT_EQ(i + 1, options->getBinForSample(minValue + binSize * i));
+    }
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace expresslog
+}  // namespace android
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index fe23b62..3362872 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -213,4 +213,17 @@
 	$(hide) $(foreach lib,$(PRIVATE_SANITIZER_RUNTIME_LIBRARIES), \
 		echo $(lib) >> $@;)
 
+#######################################
+# ramdisk_node_list
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := ramdisk_node_list
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)
+
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+include $(BUILD_PREBUILT)
+
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/rootdir/init.rc b/rootdir/init.rc
index db5113f..86c6eaa 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -1288,11 +1288,13 @@
     shutdown critical
 
 on property:ro.debuggable=1
-    # Give writes to anyone for the trace folder on debug builds.
+    # Give writes to the same group for the trace folder on debug builds,
+    # it's further protected by selinux policy.
     # The folder is used to store method traces.
     chmod 0773 /data/misc/trace
-    # Give reads to anyone for the window trace folder on debug builds.
-    chmod 0775 /data/misc/wmtrace
+    # Give writes and reads to anyone for the window trace folder on debug builds,
+    # it's further protected by selinux policy.
+    chmod 0777 /data/misc/wmtrace
     # Give reads to anyone for the accessibility trace folder on debug builds.
     chmod 0775 /data/misc/a11ytrace
 
diff --git a/rootdir/ramdisk_node_list b/rootdir/ramdisk_node_list
new file mode 100644
index 0000000..d3ab8a6
--- /dev/null
+++ b/rootdir/ramdisk_node_list
@@ -0,0 +1,3 @@
+dir dev 0755 0 0
+nod dev/null 0600 0 0 c 1 3
+nod dev/console 0600 0 0 c 5 1
diff --git a/storaged/Android.bp b/storaged/Android.bp
index 7960af3..c3447d2 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -24,7 +24,7 @@
     shared_libs: [
         "android.hardware.health@1.0",
         "android.hardware.health@2.0",
-        "android.hardware.health-V1-ndk",
+        "android.hardware.health-V2-ndk",
         "libbase",
         "libbinder",
         "libbinder_ndk",
diff --git a/storaged/storaged_diskstats.cpp b/storaged/storaged_diskstats.cpp
index 1eae5a1..c315409 100644
--- a/storaged/storaged_diskstats.cpp
+++ b/storaged/storaged_diskstats.cpp
@@ -312,7 +312,7 @@
 {
     struct disk_perf perf = get_disk_perf(&mAccumulate_pub);
     log_debug_disk_perf(&perf, "regular");
-    log_event_disk_stats(&mAccumulate, "regular");
+    log_event_disk_stats(&mAccumulate_pub, "regular");
     // Reset global structures
     memset(&mAccumulate_pub, 0, sizeof(struct disk_stats));
 }
diff --git a/toolbox/modprobe.cpp b/toolbox/modprobe.cpp
index 711586a..17f8156 100644
--- a/toolbox/modprobe.cpp
+++ b/toolbox/modprobe.cpp
@@ -23,8 +23,11 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/strings.h>
+#include <android-base/stringprintf.h>
 #include <modprobe/modprobe.h>
 
+#include <sys/utsname.h>
+
 namespace {
 
 enum modprobe_mode {
@@ -37,9 +40,8 @@
 void print_usage(void) {
     LOG(INFO) << "Usage:";
     LOG(INFO);
-    // -d option is required on Android
-    LOG(INFO) << "  modprobe [options] -d DIR [--all=FILE|MODULE]...";
-    LOG(INFO) << "  modprobe [options] -d DIR MODULE [symbol=value]...";
+    LOG(INFO) << "  modprobe [options] [-d DIR] [--all=FILE|MODULE]...";
+    LOG(INFO) << "  modprobe [options] [-d DIR] MODULE [symbol=value]...";
     LOG(INFO);
     LOG(INFO) << "Options:";
     LOG(INFO) << "  --all=FILE: FILE to acquire module names from";
@@ -189,6 +191,12 @@
         }
     }
 
+    if (mod_dirs.empty()) {
+        utsname uts;
+        uname(&uts);
+        mod_dirs.emplace_back(android::base::StringPrintf("/lib/modules/%s", uts.release));
+    }
+
     LOG(DEBUG) << "mode is " << mode;
     LOG(DEBUG) << "mod_dirs is: " << android::base::Join(mod_dirs, " ");
     LOG(DEBUG) << "modules is: " << android::base::Join(modules, " ");
diff --git a/trusty/keymaster/TEST_MAPPING b/trusty/keymaster/TEST_MAPPING
index ae48327..0dd39fb 100644
--- a/trusty/keymaster/TEST_MAPPING
+++ b/trusty/keymaster/TEST_MAPPING
@@ -1,9 +1,6 @@
 {
   "presubmit": [
       {
-        "name": "RemoteProvisionerUnitTests"
-      },
-      {
         "name": "VtsAidlKeyMintTargetTest"
       },
       {
diff --git a/trusty/libtrusty/Android.bp b/trusty/libtrusty/Android.bp
index 086051d..9d94ec4 100644
--- a/trusty/libtrusty/Android.bp
+++ b/trusty/libtrusty/Android.bp
@@ -33,5 +33,6 @@
     // TODO(b/170753563): cc_fuzz can't deal with vendor components. Build
     // libtrusty for system and vendor.
     vendor_available: true,
+    recovery_available: true,
     defaults: ["libtrusty_defaults"],
 }
diff --git a/trusty/storage/proxy/Android.bp b/trusty/storage/proxy/Android.bp
index e952ee0..2e97ee0 100644
--- a/trusty/storage/proxy/Android.bp
+++ b/trusty/storage/proxy/Android.bp
@@ -28,6 +28,7 @@
         "rpmb.c",
         "storage.c",
         "proxy.c",
+        "watchdog.cpp",
     ],
 
     shared_libs: [
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index 4f77fa2..c89c5b6 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -31,6 +31,7 @@
 #include "log.h"
 #include "rpmb.h"
 #include "storage.h"
+#include "watchdog.h"
 
 #define REQ_BUFFER_SIZE 4096
 static uint8_t req_buffer[REQ_BUFFER_SIZE + 1];
@@ -73,6 +74,8 @@
 static int handle_req(struct storage_msg* msg, const void* req, size_t req_len) {
     int rc;
 
+    struct watcher* watcher = watch_start("request", msg);
+
     if ((msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) && msg->cmd != STORAGE_RPMB_SEND &&
         msg->cmd != STORAGE_FILE_WRITE) {
         /*
@@ -81,14 +84,14 @@
          */
         ALOGE("cmd 0x%x: post commit option is not implemented\n", msg->cmd);
         msg->result = STORAGE_ERR_UNIMPLEMENTED;
-        return ipc_respond(msg, NULL, 0);
+        goto err_response;
     }
 
     if (msg->flags & STORAGE_MSG_FLAG_PRE_COMMIT) {
-        rc = storage_sync_checkpoint();
+        rc = storage_sync_checkpoint(watcher);
         if (rc < 0) {
             msg->result = STORAGE_ERR_SYNC_FAILURE;
-            return ipc_respond(msg, NULL, 0);
+            goto err_response;
         }
     }
 
@@ -99,61 +102,65 @@
         if (rc != 0) {
             ALOGE("is_data_checkpoint_active failed in an unexpected way. Aborting.\n");
             msg->result = STORAGE_ERR_GENERIC;
-            return ipc_respond(msg, NULL, 0);
+            goto err_response;
         } else if (is_checkpoint_active) {
             ALOGE("Checkpoint in progress, dropping write ...\n");
             msg->result = STORAGE_ERR_GENERIC;
-            return ipc_respond(msg, NULL, 0);
+            goto err_response;
         }
     }
 
     switch (msg->cmd) {
         case STORAGE_FILE_DELETE:
-            rc = storage_file_delete(msg, req, req_len);
+            rc = storage_file_delete(msg, req, req_len, watcher);
             break;
 
         case STORAGE_FILE_OPEN:
-            rc = storage_file_open(msg, req, req_len);
+            rc = storage_file_open(msg, req, req_len, watcher);
             break;
 
         case STORAGE_FILE_CLOSE:
-            rc = storage_file_close(msg, req, req_len);
+            rc = storage_file_close(msg, req, req_len, watcher);
             break;
 
         case STORAGE_FILE_WRITE:
-            rc = storage_file_write(msg, req, req_len);
+            rc = storage_file_write(msg, req, req_len, watcher);
             break;
 
         case STORAGE_FILE_READ:
-            rc = storage_file_read(msg, req, req_len);
+            rc = storage_file_read(msg, req, req_len, watcher);
             break;
 
         case STORAGE_FILE_GET_SIZE:
-            rc = storage_file_get_size(msg, req, req_len);
+            rc = storage_file_get_size(msg, req, req_len, watcher);
             break;
 
         case STORAGE_FILE_SET_SIZE:
-            rc = storage_file_set_size(msg, req, req_len);
+            rc = storage_file_set_size(msg, req, req_len, watcher);
             break;
 
         case STORAGE_FILE_GET_MAX_SIZE:
-            rc = storage_file_get_max_size(msg, req, req_len);
+            rc = storage_file_get_max_size(msg, req, req_len, watcher);
             break;
 
         case STORAGE_RPMB_SEND:
-            rc = rpmb_send(msg, req, req_len);
+            rc = rpmb_send(msg, req, req_len, watcher);
             break;
 
         default:
             ALOGE("unhandled command 0x%x\n", msg->cmd);
             msg->result = STORAGE_ERR_UNIMPLEMENTED;
-            rc = 1;
+            goto err_response;
     }
 
-    if (rc > 0) {
-        /* still need to send response */
-        rc = ipc_respond(msg, NULL, 0);
-    }
+    /* response was sent in handler */
+    goto finish;
+
+err_response:
+    rc = ipc_respond(msg, NULL, 0);
+
+finish:
+    watch_finish(watcher);
     return rc;
 }
 
diff --git a/trusty/storage/proxy/rpmb.c b/trusty/storage/proxy/rpmb.c
index b1b8232..22a85a7 100644
--- a/trusty/storage/proxy/rpmb.c
+++ b/trusty/storage/proxy/rpmb.c
@@ -321,7 +321,8 @@
     return SCSI_RES_ERR;
 }
 
-static int send_mmc_rpmb_req(int mmc_fd, const struct storage_rpmb_send_req* req) {
+static int send_mmc_rpmb_req(int mmc_fd, const struct storage_rpmb_send_req* req,
+                             struct watcher* watcher) {
     union {
         struct mmc_ioc_multi_cmd multi;
         uint8_t raw[sizeof(struct mmc_ioc_multi_cmd) + sizeof(struct mmc_ioc_cmd) * 3];
@@ -375,14 +376,17 @@
         cmd++;
     }
 
+    watch_progress(watcher, "rpmb mmc ioctl");
     rc = ioctl(mmc_fd, MMC_IOC_MULTI_CMD, &mmc.multi);
+    watch_progress(watcher, "rpmb mmc ioctl done");
     if (rc < 0) {
         ALOGE("%s: mmc ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
     }
     return rc;
 }
 
-static int send_ufs_rpmb_req(int sg_fd, const struct storage_rpmb_send_req* req) {
+static int send_ufs_rpmb_req(int sg_fd, const struct storage_rpmb_send_req* req,
+                             struct watcher* watcher) {
     int rc;
     int wl_rc;
     const uint8_t* write_buf = req->payload;
@@ -410,7 +414,9 @@
             set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
                           req->reliable_write_size, (void*)write_buf, (unsigned char*)&out_cdb,
                           sense_buffer);
+            watch_progress(watcher, "rpmb ufs reliable write");
             rc = ioctl(sg_fd, SG_IO, &io_hdr);
+            watch_progress(watcher, "rpmb ufs reliable write done");
             if (rc < 0) {
                 ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
                 goto err_op;
@@ -435,7 +441,9 @@
             set_sg_io_hdr(&io_hdr, SG_DXFER_TO_DEV, sizeof(out_cdb), sizeof(sense_buffer),
                           req->write_size, (void*)write_buf, (unsigned char*)&out_cdb,
                           sense_buffer);
+            watch_progress(watcher, "rpmb ufs write");
             rc = ioctl(sg_fd, SG_IO, &io_hdr);
+            watch_progress(watcher, "rpmb ufs write done");
             if (rc < 0) {
                 ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
                 goto err_op;
@@ -450,7 +458,9 @@
         sg_io_hdr_t io_hdr;
         set_sg_io_hdr(&io_hdr, SG_DXFER_FROM_DEV, sizeof(in_cdb), sizeof(sense_buffer),
                       req->read_size, read_buf, (unsigned char*)&in_cdb, sense_buffer);
+        watch_progress(watcher, "rpmb ufs read");
         rc = ioctl(sg_fd, SG_IO, &io_hdr);
+        watch_progress(watcher, "rpmb ufs read done");
         if (rc < 0) {
             ALOGE("%s: ufs ioctl failed: %d, %s\n", __func__, rc, strerror(errno));
         }
@@ -487,7 +497,7 @@
     return rc;
 }
 
-int rpmb_send(struct storage_msg* msg, const void* r, size_t req_len) {
+int rpmb_send(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher) {
     int rc;
     const struct storage_rpmb_send_req* req = r;
 
@@ -523,13 +533,13 @@
     }
 
     if (dev_type == MMC_RPMB) {
-        rc = send_mmc_rpmb_req(rpmb_fd, req);
+        rc = send_mmc_rpmb_req(rpmb_fd, req, watcher);
         if (rc < 0) {
             msg->result = STORAGE_ERR_GENERIC;
             goto err_response;
         }
     } else if (dev_type == UFS_RPMB) {
-        rc = send_ufs_rpmb_req(rpmb_fd, req);
+        rc = send_ufs_rpmb_req(rpmb_fd, req, watcher);
         if (rc < 0) {
             ALOGE("send_ufs_rpmb_req failed: %d, %s\n", rc, strerror(errno));
             msg->result = STORAGE_ERR_GENERIC;
diff --git a/trusty/storage/proxy/rpmb.h b/trusty/storage/proxy/rpmb.h
index f4e1b51..04bdf9a 100644
--- a/trusty/storage/proxy/rpmb.h
+++ b/trusty/storage/proxy/rpmb.h
@@ -18,8 +18,10 @@
 #include <stdint.h>
 #include <trusty/interface/storage.h>
 
+#include "watchdog.h"
+
 enum dev_type { UNKNOWN_RPMB, MMC_RPMB, VIRT_RPMB, UFS_RPMB, SOCK_RPMB };
 
 int rpmb_open(const char* rpmb_devname, enum dev_type dev_type);
-int rpmb_send(struct storage_msg* msg, const void* r, size_t req_len);
+int rpmb_send(struct storage_msg* msg, const void* r, size_t req_len, struct watcher* watcher);
 void rpmb_close(void);
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index a96ddcb..2299481 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -31,6 +31,7 @@
 #include "ipc.h"
 #include "log.h"
 #include "storage.h"
+#include "watchdog.h"
 
 #define FD_TBL_SIZE 64
 #define MAX_READ_SIZE 4096
@@ -180,9 +181,8 @@
     return rcnt;
 }
 
-int storage_file_delete(struct storage_msg *msg,
-                        const void *r, size_t req_len)
-{
+int storage_file_delete(struct storage_msg* msg, const void* r, size_t req_len,
+                        struct watcher* watcher) {
     char *path = NULL;
     const struct storage_file_delete_req *req = r;
 
@@ -208,6 +208,7 @@
         goto err_response;
     }
 
+    watch_progress(watcher, "unlinking file");
     rc = unlink(path);
     if (rc < 0) {
         rc = errno;
@@ -231,8 +232,9 @@
     return ipc_respond(msg, NULL, 0);
 }
 
-static void sync_parent(const char* path) {
+static void sync_parent(const char* path, struct watcher* watcher) {
     int parent_fd;
+    watch_progress(watcher, "syncing parent");
     char* parent_path = dirname(path);
     parent_fd = TEMP_FAILURE_RETRY(open(parent_path, O_RDONLY));
     if (parent_fd >= 0) {
@@ -242,9 +244,11 @@
         ALOGE("%s: failed to open parent directory \"%s\" for sync: %s\n", __func__, parent_path,
               strerror(errno));
     }
+    watch_progress(watcher, "done syncing parent");
 }
 
-int storage_file_open(struct storage_msg* msg, const void* r, size_t req_len) {
+int storage_file_open(struct storage_msg* msg, const void* r, size_t req_len,
+                      struct watcher* watcher) {
     char* path = NULL;
     const struct storage_file_open_req *req = r;
     struct storage_file_open_resp resp = {0};
@@ -306,7 +310,7 @@
             char* parent_path = dirname(path);
             rc = mkdir(parent_path, S_IRWXU);
             if (rc == 0) {
-                sync_parent(parent_path);
+                sync_parent(parent_path, watcher);
             } else if (errno != EEXIST) {
                 ALOGE("%s: Could not create parent directory \"%s\": %s\n", __func__, parent_path,
                       strerror(errno));
@@ -347,7 +351,7 @@
     }
 
     if (open_flags & O_CREAT) {
-        sync_parent(path);
+        sync_parent(path, watcher);
     }
     free(path);
 
@@ -375,9 +379,8 @@
     return ipc_respond(msg, NULL, 0);
 }
 
-int storage_file_close(struct storage_msg *msg,
-                       const void *r, size_t req_len)
-{
+int storage_file_close(struct storage_msg* msg, const void* r, size_t req_len,
+                       struct watcher* watcher) {
     const struct storage_file_close_req *req = r;
 
     if (req_len != sizeof(*req)) {
@@ -390,7 +393,9 @@
     int fd = remove_fd(req->handle);
     ALOGV("%s: handle = %u: fd = %u\n", __func__, req->handle, fd);
 
+    watch_progress(watcher, "fsyncing before file close");
     int rc = fsync(fd);
+    watch_progress(watcher, "done fsyncing before file close");
     if (rc < 0) {
         rc = errno;
         ALOGE("%s: fsync failed for fd=%u: %s\n",
@@ -414,10 +419,8 @@
     return ipc_respond(msg, NULL, 0);
 }
 
-
-int storage_file_write(struct storage_msg *msg,
-                       const void *r, size_t req_len)
-{
+int storage_file_write(struct storage_msg* msg, const void* r, size_t req_len,
+                       struct watcher* watcher) {
     int rc;
     const struct storage_file_write_req *req = r;
 
@@ -429,17 +432,20 @@
     }
 
     int fd = lookup_fd(req->handle, true);
+    watch_progress(watcher, "writing");
     if (write_with_retry(fd, &req->data[0], req_len - sizeof(*req),
                          req->offset) < 0) {
+        watch_progress(watcher, "writing done w/ error");
         rc = errno;
         ALOGW("%s: error writing file (fd=%d): %s\n",
               __func__, fd, strerror(errno));
         msg->result = translate_errno(rc);
         goto err_response;
     }
+    watch_progress(watcher, "writing done");
 
     if (msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) {
-        rc = storage_sync_checkpoint();
+        rc = storage_sync_checkpoint(watcher);
         if (rc < 0) {
             msg->result = STORAGE_ERR_SYNC_FAILURE;
             goto err_response;
@@ -452,10 +458,8 @@
     return ipc_respond(msg, NULL, 0);
 }
 
-
-int storage_file_read(struct storage_msg *msg,
-                      const void *r, size_t req_len)
-{
+int storage_file_read(struct storage_msg* msg, const void* r, size_t req_len,
+                      struct watcher* watcher) {
     int rc;
     const struct storage_file_read_req *req = r;
 
@@ -474,8 +478,10 @@
     }
 
     int fd = lookup_fd(req->handle, false);
+    watch_progress(watcher, "reading");
     ssize_t read_res = read_with_retry(fd, read_rsp.hdr.data, req->size,
                                        (off_t)req->offset);
+    watch_progress(watcher, "reading done");
     if (read_res < 0) {
         rc = errno;
         ALOGW("%s: error reading file (fd=%d): %s\n",
@@ -491,10 +497,8 @@
     return ipc_respond(msg, NULL, 0);
 }
 
-
-int storage_file_get_size(struct storage_msg *msg,
-                          const void *r, size_t req_len)
-{
+int storage_file_get_size(struct storage_msg* msg, const void* r, size_t req_len,
+                          struct watcher* watcher) {
     const struct storage_file_get_size_req *req = r;
     struct storage_file_get_size_resp resp = {0};
 
@@ -507,7 +511,9 @@
 
     struct stat stat;
     int fd = lookup_fd(req->handle, false);
+    watch_progress(watcher, "fstat");
     int rc = fstat(fd, &stat);
+    watch_progress(watcher, "fstat done");
     if (rc < 0) {
         rc = errno;
         ALOGE("%s: error stat'ing file (fd=%d): %s\n",
@@ -524,10 +530,8 @@
     return ipc_respond(msg, NULL, 0);
 }
 
-
-int storage_file_set_size(struct storage_msg *msg,
-                          const void *r, size_t req_len)
-{
+int storage_file_set_size(struct storage_msg* msg, const void* r, size_t req_len,
+                          struct watcher* watcher) {
     const struct storage_file_set_size_req *req = r;
 
     if (req_len != sizeof(*req)) {
@@ -538,7 +542,9 @@
     }
 
     int fd = lookup_fd(req->handle, true);
+    watch_progress(watcher, "ftruncate");
     int rc = TEMP_FAILURE_RETRY(ftruncate(fd, req->size));
+    watch_progress(watcher, "ftruncate done");
     if (rc < 0) {
         rc = errno;
         ALOGE("%s: error truncating file (fd=%d): %s\n",
@@ -553,7 +559,8 @@
     return ipc_respond(msg, NULL, 0);
 }
 
-int storage_file_get_max_size(struct storage_msg* msg, const void* r, size_t req_len) {
+int storage_file_get_max_size(struct storage_msg* msg, const void* r, size_t req_len,
+                              struct watcher* watcher) {
     const struct storage_file_get_max_size_req* req = r;
     struct storage_file_get_max_size_resp resp = {0};
     uint64_t max_size = 0;
@@ -566,7 +573,9 @@
 
     struct stat stat;
     int fd = lookup_fd(req->handle, false);
+    watch_progress(watcher, "fstat to get max size");
     int rc = fstat(fd, &stat);
+    watch_progress(watcher, "fstat to get max size done");
     if (rc < 0) {
         ALOGE("%s: error stat'ing file (fd=%d): %s\n", __func__, fd, strerror(errno));
         goto err_response;
@@ -606,10 +615,10 @@
     return 0;
 }
 
-int storage_sync_checkpoint(void)
-{
+int storage_sync_checkpoint(struct watcher* watcher) {
     int rc;
 
+    watch_progress(watcher, "sync fd table");
     /* sync fd table and reset it to clean state first */
     for (uint fd = 0; fd < FD_TBL_SIZE; fd++) {
          if (fd_state[fd] == SS_DIRTY) {
@@ -634,10 +643,12 @@
          * because our fd table is large enough to handle the few open files we
          * use.
          */
-        sync();
-        fs_state = SS_CLEAN;
+         watch_progress(watcher, "all fs sync");
+         sync();
+         fs_state = SS_CLEAN;
     }
 
+    watch_progress(watcher, "done syncing");
+
     return 0;
 }
-
diff --git a/trusty/storage/proxy/storage.h b/trusty/storage/proxy/storage.h
index 77bfa13..f29fdf2 100644
--- a/trusty/storage/proxy/storage.h
+++ b/trusty/storage/proxy/storage.h
@@ -18,30 +18,33 @@
 #include <stdint.h>
 #include <trusty/interface/storage.h>
 
-int storage_file_delete(struct storage_msg *msg,
-                        const void *req, size_t req_len);
+/* Defined in watchdog.h */
+struct watcher;
 
-int storage_file_open(struct storage_msg *msg,
-                      const void *req, size_t req_len);
+int storage_file_delete(struct storage_msg* msg, const void* req, size_t req_len,
+                        struct watcher* watcher);
 
-int storage_file_close(struct storage_msg *msg,
-                       const void *req, size_t req_len);
+int storage_file_open(struct storage_msg* msg, const void* req, size_t req_len,
+                      struct watcher* watcher);
 
-int storage_file_write(struct storage_msg *msg,
-                       const void *req, size_t req_len);
+int storage_file_close(struct storage_msg* msg, const void* req, size_t req_len,
+                       struct watcher* watcher);
 
-int storage_file_read(struct storage_msg *msg,
-                      const void *req, size_t req_len);
+int storage_file_write(struct storage_msg* msg, const void* req, size_t req_len,
+                       struct watcher* watcher);
 
-int storage_file_get_size(struct storage_msg *msg,
-                          const void *req, size_t req_len);
+int storage_file_read(struct storage_msg* msg, const void* req, size_t req_len,
+                      struct watcher* watcher);
 
-int storage_file_set_size(struct storage_msg *msg,
-                          const void *req, size_t req_len);
+int storage_file_get_size(struct storage_msg* msg, const void* req, size_t req_len,
+                          struct watcher* watcher);
 
-int storage_file_get_max_size(struct storage_msg* msg, const void* req, size_t req_len);
+int storage_file_set_size(struct storage_msg* msg, const void* req, size_t req_len,
+                          struct watcher* watcher);
 
-int storage_init(const char *dirname);
+int storage_file_get_max_size(struct storage_msg* msg, const void* req, size_t req_len,
+                              struct watcher* watcher);
 
-int storage_sync_checkpoint(void);
+int storage_init(const char* dirname);
 
+int storage_sync_checkpoint(struct watcher* watcher);
diff --git a/trusty/storage/proxy/watchdog.cpp b/trusty/storage/proxy/watchdog.cpp
new file mode 100644
index 0000000..6c09e26
--- /dev/null
+++ b/trusty/storage/proxy/watchdog.cpp
@@ -0,0 +1,208 @@
+/*
+ * 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.
+ */
+
+#include "watchdog.h"
+
+#include <chrono>
+#include <cstdint>
+#include <optional>
+#include <thread>
+#include <vector>
+
+#include <android-base/logging.h>
+
+struct watcher {
+    watcher(const char* id, const struct storage_msg* request);
+    void SetState(const char* new_state);
+    void LogTimeout();
+    void LogFinished();
+
+    const char* id_;
+    uint32_t cmd_;
+    uint32_t op_id_;
+    uint32_t flags_;
+    const char* state_;
+
+    using clock = std::chrono::high_resolution_clock;
+    clock::time_point start_;
+    clock::time_point state_change_;
+    std::chrono::milliseconds Elapsed(clock::time_point end);
+
+    bool triggered_;
+};
+
+watcher::watcher(const char* id, const struct storage_msg* request)
+    : id_(id), state_(nullptr), triggered_(false) {
+    cmd_ = request->cmd;
+    op_id_ = request->op_id;
+    flags_ = request->flags;
+
+    start_ = clock::now();
+    state_change_ = start_;
+}
+
+void watcher::SetState(const char* new_state) {
+    state_ = new_state;
+    state_change_ = clock::now();
+}
+
+void watcher::LogTimeout() {
+    if (!triggered_) {
+        triggered_ = true;
+        LOG(ERROR) << "Storageproxyd watchdog triggered: " << id_ << " cmd: " << cmd_
+                   << " op_id: " << op_id_ << " flags: " << flags_;
+    }
+    if (state_) {
+        LOG(ERROR) << "...elapsed: " << Elapsed(clock::now()).count() << "ms (" << state_ << " "
+                   << Elapsed(state_change_).count() << "ms)";
+    } else {
+        LOG(ERROR) << "...elapsed: " << Elapsed(clock::now()).count() << "ms";
+    }
+}
+
+void watcher::LogFinished() {
+    if (triggered_) {
+        LOG(ERROR) << "...completed: " << Elapsed(clock::now()).count() << "ms";
+    }
+}
+
+std::chrono::milliseconds watcher::Elapsed(watcher::clock::time_point end) {
+    return std::chrono::duration_cast<std::chrono::milliseconds>(end - start_);
+}
+
+namespace {
+
+class Watchdog {
+  private:
+    static constexpr std::chrono::milliseconds kDefaultTimeoutMs = std::chrono::milliseconds(500);
+    static constexpr std::chrono::milliseconds kMaxTimeoutMs = std::chrono::seconds(10);
+
+  public:
+    Watchdog() : watcher_(), done_(false) {}
+    ~Watchdog();
+    struct watcher* RegisterWatch(const char* id, const struct storage_msg* request);
+    void AddProgress(struct watcher* watcher, const char* state);
+    void UnRegisterWatch(struct watcher* watcher);
+
+  private:
+    // Syncronizes access to watcher_ and watcher_change_ between the main
+    // thread and watchdog loop thread. watcher_ may only be modified by the
+    // main thread; the watchdog loop is read-only.
+    std::mutex watcher_mutex_;
+    std::unique_ptr<struct watcher> watcher_;
+    std::condition_variable watcher_change_;
+
+    std::thread watchdog_thread_;
+    bool done_;
+
+    void WatchdogLoop();
+    void LogWatchdogTriggerLocked();
+};
+
+Watchdog gWatchdog;
+
+}  // Anonymous namespace
+
+// Assumes that caller is single-threaded. If we want to use this from a
+// multi-threaded context we need to ensure that the watchdog thread is
+// initialized safely once and accessing an existing watcher is done while the
+// watcher lock is held.
+struct watcher* Watchdog::RegisterWatch(const char* id, const struct storage_msg* request) {
+    if (!watchdog_thread_.joinable()) {
+        watchdog_thread_ = std::thread(&Watchdog::WatchdogLoop, this);
+    }
+    if (watcher_) {
+        LOG(ERROR) << "Replacing registered watcher " << watcher_->id_;
+        UnRegisterWatch(watcher_.get());
+    }
+
+    struct watcher* ret = nullptr;
+    {
+        std::unique_lock<std::mutex> watcherLock(watcher_mutex_);
+        watcher_ = std::make_unique<struct watcher>(id, request);
+        ret = watcher_.get();
+    }
+    watcher_change_.notify_one();
+    return ret;
+}
+
+void Watchdog::UnRegisterWatch(struct watcher* watcher) {
+    {
+        std::lock_guard<std::mutex> watcherLock(watcher_mutex_);
+        if (!watcher_) {
+            LOG(ERROR) << "Cannot unregister watcher, no watcher registered";
+            return;
+        }
+        if (watcher_.get() != watcher) {
+            LOG(ERROR) << "Unregistering watcher that doesn't match current watcher";
+        }
+        watcher_->LogFinished();
+        watcher_.reset(nullptr);
+    }
+    watcher_change_.notify_one();
+}
+
+void Watchdog::AddProgress(struct watcher* watcher, const char* state) {
+    std::lock_guard<std::mutex> watcherLock(watcher_mutex_);
+    if (watcher_.get() != watcher) {
+        LOG(ERROR) << "Watcher was not registered, cannot log progress: " << state;
+        return;
+    }
+    watcher->SetState(state);
+}
+
+void Watchdog::WatchdogLoop() {
+    std::unique_lock<std::mutex> lock(watcher_mutex_);
+    std::chrono::milliseconds timeout = kDefaultTimeoutMs;
+
+    while (!done_) {
+        // wait for a watch to be registered
+        watcher_change_.wait(lock, [this] { return !!watcher_; });
+
+        // wait for the timeout or unregistration
+        timeout = kDefaultTimeoutMs;
+        do {
+            if (!watcher_change_.wait_for(lock, timeout, [this] { return !watcher_; })) {
+                watcher_->LogTimeout();
+                timeout = std::min(timeout * 2, kMaxTimeoutMs);
+            }
+        } while (!!watcher_);
+    }
+}
+
+Watchdog::~Watchdog() {
+    {
+        std::lock_guard<std::mutex> watcherLock(watcher_mutex_);
+        watcher_.reset(nullptr);
+        done_ = true;
+    }
+    watcher_change_.notify_one();
+    if (watchdog_thread_.joinable()) {
+        watchdog_thread_.join();
+    }
+}
+
+struct watcher* watch_start(const char* id, const struct storage_msg* request) {
+    return gWatchdog.RegisterWatch(id, request);
+}
+
+void watch_progress(struct watcher* watcher, const char* state) {
+    gWatchdog.AddProgress(watcher, state);
+}
+
+void watch_finish(struct watcher* watcher) {
+    gWatchdog.UnRegisterWatch(watcher);
+}
diff --git a/trusty/storage/proxy/watchdog.h b/trusty/storage/proxy/watchdog.h
new file mode 100644
index 0000000..9162fcb
--- /dev/null
+++ b/trusty/storage/proxy/watchdog.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "storage.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct watcher;
+
+/**
+ * watch_start() - Create a watcher for a storage request
+ * @id:        Identifier string to distinguish watchers
+ * @request:   Incoming request from Trusty storage service
+ *
+ * Create a watcher that will start logging if not finished before a timeout.
+ * Only one watcher may be active at a time, and this function may only be
+ * called from a single thread.
+ */
+struct watcher* watch_start(const char* id, const struct storage_msg* request);
+
+/**
+ * watch_progress() - Note progress on servicing the current request
+ * @watcher:   Current watcher, created by watch()
+ *
+ * Sets the current progress state of the watcher, to allow for more granular
+ * reporting of what exactly is stuck if the timeout is reached.
+ */
+void watch_progress(struct watcher* watcher, const char* state);
+
+/**
+ * watch_finish() - Finish watching and unregister the watch
+ * @watcher:   Current watcher, created by watch(). Takes ownership of this pointer.
+ *
+ * Finish the current watch task. This function takes ownership of the watcher
+ * and destroys it, so @watcher must not be used again after calling this
+ * function.
+ */
+void watch_finish(struct watcher* watcher);
+
+#ifdef __cplusplus
+}
+#endif