[automerger skipped] [automerge] Fix misconfigured recovery host test 2p: d3cd440d2d am: f3419014dc am: 8ce6c88860 -s ours

am skip reason: Merged-In I13bb9595745f65a63070cf9fe030c9155f75e4d0 with SHA-1 d3cd440d2d is already in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/bootable/recovery/+/19900295

Change-Id: Id7629f94c35b2ef895c6411e5df82f1a98f12013
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 52de770..bd95705 100644
--- a/Android.bp
+++ b/Android.bp
@@ -157,6 +157,7 @@
     ],
 
     shared_libs: [
+        "android.hardware.health-V1-ndk", // from librecovery_utils
         "librecovery_ui",
     ],
 
diff --git a/Android.mk b/Android.mk
index 96af417..8506040 100644
--- a/Android.mk
+++ b/Android.mk
@@ -45,8 +45,8 @@
     $(TARGET_RECOVERY_UI_LIB)
 
 LOCAL_SHARED_LIBRARIES := \
-    libbase \
-    liblog \
+    libbase.recovery \
+    liblog.recovery \
     librecovery_ui.recovery
 
 include $(BUILD_SHARED_LIBRARY)
@@ -64,9 +64,15 @@
 ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
 LOCAL_REQUIRED_MODULES += \
     make_f2fs.recovery \
+    fsck.f2fs.recovery \
     sload_f2fs.recovery
 endif
 
+LOCAL_REQUIRED_MODULES += \
+    mkfs.erofs.recovery \
+    dump.erofs.recovery \
+    fsck.erofs.recovery
+
 # On A/B devices recovery-persist reads the recovery related file from the persist storage and
 # copies them into /data/misc/recovery. Then, for both A/B and non-A/B devices, recovery-persist
 # parses the last_install file and reports the embedded update metrics. Also, the last_install file
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/OWNERS b/OWNERS
index 79dd9f7..45c72e3 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,5 +1,4 @@
 elsk@google.com
-enh@google.com
 nhdo@google.com
 xunchang@google.com
-zhaojiac@google.com
+zhangkelvin@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 28aa06f..023d48b 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -6,5 +6,6 @@
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
 
 [Hook Scripts]
+aosp_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "."
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
                   --file_whitelist tools/ updater_sample/
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/etc/init.rc b/etc/init.rc
index 5cacb8b..e4afecf 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -38,12 +38,24 @@
     write /proc/sys/kernel/panic_on_oops 1
     write /proc/sys/vm/max_map_count 1000000
 
+    # Mount binderfs
+    mkdir /dev/binderfs
+    mount binder binder /dev/binderfs stats=global
+    chmod 0755 /dev/binderfs
+
+    symlink /dev/binderfs/binder /dev/binder
+    chmod 0666 /dev/binderfs/binder
+
+    # Start essential services
+    start servicemanager
+
 on boot
     ifup lo
     hostname localhost
     domainname localdomain
 
     class_start default
+    class_start hal
 
 on firmware_mounts_complete
    rm /dev/.booting
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/fuse_sideload/fuse_sideload.cpp b/fuse_sideload/fuse_sideload.cpp
index 3d94803..07cbe96 100644
--- a/fuse_sideload/fuse_sideload.cpp
+++ b/fuse_sideload/fuse_sideload.cpp
@@ -225,7 +225,7 @@
 
 // Fetch a block from the host into fd->curr_block and fd->block_data.
 // Returns 0 on successful fetch, negative otherwise.
-static int fetch_block(fuse_data* fd, uint32_t block) {
+static int fetch_block(fuse_data* fd, uint64_t block) {
   if (block == fd->curr_block) {
     return 0;
   }
diff --git a/install/Android.bp b/install/Android.bp
index e239ddc..c591714 100644
--- a/install/Android.bp
+++ b/install/Android.bp
@@ -105,12 +105,9 @@
 
     srcs: [
         "adb_install.cpp",
-        "asn1_decoder.cpp",
         "fuse_install.cpp",
         "install.cpp",
-        "package.cpp",
         "snapshot_utils.cpp",
-        "verifier.cpp",
         "wipe_data.cpp",
         "wipe_device.cpp",
         "spl_check.cpp",
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 bef23e9..0f5102f 100644
--- a/install/include/install/install.h
+++ b/install/include/install/install.h
@@ -24,7 +24,8 @@
 
 #include <ziparchive/zip_archive.h>
 
-#include "package.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/include/install/wipe_device.h b/install/include/install/wipe_device.h
index c60b999..903ddfd 100644
--- a/install/include/install/wipe_device.h
+++ b/install/include/install/wipe_device.h
@@ -19,7 +19,7 @@
 #include <string>
 #include <vector>
 
-#include "install/package.h"
+#include "otautil/package.h"
 #include "recovery_ui/device.h"
 
 // Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE.
diff --git a/install/install.cpp b/install/install.cpp
index 6e74f80..83f3cad 100644
--- a/install/install.cpp
+++ b/install/install.cpp
@@ -46,13 +46,13 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 
-#include "install/package.h"
 #include "install/spl_check.h"
-#include "install/verifier.h"
 #include "install/wipe_data.h"
 #include "otautil/error_code.h"
+#include "otautil/package.h"
 #include "otautil/paths.h"
 #include "otautil/sysutil.h"
+#include "otautil/verifier.h"
 #include "private/setup_commands.h"
 #include "recovery_ui/ui.h"
 #include "recovery_utils/roots.h"
@@ -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/install/wipe_device.cpp b/install/wipe_device.cpp
index 915c87b..0a525fa 100644
--- a/install/wipe_device.cpp
+++ b/install/wipe_device.cpp
@@ -35,7 +35,7 @@
 
 #include "bootloader_message/bootloader_message.h"
 #include "install/install.h"
-#include "install/package.h"
+#include "otautil/package.h"
 #include "recovery_ui/device.h"
 #include "recovery_ui/ui.h"
 
diff --git a/minadbd/Android.bp b/minadbd/Android.bp
index 2bcfece..9d3f733 100644
--- a/minadbd/Android.bp
+++ b/minadbd/Android.bp
@@ -97,6 +97,7 @@
     ],
 
     shared_libs: [
+        "android.hardware.health-V1-ndk", // from librecovery_utils
         "libbase",
         "libcrypto",
     ],
@@ -128,6 +129,7 @@
     ],
 
     static_libs: [
+        "android.hardware.health-V1-ndk", // from librecovery_utils
         "libminadbd_services",
         "libfusesideload",
         "librecovery_utils",
diff --git a/minui/Android.bp b/minui/Android.bp
index f68f6c8..02fb363 100644
--- a/minui/Android.bp
+++ b/minui/Android.bp
@@ -24,6 +24,7 @@
 cc_library {
     name: "libminui",
     recovery_available: true,
+    vendor_available: true,
 
     defaults: [
         "recovery_defaults",
@@ -51,4 +52,15 @@
         "libpng",
         "libz",
     ],
+
+    target: {
+        vendor: {
+            exclude_static_libs: [
+                "libsync",
+            ],
+            shared_libs: [
+                "libsync",
+            ],
+        },
+    },
 }
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 dce1e61..b24c2b1 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -36,12 +36,14 @@
 static int overscan_offset_y = 0;
 
 static uint32_t gr_current = ~0;
-static constexpr uint32_t alpha_mask = 0xff000000;
 
 // gr_draw is owned by backends.
 static GRSurface* gr_draw = nullptr;
 static GRRotation rotation = GRRotation::NONE;
 static PixelFormat pixel_format = PixelFormat::UNKNOWN;
+// The graphics backend list that provides fallback options for the default backend selection.
+// For example, it will fist try DRM, then try FBDEV if DRM is unavailable.
+constexpr auto default_backends = { GraphicsBackend::DRM, GraphicsBackend::FBDEV };
 
 static bool outside(int x, int y) {
   auto swapped = (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT);
@@ -76,7 +78,7 @@
 }
 
 // Blends gr_current onto pix value, assumes alpha as most significant byte.
-static inline uint32_t pixel_blend(uint8_t alpha, uint32_t pix) {
+static inline uint32_t pixel_blend_argb(uint8_t alpha, uint32_t pix) {
   if (alpha == 255) return gr_current;
   if (alpha == 0) return pix;
   uint32_t pix_r = pix & 0xff;
@@ -93,6 +95,48 @@
   return (out_r & 0xff) | (out_g & 0xff00) | (out_b & 0xff0000) | (gr_current & 0xff000000);
 }
 
+static inline uint32_t pixel_blend_rgba(uint8_t alpha, uint32_t pix) {
+  if (alpha == 255) return gr_current;
+  if (alpha == 0) return pix;
+  uint32_t pix_r = pix & 0xff00;
+  uint32_t pix_g = pix & 0xff0000;
+  uint32_t pix_b = pix & 0xff000000;
+  uint32_t cur_r = gr_current & 0xff00;
+  uint32_t cur_g = gr_current & 0xff0000;
+  uint32_t cur_b = gr_current & 0xff000000;
+
+  uint32_t out_r = (pix_r * (255 - alpha) + cur_r * alpha) / 255;
+  uint32_t out_g = (pix_g * (255 - alpha) + cur_g * alpha) / 255;
+  uint32_t out_b = (pix_b * (255 - alpha) + cur_b * alpha) / 255;
+
+  return (gr_current & 0xff) | (out_r & 0xff00) | (out_g & 0xff0000) | (out_b & 0xff000000);
+}
+
+static inline uint32_t pixel_blend(uint8_t alpha, uint32_t pix) {
+  if (pixel_format == PixelFormat::RGBA) {
+    return pixel_blend_rgba(alpha, pix);
+  }
+  return pixel_blend_argb(alpha, pix);
+}
+
+static inline uint32_t get_alphamask() {
+  if (pixel_format == PixelFormat::RGBA) {
+    return 0x000000ff;
+  }
+  return 0xff000000;
+}
+
+static inline uint8_t get_alpha_shift() {
+  if (pixel_format == PixelFormat::RGBA) {
+    return 0;
+  }
+  return 24;
+}
+
+static inline uint8_t get_alpha(uint32_t pix) {
+  return static_cast<uint8_t>((pix & (gr_current & get_alphamask())) >> get_alpha_shift());
+}
+
 // Increments pixel pointer right, with current rotation.
 static void incr_x(uint32_t** p, int row_pixels) {
   if (rotation == GRRotation::LEFT) {
@@ -140,7 +184,7 @@
 
 static void TextBlend(const uint8_t* src_p, int src_row_bytes, uint32_t* dst_p, int dst_row_pixels,
                       int width, int height) {
-  uint8_t alpha_current = static_cast<uint8_t>((alpha_mask & gr_current) >> 24);
+  uint8_t alpha_current = get_alpha(gr_current);
   for (int j = 0; j < height; ++j) {
     const uint8_t* sx = src_p;
     uint32_t* px = dst_p;
@@ -155,7 +199,7 @@
 }
 
 void gr_text(const GRFont* font, int x, int y, const char* s, bool bold) {
-  if (!font || !font->texture || (gr_current & alpha_mask) == 0) return;
+  if (!font || !font->texture || (gr_current & get_alphamask()) == 0) return;
 
   if (font->texture->pixel_bytes != 1) {
     printf("gr_text: font has wrong format\n");
@@ -210,6 +254,8 @@
   uint32_t r32 = r, g32 = g, b32 = b, a32 = a;
   if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) {
     gr_current = (a32 << 24) | (r32 << 16) | (g32 << 8) | b32;
+  } else if (pixel_format == PixelFormat::RGBA) {
+    gr_current = (b32 << 24) | (g32 << 16) | (r32 << 8) | a32;
   } else {
     gr_current = (a32 << 24) | (b32 << 16) | (g32 << 8) | r32;
   }
@@ -244,7 +290,7 @@
 
   int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
   uint32_t* p = PixelAt(gr_draw, x1, y1, row_pixels);
-  uint8_t alpha = static_cast<uint8_t>(((gr_current & alpha_mask) >> 24));
+  uint8_t alpha = get_alpha(gr_current);
   if (alpha > 0) {
     for (int y = y1; y < y2; ++y) {
       uint32_t* px = p;
@@ -340,7 +386,22 @@
   gr_draw = gr_backend->Flip();
 }
 
+std::unique_ptr<MinuiBackend> create_backend(GraphicsBackend backend) {
+  switch (backend) {
+    case GraphicsBackend::DRM:
+      return std::make_unique<MinuiBackendDrm>();
+    case GraphicsBackend::FBDEV:
+      return std::make_unique<MinuiBackendFbdev>();
+    default:
+      return nullptr;
+  }
+}
+
 int gr_init() {
+  return gr_init(default_backends);
+}
+
+int gr_init(std::initializer_list<GraphicsBackend> backends) {
   // pixel_format needs to be set before loading any resources or initializing backends.
   std::string format = android::base::GetProperty("ro.minui.pixel_format", "");
   if (format == "ABGR_8888") {
@@ -351,6 +412,8 @@
     pixel_format = PixelFormat::ARGB;
   } else if (format == "BGRA_8888") {
     pixel_format = PixelFormat::BGRA;
+  } else if (format == "RGBA_8888") {
+    pixel_format = PixelFormat::RGBA;
   } else {
     pixel_format = PixelFormat::UNKNOWN;
   }
@@ -361,19 +424,22 @@
            ret);
   }
 
-  auto backend = std::unique_ptr<MinuiBackend>{ std::make_unique<MinuiBackendDrm>() };
-  gr_draw = backend->Init();
-
-  if (!gr_draw) {
-    backend = std::make_unique<MinuiBackendFbdev>();
-    gr_draw = backend->Init();
+  std::unique_ptr<MinuiBackend> minui_backend;
+  for (GraphicsBackend backend : backends) {
+    minui_backend = create_backend(backend);
+    if (!minui_backend) {
+      printf("gr_init: minui_backend %d is a nullptr\n", backend);
+      continue;
+    }
+    gr_draw = minui_backend->Init();
+    if (gr_draw) break;
   }
 
   if (!gr_draw) {
     return -1;
   }
 
-  gr_backend = backend.release();
+  gr_backend = minui_backend.release();
 
   int overscan_percent = android::base::GetIntProperty("ro.minui.overscan_percent", 0);
   overscan_offset_x = gr_draw->width * overscan_percent / 100;
@@ -429,6 +495,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 95759e3..c557022 100644
--- a/minui/graphics_drm.cpp
+++ b/minui/graphics_drm.cpp
@@ -105,6 +105,8 @@
     perror("Failed to DRM_IOCTL_MODE_CREATE_DUMB");
     return nullptr;
   }
+  printf("Allocating buffer with resolution %d x %d pitch: %d bpp: %d, size: %llu\n", width, height,
+         create_dumb.pitch, create_dumb.bpp, create_dumb.size);
 
   // Cannot use std::make_unique to access non-public ctor.
   auto surface = std::unique_ptr<GRSurfaceDrm>(new GRSurfaceDrm(
@@ -128,13 +130,14 @@
     return nullptr;
   }
 
-  auto mmapped = mmap(nullptr, surface->height * surface->row_bytes, PROT_READ | PROT_WRITE,
-                      MAP_SHARED, drm_fd, map_dumb.offset);
+  auto mmapped =
+      mmap(nullptr, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd, map_dumb.offset);
   if (mmapped == MAP_FAILED) {
     perror("Failed to mmap()");
     return nullptr;
   }
   surface->mmapped_buffer_ = static_cast<uint8_t*>(mmapped);
+  printf("Framebuffer of size %llu allocated @ %p\n", create_dumb.size, surface->mmapped_buffer_);
   return surface;
 }
 
@@ -150,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;
   }
 }
 
@@ -207,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) {
@@ -236,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,
@@ -245,30 +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;
-
-  *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) {
-      *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) {
@@ -319,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,
@@ -370,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;
   }
 
@@ -399,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 2584017..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;
 
@@ -131,8 +135,6 @@
   SetDisplayedFramebuffer(0);
 
   printf("framebuffer: %d (%zu x %zu)\n", fb_fd.get(), gr_draw->width, gr_draw->height);
-
-  Blank(true);
   Blank(false);
 
   return gr_draw;
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 163e41d..f9be82f 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -102,12 +102,22 @@
   RGBX = 2,
   BGRA = 3,
   ARGB = 4,
+  RGBA = 5, // LSB Alpha
 };
 
-// Initializes the graphics backend and loads font file. Returns 0 on success, or -1 on error. Note
-// that the font initialization failure would be non-fatal, as caller may not need to draw any text
-// at all. Caller can check the font initialization result via gr_sys_font() as needed.
+enum class GraphicsBackend : int {
+  UNKNOWN = 0,
+  DRM = 1,
+  FBDEV = 2,
+};
+
+// Initializes the default graphics backend and loads font file. Returns 0 on success, or -1 on
+// error. Note that the font initialization failure would be non-fatal, as caller may not need to
+// draw any text at all. Caller can check the font initialization result via gr_sys_font() as
+// needed.
 int gr_init();
+// Supports backend selection for minui client.
+int gr_init(std::initializer_list<GraphicsBackend> backends);
 
 // Frees the allocated resources. The function is idempotent, and safe to be called if gr_init()
 // didn't finish successfully.
@@ -118,6 +128,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();
@@ -152,6 +163,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();
@@ -159,6 +171,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/minui/resources.cpp b/minui/resources.cpp
index d7b9277..1521c8f 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -153,32 +153,57 @@
                                int width) {
   const uint8_t* ip = input_row;
   uint8_t* op = output_row;
+  PixelFormat pixel_format = gr_pixel_format();
 
   switch (channels) {
     case 1:
       // expand gray level to RGBX
       for (int x = 0; x < width; ++x) {
-        *op++ = *ip;
-        *op++ = *ip;
-        *op++ = *ip;
-        *op++ = 0xff;
+        if (pixel_format == PixelFormat::RGBA) {
+          *op++ = 0xff;
+          *op++ = *ip;
+          *op++ = *ip;
+          *op++ = *ip;
+        } else {
+          *op++ = *ip;
+          *op++ = *ip;
+          *op++ = *ip;
+          *op++ = 0xff;
+        }
         ip++;
       }
       break;
 
     case 3:
-      // expand RGBA to RGBX
       for (int x = 0; x < width; ++x) {
-        *op++ = *ip++;
-        *op++ = *ip++;
-        *op++ = *ip++;
-        *op++ = 0xff;
+        // expand RGBA to RGBX
+        if (pixel_format == PixelFormat::RGBA) {
+            *op++ = 0xff;
+            *op++ = *ip++;
+            *op++ = *ip++;
+            *op++ = *ip++;
+        } else {
+            *op++ = *ip++;
+            *op++ = *ip++;
+            *op++ = *ip++;
+            *op++ = 0xff;
+        }
       }
       break;
 
     case 4:
-      // copy RGBA to RGBX
-      memcpy(output_row, input_row, width * 4);
+      if (pixel_format == PixelFormat::RGBA) {
+        for (int x = 0; x < width; ++x) {
+            *op++ = *(ip + 3);
+            *op++ = *ip++;
+            *op++ = *ip++;
+            *op++ = *ip++;
+            ip++;
+        }
+      } else {
+        // copy RGBA to RGBX
+        memcpy(output_row, input_row, width * 4);
+      }
       break;
   }
 }
@@ -201,6 +226,8 @@
   PixelFormat pixel_format = gr_pixel_format();
   if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) {
     png_set_bgr(png_ptr);
+  } else if (pixel_format == PixelFormat::RGBA) {
+    png_set_swap_alpha(png_ptr);
   }
 
   for (png_uint_32 y = 0; y < height; ++y) {
@@ -273,6 +300,8 @@
 
   if (gr_pixel_format() == PixelFormat::ARGB || gr_pixel_format() == PixelFormat::BGRA) {
     png_set_bgr(png_ptr);
+  } else if (gr_pixel_format() == PixelFormat::RGBA) {
+    png_set_swap_alpha(png_ptr);
   }
 
   for (png_uint_32 y = 0; y < height; ++y) {
@@ -316,11 +345,6 @@
     return -8;
   }
 
-  PixelFormat pixel_format = gr_pixel_format();
-  if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) {
-    png_set_bgr(png_ptr);
-  }
-
   for (png_uint_32 y = 0; y < height; ++y) {
     uint8_t* p_row = surface->data() + y * surface->row_bytes;
     png_read_row(png_ptr, p_row, nullptr);
diff --git a/otautil/Android.bp b/otautil/Android.bp
index 557b8a3..4b043ad 100644
--- a/otautil/Android.bp
+++ b/otautil/Android.bp
@@ -34,16 +34,21 @@
 
     // Minimal set of files to support host build.
     srcs: [
+        "asn1_decoder.cpp",
         "dirutil.cpp",
+        "package.cpp",
         "paths.cpp",
         "rangeset.cpp",
         "sysutil.cpp",
+        "verifier.cpp",
     ],
 
     shared_libs: [
         "libbase",
+        "libcrypto",
         "libcutils",
         "libselinux",
+        "libziparchive",
     ],
 
     export_include_dirs: [
diff --git a/install/asn1_decoder.cpp b/otautil/asn1_decoder.cpp
similarity index 100%
rename from install/asn1_decoder.cpp
rename to otautil/asn1_decoder.cpp
diff --git a/install/include/install/package.h b/otautil/include/otautil/package.h
similarity index 98%
rename from install/include/install/package.h
rename to otautil/include/otautil/package.h
index 0b42332..f4f4d34 100644
--- a/install/include/install/package.h
+++ b/otautil/include/otautil/package.h
@@ -26,7 +26,7 @@
 
 #include <ziparchive/zip_archive.h>
 
-#include "verifier.h"
+#include "otautil/verifier.h"
 
 enum class PackageType {
   kMemory,
diff --git a/install/include/install/verifier.h b/otautil/include/otautil/verifier.h
similarity index 100%
rename from install/include/install/verifier.h
rename to otautil/include/otautil/verifier.h
diff --git a/install/include/private/asn1_decoder.h b/otautil/include/private/asn1_decoder.h
similarity index 100%
rename from install/include/private/asn1_decoder.h
rename to otautil/include/private/asn1_decoder.h
diff --git a/install/package.cpp b/otautil/package.cpp
similarity index 99%
rename from install/package.cpp
rename to otautil/package.cpp
index 86fc064..242204e 100644
--- a/install/package.cpp
+++ b/otautil/package.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "install/package.h"
+#include "otautil/package.h"
 
 #include <string.h>
 #include <unistd.h>
diff --git a/install/verifier.cpp b/otautil/verifier.cpp
similarity index 98%
rename from install/verifier.cpp
rename to otautil/verifier.cpp
index 3f02601..8a65566 100644
--- a/install/verifier.cpp
+++ b/otautil/verifier.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "install/verifier.h"
+#include "otautil/verifier.h"
 
 #include <errno.h>
 #include <stdio.h>
@@ -257,8 +257,8 @@
 
   // Check to make sure at least one of the keys matches the signature. Since any key can match,
   // we need to try each before determining a verification failure has happened.
-  size_t i = 0;
-  for (const auto& key : keys) {
+  for (size_t i = 0; i < keys.size(); i++) {
+    const auto& key = keys[i];
     const uint8_t* hash;
     int hash_nid;
     switch (key.hash_len) {
@@ -296,7 +296,6 @@
     } else {
       LOG(INFO) << "Unknown key type " << key.key_type;
     }
-    i++;
   }
 
   if (need_sha1) {
diff --git a/recovery.cpp b/recovery.cpp
index 36924fb..4d39019 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -48,12 +48,12 @@
 #include "install/adb_install.h"
 #include "install/fuse_install.h"
 #include "install/install.h"
-#include "install/package.h"
 #include "install/snapshot_utils.h"
 #include "install/wipe_data.h"
 #include "install/wipe_device.h"
 #include "otautil/boot_state.h"
 #include "otautil/error_code.h"
+#include "otautil/package.h"
 #include "otautil/paths.h"
 #include "otautil/sysutil.h"
 #include "recovery_ui/screen_ui.h"
@@ -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/Android.bp b/recovery_utils/Android.bp
index e0e9ec0..9bd66c5 100644
--- a/recovery_utils/Android.bp
+++ b/recovery_utils/Android.bp
@@ -31,6 +31,7 @@
     shared_libs: [
         "android.hardware.health@2.0",
         "libbase",
+        "libbinder_ndk",
         "libext4_utils",
         "libfs_mgr",
         "libhidlbase",
@@ -42,8 +43,10 @@
         "libotautil",
 
         // External dependencies.
+        "android.hardware.health-translate-ndk",
         "libfstab",
         "libhealthhalutils",
+        "libhealthshim",
     ],
 }
 
@@ -70,6 +73,15 @@
         "libvold_headers",
     ],
 
+    shared_libs: [
+        // The following cannot be placed in librecovery_utils_defaults,
+        // because at the time of writing, android.hardware.health-V1-ndk.so
+        // is not installed to the system image yet. (It is installed
+        // to the recovery ramdisk.) Hence, minadbd_test must link to it
+        // statically.
+        "android.hardware.health-V1-ndk",
+    ],
+
     export_include_dirs: [
         "include",
     ],
diff --git a/recovery_utils/battery_utils.cpp b/recovery_utils/battery_utils.cpp
index 323f525..6b126bd 100644
--- a/recovery_utils/battery_utils.cpp
+++ b/recovery_utils/battery_utils.cpp
@@ -20,59 +20,74 @@
 #include <unistd.h>
 
 #include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <health-shim/shim.h>
 #include <healthhalutils/HealthHalUtils.h>
 
 BatteryInfo GetBatteryInfo() {
-  using android::hardware::health::V1_0::BatteryStatus;
   using android::hardware::health::V2_0::get_health_service;
-  using android::hardware::health::V2_0::IHealth;
-  using android::hardware::health::V2_0::Result;
-  using android::hardware::health::V2_0::toString;
+  using HidlHealth = android::hardware::health::V2_0::IHealth;
+  using aidl::android::hardware::health::BatteryStatus;
+  using aidl::android::hardware::health::HealthShim;
+  using aidl::android::hardware::health::IHealth;
+  using aidl::android::hardware::health::toString;
+  using std::string_literals::operator""s;
 
-  android::sp<IHealth> health = get_health_service();
+  auto service_name = IHealth::descriptor + "/default"s;
+  std::shared_ptr<IHealth> health;
+  if (AServiceManager_isDeclared(service_name.c_str())) {
+    ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str()));
+    health = IHealth::fromBinder(binder);
+  }
+  if (health == nullptr) {
+    LOG(INFO) << "Unable to get AIDL health service, trying HIDL...";
+    android::sp<HidlHealth> hidl_health = get_health_service();
+    if (hidl_health != nullptr) {
+      health = ndk::SharedRefBase::make<HealthShim>(hidl_health);
+    }
+  }
+  if (health == nullptr) {
+    LOG(WARNING) << "No health implementation is found; assuming defaults";
+  }
 
   int wait_second = 0;
   while (true) {
     auto charge_status = BatteryStatus::UNKNOWN;
-
-    if (health == nullptr) {
-      LOG(WARNING) << "No health implementation is found; assuming defaults";
-    } else {
-      health
-          ->getChargeStatus([&charge_status](auto res, auto out_status) {
-            if (res == Result::SUCCESS) {
-              charge_status = out_status;
-            }
-          })
-          .isOk();  // should not have transport error
+    if (health != nullptr) {
+      auto res = health->getChargeStatus(&charge_status);
+      if (!res.isOk()) {
+        LOG(WARNING) << "Unable to call getChargeStatus: " << res.getDescription();
+        charge_status = BatteryStatus::UNKNOWN;
+      }
     }
 
-    // Treat unknown status as on charger. See hardware/interfaces/health/1.0/types.hal for the
-    // meaning of the return values.
+    // Treat unknown status as on charger. See hardware/interfaces/health/aidl/BatteryStatus.aidl
+    // for the meaning of the return values.
     bool charging = (charge_status != BatteryStatus::DISCHARGING &&
                      charge_status != BatteryStatus::NOT_CHARGING);
 
-    Result res = Result::UNKNOWN;
     int32_t capacity = INT32_MIN;
     if (health != nullptr) {
-      health
-          ->getCapacity([&res, &capacity](auto out_res, auto out_capacity) {
-            res = out_res;
-            capacity = out_capacity;
-          })
-          .isOk();  // should not have transport error
+      auto res = health->getCapacity(&capacity);
+      if (!res.isOk()) {
+        LOG(WARNING) << "Unable to call getCapacity: " << res.getDescription();
+        capacity = INT32_MIN;
+      }
     }
 
     LOG(INFO) << "charge_status " << toString(charge_status) << ", charging " << charging
-              << ", status " << toString(res) << ", capacity " << capacity;
+              << ", capacity " << capacity;
 
     constexpr int BATTERY_READ_TIMEOUT_IN_SEC = 10;
     // At startup, the battery drivers in devices like N5X/N6P take some time to load
     // the battery profile. Before the load finishes, it reports value 50 as a fake
     // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected
     // to finish loading the battery profile earlier than 10 seconds after kernel startup.
-    if (res == Result::SUCCESS && capacity == 50) {
+    if (capacity == 50) {
       if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) {
+        LOG(INFO) << "Battery capacity == 50, waiting "
+                  << (BATTERY_READ_TIMEOUT_IN_SEC - wait_second)
+                  << " seconds to ensure this is not a fake value...";
         sleep(1);
         wait_second++;
         continue;
@@ -80,10 +95,12 @@
     }
     // If we can't read battery percentage, it may be a device without battery. In this
     // situation, use 100 as a fake battery percentage.
-    if (res != Result::SUCCESS) {
+    if (capacity == INT32_MIN) {
+      LOG(WARNING) << "Using fake battery capacity 100.";
       capacity = 100;
     }
 
+    LOG(INFO) << "GetBatteryInfo() reporting charging " << charging << ", capacity " << capacity;
     return BatteryInfo{ charging, capacity };
   }
 }
diff --git a/recovery_utils/roots.cpp b/recovery_utils/roots.cpp
index 1948447..5c95cba 100644
--- a/recovery_utils/roots.cpp
+++ b/recovery_utils/roots.cpp
@@ -33,7 +33,7 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
-#include <cryptfs.h>
+#include <ext4_utils/ext4_utils.h>
 #include <ext4_utils/wipe.h>
 #include <fs_mgr.h>
 #include <fs_mgr/roots.h>
@@ -154,53 +154,56 @@
   }
 
   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);
-  }
-
-  // If there's a key_loc that looks like a path, it should be a block device for storing encryption
-  // metadata. Wipe it too.
-  if (!v->key_loc.empty() && v->key_loc[0] == '/') {
-    LOG(INFO) << "Wiping " << v->key_loc;
-    int fd = open(v->key_loc.c_str(), O_WRONLY | O_CREAT, 0644);
-    if (fd == -1) {
-      PLOG(ERROR) << "format_volume: Failed to open " << v->key_loc;
-      return -1;
-    }
-    wipe_block_device(fd, get_file_size(fd));
-    close(fd);
   }
 
   int64_t length = 0;
   if (v->length > 0) {
     length = v->length;
-  } else if (v->length < 0 || v->key_loc == "footer") {
+  } else if (v->length < 0) {
     android::base::unique_fd fd(open(v->blk_device.c_str(), O_RDONLY));
     if (fd == -1) {
       PLOG(ERROR) << "format_volume: failed to open " << v->blk_device;
       return -1;
     }
-    length = get_file_size(fd.get(), v->length ? -v->length : CRYPT_FOOTER_OFFSET);
+    length = get_file_size(fd.get(), -v->length);
     if (length <= 0) {
       LOG(ERROR) << "get_file_size: invalid size " << length << " for " << v->blk_device;
       return -1;
     }
   }
 
+  // If the raw disk will be used as a metadata encrypted device mapper target,
+  // next boot will do encrypt_in_place the raw disk which gives a subtle duration
+  // to get any failure in the process. In order to avoid it, let's simply wipe
+  // the raw disk if we don't reserve any space, which behaves exactly same as booting
+  // after "fastboot -w".
+  if (!v->metadata_key_dir.empty() && length == 0) {
+    android::base::unique_fd fd(open(v->blk_device.c_str(), O_RDWR));
+    if (fd == -1) {
+      PLOG(ERROR) << "format_volume: failed to open " << v->blk_device;
+      return -1;
+    }
+    int64_t device_size = get_file_size(fd.get(), 0);
+    if (device_size > 0 && !wipe_block_device(fd.get(), device_size)) {
+      LOG(INFO) << "format_volume: wipe metadata encrypted " << v->blk_device << " with size "
+                << device_size;
+      return 0;
+    }
+  }
+
   if (v->fs_type == "ext4") {
     static constexpr int kBlockSize = 4096;
     std::vector<std::string> mke2fs_args = {
       "/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");
@@ -249,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 9395cbb..0708e85 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -134,11 +134,22 @@
 
     test_suites: ["device-tests"],
 
+    tidy_timeout_srcs: [
+        "unit/commands_test.cpp",
+    ],
+
     srcs: [
         "unit/*.cpp",
     ],
 
+    shared_libs: [
+        "libbinder_ndk",
+    ],
+
     static_libs: libapplypatch_static_libs + librecovery_static_libs + [
+        "android.hardware.health-translate-ndk",
+        "android.hardware.health-V1-ndk",
+        "libhealthshim",
         "librecovery_ui",
         "libfusesideload",
         "libminui",
@@ -185,6 +196,10 @@
         "libupdater_defaults",
     ],
 
+    tidy_timeout_srcs: [
+        "unit/host/imgdiff_test.cpp",
+    ],
+
     srcs: [
         "unit/host/*",
     ],
diff --git a/tests/fuzz/verify_package_fuzzer.cpp b/tests/fuzz/verify_package_fuzzer.cpp
index baa44e0..36c8534 100644
--- a/tests/fuzz/verify_package_fuzzer.cpp
+++ b/tests/fuzz/verify_package_fuzzer.cpp
@@ -17,7 +17,7 @@
 #include "fuzzer/FuzzedDataProvider.h"
 
 #include "install/install.h"
-#include "install/package.h"
+#include "otautil/package.h"
 #include "recovery_ui/stub_ui.h"
 
 std::unique_ptr<Package> CreatePackage(std::vector<uint8_t>& content) {
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/tests/unit/package_test.cpp b/tests/unit/package_test.cpp
index 164a93d..66882bb 100644
--- a/tests/unit/package_test.cpp
+++ b/tests/unit/package_test.cpp
@@ -26,7 +26,7 @@
 #include <ziparchive/zip_writer.h>
 
 #include "common/test_constants.h"
-#include "install/package.h"
+#include "otautil/package.h"
 
 class PackageTest : public ::testing::Test {
  protected:
diff --git a/tests/unit/verifier_test.cpp b/tests/unit/verifier_test.cpp
index ded23c5..08a3ddf 100644
--- a/tests/unit/verifier_test.cpp
+++ b/tests/unit/verifier_test.cpp
@@ -35,8 +35,8 @@
 #include <ziparchive/zip_writer.h>
 
 #include "common/test_constants.h"
-#include "install/package.h"
-#include "install/verifier.h"
+#include "otautil/package.h"
+#include "otautil/verifier.h"
 #include "otautil/sysutil.h"
 
 using namespace std::string_literals;
diff --git a/tools/recovery_l10n/res/values-or/strings.xml b/tools/recovery_l10n/res/values-or/strings.xml
index 25b28e6..683bf19 100644
--- a/tools/recovery_l10n/res/values-or/strings.xml
+++ b/tools/recovery_l10n/res/values-or/strings.xml
@@ -10,5 +10,5 @@
     <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/tools/recovery_l10n/res/values-ro/strings.xml b/tools/recovery_l10n/res/values-ro/strings.xml
index 585db83..2f1cccd 100644
--- a/tools/recovery_l10n/res/values-ro/strings.xml
+++ b/tools/recovery_l10n/res/values-ro/strings.xml
@@ -6,9 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nicio comandă"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Eroare!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Se instalează actualizarea de securitate"</string>
-    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Nu se poate încărca sistemul Android. Datele dvs. pot fi corupte. Dacă primiți în continuare acest mesaj, poate fi necesar să reveniți la setările din fabrică și să ștergeți toate datele utilizatorului stocate pe acest dispozitiv."</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Nu se poate încărca sistemul Android. Datele tale pot fi corupte. Dacă primești în continuare acest mesaj, poate fi necesar să revii la setările din fabrică și să ștergi toate datele utilizatorului stocate pe acest dispozitiv."</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"Reîncercați"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Revenire la setările din fabrică"</string>
-    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Ștergeți toate datele utilizatorului?\n\n ACEST LUCRU NU POATE FI ANULAT!"</string>
-    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Anulați"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Șterge toate datele utilizatorului?\n\n ACEST LUCRU NU POATE FI ANULAT!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Anulează"</string>
 </resources>
diff --git a/update_verifier/Android.bp b/update_verifier/Android.bp
index ff2eff9..220b007 100644
--- a/update_verifier/Android.bp
+++ b/update_verifier/Android.bp
@@ -33,6 +33,25 @@
     ],
 }
 
+python_library_host {
+    name: "care_map_proto_py",
+    srcs: [
+        "care_map.proto",
+    ],
+    proto: {type: "lite", canonical_path_from_root: false},
+    version: {
+        py2: {
+            enabled: true,
+        },
+        py3: {
+            enabled: true,
+        },
+    },
+    visibility: [
+        "//build/make/tools/releasetools:__subpackages__",
+    ],
+}
+
 cc_library_static {
     name: "libupdate_verifier",
 
diff --git a/update_verifier/care_map_generator.py b/update_verifier/care_map_generator.py
index c6f2dad..b1396a4 100644
--- a/update_verifier/care_map_generator.py
+++ b/update_verifier/care_map_generator.py
@@ -111,14 +111,14 @@
   logging.basicConfig(level=logging.INFO if args.verbose else logging.WARNING,
                       format=logging_format)
 
-  with open(args.input_care_map, 'r') as input_care_map:
+  with open(args.input_care_map, 'rb') as input_care_map:
     content = input_care_map.read()
 
   if args.parse_proto:
     result = ParseProtoMessage(content, args.fingerprint_enabled).encode()
   else:
     care_map_proto = GenerateCareMapProtoFromLegacyFormat(
-        content.rstrip().splitlines(), args.fingerprint_enabled)
+        content.decode().rstrip().splitlines(), args.fingerprint_enabled)
     result = care_map_proto.SerializeToString()
 
   with open(args.output_file, 'wb') as output: