Merge "Track API change."
diff --git a/METADATA b/METADATA
index a1ce3c6..68bf6f8 100644
--- a/METADATA
+++ b/METADATA
@@ -2,8 +2,8 @@
 #     CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE
 #     DEPENDING ON IT IN YOUR PROJECT. ***
 third_party {
-  # would be NOTICE save for OFL in:
-  #   fonts/README
-  #   fonts/OFL.txt
+  license_note: "would be NOTICE save for OFL in:\n"
+  "   fonts/README\n"
+  "   fonts/OFL.txt\n"
   license_type: BY_EXCEPTION_ONLY
 }
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index b70d54e..1ea56cd 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -304,6 +304,16 @@
                                        offsetof(misc_system_space_layout, virtual_ab_message), err);
 }
 
+bool ReadMiscMemtagMessage(misc_memtag_message* message, std::string* err) {
+  return ReadMiscPartitionSystemSpace(message, sizeof(*message),
+                                      offsetof(misc_system_space_layout, memtag_message), err);
+}
+
+bool WriteMiscMemtagMessage(const misc_memtag_message& message, std::string* err) {
+  return WriteMiscPartitionSystemSpace(&message, sizeof(message),
+                                       offsetof(misc_system_space_layout, memtag_message), err);
+}
+
 extern "C" bool write_reboot_bootloader(void) {
   std::string err;
   return write_reboot_bootloader(&err);
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index e4cf09b..d58158d 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -93,18 +93,35 @@
   uint8_t reserved[57];
 } __attribute__((packed));
 
+struct misc_memtag_message {
+  uint8_t version;
+  uint32_t magic; // magic string for treble compat
+  uint32_t memtag_mode;
+  uint8_t reserved[55];
+} __attribute__((packed));
+
 #define MISC_VIRTUAL_AB_MESSAGE_VERSION 2
 #define MISC_VIRTUAL_AB_MAGIC_HEADER 0x56740AB0
 
+#define MISC_MEMTAG_MESSAGE_VERSION 1
+#define MISC_MEMTAG_MAGIC_HEADER 0x5afefe5a
+#define MISC_MEMTAG_MODE_MEMTAG 0x1
+#define MISC_MEMTAG_MODE_MEMTAG_ONCE 0x2
+#define MISC_MEMTAG_MODE_MEMTAG_KERNEL 0x4
+#define MISC_MEMTAG_MODE_MEMTAG_KERNEL_ONCE 0x8
+
 #if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus)
 static_assert(sizeof(struct misc_virtual_ab_message) == 64,
               "struct misc_virtual_ab_message has wrong size");
+static_assert(sizeof(struct misc_memtag_message) == 64,
+              "struct misc_memtag_message has wrong size");
 #endif
 
 // This struct is not meant to be used directly, rather, it is to make
 // computation of offsets easier. New fields must be added to the end.
 struct misc_system_space_layout {
   misc_virtual_ab_message virtual_ab_message;
+  misc_memtag_message memtag_message;
 } __attribute__((packed));
 
 #ifdef __cplusplus
@@ -172,6 +189,9 @@
 bool ReadMiscVirtualAbMessage(misc_virtual_ab_message* message, std::string* err);
 bool WriteMiscVirtualAbMessage(const misc_virtual_ab_message& message, std::string* err);
 
+// Read or write the memtag message from system space in /misc.
+bool ReadMiscMemtagMessage(misc_memtag_message* message, std::string* err);
+bool WriteMiscMemtagMessage(const misc_memtag_message& message, std::string* err);
 #else
 
 #include <stdbool.h>
diff --git a/edify/parser.yy b/edify/parser.yy
index 37bcdd0..5e1e847 100644
--- a/edify/parser.yy
+++ b/edify/parser.yy
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
diff --git a/fuse_sideload/fuse_provider.cpp b/fuse_sideload/fuse_provider.cpp
index 8fa1b5c..2183d08 100644
--- a/fuse_sideload/fuse_provider.cpp
+++ b/fuse_sideload/fuse_provider.cpp
@@ -118,11 +118,16 @@
   }
 
   if (uint64_t tailing_bytes = fetch_size % source_block_size_; tailing_bytes != 0) {
-    // Calculate the offset to last partial block.
+    // Calculate the offset to last partial block. Two possibilities as below:
+    // 1: fetch_size < source_block_size_, the read_ranges is a blank range_set.
+    //    Get the last block num through GetBlockNumber() of the offset block.
+    // 2: fetch_size >= source_block_size_, the last block num is already stored
+    //    in read-ranges by GetSubRanges() above.
     uint64_t tailing_offset =
         read_ranges.value()
             ? static_cast<uint64_t>((read_ranges->cend() - 1)->second) * source_block_size_
-            : static_cast<uint64_t>(start_block) * source_block_size_;
+            : static_cast<uint64_t>(ranges_.GetBlockNumber(offset / source_block_size_)) *
+                  source_block_size_;
     if (!android::base::ReadFullyAtOffset(fd_, next_out, tailing_bytes, tailing_offset)) {
       PLOG(ERROR) << "Failed to read tailing " << tailing_bytes << " bytes at offset "
                   << tailing_offset;
diff --git a/install/adb_install.cpp b/install/adb_install.cpp
index ee79a32..b12e529 100644
--- a/install/adb_install.cpp
+++ b/install/adb_install.cpp
@@ -90,11 +90,12 @@
 
 // Installs the package from FUSE. Returns the installation result and whether it should continue
 // waiting for new commands.
-static auto AdbInstallPackageHandler(RecoveryUI* ui, InstallResult* result) {
+static auto AdbInstallPackageHandler(Device* device, InstallResult* result) {
   // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long
   // because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME
   // will start to exist once the host connects and starts serving a package. Poll for its
   // appearance. (Note that inotify doesn't work with FUSE.)
+  auto ui = device->GetUI();
   constexpr int ADB_INSTALL_TIMEOUT = 15;
   bool should_continue = true;
   *result = INSTALL_ERROR;
@@ -114,7 +115,7 @@
     auto package =
         Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME,
                                    std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
-    *result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0, ui);
+    *result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0, device);
     break;
   }
 
@@ -348,7 +349,7 @@
 
   InstallResult install_result = INSTALL_ERROR;
   std::map<MinadbdCommand, CommandFunction> command_map{
-    { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) },
+    { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, device, &install_result) },
     { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid,
                                                 &install_result, reboot_action) },
     { MinadbdCommand::kRebootBootloader,
@@ -368,7 +369,7 @@
         "to the device with \"adb sideload <filename>\"...\n");
   } else {
     command_map.emplace(MinadbdCommand::kWipeData, [&device]() {
-      bool result = WipeData(device, false);
+      bool result = WipeData(device);
       return std::make_pair(result, true);
     });
     command_map.emplace(MinadbdCommand::kNoOp, []() { return std::make_pair(true, true); });
diff --git a/install/fuse_install.cpp b/install/fuse_install.cpp
index 143b5d3..197e1de 100644
--- a/install/fuse_install.cpp
+++ b/install/fuse_install.cpp
@@ -146,10 +146,11 @@
   return run_fuse_sideload(std::move(fuse_data_provider)) == 0;
 }
 
-InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui) {
+InstallResult InstallWithFuseFromPath(std::string_view path, Device* device) {
   // We used to use fuse in a thread as opposed to a process. Since accessing
   // through fuse involves going from kernel to userspace to kernel, it leads
   // to deadlock when a page fault occurs. (Bug: 26313124)
+  auto ui = device->GetUI();
   pid_t child;
   if ((child = fork()) == 0) {
     bool status = StartInstallPackageFuse(path);
@@ -183,8 +184,8 @@
     auto package =
         Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME,
                                    std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
-    result =
-        InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0 /* retry_count */, ui);
+    result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0 /* retry_count */,
+                            device);
     break;
   }
 
@@ -226,7 +227,7 @@
   ui->Print("\n-- Install %s ...\n", path.c_str());
   SetSdcardUpdateBootloaderMessage();
 
-  auto result = InstallWithFuseFromPath(path, ui);
+  auto result = InstallWithFuseFromPath(path, device);
   ensure_path_unmounted(SDCARD_ROOT);
   return result;
 }
diff --git a/install/include/install/fuse_install.h b/install/include/install/fuse_install.h
index 63b116a..29c283f 100644
--- a/install/include/install/fuse_install.h
+++ b/install/include/install/fuse_install.h
@@ -25,6 +25,6 @@
 // Starts FUSE with the package from |path| as the data source. And installs the package from
 // |FUSE_SIDELOAD_HOST_PATHNAME|. The |path| can point to the location of a package zip file or a
 // block map file with the prefix '@'; e.g. /sdcard/package.zip, @/cache/recovery/block.map.
-InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui);
+InstallResult InstallWithFuseFromPath(std::string_view path, Device* device);
 
 InstallResult ApplyFromSdcard(Device* device);
diff --git a/install/include/install/install.h b/install/include/install/install.h
index 704841f..0f5102f 100644
--- a/install/include/install/install.h
+++ b/install/include/install/install.h
@@ -25,6 +25,7 @@
 #include <ziparchive/zip_archive.h>
 
 #include "otautil/package.h"
+#include "recovery_ui/device.h"
 #include "recovery_ui/ui.h"
 
 enum InstallResult {
@@ -49,7 +50,8 @@
 // cache partition after a successful installation if |should_wipe_cache| is true or an updater
 // command asks to wipe the cache.
 InstallResult InstallPackage(Package* package, const std::string_view package_id,
-                             bool should_wipe_cache, int retry_count, RecoveryUI* ui);
+                             bool should_wipe_cache, int retry_count,
+                             Device* ui);
 
 // Verifies the package by ota keys. Returns true if the package is verified successfully,
 // otherwise returns false.
diff --git a/install/include/install/wipe_data.h b/install/include/install/wipe_data.h
index b34891f..42cad87 100644
--- a/install/include/install/wipe_data.h
+++ b/install/include/install/wipe_data.h
@@ -27,4 +27,4 @@
 bool WipeCache(RecoveryUI* ui, const std::function<bool()>& confirm);
 
 // Returns true on success.
-bool WipeData(Device* device, bool convert_fbe);
+bool WipeData(Device* device);
diff --git a/install/install.cpp b/install/install.cpp
index bb8c3b8..83f3cad 100644
--- a/install/install.cpp
+++ b/install/install.cpp
@@ -235,30 +235,41 @@
   return true;
 }
 
-bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
-                           std::vector<std::string>* cmd) {
-  CHECK(cmd != nullptr);
-
+static std::string ExtractPayloadProperties(ZipArchiveHandle zip) {
   // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset
   // in the zip file.
   static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
   ZipEntry64 properties_entry;
   if (FindEntry(zip, AB_OTA_PAYLOAD_PROPERTIES, &properties_entry) != 0) {
     LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES;
-    return false;
+    return {};
   }
   auto properties_entry_length = properties_entry.uncompressed_length;
   if (properties_entry_length > std::numeric_limits<size_t>::max()) {
     LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES
                << " because's uncompressed size exceeds size of address space. "
                << properties_entry_length;
-    return false;
+    return {};
   }
-  std::vector<uint8_t> payload_properties(properties_entry_length);
+  std::string payload_properties(properties_entry_length, '\0');
   int32_t err =
-      ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length);
+      ExtractToMemory(zip, &properties_entry, reinterpret_cast<uint8_t*>(payload_properties.data()),
+                      properties_entry_length);
   if (err != 0) {
     LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err);
+    return {};
+  }
+  return payload_properties;
+}
+
+bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+                           std::vector<std::string>* cmd) {
+  CHECK(cmd != nullptr);
+
+  // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset
+  // in the zip file.
+  const auto payload_properties = ExtractPayloadProperties(zip);
+  if (payload_properties.empty()) {
     return false;
   }
 
@@ -332,10 +343,20 @@
   }
 }
 
+static bool PerformPowerwashIfRequired(ZipArchiveHandle zip, Device *device) {
+  const auto payload_properties = ExtractPayloadProperties(zip);
+  if (payload_properties.find("POWERWASH=1") != std::string::npos) {
+    LOG(INFO) << "Payload properties has POWERWASH=1, wiping userdata...";
+    return WipeData(device);
+  }
+  return true;
+}
+
 // If the package contains an update binary, extract it and run it.
 static InstallResult TryUpdateBinary(Package* package, bool* wipe_cache,
                                      std::vector<std::string>* log_buffer, int retry_count,
-                                     int* max_temperature, RecoveryUI* ui) {
+                                     int* max_temperature, Device* device) {
+  auto ui = device->GetUI();
   std::map<std::string, std::string> metadata;
   auto zip = package->GetZipArchiveHandle();
   if (!ReadMetadataFromPackage(zip, &metadata)) {
@@ -530,13 +551,15 @@
   } else {
     LOG(FATAL) << "Invalid status code " << status;
   }
+  PerformPowerwashIfRequired(zip, device);
 
   return INSTALL_SUCCESS;
 }
 
 static InstallResult VerifyAndInstallPackage(Package* package, bool* wipe_cache,
                                              std::vector<std::string>* log_buffer, int retry_count,
-                                             int* max_temperature, RecoveryUI* ui) {
+                                             int* max_temperature, Device* device) {
+  auto ui = device->GetUI();
   ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
   // Give verification half the progress bar...
   ui->SetProgressType(RecoveryUI::DETERMINATE);
@@ -554,7 +577,8 @@
     ui->Print("Retry attempt: %d\n", retry_count);
   }
   ui->SetEnableReboot(false);
-  auto result = TryUpdateBinary(package, wipe_cache, log_buffer, retry_count, max_temperature, ui);
+  auto result =
+      TryUpdateBinary(package, wipe_cache, log_buffer, retry_count, max_temperature, device);
   ui->SetEnableReboot(true);
   ui->Print("\n");
 
@@ -562,7 +586,8 @@
 }
 
 InstallResult InstallPackage(Package* package, const std::string_view package_id,
-                             bool should_wipe_cache, int retry_count, RecoveryUI* ui) {
+                             bool should_wipe_cache, int retry_count, Device* device) {
+  auto ui = device->GetUI();
   auto start = std::chrono::system_clock::now();
 
   int start_temperature = GetMaxValueFromThermalZone();
@@ -584,7 +609,7 @@
   } else {
     bool updater_wipe_cache = false;
     result = VerifyAndInstallPackage(package, &updater_wipe_cache, &log_buffer, retry_count,
-                                     &max_temperature, ui);
+                                     &max_temperature, device);
     should_wipe_cache = should_wipe_cache || updater_wipe_cache;
   }
 
diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp
index 4eecf72..024c1e1 100644
--- a/install/wipe_data.cpp
+++ b/install/wipe_data.cpp
@@ -16,9 +16,7 @@
 
 #include "install/wipe_data.h"
 
-#include <stdio.h>
 #include <string.h>
-#include <sys/stat.h>
 
 #include <functional>
 #include <vector>
@@ -37,9 +35,8 @@
 constexpr const char* DATA_ROOT = "/data";
 constexpr const char* METADATA_ROOT = "/metadata";
 
-static bool EraseVolume(const char* volume, RecoveryUI* ui, bool convert_fbe) {
+static bool EraseVolume(const char* volume, RecoveryUI* ui) {
   bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
-  bool is_data = (strcmp(volume, DATA_ROOT) == 0);
 
   std::vector<saved_log_file> log_files;
   if (is_cache) {
@@ -52,28 +49,7 @@
 
   ensure_path_unmounted(volume);
 
-  int result;
-  if (is_data && convert_fbe) {
-    constexpr const char* CONVERT_FBE_DIR = "/tmp/convert_fbe";
-    constexpr const char* CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe";
-    // Create convert_fbe breadcrumb file to signal init to convert to file based encryption, not
-    // full disk encryption.
-    if (mkdir(CONVERT_FBE_DIR, 0700) != 0) {
-      PLOG(ERROR) << "Failed to mkdir " << CONVERT_FBE_DIR;
-      return false;
-    }
-    FILE* f = fopen(CONVERT_FBE_FILE, "wbe");
-    if (!f) {
-      PLOG(ERROR) << "Failed to convert to file encryption";
-      return false;
-    }
-    fclose(f);
-    result = format_volume(volume, CONVERT_FBE_DIR);
-    remove(CONVERT_FBE_FILE);
-    rmdir(CONVERT_FBE_DIR);
-  } else {
-    result = format_volume(volume);
-  }
+  int result = format_volume(volume);
 
   if (is_cache) {
     RestoreLogFilesAfterFormat(log_files);
@@ -97,12 +73,12 @@
   ui->SetBackground(RecoveryUI::ERASING);
   ui->SetProgressType(RecoveryUI::INDETERMINATE);
 
-  bool success = EraseVolume("/cache", ui, false);
+  bool success = EraseVolume("/cache", ui);
   ui->Print("Cache wipe %s.\n", success ? "complete" : "failed");
   return success;
 }
 
-bool WipeData(Device* device, bool convert_fbe) {
+bool WipeData(Device* device) {
   RecoveryUI* ui = device->GetUI();
   ui->Print("\n-- Wiping data...\n");
   ui->SetBackground(RecoveryUI::ERASING);
@@ -115,13 +91,13 @@
 
   bool success = device->PreWipeData();
   if (success) {
-    success &= EraseVolume(DATA_ROOT, ui, convert_fbe);
+    success &= EraseVolume(DATA_ROOT, ui);
     bool has_cache = volume_for_mount_point("/cache") != nullptr;
     if (has_cache) {
-      success &= EraseVolume(CACHE_ROOT, ui, false);
+      success &= EraseVolume(CACHE_ROOT, ui);
     }
     if (volume_for_mount_point(METADATA_ROOT) != nullptr) {
-      success &= EraseVolume(METADATA_ROOT, ui, false);
+      success &= EraseVolume(METADATA_ROOT, ui);
     }
   }
   if (success) {
diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp
index 7b82faa..1df342c 100644
--- a/minadbd/minadbd.cpp
+++ b/minadbd/minadbd.cpp
@@ -64,6 +64,7 @@
 
   // We can't require authentication for sideloading. http://b/22025550.
   auth_required = false;
+  socket_access_allowed = false;
 
   init_transport_registration();
   usb_init();
diff --git a/minui/events.cpp b/minui/events.cpp
index 863ac74..b307a49 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -267,6 +267,35 @@
   return -1;
 }
 
+int ev_sync_sw_state(const ev_set_sw_callback& set_sw_cb) {
+  // Use unsigned long to match ioctl's parameter type.
+  unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
+  unsigned long sw_bits[BITS_TO_LONGS(SW_MAX)];  // NOLINT
+
+  for (size_t i = 0; i < g_ev_dev_count; ++i) {
+    memset(ev_bits, 0, sizeof(ev_bits));
+    memset(sw_bits, 0, sizeof(sw_bits));
+
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+      continue;
+    }
+    if (!test_bit(EV_SW, ev_bits)) {
+      continue;
+    }
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGSW(sizeof(sw_bits)), sw_bits) == -1) {
+      continue;
+    }
+
+    for (int code = 0; code <= SW_MAX; code++) {
+      if (test_bit(code, sw_bits)) {
+        set_sw_cb(code, 1);
+      }
+    }
+  }
+
+  return 0;
+}
+
 int ev_sync_key_state(const ev_set_key_callback& set_key_cb) {
   // Use unsigned long to match ioctl's parameter type.
   unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];    // NOLINT
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index f25694a..597f2ce 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -450,6 +450,10 @@
   gr_backend->Blank(blank);
 }
 
+void gr_fb_blank(bool blank, int index) {
+  gr_backend->Blank(blank, static_cast<MinuiBackend::DrmConnector>(index));
+}
+
 void gr_rotate(GRRotation rot) {
   rotation = rot;
 }
diff --git a/minui/graphics.h b/minui/graphics.h
index 3c45a40..5408c93 100644
--- a/minui/graphics.h
+++ b/minui/graphics.h
@@ -21,6 +21,12 @@
 
 class MinuiBackend {
  public:
+  enum DrmConnector {
+    DRM_MAIN = 0,
+    DRM_SEC,
+    DRM_MAX,
+  };
+
   // Initializes the backend and returns a GRSurface* to draw into.
   virtual GRSurface* Init() = 0;
 
@@ -28,9 +34,12 @@
   // be displayed, and returns a new drawing surface.
   virtual GRSurface* Flip() = 0;
 
-  // Blank (or unblank) the screen.
+  // Blank (or unblank) the default screen.
   virtual void Blank(bool) = 0;
 
+  // Blank (or unblank) the specific screen.
+  virtual void Blank(bool blank, DrmConnector index) = 0;
+
   // Device cleanup when drawing is done.
   virtual ~MinuiBackend() {};
 };
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
index 9d31ff7..c557022 100644
--- a/minui/graphics_drm.cpp
+++ b/minui/graphics_drm.cpp
@@ -153,22 +153,50 @@
 }
 
 bool MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc,
-                                    const std::unique_ptr<GRSurfaceDrm>& surface) {
+                                    const std::unique_ptr<GRSurfaceDrm>& surface,
+                                    uint32_t* connector_id) {
   if (drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0,  // x,y
-                     &main_monitor_connector->connector_id,
-                     1,  // connector_count
-                     &main_monitor_crtc->mode) != 0) {
-    perror("Failed to drmModeSetCrtc");
+                     connector_id, 1,                              // connector_count
+                     &crtc->mode) != 0) {
+    fprintf(stderr, "Failed to drmModeSetCrtc(%d)\n", *connector_id);
     return false;
   }
+
   return true;
 }
 
 void MinuiBackendDrm::Blank(bool blank) {
+  Blank(blank, DRM_MAIN);
+}
+
+void MinuiBackendDrm::Blank(bool blank, DrmConnector index) {
+  const auto* drmInterface = &drm[DRM_MAIN];
+
+  switch (index) {
+    case DRM_MAIN:
+      drmInterface = &drm[DRM_MAIN];
+      break;
+    case DRM_SEC:
+      drmInterface = &drm[DRM_SEC];
+      break;
+    default:
+      fprintf(stderr, "Invalid index: %d\n", index);
+      return;
+  }
+
+  if (!drmInterface->monitor_connector) {
+    fprintf(stderr, "Unsupported. index = %d\n", index);
+    return;
+  }
+
   if (blank) {
-    DrmDisableCrtc(drm_fd, main_monitor_crtc);
+    DrmDisableCrtc(drm_fd, drmInterface->monitor_crtc);
   } else {
-    DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]);
+    DrmEnableCrtc(drm_fd, drmInterface->monitor_crtc,
+                  drmInterface->GRSurfaceDrms[drmInterface->current_buffer],
+                  &drmInterface->monitor_connector->connector_id);
+
+    active_display = index;
   }
 }
 
@@ -210,18 +238,21 @@
   return nullptr;
 }
 
-static drmModeConnector* find_used_connector_by_type(int fd, drmModeRes* resources, unsigned type) {
+std::vector<drmModeConnector*> find_used_connector_by_type(int fd, drmModeRes* resources,
+                                                           unsigned type) {
+  std::vector<drmModeConnector*> drmConnectors;
   for (int i = 0; i < resources->count_connectors; i++) {
     drmModeConnector* connector = drmModeGetConnector(fd, resources->connectors[i]);
     if (connector) {
       if ((connector->connector_type == type) && (connector->connection == DRM_MODE_CONNECTED) &&
           (connector->count_modes > 0)) {
-        return connector;
+        drmConnectors.push_back(connector);
+      } else {
+        drmModeFreeConnector(connector);
       }
-      drmModeFreeConnector(connector);
     }
   }
-  return nullptr;
+  return drmConnectors;
 }
 
 static drmModeConnector* find_first_connected_connector(int fd, drmModeRes* resources) {
@@ -239,8 +270,7 @@
   return nullptr;
 }
 
-drmModeConnector* MinuiBackendDrm::FindMainMonitor(int fd, drmModeRes* resources,
-                                                   uint32_t* mode_index) {
+bool MinuiBackendDrm::FindAndSetMonitor(int fd, drmModeRes* resources) {
   /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */
   static constexpr unsigned kConnectorPriority[] = {
     DRM_MODE_CONNECTOR_LVDS,
@@ -248,37 +278,41 @@
     DRM_MODE_CONNECTOR_DSI,
   };
 
-  drmModeConnector* main_monitor_connector = nullptr;
-  unsigned i = 0;
-  do {
-    main_monitor_connector = find_used_connector_by_type(fd, resources, kConnectorPriority[i]);
-    i++;
-  } while (!main_monitor_connector && i < arraysize(kConnectorPriority));
-
-  /* If we didn't find a connector, grab the first one that is connected. */
-  if (!main_monitor_connector) {
-    main_monitor_connector = find_first_connected_connector(fd, resources);
-  }
-
-  /* If we still didn't find a connector, give up and return. */
-  if (!main_monitor_connector) return nullptr;
-
-  for (int modes = 0; modes < main_monitor_connector->count_modes; modes++) {
-    printf("Display Mode %d resolution: %d x %d @ %d FPS\n", modes,
-           main_monitor_connector->modes[modes].hdisplay,
-           main_monitor_connector->modes[modes].vdisplay,
-           main_monitor_connector->modes[modes].vrefresh);
-  }
-  *mode_index = 0;
-  for (int modes = 0; modes < main_monitor_connector->count_modes; modes++) {
-    if (main_monitor_connector->modes[modes].type & DRM_MODE_TYPE_PREFERRED) {
-      printf("Choosing display mode #%d\n", modes);
-      *mode_index = modes;
-      break;
+  std::vector<drmModeConnector*> drmConnectors;
+  for (int i = 0; i < arraysize(kConnectorPriority) && drmConnectors.size() < DRM_MAX; i++) {
+    auto connectors = find_used_connector_by_type(fd, resources, kConnectorPriority[i]);
+    for (auto connector : connectors) {
+      drmConnectors.push_back(connector);
+      if (drmConnectors.size() >= DRM_MAX) break;
     }
   }
 
-  return main_monitor_connector;
+  /* If we didn't find a connector, grab the first one that is connected. */
+  if (drmConnectors.empty()) {
+    drmModeConnector* connector = find_first_connected_connector(fd, resources);
+    if (connector) {
+      drmConnectors.push_back(connector);
+    }
+  }
+
+  for (int drm_index = 0; drm_index < drmConnectors.size(); drm_index++) {
+    drm[drm_index].monitor_connector = drmConnectors[drm_index];
+
+    drm[drm_index].selected_mode = 0;
+    for (int modes = 0; modes < drmConnectors[drm_index]->count_modes; modes++) {
+      printf("Display Mode %d resolution: %d x %d @ %d FPS\n", modes,
+             drmConnectors[drm_index]->modes[modes].hdisplay,
+             drmConnectors[drm_index]->modes[modes].vdisplay,
+             drmConnectors[drm_index]->modes[modes].vrefresh);
+      if (drmConnectors[drm_index]->modes[modes].type & DRM_MODE_TYPE_PREFERRED) {
+        printf("Choosing display mode #%d\n", modes);
+        drm[drm_index].selected_mode = modes;
+        break;
+      }
+    }
+  }
+
+  return drmConnectors.size() > 0;
 }
 
 void MinuiBackendDrm::DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc) {
@@ -329,46 +363,49 @@
     return nullptr;
   }
 
-  uint32_t selected_mode;
-  main_monitor_connector = FindMainMonitor(drm_fd, res, &selected_mode);
-  if (!main_monitor_connector) {
-    fprintf(stderr, "Failed to find main_monitor_connector\n");
+  if (!FindAndSetMonitor(drm_fd, res)) {
+    fprintf(stderr, "Failed to find main monitor_connector\n");
     drmModeFreeResources(res);
-    close(drm_fd);
     return nullptr;
   }
 
-  main_monitor_crtc = find_crtc_for_connector(drm_fd, res, main_monitor_connector);
-  if (!main_monitor_crtc) {
-    fprintf(stderr, "Failed to find main_monitor_crtc\n");
-    drmModeFreeResources(res);
-    close(drm_fd);
-    return nullptr;
+  for (int i = 0; i < DRM_MAX; i++) {
+    if (drm[i].monitor_connector) {
+      drm[i].monitor_crtc = find_crtc_for_connector(drm_fd, res, drm[i].monitor_connector);
+      if (!drm[i].monitor_crtc) {
+        fprintf(stderr, "Failed to find monitor_crtc, drm index=%d\n", i);
+        drmModeFreeResources(res);
+        return nullptr;
+      }
+
+      drm[i].monitor_crtc->mode = drm[i].monitor_connector->modes[drm[i].selected_mode];
+
+      int width = drm[i].monitor_crtc->mode.hdisplay;
+      int height = drm[i].monitor_crtc->mode.vdisplay;
+
+      drm[i].GRSurfaceDrms[0] = GRSurfaceDrm::Create(drm_fd, width, height);
+      drm[i].GRSurfaceDrms[1] = GRSurfaceDrm::Create(drm_fd, width, height);
+      if (!drm[i].GRSurfaceDrms[0] || !drm[i].GRSurfaceDrms[1]) {
+        fprintf(stderr, "Failed to create GRSurfaceDrm, drm index=%d\n", i);
+        drmModeFreeResources(res);
+        return nullptr;
+      }
+
+      drm[i].current_buffer = 0;
+    }
   }
 
-  DisableNonMainCrtcs(drm_fd, res, main_monitor_crtc);
-
-  main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode];
-
-  int width = main_monitor_crtc->mode.hdisplay;
-  int height = main_monitor_crtc->mode.vdisplay;
+  DisableNonMainCrtcs(drm_fd, res, drm[DRM_MAIN].monitor_crtc);
 
   drmModeFreeResources(res);
 
-  GRSurfaceDrms[0] = GRSurfaceDrm::Create(drm_fd, width, height);
-  GRSurfaceDrms[1] = GRSurfaceDrm::Create(drm_fd, width, height);
-  if (!GRSurfaceDrms[0] || !GRSurfaceDrms[1]) {
-    return nullptr;
-  }
-
-  current_buffer = 0;
-
   // We will likely encounter errors in the backend functions (i.e. Flip) if EnableCrtc fails.
-  if (!DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1])) {
+  if (!DrmEnableCrtc(drm_fd, drm[DRM_MAIN].monitor_crtc, drm[DRM_MAIN].GRSurfaceDrms[1],
+                     &drm[DRM_MAIN].monitor_connector->connector_id)) {
     return nullptr;
   }
 
-  return GRSurfaceDrms[0].get();
+  return drm[DRM_MAIN].GRSurfaceDrms[0].get();
 }
 
 static void page_flip_complete(__unused int fd,
@@ -380,10 +417,19 @@
 }
 
 GRSurface* MinuiBackendDrm::Flip() {
+  GRSurface* surface = NULL;
+  DrmInterface* current_drm = &drm[active_display];
   bool ongoing_flip = true;
-  if (drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, GRSurfaceDrms[current_buffer]->fb_id,
+
+  if (!current_drm->monitor_connector) {
+    fprintf(stderr, "Unsupported. active_display = %d\n", active_display);
+    return nullptr;
+  }
+
+  if (drmModePageFlip(drm_fd, current_drm->monitor_crtc->crtc_id,
+                      current_drm->GRSurfaceDrms[current_drm->current_buffer]->fb_id,
                       DRM_MODE_PAGE_FLIP_EVENT, &ongoing_flip) != 0) {
-    perror("Failed to drmModePageFlip");
+    fprintf(stderr, "Failed to drmModePageFlip, active_display=%d", active_display);
     return nullptr;
   }
 
@@ -409,14 +455,19 @@
     }
   }
 
-  current_buffer = 1 - current_buffer;
-  return GRSurfaceDrms[current_buffer].get();
+  current_drm->current_buffer = 1 - current_drm->current_buffer;
+  surface = current_drm->GRSurfaceDrms[current_drm->current_buffer].get();
+  return surface;
 }
 
 MinuiBackendDrm::~MinuiBackendDrm() {
-  DrmDisableCrtc(drm_fd, main_monitor_crtc);
-  drmModeFreeCrtc(main_monitor_crtc);
-  drmModeFreeConnector(main_monitor_connector);
+  for (int i = 0; i < DRM_MAX; i++) {
+    if (drm[i].monitor_connector) {
+      DrmDisableCrtc(drm_fd, drm[i].monitor_crtc);
+      drmModeFreeCrtc(drm[i].monitor_crtc);
+      drmModeFreeConnector(drm[i].monitor_connector);
+    }
+  }
   close(drm_fd);
   drm_fd = -1;
 }
diff --git a/minui/graphics_drm.h b/minui/graphics_drm.h
index 57ba39b..fe3beaf 100644
--- a/minui/graphics_drm.h
+++ b/minui/graphics_drm.h
@@ -59,16 +59,23 @@
   GRSurface* Init() override;
   GRSurface* Flip() override;
   void Blank(bool) override;
+  void Blank(bool blank, DrmConnector index) override;
 
  private:
   void DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc);
-  bool DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, const std::unique_ptr<GRSurfaceDrm>& surface);
+  bool DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, const std::unique_ptr<GRSurfaceDrm>& surface,
+                     uint32_t* conntcors);
   void DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc);
-  drmModeConnector* FindMainMonitor(int fd, drmModeRes* resources, uint32_t* mode_index);
+  bool FindAndSetMonitor(int fd, drmModeRes* resources);
 
-  std::unique_ptr<GRSurfaceDrm> GRSurfaceDrms[2];
-  int current_buffer{ 0 };
-  drmModeCrtc* main_monitor_crtc{ nullptr };
-  drmModeConnector* main_monitor_connector{ nullptr };
+  struct DrmInterface {
+    std::unique_ptr<GRSurfaceDrm> GRSurfaceDrms[2];
+    int current_buffer{ 0 };
+    drmModeCrtc* monitor_crtc{ nullptr };
+    drmModeConnector* monitor_connector{ nullptr };
+    uint32_t selected_mode{ 0 };
+  } drm[DRM_MAX];
+
   int drm_fd{ -1 };
+  DrmConnector active_display = DRM_MAIN;
 };
diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp
index 0d0fabc..1cb0c0a 100644
--- a/minui/graphics_fbdev.cpp
+++ b/minui/graphics_fbdev.cpp
@@ -43,6 +43,10 @@
   if (ret < 0) perror("ioctl(): blank");
 }
 
+void MinuiBackendFbdev::Blank(bool blank, DrmConnector index) {
+  fprintf(stderr, "Unsupported multiple connectors, blank = %d, index = %d\n", blank, index);
+}
+
 void MinuiBackendFbdev::SetDisplayedFramebuffer(size_t n) {
   if (n > 1 || !double_buffered) return;
 
diff --git a/minui/graphics_fbdev.h b/minui/graphics_fbdev.h
index 596ba74..7e193c4 100644
--- a/minui/graphics_fbdev.h
+++ b/minui/graphics_fbdev.h
@@ -56,6 +56,7 @@
   GRSurface* Init() override;
   GRSurface* Flip() override;
   void Blank(bool) override;
+  void Blank(bool blank, DrmConnector index) override;
 
  private:
   void SetDisplayedFramebuffer(size_t n);
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index 5470457..a2f62f0 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -127,6 +127,7 @@
 
 void gr_flip();
 void gr_fb_blank(bool blank);
+void gr_fb_blank(bool blank, int index);
 
 // Clears entire surface to current color.
 void gr_clear();
@@ -161,6 +162,7 @@
 
 using ev_callback = std::function<int(int fd, uint32_t epevents)>;
 using ev_set_key_callback = std::function<int(int code, int value)>;
+using ev_set_sw_callback = std::function<int(int code, int value)>;
 
 int ev_init(ev_callback input_cb, bool allow_touch_inputs = false);
 void ev_exit();
@@ -168,6 +170,7 @@
 void ev_iterate_available_keys(const std::function<void(int)>& f);
 void ev_iterate_touch_inputs(const std::function<void(int)>& action);
 int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
+int ev_sync_sw_state(const ev_set_sw_callback& set_sw_cb);
 
 // 'timeout' has the same semantics as poll(2).
 //    0 : don't block
diff --git a/otautil/verifier.cpp b/otautil/verifier.cpp
index 8a65566..83ad708 100644
--- a/otautil/verifier.cpp
+++ b/otautil/verifier.cpp
@@ -309,13 +309,15 @@
 }
 
 static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) {
-  void* cookie;
+  void* cookie{};
+
   int32_t iter_status = StartIteration(handle, &cookie, "", "x509.pem");
   if (iter_status != 0) {
     LOG(ERROR) << "Failed to iterate over entries in the certificate zipfile: "
                << ErrorCodeString(iter_status);
     return {};
   }
+  std::unique_ptr<void, decltype(&EndIteration)> cookie_guard(cookie, &EndIteration);
 
   std::vector<Certificate> result;
 
diff --git a/recovery-persist.cpp b/recovery-persist.cpp
index ad101ed..55699b2 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -77,6 +77,10 @@
   }
 }
 
+static bool file_exists(const char* filename) {
+  return access(filename, R_OK) == 0;
+}
+
 static bool rotated = false;
 
 ssize_t logsave(
@@ -141,7 +145,7 @@
     if (has_cache) {
       // Collects and reports the non-a/b update metrics from last_install; and removes the file
       // to avoid duplicate report.
-      if (access(LAST_INSTALL_FILE_IN_CACHE, F_OK) && unlink(LAST_INSTALL_FILE_IN_CACHE) == -1) {
+      if (file_exists(LAST_INSTALL_FILE_IN_CACHE) && unlink(LAST_INSTALL_FILE_IN_CACHE) == -1) {
         PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE_IN_CACHE;
       }
 
@@ -152,9 +156,9 @@
       }
     }
 
-    /* Is there something in pmsg? */
-    if (access(LAST_PMSG_FILE, R_OK)) {
-        return 0;
+    /* Is there something in pmsg? If not, no need to proceed. */
+    if (!file_exists(LAST_PMSG_FILE)) {
+      return 0;
     }
 
     // Take last pmsg file contents and send it off to the logsave
@@ -164,18 +168,18 @@
     // For those device without /cache, the last_install file has been copied to
     // /data/misc/recovery from pmsg. Looks for the sideload history only.
     if (!has_cache) {
-      if (access(LAST_INSTALL_FILE, F_OK) && unlink(LAST_INSTALL_FILE) == -1) {
+      if (file_exists(LAST_INSTALL_FILE) && unlink(LAST_INSTALL_FILE) == -1) {
         PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE;
       }
     }
 
     /* Is there a last console log too? */
     if (rotated) {
-        if (!access(LAST_CONSOLE_FILE, R_OK)) {
-            copy_file(LAST_CONSOLE_FILE, LAST_KMSG_FILE);
-        } else if (!access(ALT_LAST_CONSOLE_FILE, R_OK)) {
-            copy_file(ALT_LAST_CONSOLE_FILE, LAST_KMSG_FILE);
-        }
+      if (file_exists(LAST_CONSOLE_FILE)) {
+        copy_file(LAST_CONSOLE_FILE, LAST_KMSG_FILE);
+      } else if (file_exists(ALT_LAST_CONSOLE_FILE)) {
+        copy_file(ALT_LAST_CONSOLE_FILE, LAST_KMSG_FILE);
+      }
     }
 
     return 0;
diff --git a/recovery.cpp b/recovery.cpp
index 641fe47..4d39019 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -207,8 +207,7 @@
 
     if (ask_to_wipe_data(device)) {
       CHECK(device->GetReason().has_value());
-      bool convert_fbe = device->GetReason().value() == "convert_fbe";
-      if (WipeData(device, convert_fbe)) {
+      if (WipeData(device)) {
         return INSTALL_SUCCESS;
       } else {
         return INSTALL_ERROR;
@@ -437,10 +436,10 @@
         save_current_log = true;
         if (ui->IsTextVisible()) {
           if (ask_to_wipe_data(device)) {
-            WipeData(device, false);
+            WipeData(device);
           }
         } else {
-          WipeData(device, false);
+          WipeData(device);
           return Device::NO_ACTION;
         }
         break;
@@ -752,20 +751,20 @@
         status = INSTALL_ERROR;
       } else if (install_with_fuse || should_use_fuse) {
         LOG(INFO) << "Installing package " << update_package << " with fuse";
-        status = InstallWithFuseFromPath(update_package, ui);
+        status = InstallWithFuseFromPath(update_package, device);
       } else if (auto memory_package = Package::CreateMemoryPackage(
                      update_package,
                      std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
                  memory_package != nullptr) {
         status = InstallPackage(memory_package.get(), update_package, should_wipe_cache,
-                                retry_count, ui);
+                                retry_count, device);
       } else {
         // We may fail to memory map the package on 32 bit builds for packages with 2GiB+ size.
         // In such cases, we will try to install the package with fuse. This is not the default
         // installation method because it introduces a layer of indirection from the kernel space.
         LOG(WARNING) << "Failed to memory map package " << update_package
                      << "; falling back to install with fuse";
-        status = InstallWithFuseFromPath(update_package, ui);
+        status = InstallWithFuseFromPath(update_package, device);
       }
       if (status != INSTALL_SUCCESS) {
         ui->Print("Installation aborted.\n");
@@ -794,8 +793,7 @@
   } else if (should_wipe_data) {
     save_current_log = true;
     CHECK(device->GetReason().has_value());
-    bool convert_fbe = device->GetReason().value() == "convert_fbe";
-    if (!WipeData(device, convert_fbe)) {
+    if (!WipeData(device)) {
       status = INSTALL_ERROR;
     }
   } else if (should_prompt_and_wipe_data) {
diff --git a/recovery_main.cpp b/recovery_main.cpp
index 80cba61..9a358ab 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -73,12 +73,12 @@
   return "orange" == android::base::GetProperty("ro.boot.verifiedbootstate", "");
 }
 
-static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity,
-                     const char* /* tag */, const char* /* file */, unsigned int /* line */,
-                     const char* message) {
-  static constexpr char log_characters[] = "VDIWEF";
+static void UiLogger(android::base::LogId log_buffer_id, android::base::LogSeverity severity,
+                     const char* tag, const char* file, unsigned int line, const char* message) {
+  android::base::KernelLogger(log_buffer_id, severity, tag, file, line, message);
+  static constexpr auto&& log_characters = "VDIWEF";
   if (severity >= android::base::ERROR && ui != nullptr) {
-    ui->Print("E:%s\n", message);
+    ui->Print("ERROR: %10s: %s\n", tag, message);
   } else {
     fprintf(stdout, "%c:%s\n", log_characters[severity], message);
   }
diff --git a/recovery_ui/ethernet_device.cpp b/recovery_ui/ethernet_device.cpp
index d79f41d..0318db8 100644
--- a/recovery_ui/ethernet_device.cpp
+++ b/recovery_ui/ethernet_device.cpp
@@ -30,10 +30,12 @@
 #include "recovery_ui/ethernet_device.h"
 #include "recovery_ui/ethernet_ui.h"
 
-const std::string EthernetDevice::interface = "eth0";
+// Android TV defaults to eth0 for it's interface
+EthernetDevice::EthernetDevice(EthernetRecoveryUI* ui) : EthernetDevice(ui, "eth0") {}
 
-EthernetDevice::EthernetDevice(EthernetRecoveryUI* ui)
-    : Device(ui), ctl_sock_(socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)) {
+// Allow future users to define the interface as they prefer
+EthernetDevice::EthernetDevice(EthernetRecoveryUI* ui, std::string interface)
+    : Device(ui), ctl_sock_(socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)), interface_(interface) {
   if (ctl_sock_ < 0) {
     PLOG(ERROR) << "Failed to open socket";
   }
@@ -63,7 +65,7 @@
   }
 
   memset(&ifr, 0, sizeof(struct ifreq));
-  strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ);
+  strncpy(ifr.ifr_name, interface_.c_str(), IFNAMSIZ);
   ifr.ifr_name[IFNAMSIZ - 1] = 0;
 
   if (ioctl(ctl_sock_, SIOCGIFFLAGS, &ifr) < 0) {
@@ -96,7 +98,7 @@
 
   std::unique_ptr<struct ifaddrs, decltype(&freeifaddrs)> guard{ ifaddr, freeifaddrs };
   for (struct ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
-    if (ifa->ifa_addr->sa_family != AF_INET6 || interface != ifa->ifa_name) {
+    if (ifa->ifa_addr->sa_family != AF_INET6 || interface_ != ifa->ifa_name) {
       continue;
     }
 
diff --git a/recovery_ui/include/recovery_ui/ethernet_device.h b/recovery_ui/include/recovery_ui/ethernet_device.h
index ea710ab..3aadea2 100644
--- a/recovery_ui/include/recovery_ui/ethernet_device.h
+++ b/recovery_ui/include/recovery_ui/ethernet_device.h
@@ -27,6 +27,7 @@
 class EthernetDevice : public Device {
  public:
   explicit EthernetDevice(EthernetRecoveryUI* ui);
+  explicit EthernetDevice(EthernetRecoveryUI* ui, std::string interface);
 
   void PreRecovery() override;
   void PreFastboot() override;
@@ -36,7 +37,7 @@
   void SetTitleIPv6LinkLocalAddress(const bool interface_up);
 
   android::base::unique_fd ctl_sock_;
-  static const std::string interface;
+  std::string interface_;
 };
 
 #endif  // _ETHERNET_RECOVERY_DEVICE_H
diff --git a/recovery_utils/roots.cpp b/recovery_utils/roots.cpp
index 7fe385b..5c95cba 100644
--- a/recovery_utils/roots.cpp
+++ b/recovery_utils/roots.cpp
@@ -154,11 +154,9 @@
   }
 
   bool needs_casefold = false;
-  bool needs_projid = false;
 
   if (volume == "/data") {
     needs_casefold = android::base::GetBoolProperty("external_storage.casefold.enabled", false);
-    needs_projid = android::base::GetBoolProperty("external_storage.projid.enabled", false);
   }
 
   int64_t length = 0;
@@ -202,11 +200,10 @@
       "/system/bin/mke2fs", "-F", "-t", "ext4", "-b", std::to_string(kBlockSize),
     };
 
-    // Project ID's require wider inodes. The Quotas themselves are enabled by tune2fs on boot.
-    if (needs_projid) {
-      mke2fs_args.push_back("-I");
-      mke2fs_args.push_back("512");
-    }
+    // Following is added for Project ID's quota as they require wider inodes.
+    // The Quotas themselves are enabled by tune2fs on boot.
+    mke2fs_args.push_back("-I");
+    mke2fs_args.push_back("512");
 
     if (v->fs_mgr_flags.ext_meta_csum) {
       mke2fs_args.push_back("-O");
@@ -255,10 +252,10 @@
     "-g",
     "android",
   };
-  if (needs_projid) {
-    make_f2fs_cmd.push_back("-O");
-    make_f2fs_cmd.push_back("project_quota,extra_attr");
-  }
+
+  make_f2fs_cmd.push_back("-O");
+  make_f2fs_cmd.push_back("project_quota,extra_attr");
+
   if (needs_casefold) {
     make_f2fs_cmd.push_back("-O");
     make_f2fs_cmd.push_back("casefold");
diff --git a/tests/Android.bp b/tests/Android.bp
index 7f00adc..9ad3d3b 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -134,6 +134,10 @@
 
     test_suites: ["device-tests"],
 
+    tidy_timeout_srcs: [
+        "unit/commands_test.cpp",
+    ],
+
     srcs: [
         "unit/*.cpp",
     ],
@@ -192,6 +196,10 @@
         "libupdater_defaults",
     ],
 
+    tidy_timeout_srcs: [
+        "unit/host/imgdiff_test.cpp",
+    ],
+
     srcs: [
         "unit/host/*",
     ],
diff --git a/tests/unit/host/update_simulator_test.cpp b/tests/unit/host/update_simulator_test.cpp
index fb12178..1603982 100644
--- a/tests/unit/host/update_simulator_test.cpp
+++ b/tests/unit/host/update_simulator_test.cpp
@@ -101,7 +101,7 @@
   // TODO(xunchang) check the recovery&system has the expected contents.
 }
 
-class UpdateSimulatorTest : public ::testing::Test {
+class DISABLED_UpdateSimulatorTest : public ::testing::Test {
  protected:
   void SetUp() override {
     std::vector<string> props = {
@@ -147,7 +147,7 @@
   string sparse_system_string_;
 };
 
-TEST_F(UpdateSimulatorTest, TargetFile_ExtractImage) {
+TEST_F(DISABLED_UpdateSimulatorTest, TargetFile_ExtractImage) {
   TemporaryFile zip_file;
   AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" },
                                       { "IMAGES/system.img", sparse_system_string_ } });
@@ -166,7 +166,7 @@
   ASSERT_EQ(expected_content, content);
 }
 
-TEST_F(UpdateSimulatorTest, TargetFile_ParseFstabInfo) {
+TEST_F(DISABLED_UpdateSimulatorTest, TargetFile_ParseFstabInfo) {
   TemporaryFile zip_file;
   AddZipEntries(zip_file.release(),
                 { { "META/misc_info.txt", "" },
@@ -195,7 +195,7 @@
   EXPECT_EQ(expected, transformed);
 }
 
-TEST_F(UpdateSimulatorTest, BuildInfo_ParseTargetFile) {
+TEST_F(DISABLED_UpdateSimulatorTest, BuildInfo_ParseTargetFile) {
   std::map<string, string> entries = {
     { "META/misc_info.txt", "" },
     { "SYSTEM/build.prop", build_prop_string_ },
@@ -240,7 +240,7 @@
   }
 }
 
-TEST_F(UpdateSimulatorTest, RunUpdateSmoke) {
+TEST_F(DISABLED_UpdateSimulatorTest, RunUpdateSmoke) {
   string recovery_img_string = "recovery.img";
   string boot_img_string = "boot.img";
 
@@ -326,7 +326,7 @@
   RunSimulation(src_tf.path, ota_package.path, true);
 }
 
-TEST_F(UpdateSimulatorTest, RunUpdateUnrecognizedFunction) {
+TEST_F(DISABLED_UpdateSimulatorTest, RunUpdateUnrecognizedFunction) {
   std::map<string, string> src_entries{
     { "META/misc_info.txt", "extfs_sparse_flag=-s" },
     { "IMAGES/system.img", sparse_system_string_ },
@@ -350,7 +350,7 @@
   RunSimulation(src_tf.path, ota_package.path, false);
 }
 
-TEST_F(UpdateSimulatorTest, RunUpdateApplyPatchFailed) {
+TEST_F(DISABLED_UpdateSimulatorTest, RunUpdateApplyPatchFailed) {
   string recovery_img_string = "recovery.img";
   string boot_img_string = "boot.img";
 
diff --git a/tools/recovery_l10n/res/values-ky/strings.xml b/tools/recovery_l10n/res/values-ky/strings.xml
index 45fcd15..67dca2a 100644
--- a/tools/recovery_l10n/res/values-ky/strings.xml
+++ b/tools/recovery_l10n/res/values-ky/strings.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="recovery_installing" msgid="2013591905463558223">"Тутум жаңырууда"</string>
+    <string name="recovery_installing" msgid="2013591905463558223">"Система жаңырууда"</string>
     <string name="recovery_erasing" msgid="7334826894904037088">"Тазаланууда"</string>
     <string name="recovery_no_command" msgid="4465476568623024327">"Буйрук берилген жок"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Ката!"</string>
diff --git a/tools/recovery_l10n/res/values-nb/strings.xml b/tools/recovery_l10n/res/values-nb/strings.xml
index e8cad13..61d1173 100644
--- a/tools/recovery_l10n/res/values-nb/strings.xml
+++ b/tools/recovery_l10n/res/values-nb/strings.xml
@@ -7,7 +7,7 @@
     <string name="recovery_error" msgid="5748178989622716736">"Feil!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installerer sikkerhetsoppdateringen"</string>
     <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Kan ikke laste inn Android-systemet. Dataene dine er muligens skadet. Hvis du fortsetter å se denne meldingen, må du muligens tilbakestille til fabrikkstandard og tømme alle brukerdataene som er lagret på denne enheten."</string>
-    <string name="recovery_try_again" msgid="7168248750158873496">"Prøv igjen"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Prøv på nytt"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Tilbakestill til fabrikkstandard"</string>
     <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Vil du viske ut alle brukerdataene?\n\n DETTE KAN IKKE ANGRES!"</string>
     <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Avbryt"</string>
diff --git a/tools/recovery_l10n/res/values-te/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml
index ecea432..5747075 100644
--- a/tools/recovery_l10n/res/values-te/strings.xml
+++ b/tools/recovery_l10n/res/values-te/strings.xml
@@ -4,11 +4,11 @@
     <string name="recovery_installing" msgid="2013591905463558223">"సిస్టమ్ అప్‌డేట్‌ను ఇన్‍స్టాల్ చేస్తోంది"</string>
     <string name="recovery_erasing" msgid="7334826894904037088">"డేటాను తొలగిస్తోంది"</string>
     <string name="recovery_no_command" msgid="4465476568623024327">"ఆదేశం లేదు"</string>
-    <string name="recovery_error" msgid="5748178989622716736">"ఎర్రర్ సంభవించింది!"</string>
+    <string name="recovery_error" msgid="5748178989622716736">"ఎర్రర్ ఏర్పడింది!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"సెక్యూరిటీ అప్‌డేట్‌ను ఇన్‌స్టాల్ చేస్తోంది"</string>
     <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android సిస్టమ్‌ని లోడ్ చేయడం సాధ్యం కాదు. మీ డేటా పాడై ఉండవచ్చు. మీకు ఈ మెసేజ్‌ వస్తూనే ఉంటే, మీరు ఫ్యాక్టరీ డేటా రీసెట్ చేసి, పరికరంలో నిల్వ అయిన వినియోగదారు డేటా మొత్తాన్ని తొలగించాల్సి రావచ్చు."</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"మళ్లీ ప్రయత్నించు"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ఫ్యాక్టరీ డేటా రీసెట్"</string>
     <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"వినియోగదారు డేటా మొత్తాన్ని తొలగించాలా?\n\n ఈ చర్యను రద్దు చేయలేరు!"</string>
-    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"రద్దు చేయి"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"రద్దు చేయండి"</string>
 </resources>
diff --git a/update_verifier/Android.bp b/update_verifier/Android.bp
index 220b007..cb97bd1 100644
--- a/update_verifier/Android.bp
+++ b/update_verifier/Android.bp
@@ -73,6 +73,10 @@
         "libvold_binder",
     ],
 
+    whole_static_libs: [
+	"libsnapshot_snapuserd",
+    ],
+
     shared_libs: [
         "android.hardware.boot@1.0",
         "libbase",
diff --git a/update_verifier/include/update_verifier/update_verifier.h b/update_verifier/include/update_verifier/update_verifier.h
index 4c64b1e..0cccc90 100644
--- a/update_verifier/include/update_verifier/update_verifier.h
+++ b/update_verifier/include/update_verifier/update_verifier.h
@@ -21,6 +21,7 @@
 #include <string>
 #include <vector>
 
+#include <snapuserd/snapuserd_client.h>
 #include "otautil/rangeset.h"
 
 // The update verifier performs verification upon the first boot to a new slot on A/B devices.
@@ -68,4 +69,8 @@
 
   // The function to read the device property; default value: android::base::GetProperty()
   std::function<std::string(const std::string&)> property_reader_;
+
+  // Check if snapuserd daemon has already completed the update verification
+  // Applicable only for VABC with userspace snapshots
+  bool CheckVerificationStatus();
 };
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index a042f90..88fcfa5 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -52,6 +52,7 @@
 #include <future>
 #include <thread>
 
+#include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
@@ -188,7 +189,28 @@
   return ret;
 }
 
+bool UpdateVerifier::CheckVerificationStatus() {
+  auto client =
+      android::snapshot::SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 5s);
+  if (!client) {
+    LOG(ERROR) << "Unable to connect to snapuserd";
+    return false;
+  }
+
+  return client->QueryUpdateVerification();
+}
+
 bool UpdateVerifier::VerifyPartitions() {
+  const bool userspace_snapshots =
+      android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
+
+  if (userspace_snapshots && CheckVerificationStatus()) {
+    LOG(INFO) << "Partitions verified by snapuserd daemon";
+    return true;
+  }
+
+  LOG(INFO) << "Partitions not verified by snapuserd daemon";
+
   auto dm_block_devices = FindDmPartitions();
   if (dm_block_devices.empty()) {
     LOG(ERROR) << "No dm-enabled block device is found.";