Merge "Snap for 9679998 from bb4c61aa349437f1993b6df0966da83dd8ec9d1f to sdk-release" into sdk-release
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 70f333e..442392d 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);
@@ -660,7 +665,7 @@
         "* Process %d has been suspended while crashing.\n"
         "* To attach the debugger, run this on the host:\n"
         "*\n"
-        "*     gdbclient.py -p %d\n"
+        "*     lldbclient.py -p %d\n"
         "*\n"
         "***********************************************************",
         target_process, target_process);
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 895c111..4d60ddb 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -1680,6 +1680,24 @@
   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)));
+
+  bool recoverable = std::get<1>(GetParam());
+  ASSERT_TRUE(recoverable);  // Non-recoverable should have crashed.
+
+  // As we're in recoverable mode, trigger another 2x use-after-frees (ensuring
+  // we end with at least one in a different slot), make sure the process still
+  // doesn't crash.
+  p = reinterpret_cast<char* volatile>(malloc(params.alloc_size));
+  char* volatile p2 = reinterpret_cast<char* volatile>(malloc(params.alloc_size));
+  free(static_cast<void*>(const_cast<char*>(p)));
+  free(static_cast<void*>(const_cast<char*>(p2)));
+  *p = 42;
+  *p2 = 42;
+
+  // Under clang coverage (which is a default TEST_MAPPING presubmit target), the
+  // recoverable+seccomp tests fail because the minijail prevents some atexit syscalls that clang
+  // coverage does. Thus, skip the atexit handlers.
+  _exit(0);
 }
 
 TEST_F(CrasherTest, fdsan_warning_abort_message) {
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index d2bf0d7..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,
@@ -572,14 +573,13 @@
   // 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();
-  bool gwp_asan_recoverable = false;
   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);
-    gwp_asan_recoverable = true;
+    process_info.recoverable_gwp_asan_crash = true;
   }
 
   // If sival_int is ~0, it means that the fallback handler has been called
@@ -593,7 +593,7 @@
     // 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 && gwp_asan_recoverable) {
+    if (no_new_privs && process_info.recoverable_gwp_asan_crash) {
       gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr);
       return;
     }
@@ -670,7 +670,7 @@
     // 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 (gwp_asan_recoverable) {
+  } 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);
   }
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index de88be5..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,6 +44,7 @@
   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
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 9512e7e..3b786e8 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -283,15 +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
@@ -373,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: {
@@ -391,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/README.md b/fastboot/README.md
index d3b6c1a..63db5c3 100644
--- a/fastboot/README.md
+++ b/fastboot/README.md
@@ -29,20 +29,27 @@
 
 2. Client response with a single packet no greater than 256 bytes.
    The first four bytes of the response are "OKAY", "FAIL", "DATA",
-   or "INFO".  Additional bytes may contain an (ascii) informative
+   "INFO" or "TEXT".  Additional bytes may contain an (ascii) informative
    message.
 
    a. INFO -> the remaining 252 bytes are an informative message
       (providing progress or diagnostic messages).  They should
-      be displayed and then step #2 repeats
+      be displayed and then step #2 repeats. The print format is:
+      "(bootloader) " + InfoMessagePayload + '\n'
 
-   b. FAIL -> the requested command failed.  The remaining 252 bytes
+   b. TEXT -> the remaining 252 bytes are arbitrary. They should
+      be displayed and then step #2 repeats.
+      It differs from info in that no formatting is applied.
+      The payload is printed as-is with no newline at the end.
+      Payload is expected to be NULL terminated.
+
+   c. FAIL -> the requested command failed.  The remaining 252 bytes
       of the response (if present) provide a textual failure message
       to present to the user.  Stop.
 
-   c. OKAY -> the requested command completed successfully.  Go to #5
+   d. OKAY -> the requested command completed successfully.  Go to #5
 
-   d. DATA -> the requested command is ready for the data phase.
+   e. DATA -> the requested command is ready for the data phase.
       A DATA response packet will be 12 bytes long, in the form of
       DATA00000000 where the 8 digit hexadecimal number represents
       the total data size to transfer.
@@ -54,15 +61,17 @@
    in the "DATA" response above.
 
 4. Client responds with a single packet no greater than 256 bytes.
-   The first four bytes of the response are "OKAY", "FAIL", or "INFO".
-   Similar to #2:
+   The first four bytes of the response are "OKAY", "FAIL",
+   "INFO" or "TEXT". Similar to #2:
 
-   a. INFO -> display the remaining 252 bytes and return to #4
+   a. INFO -> display the formatted remaining 252 bytes and return to #4
 
-   b. FAIL -> display the remaining 252 bytes (if present) as a failure
+   b. TEXT -> display the unformatted remaining 252 bytes and return to #4
+
+   c. FAIL -> display the remaining 252 bytes (if present) as a failure
       reason and consider the command failed.  Stop.
 
-   c. OKAY -> success.  Go to #5
+   d. OKAY -> success.  Go to #5
 
 5. Success.  Stop.
 
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 8f7cced..5dac3f5 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>
@@ -73,7 +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"
@@ -214,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);
@@ -249,6 +255,10 @@
     fprintf(stderr, "(bootloader) %s\n", info.c_str());
 }
 
+static void TextMessage(const std::string& text) {
+    fprintf(stderr, "%s", text.c_str());
+}
+
 bool ReadFileToVector(const std::string& file, std::vector<char>* out) {
     out->clear();
 
@@ -274,8 +284,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) {
@@ -291,91 +329,256 @@
         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;
+};
+
+class ParseNetworkAddressError {
+  public:
+    enum Type { WRONG_PREFIX = 1, WRONG_ADDRESS = 2 };
+
+    ParseNetworkAddressError(Type&& type) : type_(std::forward<Type>(type)) {}
+
+    Type value() const { return type_; }
+    operator Type() const { return value(); }
+    std::string print() const { return ""; }
+
+  private:
+    Type type_;
+};
+
+static Result<NetworkSerial, ParseNetworkAddressError> ParseNetworkSerial(
+        const std::string& serial) {
+    Socket::Protocol protocol;
+    const char* net_address = nullptr;
+    int port = 0;
+
+    if (android::base::StartsWith(serial, "tcp:")) {
+        protocol = Socket::Protocol::kTcp;
+        net_address = serial.c_str() + strlen("tcp:");
+        port = tcp::kDefaultPort;
+    } else if (android::base::StartsWith(serial, "udp:")) {
+        protocol = Socket::Protocol::kUdp;
+        net_address = serial.c_str() + strlen("udp:");
+        port = udp::kDefaultPort;
+    } else {
+        return Error<ParseNetworkAddressError>(ParseNetworkAddressError::Type::WRONG_PREFIX)
+               << "protocol prefix ('tcp:' or 'udp:') is missed: " << serial << ". "
+               << "Expected address format:\n"
+               << "<protocol>:<address>:<port> (tcp:localhost:5554)";
+    }
+
+    std::string error;
+    std::string host;
+    if (!android::base::ParseNetAddress(net_address, &host, &port, nullptr, &error)) {
+        return Error<ParseNetworkAddressError>(ParseNetworkAddressError::Type::WRONG_ADDRESS)
+               << "invalid network address '" << net_address << "': " << error;
+    }
+
+    return NetworkSerial{protocol, host, 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, ParseNetworkAddressError> 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 if (network_serial.error().code() == ParseNetworkAddressError::Type::WRONG_PREFIX) {
+            // WRONG_PREFIX is special because it happens when user wants to communicate with USB
+            // device
+            transport = usb_open(match_fastboot(local_serial));
         } else {
-            transport = usb_open(match_fastboot);
+            Expect(network_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));
+        std::this_thread::sleep_for(std::chrono::seconds(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" : "fastboot");
+        }
+
+        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::seconds(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;
@@ -920,7 +1123,9 @@
     unique_fd fd(TEMP_FAILURE_RETRY(open(fname, O_RDONLY | O_BINARY)));
 
     if (fd == -1) {
-        return false;
+        auto path = find_item_given_name(fname);
+        fd = unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY)));
+        if (fd == -1) return false;
     }
 
     struct stat s;
@@ -1238,9 +1443,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, ":");
@@ -1336,7 +1540,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;
 
@@ -1365,19 +1569,19 @@
     }
 }
 
-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);
     delete old_transport;
 
     // Give the current connection time to close.
-    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+    std::this_thread::sleep_for(std::chrono::seconds(1));
 
     fb->set_transport(open_device());
 
@@ -1857,7 +2061,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;
     }
@@ -1942,10 +2146,19 @@
     }
 }
 
-static void FastbootLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
+static void FastbootLogger(android::base::LogId /* id */, android::base::LogSeverity severity,
                            const char* /* tag */, const char* /* file */, unsigned int /* line */,
                            const char* message) {
-    verbose("%s", 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) {
@@ -1956,10 +2169,6 @@
     android::base::InitLogging(argv, FastbootLogger, FastbootAborter);
 
     bool wants_wipe = false;
-    bool wants_reboot = false;
-    bool wants_reboot_bootloader = false;
-    bool wants_reboot_recovery = false;
-    bool wants_reboot_fastboot = false;
     bool skip_reboot = false;
     bool wants_set_active = false;
     bool skip_secondary = false;
@@ -2098,6 +2307,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();
     }
@@ -2110,7 +2331,9 @@
             .prolog = Status,
             .epilog = Epilog,
             .info = InfoMessage,
+            .text = TextMessage,
     };
+
     fastboot::FastBootDriver fastboot_driver(transport, driver_callbacks, false);
     fb = &fastboot_driver;
 
@@ -2134,7 +2357,7 @@
             }
         }
     }
-
+    std::unique_ptr<Task> reboot_task = nullptr;
     std::vector<std::string> args(argv, argv + argc);
     while (!args.empty()) {
         std::string command = next_arg(&args);
@@ -2186,30 +2409,19 @@
             fb->Download("signature", data);
             fb->RawCommand("signature", "installing signature");
         } else if (command == FB_CMD_REBOOT) {
-            wants_reboot = true;
-
             if (args.size() == 1) {
-                std::string what = next_arg(&args);
-                if (what == "bootloader") {
-                    wants_reboot = false;
-                    wants_reboot_bootloader = true;
-                } else if (what == "recovery") {
-                    wants_reboot = false;
-                    wants_reboot_recovery = true;
-                } else if (what == "fastboot") {
-                    wants_reboot = false;
-                    wants_reboot_fastboot = true;
-                } else {
-                    syntax_error("unknown reboot target %s", what.c_str());
-                }
+                std::string reboot_target = next_arg(&args);
+                reboot_task = std::make_unique<RebootTask>(fb, reboot_target);
+            } else {
+                reboot_task = std::make_unique<RebootTask>(fb);
             }
             if (!args.empty()) syntax_error("junk after reboot command");
         } else if (command == FB_CMD_REBOOT_BOOTLOADER) {
-            wants_reboot_bootloader = true;
+            reboot_task = std::make_unique<RebootTask>(fb, "bootloader");
         } else if (command == FB_CMD_REBOOT_RECOVERY) {
-            wants_reboot_recovery = true;
+            reboot_task = std::make_unique<RebootTask>(fb, "recovery");
         } else if (command == FB_CMD_REBOOT_FASTBOOT) {
-            wants_reboot_fastboot = true;
+            reboot_task = std::make_unique<RebootTask>(fb, "fastboot");
         } else if (command == FB_CMD_CONTINUE) {
             fb->Continue();
         } else if (command == FB_CMD_BOOT) {
@@ -2223,7 +2435,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);
@@ -2231,21 +2442,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);
@@ -2267,7 +2465,7 @@
             } else {
                 do_flashall(slot_override, skip_secondary, wants_wipe, force_flash);
             }
-            wants_reboot = true;
+            reboot_task = std::make_unique<RebootTask>(fb);
         } else if (command == "update") {
             bool slot_all = (slot_override == "all");
             if (slot_all) {
@@ -2279,7 +2477,7 @@
                 filename = next_arg(&args);
             }
             do_update(filename.c_str(), slot_override, skip_secondary || slot_all, force_flash);
-            wants_reboot = true;
+            reboot_task = std::make_unique<RebootTask>(fb);
         } else if (command == FB_CMD_SET_ACTIVE) {
             std::string slot = verify_slot(next_arg(&args), false);
             fb->SetActive(slot);
@@ -2351,7 +2549,6 @@
             syntax_error("unknown command %s", command.c_str());
         }
     }
-
     if (wants_wipe) {
         if (force_flash) {
             CancelSnapshotIfNeeded();
@@ -2370,19 +2567,9 @@
     if (wants_set_active) {
         fb->SetActive(next_active);
     }
-    if (wants_reboot && !skip_reboot) {
-        fb->Reboot();
-        fb->WaitForDisconnect();
-    } else if (wants_reboot_bootloader) {
-        fb->RebootTo("bootloader");
-        fb->WaitForDisconnect();
-    } else if (wants_reboot_recovery) {
-        fb->RebootTo("recovery");
-        fb->WaitForDisconnect();
-    } else if (wants_reboot_fastboot) {
-        reboot_to_userspace_fastboot();
+    if (reboot_task && !skip_reboot) {
+        reboot_task->Run();
     }
-
     fprintf(stderr, "Finished. Total time: %.3fs\n", (now() - start));
 
     auto* old_transport = fb->set_transport(nullptr);
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.cpp b/fastboot/fastboot_driver.cpp
index 99a4873..9770ab2 100644
--- a/fastboot/fastboot_driver.cpp
+++ b/fastboot/fastboot_driver.cpp
@@ -64,6 +64,7 @@
       prolog_(std::move(driver_callbacks.prolog)),
       epilog_(std::move(driver_callbacks.epilog)),
       info_(std::move(driver_callbacks.info)),
+      text_(std::move(driver_callbacks.text)),
       disable_checks_(no_checks) {}
 
 FastBootDriver::~FastBootDriver() {
@@ -498,6 +499,10 @@
             error_ = android::base::StringPrintf("remote: '%s'", status + strlen("FAIL"));
             set_response(input.substr(strlen("FAIL")));
             return DEVICE_FAIL;
+        } else if (android::base::StartsWith(input, "TEXT")) {
+            text_(input.substr(strlen("TEXT")));
+            // Reset timeout as many more TEXT may come
+            start = std::chrono::steady_clock::now();
         } else if (android::base::StartsWith(input, "DATA")) {
             std::string tmp = input.substr(strlen("DATA"));
             uint32_t num = strtol(tmp.c_str(), 0, 16);
diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h
index b422c91..f60c9f1 100644
--- a/fastboot/fastboot_driver.h
+++ b/fastboot/fastboot_driver.h
@@ -60,6 +60,7 @@
     std::function<void(const std::string&)> prolog = [](const std::string&) {};
     std::function<void(int)> epilog = [](int) {};
     std::function<void(const std::string&)> info = [](const std::string&) {};
+    std::function<void(const std::string&)> text = [](const std::string&) {};
 };
 
 class FastBootDriver {
@@ -169,6 +170,7 @@
     std::function<void(const std::string&)> prolog_;
     std::function<void(int)> epilog_;
     std::function<void(const std::string&)> info_;
+    std::function<void(const std::string&)> text_;
     bool disable_checks_;
 };
 
diff --git a/fastboot/fastboot_driver_test.cpp b/fastboot/fastboot_driver_test.cpp
new file mode 100644
index 0000000..6f6cf8c
--- /dev/null
+++ b/fastboot/fastboot_driver_test.cpp
@@ -0,0 +1,95 @@
+//
+// 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");
+}
+
+TEST_F(DriverTest, TextMessage) {
+    MockTransport transport;
+    std::string text;
+
+    DriverCallbacks callbacks{[](const std::string&) {}, [](int) {}, [](const std::string&) {},
+                              [&text](const std::string& extra_text) { text += extra_text; }};
+
+    FastBootDriver driver(&transport, callbacks);
+
+    EXPECT_CALL(transport, Write(_, _))
+            .With(AllArgs(RawData("oem trusty runtest trusty.hwaes.bench")))
+            .WillOnce(ReturnArg<1>());
+    EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("TEXTthis is a text line")));
+    EXPECT_CALL(transport, Read(_, _))
+            .WillOnce(Invoke(
+                    CopyData("TEXT, albeit very long and split over multiple TEXT messages.")));
+    EXPECT_CALL(transport, Read(_, _))
+            .WillOnce(Invoke(CopyData("TEXT Indeed we can do that now with a TEXT message whenever "
+                                      "we feel like it.")));
+    EXPECT_CALL(transport, Read(_, _))
+            .WillOnce(Invoke(CopyData("TEXT Isn't that truly super cool?")));
+
+    EXPECT_CALL(transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY")));
+
+    std::vector<std::string> info;
+    ASSERT_EQ(driver.RawCommand("oem trusty runtest trusty.hwaes.bench", "", nullptr, &info),
+              SUCCESS)
+            << driver.Error();
+    ASSERT_EQ(text,
+              "this is a text line"
+              ", albeit very long and split over multiple TEXT messages."
+              " Indeed we can do that now with a TEXT message whenever we feel like it."
+              " Isn't that truly super cool?");
+}
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_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..94dd5c3
--- /dev/null
+++ b/fastboot/task.cpp
@@ -0,0 +1,72 @@
+//
+// 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"
+
+#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);
+}
+
+RebootTask::RebootTask(fastboot::FastBootDriver* _fb) : fb_(_fb){};
+RebootTask::RebootTask(fastboot::FastBootDriver* _fb, std::string _reboot_target)
+    : reboot_target_(std::move(_reboot_target)), fb_(_fb){};
+
+void RebootTask::Run() {
+    if ((reboot_target_ == "userspace" || reboot_target_ == "fastboot")) {
+        if (!is_userspace_fastboot()) {
+            reboot_to_userspace_fastboot();
+            fb_->WaitForDisconnect();
+        }
+    } else if (reboot_target_ == "recovery") {
+        fb_->RebootTo("recovery");
+        fb_->WaitForDisconnect();
+    } else if (reboot_target_ == "bootloader") {
+        fb_->RebootTo("bootloader");
+        fb_->WaitForDisconnect();
+    } else if (reboot_target_ == "") {
+        fb_->Reboot();
+        fb_->WaitForDisconnect();
+    } else {
+        syntax_error("unknown reboot target %s", reboot_target_.c_str());
+    }
+}
diff --git a/fastboot/task.h b/fastboot/task.h
new file mode 100644
index 0000000..582fa2f
--- /dev/null
+++ b/fastboot/task.h
@@ -0,0 +1,58 @@
+//
+// 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_driver.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;
+};
+
+class RebootTask : public Task {
+  public:
+    RebootTask(fastboot::FastBootDriver* _fb);
+    RebootTask(fastboot::FastBootDriver* _fb, const std::string _reboot_target);
+    void Run() override;
+    ~RebootTask() {}
+
+  private:
+    const std::string reboot_target_ = "";
+    fastboot::FastBootDriver* fb_;
+};
\ No newline at end of file
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/usb_osx.cpp b/fastboot/usb_osx.cpp
index a4b9307..5b9e5c8 100644
--- a/fastboot/usb_osx.cpp
+++ b/fastboot/usb_osx.cpp
@@ -456,8 +456,7 @@
         }
 
         if (h.success) {
-            handle->reset(new usb_handle);
-            memcpy(handle->get(), &h, sizeof(usb_handle));
+            handle->reset(new usb_handle(h));
             ret = 0;
             break;
         }
diff --git a/fastboot/util.h b/fastboot/util.h
index 290d0d5..8a79e13 100644
--- a/fastboot/util.h
+++ b/fastboot/util.h
@@ -6,11 +6,29 @@
 #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;
+
+template <typename T, typename U>
+inline T Expect(Result<T, U> r) {
+    if (r.ok()) {
+        return r.value();
+    }
+
+    LOG(FATAL) << r.error().message();
+
+    return r.value();
+}
+
 using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>;
 
 /* util stuff */
diff --git a/fastboot/vendor_boot_img_utils_test.cpp b/fastboot/vendor_boot_img_utils_test.cpp
index 1563b89..467c6e9 100644
--- a/fastboot/vendor_boot_img_utils_test.cpp
+++ b/fastboot/vendor_boot_img_utils_test.cpp
@@ -73,8 +73,8 @@
 
 // Seek to beginning then read the whole file.
 Result<std::string> ReadStartOfFdToString(borrowed_fd fd, std::filesystem::path path) {
-    if (lseek64(fd.get(), 0, SEEK_SET) != 0)
-        return ErrnoError() << "lseek64(" << path << ", 0, SEEK_SET)";
+    if (lseek(fd.get(), 0, SEEK_SET) != 0)
+        return ErrnoError() << "lseek(" << path << ", 0, SEEK_SET)";
     std::string content;
     if (!android::base::ReadFdToString(fd, &content)) return ErrnoError() << "read(" << path << ")";
     return content;
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/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/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index b3763ae..fa04c43 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -108,6 +108,12 @@
 
     // Estimated COW size from OTA manifest.
     uint64 estimated_cow_size = 12;
+
+    // Enable multi-threaded compression
+    bool enable_threading = 13;
+
+    // Enable batching for COW writes
+    bool batched_writes = 14;
 }
 
 // Next: 8
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/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h
index 949e6c5..bd5c8cb 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.h
+++ b/fs_mgr/libsnapshot/partition_cow_creator.h
@@ -60,6 +60,12 @@
     bool using_snapuserd = false;
     std::string compression_algorithm;
 
+    // True if multi-threaded compression should be enabled
+    bool enable_threading;
+
+    // True if COW writes should be batched in memory
+    bool batched_writes;
+
     struct Return {
         SnapshotStatus snapshot_status;
         std::vector<Interval> cow_partition_usable_regions;
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 961db02..15f025c 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -400,6 +400,12 @@
     status->set_metadata_sectors(0);
     status->set_using_snapuserd(cow_creator->using_snapuserd);
     status->set_compression_algorithm(cow_creator->compression_algorithm);
+    if (cow_creator->enable_threading) {
+        status->set_enable_threading(cow_creator->enable_threading);
+    }
+    if (cow_creator->batched_writes) {
+        status->set_batched_writes(cow_creator->batched_writes);
+    }
 
     if (!WriteSnapshotStatus(lock, *status)) {
         PLOG(ERROR) << "Could not write snapshot status: " << status->name();
@@ -3248,6 +3254,12 @@
             .using_snapuserd = using_snapuserd,
             .compression_algorithm = compression_algorithm,
     };
+    if (dap_metadata.vabc_feature_set().has_threaded()) {
+        cow_creator.enable_threading = dap_metadata.vabc_feature_set().threaded();
+    }
+    if (dap_metadata.vabc_feature_set().has_batch_writes()) {
+        cow_creator.batched_writes = dap_metadata.vabc_feature_set().batch_writes();
+    }
 
     auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices,
                                              &all_snapshot_status);
@@ -3635,6 +3647,8 @@
     CowOptions cow_options;
     cow_options.compression = status.compression_algorithm();
     cow_options.max_blocks = {status.device_size() / cow_options.block_size};
+    cow_options.batch_write = status.batched_writes();
+    cow_options.num_compress_threads = status.enable_threading() ? 2 : 0;
     // Disable scratch space for vts tests
     if (device()->IsTestDevice()) {
         cow_options.scratch_space = false;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 4014380..13314da 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -2669,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/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index d670f1e..1421403 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -99,9 +99,9 @@
     bool valid_;
 };
 
-class SnapuserTest final {
+class SnapuserdTest : public ::testing::Test {
   public:
-    bool Setup();
+    bool SetupDefault();
     bool SetupOrderedOps();
     bool SetupOrderedOpsInverted();
     bool SetupCopyOverlap_1();
@@ -118,6 +118,10 @@
 
     static const uint64_t kSectorSize = 512;
 
+  protected:
+    void SetUp() override {}
+    void TearDown() override { Shutdown(); }
+
   private:
     void SetupImpl();
 
@@ -172,7 +176,7 @@
     return fd;
 }
 
-void SnapuserTest::Shutdown() {
+void SnapuserdTest::Shutdown() {
     ASSERT_TRUE(dmuser_dev_->Destroy());
 
     auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
@@ -181,36 +185,36 @@
     ASSERT_TRUE(client_->DetachSnapuserd());
 }
 
-bool SnapuserTest::Setup() {
+bool SnapuserdTest::SetupDefault() {
     SetupImpl();
     return setup_ok_;
 }
 
-bool SnapuserTest::SetupOrderedOps() {
+bool SnapuserdTest::SetupOrderedOps() {
     CreateBaseDevice();
     CreateCowDeviceOrderedOps();
     return SetupDaemon();
 }
 
-bool SnapuserTest::SetupOrderedOpsInverted() {
+bool SnapuserdTest::SetupOrderedOpsInverted() {
     CreateBaseDevice();
     CreateCowDeviceOrderedOpsInverted();
     return SetupDaemon();
 }
 
-bool SnapuserTest::SetupCopyOverlap_1() {
+bool SnapuserdTest::SetupCopyOverlap_1() {
     CreateBaseDevice();
     CreateCowDeviceWithCopyOverlap_1();
     return SetupDaemon();
 }
 
-bool SnapuserTest::SetupCopyOverlap_2() {
+bool SnapuserdTest::SetupCopyOverlap_2() {
     CreateBaseDevice();
     CreateCowDeviceWithCopyOverlap_2();
     return SetupDaemon();
 }
 
-bool SnapuserTest::SetupDaemon() {
+bool SnapuserdTest::SetupDaemon() {
     SetDeviceControlName();
 
     StartSnapuserdDaemon();
@@ -224,7 +228,7 @@
     return setup_ok_;
 }
 
-void SnapuserTest::StartSnapuserdDaemon() {
+void SnapuserdTest::StartSnapuserdDaemon() {
     pid_t pid = fork();
     ASSERT_GE(pid, 0);
     if (pid == 0) {
@@ -238,7 +242,7 @@
     }
 }
 
-void SnapuserTest::CreateBaseDevice() {
+void SnapuserdTest::CreateBaseDevice() {
     unique_fd rnd_fd;
 
     total_base_size_ = (size_ * 5);
@@ -261,7 +265,7 @@
     ASSERT_TRUE(base_loop_->valid());
 }
 
-void SnapuserTest::ReadSnapshotDeviceAndValidate() {
+void SnapuserdTest::ReadSnapshotDeviceAndValidate() {
     unique_fd fd(open(dmuser_dev_->path().c_str(), O_RDONLY));
     ASSERT_GE(fd, 0);
     std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
@@ -292,7 +296,7 @@
     ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
 }
 
-void SnapuserTest::CreateCowDeviceWithCopyOverlap_2() {
+void SnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
     std::string path = android::base::GetExecutableDirectory();
     cow_system_ = std::make_unique<TemporaryFile>(path);
 
@@ -344,7 +348,7 @@
     }
 }
 
-void SnapuserTest::CreateCowDeviceWithCopyOverlap_1() {
+void SnapuserdTest::CreateCowDeviceWithCopyOverlap_1() {
     std::string path = android::base::GetExecutableDirectory();
     cow_system_ = std::make_unique<TemporaryFile>(path);
 
@@ -387,7 +391,7 @@
               true);
 }
 
-void SnapuserTest::CreateCowDeviceOrderedOpsInverted() {
+void SnapuserdTest::CreateCowDeviceOrderedOpsInverted() {
     unique_fd rnd_fd;
     loff_t offset = 0;
 
@@ -450,7 +454,7 @@
     }
 }
 
-void SnapuserTest::CreateCowDeviceOrderedOps() {
+void SnapuserdTest::CreateCowDeviceOrderedOps() {
     unique_fd rnd_fd;
     loff_t offset = 0;
 
@@ -511,7 +515,7 @@
     }
 }
 
-void SnapuserTest::CreateCowDevice() {
+void SnapuserdTest::CreateCowDevice() {
     unique_fd rnd_fd;
     loff_t offset = 0;
 
@@ -601,13 +605,13 @@
     }
 }
 
-void SnapuserTest::InitCowDevice() {
+void SnapuserdTest::InitCowDevice() {
     uint64_t num_sectors = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
                                                   base_loop_->device(), base_loop_->device());
     ASSERT_NE(num_sectors, 0);
 }
 
-void SnapuserTest::SetDeviceControlName() {
+void SnapuserdTest::SetDeviceControlName() {
     system_device_name_.clear();
     system_device_ctrl_name_.clear();
 
@@ -619,7 +623,7 @@
     system_device_ctrl_name_ = system_device_name_ + "-ctrl";
 }
 
-void SnapuserTest::CreateDmUserDevice() {
+void SnapuserdTest::CreateDmUserDevice() {
     unique_fd fd(TEMP_FAILURE_RETRY(open(base_loop_->device().c_str(), O_RDONLY | O_CLOEXEC)));
     ASSERT_TRUE(fd > 0);
 
@@ -641,12 +645,12 @@
     ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s));
 }
 
-void SnapuserTest::InitDaemon() {
+void SnapuserdTest::InitDaemon() {
     bool ok = client_->AttachDmUser(system_device_ctrl_name_);
     ASSERT_TRUE(ok);
 }
 
-void SnapuserTest::CheckMergeCompletion() {
+void SnapuserdTest::CheckMergeCompletion() {
     while (true) {
         double percentage = client_->GetMergePercent();
         if ((int)percentage == 100) {
@@ -657,7 +661,7 @@
     }
 }
 
-void SnapuserTest::SetupImpl() {
+void SnapuserdTest::SetupImpl() {
     CreateBaseDevice();
     CreateCowDevice();
 
@@ -672,26 +676,26 @@
     setup_ok_ = true;
 }
 
-bool SnapuserTest::Merge() {
+bool SnapuserdTest::Merge() {
     StartMerge();
     CheckMergeCompletion();
     merge_ok_ = true;
     return merge_ok_;
 }
 
-void SnapuserTest::StartMerge() {
+void SnapuserdTest::StartMerge() {
     bool ok = client_->InitiateMerge(system_device_ctrl_name_);
     ASSERT_TRUE(ok);
 }
 
-void SnapuserTest::ValidateMerge() {
+void SnapuserdTest::ValidateMerge() {
     merged_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
     ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0),
               true);
     ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0);
 }
 
-void SnapuserTest::SimulateDaemonRestart() {
+void SnapuserdTest::SimulateDaemonRestart() {
     Shutdown();
     std::this_thread::sleep_for(500ms);
     SetDeviceControlName();
@@ -701,7 +705,7 @@
     InitDaemon();
 }
 
-void SnapuserTest::MergeInterruptRandomly(int max_duration) {
+void SnapuserdTest::MergeInterruptRandomly(int max_duration) {
     std::srand(std::time(nullptr));
     StartMerge();
 
@@ -716,7 +720,7 @@
     ASSERT_TRUE(Merge());
 }
 
-void SnapuserTest::MergeInterruptFixed(int duration) {
+void SnapuserdTest::MergeInterruptFixed(int duration) {
     StartMerge();
 
     for (int i = 0; i < 25; i++) {
@@ -729,7 +733,7 @@
     ASSERT_TRUE(Merge());
 }
 
-void SnapuserTest::MergeInterrupt() {
+void SnapuserdTest::MergeInterrupt() {
     // Interrupt merge at various intervals
     StartMerge();
     std::this_thread::sleep_for(250ms);
@@ -758,104 +762,93 @@
     ASSERT_TRUE(Merge());
 }
 
-TEST(Snapuserd_Test, Snapshot_IO_TEST) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.Setup());
+TEST_F(SnapuserdTest, Snapshot_IO_TEST) {
+    ASSERT_TRUE(SetupDefault());
     // I/O before merge
-    harness.ReadSnapshotDeviceAndValidate();
-    ASSERT_TRUE(harness.Merge());
-    harness.ValidateMerge();
+    ReadSnapshotDeviceAndValidate();
+    ASSERT_TRUE(Merge());
+    ValidateMerge();
     // I/O after merge - daemon should read directly
     // from base device
-    harness.ReadSnapshotDeviceAndValidate();
-    harness.Shutdown();
+    ReadSnapshotDeviceAndValidate();
+    Shutdown();
 }
 
-TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.Setup());
+TEST_F(SnapuserdTest, Snapshot_MERGE_IO_TEST) {
+    ASSERT_TRUE(SetupDefault());
     // Issue I/O before merge begins
-    std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness);
+    std::async(std::launch::async, &SnapuserdTest::ReadSnapshotDeviceAndValidate, this);
     // Start the merge
-    ASSERT_TRUE(harness.Merge());
-    harness.ValidateMerge();
-    harness.Shutdown();
+    ASSERT_TRUE(Merge());
+    ValidateMerge();
+    Shutdown();
 }
 
-TEST(Snapuserd_Test, Snapshot_MERGE_IO_TEST_1) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.Setup());
+TEST_F(SnapuserdTest, Snapshot_MERGE_IO_TEST_1) {
+    ASSERT_TRUE(SetupDefault());
     // Start the merge
-    harness.StartMerge();
+    StartMerge();
     // Issue I/O in parallel when merge is in-progress
-    std::async(std::launch::async, &SnapuserTest::ReadSnapshotDeviceAndValidate, &harness);
-    harness.CheckMergeCompletion();
-    harness.ValidateMerge();
-    harness.Shutdown();
+    std::async(std::launch::async, &SnapuserdTest::ReadSnapshotDeviceAndValidate, this);
+    CheckMergeCompletion();
+    ValidateMerge();
+    Shutdown();
 }
 
-TEST(Snapuserd_Test, Snapshot_Merge_Resume) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.Setup());
-    harness.MergeInterrupt();
-    harness.ValidateMerge();
-    harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_Merge_Resume) {
+    ASSERT_TRUE(SetupDefault());
+    MergeInterrupt();
+    ValidateMerge();
+    Shutdown();
 }
 
-TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.SetupCopyOverlap_1());
-    ASSERT_TRUE(harness.Merge());
-    harness.ValidateMerge();
-    harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_COPY_Overlap_TEST_1) {
+    ASSERT_TRUE(SetupCopyOverlap_1());
+    ASSERT_TRUE(Merge());
+    ValidateMerge();
+    Shutdown();
 }
 
-TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.SetupCopyOverlap_2());
-    ASSERT_TRUE(harness.Merge());
-    harness.ValidateMerge();
-    harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_COPY_Overlap_TEST_2) {
+    ASSERT_TRUE(SetupCopyOverlap_2());
+    ASSERT_TRUE(Merge());
+    ValidateMerge();
+    Shutdown();
 }
 
-TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.SetupCopyOverlap_1());
-    harness.MergeInterrupt();
-    harness.ValidateMerge();
-    harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
+    ASSERT_TRUE(SetupCopyOverlap_1());
+    MergeInterrupt();
+    ValidateMerge();
+    Shutdown();
 }
 
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Ordered) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.SetupOrderedOps());
-    harness.MergeInterruptFixed(300);
-    harness.ValidateMerge();
-    harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Fixed_Ordered) {
+    ASSERT_TRUE(SetupOrderedOps());
+    MergeInterruptFixed(300);
+    ValidateMerge();
+    Shutdown();
 }
 
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Ordered) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.SetupOrderedOps());
-    harness.MergeInterruptRandomly(500);
-    harness.ValidateMerge();
-    harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Random_Ordered) {
+    ASSERT_TRUE(SetupOrderedOps());
+    MergeInterruptRandomly(500);
+    ValidateMerge();
+    Shutdown();
 }
 
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Fixed_Inverted) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.SetupOrderedOpsInverted());
-    harness.MergeInterruptFixed(50);
-    harness.ValidateMerge();
-    harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Fixed_Inverted) {
+    ASSERT_TRUE(SetupOrderedOpsInverted());
+    MergeInterruptFixed(50);
+    ValidateMerge();
+    Shutdown();
 }
 
-TEST(Snapuserd_Test, Snapshot_Merge_Crash_Random_Inverted) {
-    SnapuserTest harness;
-    ASSERT_TRUE(harness.SetupOrderedOpsInverted());
-    harness.MergeInterruptRandomly(50);
-    harness.ValidateMerge();
-    harness.Shutdown();
+TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Random_Inverted) {
+    ASSERT_TRUE(SetupOrderedOpsInverted());
+    MergeInterruptRandomly(50);
+    ValidateMerge();
+    Shutdown();
 }
 
 }  // namespace snapshot
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/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
index 69d72e1..cc12d1d 100644
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
@@ -71,11 +71,18 @@
     repeated string partition_names = 3;
 }
 
+message VABCFeatureSet {
+  optional bool threaded = 1;
+  optional bool batch_writes = 2;
+}
+
 message DynamicPartitionMetadata {
     repeated DynamicPartitionGroup groups = 1;
     optional bool vabc_enabled = 3;
     optional string vabc_compression_param = 4;
     optional uint32 cow_version = 5;
+    // A collection of knobs to tune Virtual AB Compression
+    optional VABCFeatureSet vabc_feature_set = 6;
 }
 
 message DeltaArchiveManifest {
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 4ea452a..66e1e63 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -138,6 +138,7 @@
       mBatteryDevicePresent(false),
       mBatteryFixedCapacity(0),
       mBatteryFixedTemperature(0),
+      mBatteryHealthStatus(BatteryMonitor::BH_UNKNOWN),
       mHealthInfo(std::make_unique<HealthInfo>()) {
     initHealthInfo(mHealthInfo.get());
 }
@@ -230,6 +231,23 @@
     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},
@@ -374,8 +392,12 @@
         mHealthInfo->batteryFullChargeDesignCapacityUah =
                 getIntField(mHealthdConfig->batteryFullChargeDesignCapacityUahPath);
 
+    if (!mHealthdConfig->batteryHealthStatusPath.isEmpty())
+        mBatteryHealthStatus = getIntField(mHealthdConfig->batteryHealthStatusPath);
+
     if (!mHealthdConfig->batteryStateOfHealthPath.isEmpty())
-        mHealthInfo->batteryStateOfHealth = getIntField(mHealthdConfig->batteryStateOfHealthPath);
+        mHealthInfo->batteryHealthData->batteryStateOfHealth =
+                getIntField(mHealthdConfig->batteryStateOfHealthPath);
 
     if (!mHealthdConfig->batteryManufacturingDatePath.isEmpty())
         mHealthInfo->batteryHealthData->batteryManufacturingDateSeconds =
@@ -397,8 +419,13 @@
     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());
@@ -565,6 +592,10 @@
         if (!mHealthdConfig->batteryFirstUsageDatePath.isEmpty())
             return getIntField(mHealthdConfig->batteryFirstUsageDatePath);
     }
+    if (id == BATTERY_PROP_STATE_OF_HEALTH) {
+        if (!mHealthdConfig->batteryStateOfHealthPath.isEmpty())
+            return getIntField(mHealthdConfig->batteryStateOfHealthPath);
+    }
     return 0;
 }
 
@@ -643,6 +674,11 @@
         ret = OK;
         break;
 
+    case BATTERY_PROP_STATE_OF_HEALTH:
+        val->valueInt64 = getBatteryHealthData(BATTERY_PROP_STATE_OF_HEALTH);
+        ret = OK;
+        break;
+
     default:
         break;
     }
@@ -878,6 +914,12 @@
                     }
                 }
 
+                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);
@@ -957,6 +999,8 @@
             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())
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index 4af2a87..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);
@@ -85,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/healthd.h b/healthd/include/healthd/healthd.h
index e1c357c..688e458 100644
--- a/healthd/include/healthd/healthd.h
+++ b/healthd/include/healthd/healthd.h
@@ -73,6 +73,7 @@
     android::String8 batteryChargeTimeToFullNowPath;
     android::String8 batteryFullChargeDesignCapacityUahPath;
     android::String8 batteryStateOfHealthPath;
+    android::String8 batteryHealthStatusPath;
     android::String8 batteryManufacturingDatePath;
     android::String8 batteryFirstUsageDatePath;
     android::String8 chargingStatePath;
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/action.cpp b/init/action.cpp
index 1e998ae..18f6360 100644
--- a/init/action.cpp
+++ b/init/action.cpp
@@ -30,7 +30,7 @@
 
 Result<void> RunBuiltinFunction(const BuiltinFunction& function,
                                 const std::vector<std::string>& args, const std::string& context) {
-    auto builtin_arguments = BuiltinArguments(context);
+    BuiltinArguments builtin_arguments{.context = context};
 
     builtin_arguments.args.resize(args.size());
     builtin_arguments.args[0] = args[0];
@@ -69,7 +69,7 @@
 }
 
 Result<void> Command::CheckCommand() const {
-    auto builtin_arguments = BuiltinArguments("host_init_verifier");
+    BuiltinArguments builtin_arguments{.context = "host_init_verifier"};
 
     builtin_arguments.args.resize(args_.size());
     builtin_arguments.args[0] = args_[0];
diff --git a/init/builtin_arguments.h b/init/builtin_arguments.h
index 1742b78..890a216 100644
--- a/init/builtin_arguments.h
+++ b/init/builtin_arguments.h
@@ -24,10 +24,6 @@
 namespace init {
 
 struct BuiltinArguments {
-    BuiltinArguments(const std::string& context) : context(context) {}
-    BuiltinArguments(std::vector<std::string> args, const std::string& context)
-        : args(std::move(args)), context(context) {}
-
     const std::string& operator[](std::size_t i) const { return args[i]; }
     auto begin() const { return args.begin(); }
     auto end() const { return args.end(); }
diff --git a/init/builtins.cpp b/init/builtins.cpp
index a89813e..bc23972 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -1074,7 +1074,7 @@
 static Result<void> do_restorecon_recursive(const BuiltinArguments& args) {
     std::vector<std::string> non_const_args(args.args);
     non_const_args.insert(std::next(non_const_args.begin()), "--recursive");
-    return do_restorecon({std::move(non_const_args), args.context});
+    return do_restorecon({.args = std::move(non_const_args), .context = args.context});
 }
 
 static Result<void> do_loglevel(const BuiltinArguments& args) {
diff --git a/init/check_builtins.cpp b/init/check_builtins.cpp
index 481fa31..461ed22 100644
--- a/init/check_builtins.cpp
+++ b/init/check_builtins.cpp
@@ -85,7 +85,7 @@
 }
 
 Result<void> check_exec_reboot_on_failure(const BuiltinArguments& args) {
-    BuiltinArguments remaining_args(args.context);
+    BuiltinArguments remaining_args{.context = args.context};
 
     remaining_args.args = std::vector<std::string>(args.begin() + 1, args.end());
     remaining_args.args[0] = args[0];
diff --git a/init/init.cpp b/init/init.cpp
index f964c60..c965fe6 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -51,6 +51,7 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <android-base/thread_annotations.h>
 #include <fs_avb/fs_avb.h>
 #include <fs_mgr_vendor_overlay.h>
 #include <keyutils.h>
@@ -211,16 +212,16 @@
     }
 
   private:
-    void ResetWaitForPropLocked() {
+    void ResetWaitForPropLocked() EXCLUSIVE_LOCKS_REQUIRED(lock_) {
         wait_prop_name_.clear();
         wait_prop_value_.clear();
         waiting_for_prop_.reset();
     }
 
     std::mutex lock_;
-    std::unique_ptr<Timer> waiting_for_prop_{nullptr};
-    std::string wait_prop_name_;
-    std::string wait_prop_value_;
+    GUARDED_BY(lock_) std::unique_ptr<Timer> waiting_for_prop_{nullptr};
+    GUARDED_BY(lock_) std::string wait_prop_name_;
+    GUARDED_BY(lock_) std::string wait_prop_value_;
 
 } prop_waiter_state;
 
@@ -259,7 +260,7 @@
 
   private:
     std::mutex shutdown_command_lock_;
-    std::string shutdown_command_;
+    std::string shutdown_command_ GUARDED_BY(shutdown_command_lock_);
     bool do_shutdown_ = false;
 } shutdown_state;
 
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 1e69ede..305bf95 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <fstream>
 #include <functional>
 #include <string_view>
 #include <thread>
@@ -22,6 +23,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <android-base/stringprintf.h>
 #include <android/api-level.h>
 #include <gtest/gtest.h>
 #include <selinux/selinux.h>
@@ -44,6 +46,7 @@
 using android::base::GetIntProperty;
 using android::base::GetProperty;
 using android::base::SetProperty;
+using android::base::StringPrintf;
 using android::base::StringReplace;
 using android::base::WaitForProperty;
 using namespace std::literals;
@@ -643,34 +646,32 @@
     ASSERT_LE(curr_limit.rlim_max, max_limit);
 }
 
-static std::vector<const char*> ConvertToArgv(const std::vector<std::string>& args) {
-    std::vector<const char*> argv;
-    argv.reserve(args.size() + 1);
-    for (const auto& arg : args) {
-        if (argv.empty()) {
-            LOG(DEBUG) << arg;
-        } else {
-            LOG(DEBUG) << "    " << arg;
+void CloseAllFds() {
+    DIR* dir;
+    struct dirent* ent;
+    int fd;
+
+    if ((dir = opendir("/proc/self/fd"))) {
+        while ((ent = readdir(dir))) {
+            if (sscanf(ent->d_name, "%d", &fd) == 1) {
+                close(fd);
+            }
         }
-        argv.emplace_back(arg.data());
+        closedir(dir);
     }
-    argv.emplace_back(nullptr);
-    return argv;
 }
 
-pid_t ForkExecvpAsync(const std::vector<std::string>& args) {
-    auto argv = ConvertToArgv(args);
-
+pid_t ForkExecvpAsync(const char* argv[]) {
     pid_t pid = fork();
     if (pid == 0) {
-        close(STDIN_FILENO);
-        close(STDOUT_FILENO);
-        close(STDERR_FILENO);
+        // Child process.
+        CloseAllFds();
 
-        execvp(argv[0], const_cast<char**>(argv.data()));
+        execvp(argv[0], const_cast<char**>(argv));
         PLOG(ERROR) << "exec in ForkExecvpAsync init test";
         _exit(EXIT_FAILURE);
     }
+    // Parent process.
     if (pid == -1) {
         PLOG(ERROR) << "fork in ForkExecvpAsync init test";
         return -1;
@@ -678,7 +679,23 @@
     return pid;
 }
 
+pid_t TracerPid(pid_t pid) {
+    static constexpr std::string_view prefix{"TracerPid:"};
+    std::ifstream is(StringPrintf("/proc/%d/status", pid));
+    std::string line;
+    while (std::getline(is, line)) {
+        if (line.find(prefix) == 0) {
+            return atoi(line.substr(prefix.length()).c_str());
+        }
+    }
+    return -1;
+}
+
 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
@@ -705,18 +722,15 @@
     logfile.DoNotRemove();
     ASSERT_TRUE(logfile.fd != -1);
 
-    std::vector<std::string> cmd;
-    cmd.push_back("system/bin/strace");
-    cmd.push_back("-o");
-    cmd.push_back(logfile.path);
-    cmd.push_back("-e");
-    cmd.push_back("signal");
-    cmd.push_back("-p");
-    cmd.push_back(std::to_string(pid));
-    pid_t strace_pid = ForkExecvpAsync(cmd);
+    std::string pid_str = std::to_string(pid);
+    const char* argv[] = {"/system/bin/strace", "-o", logfile.path, "-e", "signal", "-p",
+                          pid_str.c_str(),      nullptr};
+    pid_t strace_pid = ForkExecvpAsync(argv);
 
-    // Give strace a moment to connect
-    std::this_thread::sleep_for(1s);
+    // Give strace the chance to connect
+    while (TracerPid(pid) == 0) {
+        std::this_thread::sleep_for(10ms);
+    }
     service->Stop();
 
     int status;
@@ -724,8 +738,7 @@
 
     std::string logs;
     android::base::ReadFdToString(logfile.fd, &logs);
-    int pos = logs.find("killed by SIGTERM");
-    ASSERT_NE(pos, (int)std::string::npos);
+    ASSERT_NE(logs.find("killed by SIGTERM"), std::string::npos);
 }
 
 class TestCaseLogger : public ::testing::EmptyTestEventListener {
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 4cc00fe..062ed39 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -897,29 +897,31 @@
             continue;
         }
 
-        auto system_entry = GetEntryForMountPoint(&fstab, "/system");
-        if (!system_entry) {
-            LOG(ERROR) << "Could not find mount entry for /system";
-            break;
-        }
-        if (!system_entry->fs_mgr_flags.logical) {
-            LOG(INFO) << "Skipping mount of " << name << ", system is not dynamic.";
-            break;
-        }
+        auto system_entries = GetEntriesForMountPoint(&fstab, "/system");
+        for (auto& system_entry : system_entries) {
+            if (!system_entry) {
+                LOG(ERROR) << "Could not find mount entry for /system";
+                break;
+            }
+            if (!system_entry->fs_mgr_flags.logical) {
+                LOG(INFO) << "Skipping mount of " << name << ", system is not dynamic.";
+                break;
+            }
 
-        auto entry = *system_entry;
-        auto partition_name = name + fs_mgr_get_slot_suffix();
-        auto replace_name = "system"s + fs_mgr_get_slot_suffix();
+            auto entry = *system_entry;
+            auto partition_name = name + fs_mgr_get_slot_suffix();
+            auto replace_name = "system"s + fs_mgr_get_slot_suffix();
 
-        entry.mount_point = "/"s + name;
-        entry.blk_device =
+            entry.mount_point = "/"s + name;
+            entry.blk_device =
                 android::base::StringReplace(entry.blk_device, replace_name, partition_name, false);
-        if (!fs_mgr_update_logical_partition(&entry)) {
-            LOG(ERROR) << "Could not update logical partition";
-            continue;
-        }
+            if (!fs_mgr_update_logical_partition(&entry)) {
+                LOG(ERROR) << "Could not update logical partition";
+                continue;
+            }
 
-        extra_fstab.emplace_back(std::move(entry));
+            extra_fstab.emplace_back(std::move(entry));
+        }
     }
 
     SkipMountingPartitions(&extra_fstab, true /* verbose */);
diff --git a/init/service.cpp b/init/service.cpp
index 87d9c3a..cce24c3 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -755,6 +755,9 @@
 
     NotifyStateChange("running");
     reboot_on_failure.Disable();
+
+    LOG(INFO) << "... started service '" << name_ << "' has pid " << pid_;
+
     return {};
 }
 
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/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 1da69ba..384a8f0 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -406,18 +406,15 @@
                 pids.emplace(pid);
             }
         }
-        if (file_is_empty) {
-            // This happens when process is already dead
-            return 0;
-        }
-
-        // Erase all pids that will be killed when we kill the process groups.
-        for (auto it = pids.begin(); it != pids.end();) {
-            pid_t pgid = getpgid(*it);
-            if (pgids.count(pgid) == 1) {
-                it = pids.erase(it);
-            } else {
-                ++it;
+        if (!file_is_empty) {
+            // Erase all pids that will be killed when we kill the process groups.
+            for (auto it = pids.begin(); it != pids.end();) {
+                pid_t pgid = getpgid(*it);
+                if (pgids.count(pgid) == 1) {
+                    it = pids.erase(it);
+                } else {
+                    ++it;
+                }
             }
         }
     }
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/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 35adf36..4db7372 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -815,11 +815,11 @@
                 profile->EnableResourceCaching(ProfileAction::RCT_PROCESS);
             }
             if (!profile->ExecuteForProcess(uid, pid)) {
-                PLOG(WARNING) << "Failed to apply " << name << " process profile";
+                LOG(WARNING) << "Failed to apply " << name << " process profile";
                 success = false;
             }
         } else {
-            PLOG(WARNING) << "Failed to find " << name << " process profile";
+            LOG(WARNING) << "Failed to find " << name << " process profile";
             success = false;
         }
     }
@@ -836,11 +836,11 @@
                 profile->EnableResourceCaching(ProfileAction::RCT_TASK);
             }
             if (!profile->ExecuteForTask(tid)) {
-                PLOG(WARNING) << "Failed to apply " << name << " task profile";
+                LOG(WARNING) << "Failed to apply " << name << " task profile";
                 success = false;
             }
         } else {
-            PLOG(WARNING) << "Failed to find " << name << " task profile";
+            LOG(WARNING) << "Failed to find " << name << " task profile";
             success = false;
         }
     }
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/libutils/ProcessCallStack_fuzz.cpp b/libutils/ProcessCallStack_fuzz.cpp
index 30136cd..552a11e 100644
--- a/libutils/ProcessCallStack_fuzz.cpp
+++ b/libutils/ProcessCallStack_fuzz.cpp
@@ -44,7 +44,7 @@
                 dataProvider->ConsumeRandomLengthString(MAX_NAME_SIZE).append(std::to_string(i));
         std::thread th = std::thread(loop);
         pthread_setname_np(th.native_handle(), threadName.c_str());
-        threads.push_back(move(th));
+        threads.push_back(std::move(th));
     }
 
     // Collect thread information
diff --git a/libutils/RefBase_fuzz.cpp b/libutils/RefBase_fuzz.cpp
index 69288b3..8291be9 100644
--- a/libutils/RefBase_fuzz.cpp
+++ b/libutils/RefBase_fuzz.cpp
@@ -177,7 +177,7 @@
         uint8_t opCount = dataProvider->ConsumeIntegralInRange<uint8_t>(1, kMaxOperations);
         std::vector<uint8_t> threadOperations = dataProvider->ConsumeBytes<uint8_t>(opCount);
         std::thread tmpThread = std::thread(loop, threadOperations);
-        threads.push_back(move(tmpThread));
+        threads.push_back(std::move(tmpThread));
     }
 
     for (auto& th : threads) {
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 3dd269a..3362872 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -222,6 +222,8 @@
 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 86c6eaa..68191bb 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -925,6 +925,10 @@
     mkdir /data/system/dropbox 0700 system system
     mkdir /data/system/heapdump 0700 system system
     mkdir /data/system/users 0775 system system
+    # Mkdir and set SELinux security contexts for shutdown-checkpoints.
+    # TODO(b/270286197): remove these after couple releases.
+    mkdir /data/system/shutdown-checkpoints 0700 system system
+    restorecon_recursive /data/system/shutdown-checkpoints
 
     # Create the parent directories of the user CE and DE storage directories.
     # These parent directories must use encryption=None, since each of their
@@ -993,6 +997,7 @@
     # Create directories for statsd
     mkdir /data/misc/stats-active-metric/ 0770 statsd system
     mkdir /data/misc/stats-data/ 0770 statsd system
+    mkdir /data/misc/stats-data/restricted-data 0770 statsd system
     mkdir /data/misc/stats-metadata/ 0770 statsd system
     mkdir /data/misc/stats-service/ 0770 statsd system
     mkdir /data/misc/train-info/ 0770 statsd system
diff --git a/shell_and_utilities/README.md b/shell_and_utilities/README.md
index ca522b7..9a733bb 100644
--- a/shell_and_utilities/README.md
+++ b/shell_and_utilities/README.md
@@ -18,8 +18,11 @@
 in Marshmallow we changed direction and started the move to toybox.
 
 Not everything is provided by toybox, though. For the bzip2 command-line tools
-we use the ones that are part of the bzip2 distribution. The awk added in
-Android P is Brian Kernighan's "one true" awk.
+we use the ones that are part of the bzip2 distribution.
+The awk added in Android P is the
+["one true" awk](https://github.com/onetrueawk/awk).
+The bc added in Android Q is
+[Gavin Howard's bc](https://github.com/gavinhoward/bc).
 
 The lists below show what tools were provided and where they came from in
 each release starting with Gingerbread. This doesn't tell the full story,
@@ -34,6 +37,40 @@
 full list for a release by running `toybox` directly.
 
 
+## Android 14 ("U")
+
+BSD: fsck\_msdos newfs\_msdos
+
+bzip2: bzcat bzip2 bunzip2
+
+gavinhoward/bc: bc
+
+one-true-awk: awk
+
+toolbox: getevent getprop setprop start stop
+
+toybox ([0.8.9](http://landley.net/toybox/#10-01-2023)-ish):
+[ acpi base64 basename blkdiscard blkid blockdev **brctl** cal cat chattr
+chcon chgrp chmod chown chroot chrt cksum clear cmp comm cp cpio cut
+date dd devmem df diff dirname dmesg dos2unix du echo egrep env expand
+expr fallocate false fgrep file find flock fmt free freeramdisk fsfreeze
+fsync getconf getenforce getfattr getopt grep groups gunzip gzip head
+help hostname hwclock i2cdetect i2cdump i2cget i2cset iconv id ifconfig
+inotifyd insmod install ionice iorenice iotop kill killall ln load\_policy
+log **logger** logname losetup ls lsattr lsmod lsof lspci lsusb makedevs
+md5sum microcom mkdir mkfifo mknod mkswap mktemp modinfo modprobe
+more mount mountpoint mv nbd-client nc netcat netstat nice nl nohup
+nproc nsenter od partprobe paste patch pgrep pidof ping ping6 pivot\_root
+pkill pmap printenv printf prlimit ps pwd pwdx readelf readlink realpath
+renice restorecon rev rfkill rm rmdir rmmod rtcwake runcon sed sendevent
+seq setenforce setfattr setsid sha1sum sha224sum sha256sum sha384sum
+sha512sum sleep sort split stat strings stty swapoff swapon sync sysctl
+tac tail tar taskset tee test time timeout top touch tr traceroute
+traceroute6 true truncate tty tunctl uclampset ulimit umount uname
+uniq unix2dos unlink unshare uptime usleep uudecode uuencode uuidgen
+vconfig vi vmstat watch wc which whoami xargs xxd yes zcat
+
+
 ## Android 13 ("T")
 
 BSD: fsck\_msdos newfs\_msdos
@@ -46,7 +83,8 @@
 
 toolbox: getevent getprop setprop start stop
 
-toybox (0.8.6-ish): [ acpi base64 basename blkdiscard blkid blockdev cal cat chattr chcon
+toybox ([0.8.6](http://landley.net/toybox/#30-11-2021)-ish):
+[ acpi base64 basename blkdiscard blkid blockdev cal cat chattr chcon
 chgrp chmod chown chroot chrt cksum clear cmp comm cp cpio cut date
 dd devmem df diff dirname dmesg dos2unix du echo egrep env expand
 expr fallocate false fgrep file find flock fmt free freeramdisk fsfreeze
@@ -79,7 +117,8 @@
 
 toolbox: getevent getprop setprop start stop
 
-toybox (0.8.4-ish): **[** acpi base64 basename **blkdiscard** blkid blockdev cal cat chattr chcon
+toybox ([0.8.4](http://landley.net/toybox/#24-10-2020)-ish):
+**[** acpi base64 basename **blkdiscard** blkid blockdev cal cat chattr chcon
 chgrp chmod chown chroot chrt cksum clear cmp comm cp cpio cut date
 dd devmem df diff dirname dmesg dos2unix du echo egrep env expand
 expr fallocate false fgrep file find flock fmt free freeramdisk fsfreeze
@@ -112,7 +151,8 @@
 
 toolbox: getevent getprop setprop start stop
 
-toybox (0.8.3-ish): acpi base64 basename blkid blockdev cal cat chattr chcon chgrp chmod
+toybox ([0.8.3](http://landley.net/toybox/#11-05-2020)-ish):
+acpi base64 basename blkid blockdev cal cat chattr chcon chgrp chmod
 chown chroot chrt cksum clear cmp comm cp cpio cut date dd **devmem**
 df diff dirname dmesg dos2unix du echo egrep env expand expr fallocate
 false fgrep file find flock fmt free freeramdisk fsfreeze **fsync** getconf
@@ -143,7 +183,8 @@
 
 toolbox: getevent getprop
 
-toybox (0.8.0-ish): acpi base64 basename **bc** **blkid** blockdev cal cat **chattr** chcon chgrp
+toybox ([0.8.0](http://landley.net/toybox/#08-02-2019)-ish):
+acpi base64 basename **bc** **blkid** blockdev cal cat **chattr** chcon chgrp
 chmod chown chroot chrt cksum clear cmp comm cp cpio cut date dd df
 diff dirname dmesg dos2unix du echo **egrep** env expand expr fallocate
 false **fgrep** file find flock fmt free **freeramdisk** **fsfreeze** **getconf**
@@ -174,7 +215,8 @@
 
 toolbox: getevent getprop newfs\_msdos
 
-toybox (0.7.6-ish): acpi base64 basename blockdev cal cat chcon chgrp chmod chown
+toybox ([0.7.6](http://landley.net/toybox/#24-02-2018)-ish):
+acpi base64 basename blockdev cal cat chcon chgrp chmod chown
 chroot chrt cksum clear cmp comm cp cpio cut date df diff dirname dmesg
 dos2unix du echo env expand expr fallocate false file find flock **fmt** free
 getenforce groups gunzip gzip head hostname hwclock id ifconfig inotifyd
@@ -198,7 +240,8 @@
 
 toolbox: getevent newfs\_msdos
 
-toybox (0.7.3-ish): acpi base64 basename blockdev cal cat chcon chgrp chmod chown
+toybox ([0.7.3](http://landley.net/toybox/#21-02-2017)-ish):
+acpi base64 basename blockdev cal cat chcon chgrp chmod chown
 chroot chrt cksum clear cmp comm cp cpio cut date df **diff** dirname dmesg
 dos2unix du echo env expand expr fallocate false **file** find flock free
 getenforce getprop groups **gunzip** **gzip** head hostname hwclock id ifconfig
@@ -221,7 +264,8 @@
 toolbox: getevent iftop ioctl log nandread newfs\_msdos ps prlimit
 sendevent start stop top
 
-toybox (0.7.0-ish): acpi **base64** basename blockdev bzcat cal cat chcon chgrp chmod
+toybox ([0.7.0](http://landley.net/toybox/#02-02-2016)-ish):
+acpi **base64** basename blockdev bzcat cal cat chcon chgrp chmod
 chown chroot cksum clear comm cmp cp cpio cut date **df** dirname dmesg
 dos2unix **du** echo env expand expr fallocate false find **flock** free
 getenforce getprop groups head hostname hwclock id ifconfig inotifyd
@@ -242,7 +286,8 @@
 toolbox: df getevent iftop ioctl ionice log ls lsof mount nandread
 newfs\_msdos ps prlimit renice sendevent start stop top uptime watchprops
 
-toybox (0.5.2-ish): acpi basename blockdev bzcat cal cat chcon chgrp chmod chown
+toybox ([0.5.2](http://landley.net/toybox/#25-02-2015)-ish):
+acpi basename blockdev bzcat cal cat chcon chgrp chmod chown
 chroot cksum clear comm cmp cp cpio cut date dirname dmesg dos2unix echo
 env expand expr fallocate false find free getenforce getprop groups
 head hostname hwclock id ifconfig inotifyd insmod kill load\_policy ln
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/trusty/gatekeeper/Android.bp b/trusty/gatekeeper/Android.bp
index 81f012f..0b43754 100644
--- a/trusty/gatekeeper/Android.bp
+++ b/trusty/gatekeeper/Android.bp
@@ -24,11 +24,10 @@
 }
 
 cc_binary {
-    name: "android.hardware.gatekeeper@1.0-service.trusty",
-    defaults: ["hidl_defaults"],
+    name: "android.hardware.gatekeeper-service.trusty",
     vendor: true,
     relative_install_path: "hw",
-    init_rc: ["android.hardware.gatekeeper@1.0-service.trusty.rc"],
+    init_rc: ["android.hardware.gatekeeper-service.trusty.rc"],
 
     srcs: [
         "service.cpp",
@@ -42,16 +41,21 @@
         "-Werror",
     ],
 
+    static_libs: [
+        "libgflags",
+    ],
+
     shared_libs: [
-        "android.hardware.gatekeeper@1.0",
+        "android.hardware.gatekeeper-V1-ndk",
         "libbase",
-        "libhidlbase",
+        "libbinder_ndk",
         "libgatekeeper",
+        "libhardware",
         "libutils",
         "liblog",
         "libcutils",
         "libtrusty",
     ],
 
-    vintf_fragments: ["android.hardware.gatekeeper@1.0-service.trusty.xml"],
+    vintf_fragments: ["android.hardware.gatekeeper-service.trusty.xml"],
 }
diff --git a/trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.rc b/trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.rc
new file mode 100644
index 0000000..66ecbd1
--- /dev/null
+++ b/trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.rc
@@ -0,0 +1,4 @@
+service vendor.gatekeeper_default /vendor/bin/hw/android.hardware.gatekeeper-service.trusty
+    class hal
+    user system
+    group system
diff --git a/trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.xml b/trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.xml
similarity index 60%
rename from trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.xml
rename to trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.xml
index 19714a8..c35421e 100644
--- a/trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.xml
+++ b/trusty/gatekeeper/android.hardware.gatekeeper-service.trusty.xml
@@ -1,10 +1,9 @@
 <manifest version="1.0" type="device">
-    <hal format="hidl">
+    <hal format="aidl">
         <name>android.hardware.gatekeeper</name>
-        <transport>hwbinder</transport>
-        <version>1.0</version>
+        <version>1</version>
         <interface>
-        <name>IGatekeeper</name>
+            <name>IGatekeeper</name>
             <instance>default</instance>
         </interface>
     </hal>
diff --git a/trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.rc b/trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.rc
deleted file mode 100644
index 5413a6c..0000000
--- a/trusty/gatekeeper/android.hardware.gatekeeper@1.0-service.trusty.rc
+++ /dev/null
@@ -1,4 +0,0 @@
-service vendor.gatekeeper-1-0 /vendor/bin/hw/android.hardware.gatekeeper@1.0-service.trusty
-    class hal
-    user system
-    group system
diff --git a/trusty/gatekeeper/service.cpp b/trusty/gatekeeper/service.cpp
index c5ee488..d09804f 100644
--- a/trusty/gatekeeper/service.cpp
+++ b/trusty/gatekeeper/service.cpp
@@ -13,27 +13,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#define LOG_TAG "android.hardware.gatekeeper@1.0-service.trusty"
+#define LOG_TAG "android.hardware.gatekeeper-service.trusty"
 
 #include <android-base/logging.h>
-#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
-
-#include <hidl/LegacySupport.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
 
 #include "trusty_gatekeeper.h"
 
-// Generated HIDL files
-using android::hardware::gatekeeper::V1_0::IGatekeeper;
-using gatekeeper::TrustyGateKeeperDevice;
+using aidl::android::hardware::gatekeeper::TrustyGateKeeperDevice;
 
 int main() {
-    ::android::hardware::configureRpcThreadpool(1, true /* willJoinThreadpool */);
-    android::sp<TrustyGateKeeperDevice> gatekeeper(new TrustyGateKeeperDevice());
-    auto status = gatekeeper->registerAsService();
-    if (status != android::OK) {
-        LOG(FATAL) << "Could not register service for Gatekeeper 1.0 (trusty) (" << status << ")";
-    }
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
 
-    android::hardware::joinRpcThreadpool();
+    std::shared_ptr<TrustyGateKeeperDevice> gatekeeper =
+            ndk::SharedRefBase::make<TrustyGateKeeperDevice>();
+
+    const std::string instance = std::string() + TrustyGateKeeperDevice::descriptor + "/default";
+    binder_status_t status =
+            AServiceManager_addService(gatekeeper->asBinder().get(), instance.c_str());
+    CHECK_EQ(status, STATUS_OK);
+
+    ABinderProcess_joinThreadPool();
+
     return -1;  // Should never get here.
 }
diff --git a/trusty/gatekeeper/trusty_gatekeeper.cpp b/trusty/gatekeeper/trusty_gatekeeper.cpp
index ec4f81b..d0647df 100644
--- a/trusty/gatekeeper/trusty_gatekeeper.cpp
+++ b/trusty/gatekeeper/trusty_gatekeeper.cpp
@@ -16,28 +16,26 @@
 
 #define LOG_TAG "TrustyGateKeeper"
 
-#include <android-base/logging.h>
+#include <endian.h>
 #include <limits>
 
+#include <android-base/logging.h>
+#include <gatekeeper/password_handle.h>
+#include <hardware/hw_auth_token.h>
+
+#include "gatekeeper_ipc.h"
 #include "trusty_gatekeeper.h"
 #include "trusty_gatekeeper_ipc.h"
-#include "gatekeeper_ipc.h"
 
-using ::android::hardware::hidl_vec;
-using ::android::hardware::Return;
-using ::android::hardware::gatekeeper::V1_0::GatekeeperStatusCode;
-using ::gatekeeper::EnrollRequest;
-using ::gatekeeper::EnrollResponse;
+namespace aidl::android::hardware::gatekeeper {
+
 using ::gatekeeper::ERROR_INVALID;
-using ::gatekeeper::ERROR_MEMORY_ALLOCATION_FAILED;
 using ::gatekeeper::ERROR_NONE;
 using ::gatekeeper::ERROR_RETRY;
 using ::gatekeeper::SizedBuffer;
 using ::gatekeeper::VerifyRequest;
 using ::gatekeeper::VerifyResponse;
 
-namespace gatekeeper {
-
 constexpr const uint32_t SEND_BUF_SIZE = 8192;
 constexpr const uint32_t RECV_BUF_SIZE = 8192;
 
@@ -54,89 +52,101 @@
     trusty_gatekeeper_disconnect();
 }
 
-SizedBuffer hidl_vec2sized_buffer(const hidl_vec<uint8_t>& vec) {
+SizedBuffer vec2sized_buffer(const std::vector<uint8_t>& vec) {
     if (vec.size() == 0 || vec.size() > std::numeric_limits<uint32_t>::max()) return {};
     auto buffer = new uint8_t[vec.size()];
     std::copy(vec.begin(), vec.end(), buffer);
     return {buffer, static_cast<uint32_t>(vec.size())};
 }
 
-Return<void> TrustyGateKeeperDevice::enroll(uint32_t uid,
-                                            const hidl_vec<uint8_t>& currentPasswordHandle,
-                                            const hidl_vec<uint8_t>& currentPassword,
-                                            const hidl_vec<uint8_t>& desiredPassword,
-                                            enroll_cb _hidl_cb) {
+void sizedBuffer2AidlHWToken(SizedBuffer& buffer,
+                             android::hardware::security::keymint::HardwareAuthToken* aidlToken) {
+    const hw_auth_token_t* authToken =
+            reinterpret_cast<const hw_auth_token_t*>(buffer.Data<uint8_t>());
+    aidlToken->challenge = authToken->challenge;
+    aidlToken->userId = authToken->user_id;
+    aidlToken->authenticatorId = authToken->authenticator_id;
+    // these are in network order: translate to host
+    aidlToken->authenticatorType =
+            static_cast<android::hardware::security::keymint::HardwareAuthenticatorType>(
+                    be32toh(authToken->authenticator_type));
+    aidlToken->timestamp.milliSeconds = be64toh(authToken->timestamp);
+    aidlToken->mac.insert(aidlToken->mac.begin(), std::begin(authToken->hmac),
+                          std::end(authToken->hmac));
+}
+
+::ndk::ScopedAStatus TrustyGateKeeperDevice::enroll(
+        int32_t uid, const std::vector<uint8_t>& currentPasswordHandle,
+        const std::vector<uint8_t>& currentPassword, const std::vector<uint8_t>& desiredPassword,
+        GatekeeperEnrollResponse* rsp) {
     if (error_ != 0) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
-        return {};
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     }
 
     if (desiredPassword.size() == 0) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
-        return {};
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     }
 
-    EnrollRequest request(uid, hidl_vec2sized_buffer(currentPasswordHandle),
-                          hidl_vec2sized_buffer(desiredPassword),
-                          hidl_vec2sized_buffer(currentPassword));
+    EnrollRequest request(uid, vec2sized_buffer(currentPasswordHandle),
+                          vec2sized_buffer(desiredPassword), vec2sized_buffer(currentPassword));
     EnrollResponse response;
     auto error = Send(request, &response);
     if (error != ERROR_NONE) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     } else if (response.error == ERROR_RETRY) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_RETRY_TIMEOUT, response.retry_timeout, {}});
+        *rsp = {ERROR_RETRY_TIMEOUT, static_cast<int32_t>(response.retry_timeout), 0, {}};
+        return ndk::ScopedAStatus::ok();
     } else if (response.error != ERROR_NONE) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     } else {
-        hidl_vec<uint8_t> new_handle(response.enrolled_password_handle.Data<uint8_t>(),
-                                     response.enrolled_password_handle.Data<uint8_t>() +
-                                             response.enrolled_password_handle.size());
-        _hidl_cb({GatekeeperStatusCode::STATUS_OK, response.retry_timeout, new_handle});
+        const ::gatekeeper::password_handle_t* password_handle =
+                response.enrolled_password_handle.Data<::gatekeeper::password_handle_t>();
+        *rsp = {STATUS_OK,
+                0,
+                static_cast<int64_t>(password_handle->user_id),
+                {response.enrolled_password_handle.Data<uint8_t>(),
+                 (response.enrolled_password_handle.Data<uint8_t>() +
+                  response.enrolled_password_handle.size())}};
     }
-    return {};
+    return ndk::ScopedAStatus::ok();
 }
 
-Return<void> TrustyGateKeeperDevice::verify(
-        uint32_t uid, uint64_t challenge,
-        const ::android::hardware::hidl_vec<uint8_t>& enrolledPasswordHandle,
-        const ::android::hardware::hidl_vec<uint8_t>& providedPassword, verify_cb _hidl_cb) {
+::ndk::ScopedAStatus TrustyGateKeeperDevice::verify(
+        int32_t uid, int64_t challenge, const std::vector<uint8_t>& enrolledPasswordHandle,
+        const std::vector<uint8_t>& providedPassword, GatekeeperVerifyResponse* rsp) {
     if (error_ != 0) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
-        return {};
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     }
 
     if (enrolledPasswordHandle.size() == 0) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
-        return {};
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     }
 
-    VerifyRequest request(uid, challenge, hidl_vec2sized_buffer(enrolledPasswordHandle),
-                          hidl_vec2sized_buffer(providedPassword));
+    VerifyRequest request(uid, challenge, vec2sized_buffer(enrolledPasswordHandle),
+                          vec2sized_buffer(providedPassword));
     VerifyResponse response;
 
     auto error = Send(request, &response);
     if (error != ERROR_NONE) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     } else if (response.error == ERROR_RETRY) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_RETRY_TIMEOUT, response.retry_timeout, {}});
+        *rsp = {ERROR_RETRY_TIMEOUT, static_cast<int32_t>(response.retry_timeout), {}};
+        return ndk::ScopedAStatus::ok();
     } else if (response.error != ERROR_NONE) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     } else {
-        hidl_vec<uint8_t> auth_token(
-                response.auth_token.Data<uint8_t>(),
-                response.auth_token.Data<uint8_t>() + response.auth_token.size());
-
-        _hidl_cb({response.request_reenroll ? GatekeeperStatusCode::STATUS_REENROLL
-                                            : GatekeeperStatusCode::STATUS_OK,
-                  response.retry_timeout, auth_token});
+        // On Success, return GatekeeperVerifyResponse with Success Status, timeout{0} and
+        // valid HardwareAuthToken.
+        *rsp = {response.request_reenroll ? STATUS_REENROLL : STATUS_OK, 0, {}};
+        // Convert the hw_auth_token_t to HardwareAuthToken in the response.
+        sizedBuffer2AidlHWToken(response.auth_token, &rsp->hardwareAuthToken);
     }
-    return {};
+    return ndk::ScopedAStatus::ok();
 }
 
-Return<void> TrustyGateKeeperDevice::deleteUser(uint32_t uid, deleteUser_cb _hidl_cb) {
+::ndk::ScopedAStatus TrustyGateKeeperDevice::deleteUser(int32_t uid) {
     if (error_ != 0) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
-        return {};
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     }
 
     DeleteUserRequest request(uid);
@@ -144,21 +154,19 @@
     auto error = Send(request, &response);
 
     if (error != ERROR_NONE) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     } else if (response.error == ERROR_NOT_IMPLEMENTED) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_NOT_IMPLEMENTED));
     } else if (response.error != ERROR_NONE) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     } else {
-        _hidl_cb({GatekeeperStatusCode::STATUS_OK, response.retry_timeout, {}});
+        return ndk::ScopedAStatus::ok();
     }
-    return {};
 }
 
-Return<void> TrustyGateKeeperDevice::deleteAllUsers(deleteAllUsers_cb _hidl_cb) {
+::ndk::ScopedAStatus TrustyGateKeeperDevice::deleteAllUsers() {
     if (error_ != 0) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
-        return {};
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     }
 
     DeleteAllUsersRequest request;
@@ -166,16 +174,14 @@
     auto error = Send(request, &response);
 
     if (error != ERROR_NONE) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     } else if (response.error == ERROR_NOT_IMPLEMENTED) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_NOT_IMPLEMENTED));
     } else if (response.error != ERROR_NONE) {
-        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(ERROR_GENERAL_FAILURE));
     } else {
-        _hidl_cb({GatekeeperStatusCode::STATUS_OK, response.retry_timeout, {}});
+        return ndk::ScopedAStatus::ok();
     }
-
-    return {};
 }
 
 gatekeeper_error_t TrustyGateKeeperDevice::Send(uint32_t command, const GateKeeperMessage& request,
@@ -201,4 +207,4 @@
     return response->Deserialize(payload, payload + response_size);
 }
 
-};
+}  // namespace aidl::android::hardware::gatekeeper
diff --git a/trusty/gatekeeper/trusty_gatekeeper.h b/trusty/gatekeeper/trusty_gatekeeper.h
index 420dd7a..5cb5d4b 100644
--- a/trusty/gatekeeper/trusty_gatekeeper.h
+++ b/trusty/gatekeeper/trusty_gatekeeper.h
@@ -17,18 +17,30 @@
 #ifndef TRUSTY_GATEKEEPER_H
 #define TRUSTY_GATEKEEPER_H
 
-#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
-#include <hidl/Status.h>
-
 #include <memory>
 
+#include <aidl/android/hardware/gatekeeper/BnGatekeeper.h>
+
 #include <gatekeeper/gatekeeper_messages.h>
 
 #include "gatekeeper_ipc.h"
 
-namespace gatekeeper {
+namespace aidl::android::hardware::gatekeeper {
 
-class TrustyGateKeeperDevice : public ::android::hardware::gatekeeper::V1_0::IGatekeeper {
+using aidl::android::hardware::gatekeeper::GatekeeperEnrollResponse;
+using aidl::android::hardware::gatekeeper::GatekeeperVerifyResponse;
+using ::gatekeeper::DeleteAllUsersRequest;
+using ::gatekeeper::DeleteAllUsersResponse;
+using ::gatekeeper::DeleteUserRequest;
+using ::gatekeeper::DeleteUserResponse;
+using ::gatekeeper::EnrollRequest;
+using ::gatekeeper::EnrollResponse;
+using ::gatekeeper::gatekeeper_error_t;
+using ::gatekeeper::GateKeeperMessage;
+using ::gatekeeper::VerifyRequest;
+using ::gatekeeper::VerifyResponse;
+
+class TrustyGateKeeperDevice : public BnGatekeeper {
   public:
     explicit TrustyGateKeeperDevice();
     ~TrustyGateKeeperDevice();
@@ -40,11 +52,10 @@
      * Returns: 0 on success or an error code less than 0 on error.
      * On error, enrolled_password_handle will not be allocated.
      */
-    ::android::hardware::Return<void> enroll(
-            uint32_t uid, const ::android::hardware::hidl_vec<uint8_t>& currentPasswordHandle,
-            const ::android::hardware::hidl_vec<uint8_t>& currentPassword,
-            const ::android::hardware::hidl_vec<uint8_t>& desiredPassword,
-            enroll_cb _hidl_cb) override;
+    ::ndk::ScopedAStatus enroll(int32_t uid, const std::vector<uint8_t>& currentPasswordHandle,
+                                const std::vector<uint8_t>& currentPassword,
+                                const std::vector<uint8_t>& desiredPassword,
+                                GatekeeperEnrollResponse* _aidl_return) override;
 
     /**
      * Verifies provided_password matches enrolled_password_handle.
@@ -59,25 +70,24 @@
      * Returns: 0 on success or an error code less than 0 on error
      * On error, verification token will not be allocated
      */
-    ::android::hardware::Return<void> verify(
-            uint32_t uid, uint64_t challenge,
-            const ::android::hardware::hidl_vec<uint8_t>& enrolledPasswordHandle,
-            const ::android::hardware::hidl_vec<uint8_t>& providedPassword,
-            verify_cb _hidl_cb) override;
+    ::ndk::ScopedAStatus verify(int32_t uid, int64_t challenge,
+                                const std::vector<uint8_t>& enrolledPasswordHandle,
+                                const std::vector<uint8_t>& providedPassword,
+                                GatekeeperVerifyResponse* _aidl_return) override;
 
-    ::android::hardware::Return<void> deleteUser(uint32_t uid, deleteUser_cb _hidl_cb) override;
+    ::ndk::ScopedAStatus deleteAllUsers() override;
 
-    ::android::hardware::Return<void> deleteAllUsers(deleteAllUsers_cb _hidl_cb) override;
+    ::ndk::ScopedAStatus deleteUser(int32_t uid) override;
 
   private:
     gatekeeper_error_t Send(uint32_t command, const GateKeeperMessage& request,
                            GateKeeperMessage* response);
 
-    gatekeeper_error_t Send(const EnrollRequest& request, EnrollResponse *response) {
+    gatekeeper_error_t Send(const EnrollRequest& request, EnrollResponse* response) {
         return Send(GK_ENROLL, request, response);
     }
 
-    gatekeeper_error_t Send(const VerifyRequest& request, VerifyResponse *response) {
+    gatekeeper_error_t Send(const VerifyRequest& request, VerifyResponse* response) {
         return Send(GK_VERIFY, request, response);
     }
 
@@ -93,7 +103,6 @@
     int error_;
 };
 
-}  // namespace gatekeeper
+}  // namespace aidl::android::hardware::gatekeeper
 
 #endif
-
diff --git a/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h b/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
index f767d40..09f696b 100644
--- a/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
+++ b/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
@@ -76,6 +76,7 @@
     KM_CLEAR_ATTESTATION_CERT_CHAIN = (0xa000 << KEYMASTER_REQ_SHIFT),
     KM_SET_WRAPPED_ATTESTATION_KEY = (0xb000 << KEYMASTER_REQ_SHIFT),
     KM_SET_ATTESTATION_IDS = (0xc000 << KEYMASTER_REQ_SHIFT),
+    KM_SET_ATTESTATION_IDS_KM3 = (0xc001 << KEYMASTER_REQ_SHIFT),
     KM_CONFIGURE_BOOT_PATCHLEVEL = (0xd000 << KEYMASTER_REQ_SHIFT),
 };
 
diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c
index eb0acb5..81c9881 100644
--- a/trusty/libtrusty/tipc-test/tipc_test.c
+++ b/trusty/libtrusty/tipc-test/tipc_test.c
@@ -596,6 +596,7 @@
         TEST_PASSED = 0,
         TEST_FAILED = 1,
         TEST_MESSAGE = 2,
+        TEST_TEXT = 3,
     };
 
     int fd;
@@ -625,7 +626,7 @@
             break;
         } else if (rx_buf[0] == TEST_FAILED) {
             break;
-        } else if (rx_buf[0] == TEST_MESSAGE) {
+        } else if (rx_buf[0] == TEST_MESSAGE || rx_buf[0] == TEST_TEXT) {
             write(STDOUT_FILENO, rx_buf + 1, ret - 1);
         } else {
             fprintf(stderr, "%s: Bad message header: %d\n", __func__, rx_buf[0]);
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
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index 7b4aa26..5a3a320 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -37,7 +37,7 @@
 
 PRODUCT_PACKAGES += \
 	$(LOCAL_KEYMINT_PRODUCT_PACKAGE) \
-	android.hardware.gatekeeper@1.0-service.trusty \
+	android.hardware.gatekeeper-service.trusty \
 	trusty_apploader \
 	RemoteProvisioner
 
diff --git a/trusty/utils/trusty-ut-ctrl/ut-ctrl.c b/trusty/utils/trusty-ut-ctrl/ut-ctrl.c
index 9e72af3..6cc6670 100644
--- a/trusty/utils/trusty-ut-ctrl/ut-ctrl.c
+++ b/trusty/utils/trusty-ut-ctrl/ut-ctrl.c
@@ -94,6 +94,7 @@
     TEST_PASSED = 0,
     TEST_FAILED = 1,
     TEST_MESSAGE = 2,
+    TEST_TEXT = 3,
 };
 
 static int run_trusty_unitest(const char* utapp) {
@@ -121,7 +122,7 @@
             break;
         } else if (rx_buf[0] == TEST_FAILED) {
             break;
-        } else if (rx_buf[0] == TEST_MESSAGE) {
+        } else if (rx_buf[0] == TEST_MESSAGE || rx_buf[0] == TEST_TEXT) {
             write(STDOUT_FILENO, rx_buf + 1, rc - 1);
         } else {
             fprintf(stderr, "%s: Bad message header: %d\n", __func__, rx_buf[0]);