[automerger skipped] Import translations. DO NOT MERGE am: 2f6ed0524e -s ours

am skip reason: subject contains skip directive

Change-Id: I7919efcc5bb4eda2cf60d5119727d4920120ca26
diff --git a/Android.bp b/Android.bp
index f367e5e..e7e1938 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,13 +58,16 @@
     ],
 
     shared_libs: [
-        "android.hardware.health@2.0",
+        "android.hardware.boot@1.0",
+        "android.hardware.boot@1.1",
         "libbase",
         "libbootloader_message",
         "libcrypto",
         "libcutils",
         "libfs_mgr",
+        "liblp",
         "liblog",
+        "libprotobuf-cpp-lite",
         "libziparchive",
     ],
 
@@ -73,11 +76,9 @@
         "libinstall",
         "librecovery_fastboot",
         "libminui",
+        "librecovery_utils",
         "libotautil",
-
-        // external dependencies
-        "libhealthhalutils",
-        "libfstab",
+        "libsnapshot_nobinder",
     ],
 }
 
@@ -90,7 +91,6 @@
     ],
 
     srcs: [
-        "fsck_unshare_blocks.cpp",
         "recovery.cpp",
     ],
 
@@ -100,6 +100,14 @@
     ],
 }
 
+prebuilt_etc {
+    name: "init_recovery.rc",
+    filename: "init.rc",
+    src: "etc/init.rc",
+    sub_dir: "init/hw",
+    recovery: true,
+}
+
 cc_binary {
     name: "recovery",
     recovery: true,
@@ -107,6 +115,7 @@
     defaults: [
         "libinstall_defaults",
         "librecovery_defaults",
+        "librecovery_utils_defaults",
     ],
 
     srcs: [
@@ -124,11 +133,13 @@
 
     required: [
         "e2fsdroid.recovery",
+        "init_recovery.rc",
         "librecovery_ui_ext",
         "minadbd",
         "mke2fs.conf.recovery",
         "mke2fs.recovery",
         "recovery_deps",
+        "ueventd.rc.recovery",
     ],
 }
 
@@ -147,12 +158,10 @@
     shared_libs: [
         "libbase",
         "liblog",
-        "libmetricslogger",
     ],
 
     static_libs: [
-        "libotautil",
-        "libfstab",
+        "librecovery_utils",
     ],
 
     init_rc: [
@@ -178,8 +187,7 @@
     ],
 
     static_libs: [
-        "libotautil",
-        "libfstab",
+        "librecovery_utils",
     ],
 
     init_rc: [
diff --git a/Android.mk b/Android.mk
index 9806d10..d727ca2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -56,12 +56,10 @@
 LOCAL_MODULE := recovery_deps
 
 ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
-ifeq ($(HOST_OS),linux)
 LOCAL_REQUIRED_MODULES += \
     make_f2fs.recovery \
     sload_f2fs.recovery
 endif
-endif
 
 # 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
diff --git a/CleanSpec.mk b/CleanSpec.mk
index a7ab0d9..d4e9e43 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -51,6 +51,24 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinstall.recovery_intermediates)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/system/lib64/libinstall.so)
 
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest/recovery_component_test)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest64/recovery_component_test)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/testcases/recovery_component_test)
+
+$(call add-clean-step, find $(OUT_DIR) -type f -name "SystemUpdaterSample*" -print0 | xargs -0 rm -f)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/SystemUpdaterSample)
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib*/libbrotli.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib*/libbz.so)
+
+# Move recovery resources from /system to /vendor.
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/bin/applypatch)
+$(call add-clean-step, rm -r $(PRODUCT_OUT)/symbols/system/bin/applypatch)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/bin/applypatch)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/bin/install-recovery.sh)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/etc/recovery-resource.dat)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/PACKAGING/target_files_intermediates/*-target_files-*/SYSTEM/recovery-from-boot.p)
+
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/OWNERS b/OWNERS
index b3f11dc..79dd9f7 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,5 @@
+elsk@google.com
 enh@google.com
-tbao@google.com
+nhdo@google.com
 xunchang@google.com
+zhaojiac@google.com
diff --git a/README.md b/README.md
index efcd318..bd1cf7d 100644
--- a/README.md
+++ b/README.md
@@ -4,29 +4,41 @@
 Quick turn-around testing
 -------------------------
 
-    mm -j && m ramdisk-nodeps && m recoveryimage-nodeps
+* Devices using recovery-as-boot (e.g. Pixels, which set BOARD\_USES\_RECOVERY\_AS\_BOOT)
 
-    # To boot into the new recovery image
-    # without flashing the recovery partition:
-    adb reboot bootloader
-    fastboot boot $ANDROID_PRODUCT_OUT/recovery.img
+      # After setting up environment and lunch.
+      m -j bootimage
+      adb reboot bootloader
+
+      # Pixel devices don't support booting into recovery mode with `fastboot boot`.
+      fastboot flash boot
+
+      # Manually choose `Recovery mode` from bootloader menu.
+
+* Devices with a separate recovery image (e.g. Nexus)
+
+      # After setting up environment and lunch.
+      mm -j && m ramdisk-nodeps && m recoveryimage-nodeps
+      adb reboot bootloader
+
+      # To boot into the new recovery image without flashing the recovery partition:
+      fastboot boot $ANDROID_PRODUCT_OUT/recovery.img
 
 Running the tests
 -----------------
+
     # After setting up environment and lunch.
     mmma -j bootable/recovery
 
-    # Running the tests on device.
+    # Running the tests on device (under normal boot).
     adb root
     adb sync data
 
     # 32-bit device
     adb shell /data/nativetest/recovery_unit_test/recovery_unit_test
-    adb shell /data/nativetest/recovery_component_test/recovery_component_test
 
     # Or 64-bit device
     adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test
-    adb shell /data/nativetest64/recovery_component_test/recovery_component_test
 
 Running the manual tests
 ------------------------
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..a304582
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,14 @@
+{
+  "presubmit": [
+    {
+      "name": "minadbd_test"
+    },
+    {
+      "name": "recovery_unit_test"
+    },
+    {
+      "name": "recovery_host_test",
+      "host": true
+    }
+  ]
+}
diff --git a/applypatch/Android.bp b/applypatch/Android.bp
index 620ca6c..13a9625 100644
--- a/applypatch/Android.bp
+++ b/applypatch/Android.bp
@@ -31,6 +31,7 @@
     name: "libapplypatch",
 
     host_supported: true,
+    vendor_available: true,
 
     defaults: [
         "applypatch_defaults",
@@ -51,12 +52,15 @@
         "libbase",
         "libbspatch",
         "libbz",
-        "libcrypto",
         "libedify",
         "libotautil",
         "libz",
     ],
 
+    shared_libs: [
+        "libcrypto",
+    ],
+
     target: {
         darwin: {
             enabled: false,
@@ -66,6 +70,7 @@
 
 cc_library_static {
     name: "libapplypatch_modes",
+    vendor_available: true,
 
     defaults: [
         "applypatch_defaults",
@@ -78,14 +83,18 @@
     static_libs: [
         "libapplypatch",
         "libbase",
-        "libcrypto",
         "libedify",
         "libotautil",
     ],
+
+    shared_libs: [
+        "libcrypto",
+    ],
 }
 
 cc_binary {
     name: "applypatch",
+    vendor: true,
 
     defaults: [
         "applypatch_defaults",
@@ -100,25 +109,29 @@
         "libapplypatch",
         "libedify",
         "libotautil",
+
+        // External dependencies.
         "libbspatch",
+        "libbrotli",
+        "libbz",
     ],
 
     shared_libs: [
         "libbase",
-        "libbrotli",
-        "libbz",
         "libcrypto",
         "liblog",
         "libz",
         "libziparchive",
     ],
+
+    init_rc: [
+        "vendor_flash_recovery.rc",
+    ],
 }
 
-cc_library_static {
+cc_library_host_static {
     name: "libimgdiff",
 
-    host_supported: true,
-
     defaults: [
         "applypatch_defaults",
     ],
@@ -170,35 +183,3 @@
         "libz",
     ],
 }
-
-cc_library_static {
-    name: "libimgpatch",
-
-    // The host module is for recovery_host_test (Linux only).
-    host_supported: true,
-
-    defaults: [
-        "applypatch_defaults",
-    ],
-
-    srcs: [
-        "bspatch.cpp",
-        "imgpatch.cpp",
-    ],
-
-    static_libs: [
-        "libbase",
-        "libbspatch",
-        "libbz",
-        "libcrypto",
-        "libedify",
-        "libotautil",
-        "libz",
-    ],
-
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-}
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 90d8e86..adda697 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -47,7 +47,7 @@
 using namespace std::string_literals;
 
 static bool GenerateTarget(const Partition& target, const FileContents& source_file,
-                           const Value& patch, const Value* bonus_data);
+                           const Value& patch, const Value* bonus_data, bool backup_source);
 
 bool LoadFileContents(const std::string& filename, FileContents* file) {
   // No longer allow loading contents from eMMC partitions.
@@ -266,7 +266,7 @@
 }
 
 bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
-                    const Value* bonus) {
+                    const Value* bonus, bool backup_source) {
   LOG(INFO) << "Patching " << target.name;
 
   // We try to load and check against the target hash first.
@@ -279,8 +279,8 @@
   }
 
   FileContents source_file;
-  if (ReadPartitionToBuffer(source, &source_file, true)) {
-    return GenerateTarget(target, source_file, patch, bonus);
+  if (ReadPartitionToBuffer(source, &source_file, backup_source)) {
+    return GenerateTarget(target, source_file, patch, bonus, backup_source);
   }
 
   LOG(ERROR) << "Failed to find any match";
@@ -326,7 +326,7 @@
 }
 
 static bool GenerateTarget(const Partition& target, const FileContents& source_file,
-                           const Value& patch, const Value* bonus_data) {
+                           const Value& patch, const Value* bonus_data, bool backup_source) {
   uint8_t expected_sha1[SHA_DIGEST_LENGTH];
   if (ParseSha1(target.hash, expected_sha1) != 0) {
     LOG(ERROR) << "Failed to parse target hash \"" << target.hash << "\"";
@@ -351,11 +351,11 @@
   }
 
   // We write the original source to cache, in case the partition write is interrupted.
-  if (!CheckAndFreeSpaceOnCache(source_file.data.size())) {
+  if (backup_source && !CheckAndFreeSpaceOnCache(source_file.data.size())) {
     LOG(ERROR) << "Not enough free space on /cache";
     return false;
   }
-  if (!SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) {
+  if (backup_source && !SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) {
     LOG(ERROR) << "Failed to back up source file";
     return false;
   }
@@ -415,7 +415,9 @@
   }
 
   // Delete the backup copy of the source.
-  unlink(Paths::Get().cache_temp_source().c_str());
+  if (backup_source) {
+    unlink(Paths::Get().cache_temp_source().c_str());
+  }
 
   // Success!
   return true;
diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp
index b466598..bb5eeae 100644
--- a/applypatch/applypatch_modes.cpp
+++ b/applypatch/applypatch_modes.cpp
@@ -87,7 +87,7 @@
     bonus = std::make_unique<Value>(Value::Type::BLOB, std::move(bonus_contents));
   }
 
-  return PatchPartition(target, source, patch, bonus.get()) ? 0 : 1;
+  return PatchPartition(target, source, patch, bonus.get(), false) ? 0 : 1;
 }
 
 static void Usage() {
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index 415d95f..91007ac 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -675,7 +675,7 @@
 // Iterate the zip entries and compose the image chunks accordingly.
 bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandle handle) {
   void* cookie;
-  int ret = StartIteration(handle, &cookie, nullptr, nullptr);
+  int ret = StartIteration(handle, &cookie);
   if (ret != 0) {
     LOG(ERROR) << "Failed to iterate over entries in " << filename << ": " << ErrorCodeString(ret);
     return false;
@@ -683,12 +683,11 @@
 
   // Create a list of deflated zip entries, sorted by offset.
   std::vector<std::pair<std::string, ZipEntry>> temp_entries;
-  ZipString name;
+  std::string name;
   ZipEntry entry;
   while ((ret = Next(cookie, &entry, &name)) == 0) {
     if (entry.method == kCompressDeflated || limit_ > 0) {
-      std::string entry_name(name.name, name.name + name.name_length);
-      temp_entries.emplace_back(entry_name, entry);
+      temp_entries.emplace_back(name, entry);
     }
   }
 
@@ -966,7 +965,7 @@
         used_src_ranges.Insert(src_ranges);
         split_src_ranges->push_back(std::move(src_ranges));
       }
-      src_ranges.Clear();
+      src_ranges = {};
 
       // We don't have enough space for the current chunk; start a new split image and handle
       // this chunk there.
@@ -1036,7 +1035,7 @@
   }
 
   ZipModeImage split_tgt_image(false);
-  split_tgt_image.Initialize(std::move(aligned_tgt_chunks), {});
+  split_tgt_image.Initialize(aligned_tgt_chunks, {});
   split_tgt_image.MergeAdjacentNormalChunks();
 
   // Construct the dummy source file based on the src_ranges.
@@ -1052,7 +1051,7 @@
   CHECK(!src_content.empty());
 
   ZipModeImage split_src_image(true);
-  split_src_image.Initialize(split_src_chunks, std::move(src_content));
+  split_src_image.Initialize(split_src_chunks, src_content);
 
   split_tgt_images->push_back(std::move(split_tgt_image));
   split_src_images->push_back(std::move(split_src_image));
diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h
index 6fc6f0f..799f4b2 100644
--- a/applypatch/include/applypatch/applypatch.h
+++ b/applypatch/include/applypatch/applypatch.h
@@ -73,10 +73,11 @@
 // the 'target' Partition. While patching, it will backup the data on the source partition to
 // /cache, so that the patching could be resumed on interruption even if both of the source and
 // target partitions refer to the same device. The function is idempotent if called multiple times.
-// An optional arg 'bonus' can be provided, if the patch was generated with a bonus output.
-// Returns the patching result.
+// 'bonus' can be provided if the patch was generated with a bonus output, or nullptr.
+// 'backup_source' indicates whether the source partition should be backed up prior to the update
+// (e.g. when doing in-place update). Returns the patching result.
 bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
-                    const Value* bonus);
+                    const Value* bonus, bool backup_source);
 
 // Returns whether the contents of the eMMC target or the cached file match the embedded hash.
 // It will look for the backup on /cache if the given partition doesn't match the checksum.
diff --git a/applypatch/vendor_flash_recovery.rc b/applypatch/vendor_flash_recovery.rc
new file mode 100644
index 0000000..37a7c2b
--- /dev/null
+++ b/applypatch/vendor_flash_recovery.rc
@@ -0,0 +1,3 @@
+service vendor_flash_recovery /vendor/bin/install-recovery.sh
+    class main
+    oneshot
diff --git a/boot_control/Android.bp b/boot_control/Android.bp
deleted file mode 100644
index 7720ead..0000000
--- a/boot_control/Android.bp
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-cc_library_shared {
-    name: "bootctrl.default",
-    recovery_available: true,
-    relative_install_path: "hw",
-
-    srcs: ["boot_control.cpp"],
-
-    cflags: [
-        "-D_FILE_OFFSET_BITS=64",
-        "-Werror",
-        "-Wall",
-        "-Wextra",
-    ],
-
-    shared_libs: [
-        "libbase",
-        "libbootloader_message",
-        "libfs_mgr",
-        "liblog",
-    ],
-}
diff --git a/boot_control/boot_control.cpp b/boot_control/boot_control.cpp
deleted file mode 100644
index ec97b6c..0000000
--- a/boot_control/boot_control.cpp
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <endian.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <string.h>
-
-#include <string>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
-#include <hardware/boot_control.h>
-#include <hardware/hardware.h>
-
-#include <bootloader_message/bootloader_message.h>
-
-struct boot_control_private_t {
-  // The base struct needs to be first in the list.
-  boot_control_module_t base;
-
-  // Whether this struct was initialized with data from the bootloader message
-  // that doesn't change until next reboot.
-  bool initialized;
-
-  // The path to the misc_device as reported in the fstab.
-  const char* misc_device;
-
-  // The number of slots present on the device.
-  unsigned int num_slots;
-
-  // The slot where we are running from.
-  unsigned int current_slot;
-};
-
-namespace {
-
-// The number of boot attempts that should be made from a new slot before
-// rolling back to the previous slot.
-constexpr unsigned int kDefaultBootAttempts = 7;
-static_assert(kDefaultBootAttempts < 8, "tries_remaining field only has 3 bits");
-
-constexpr unsigned int kMaxNumSlots =
-    sizeof(bootloader_control::slot_info) / sizeof(bootloader_control::slot_info[0]);
-constexpr const char* kSlotSuffixes[kMaxNumSlots] = { "_a", "_b", "_c", "_d" };
-constexpr off_t kBootloaderControlOffset = offsetof(bootloader_message_ab, slot_suffix);
-
-static uint32_t CRC32(const uint8_t* buf, size_t size) {
-  static uint32_t crc_table[256];
-
-  // Compute the CRC-32 table only once.
-  if (!crc_table[1]) {
-    for (uint32_t i = 0; i < 256; ++i) {
-      uint32_t crc = i;
-      for (uint32_t j = 0; j < 8; ++j) {
-        uint32_t mask = -(crc & 1);
-        crc = (crc >> 1) ^ (0xEDB88320 & mask);
-      }
-      crc_table[i] = crc;
-    }
-  }
-
-  uint32_t ret = -1;
-  for (size_t i = 0; i < size; ++i) {
-    ret = (ret >> 8) ^ crc_table[(ret ^ buf[i]) & 0xFF];
-  }
-
-  return ~ret;
-}
-
-// Return the little-endian representation of the CRC-32 of the first fields
-// in |boot_ctrl| up to the crc32_le field.
-uint32_t BootloaderControlLECRC(const bootloader_control* boot_ctrl) {
-  return htole32(
-      CRC32(reinterpret_cast<const uint8_t*>(boot_ctrl), offsetof(bootloader_control, crc32_le)));
-}
-
-bool LoadBootloaderControl(const char* misc_device, bootloader_control* buffer) {
-  android::base::unique_fd fd(open(misc_device, O_RDONLY));
-  if (fd.get() == -1) {
-    PLOG(ERROR) << "failed to open " << misc_device;
-    return false;
-  }
-  if (lseek(fd, kBootloaderControlOffset, SEEK_SET) != kBootloaderControlOffset) {
-    PLOG(ERROR) << "failed to lseek " << misc_device;
-    return false;
-  }
-  if (!android::base::ReadFully(fd.get(), buffer, sizeof(bootloader_control))) {
-    PLOG(ERROR) << "failed to read " << misc_device;
-    return false;
-  }
-  return true;
-}
-
-bool UpdateAndSaveBootloaderControl(const char* misc_device, bootloader_control* buffer) {
-  buffer->crc32_le = BootloaderControlLECRC(buffer);
-  android::base::unique_fd fd(open(misc_device, O_WRONLY | O_SYNC));
-  if (fd.get() == -1) {
-    PLOG(ERROR) << "failed to open " << misc_device;
-    return false;
-  }
-  if (lseek(fd.get(), kBootloaderControlOffset, SEEK_SET) != kBootloaderControlOffset) {
-    PLOG(ERROR) << "failed to lseek " << misc_device;
-    return false;
-  }
-  if (!android::base::WriteFully(fd.get(), buffer, sizeof(bootloader_control))) {
-    PLOG(ERROR) << "failed to write " << misc_device;
-    return false;
-  }
-  return true;
-}
-
-void InitDefaultBootloaderControl(const boot_control_private_t* module,
-                                  bootloader_control* boot_ctrl) {
-  memset(boot_ctrl, 0, sizeof(*boot_ctrl));
-
-  if (module->current_slot < kMaxNumSlots) {
-    strlcpy(boot_ctrl->slot_suffix, kSlotSuffixes[module->current_slot],
-            sizeof(boot_ctrl->slot_suffix));
-  }
-  boot_ctrl->magic = BOOT_CTRL_MAGIC;
-  boot_ctrl->version = BOOT_CTRL_VERSION;
-
-  // Figure out the number of slots by checking if the partitions exist,
-  // otherwise assume the maximum supported by the header.
-  boot_ctrl->nb_slot = kMaxNumSlots;
-  std::string base_path = module->misc_device;
-  size_t last_path_sep = base_path.rfind('/');
-  if (last_path_sep != std::string::npos) {
-    // We test the existence of the "boot" partition on each possible slot,
-    // which is a partition required by Android Bootloader Requirements.
-    base_path = base_path.substr(0, last_path_sep + 1) + "boot";
-    int last_existing_slot = -1;
-    int first_missing_slot = -1;
-    for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) {
-      std::string partition_path = base_path + kSlotSuffixes[slot];
-      struct stat part_stat;
-      int err = stat(partition_path.c_str(), &part_stat);
-      if (!err) {
-        last_existing_slot = slot;
-        LOG(INFO) << "Found slot: " << kSlotSuffixes[slot];
-      } else if (err < 0 && errno == ENOENT && first_missing_slot == -1) {
-        first_missing_slot = slot;
-      }
-    }
-    // We only declare that we found the actual number of slots if we found all
-    // the boot partitions up to the number of slots, and no boot partition
-    // after that. Not finding any of the boot partitions implies a problem so
-    // we just leave the number of slots in the maximum value.
-    if ((last_existing_slot != -1 && last_existing_slot + 1 == first_missing_slot) ||
-        (first_missing_slot == -1 && last_existing_slot + 1 == kMaxNumSlots)) {
-      boot_ctrl->nb_slot = last_existing_slot + 1;
-      LOG(INFO) << "Found a system with " << last_existing_slot + 1 << " slots.";
-    }
-  }
-
-  for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) {
-    slot_metadata entry = {};
-
-    if (slot < boot_ctrl->nb_slot) {
-      entry.priority = 7;
-      entry.tries_remaining = kDefaultBootAttempts;
-      entry.successful_boot = 0;
-    } else {
-      entry.priority = 0;  // Unbootable
-    }
-
-    // When the boot_control stored on disk is invalid, we assume that the
-    // current slot is successful. The bootloader should repair this situation
-    // before booting and write a valid boot_control slot, so if we reach this
-    // stage it means that the misc partition was corrupted since boot.
-    if (module->current_slot == slot) {
-      entry.successful_boot = 1;
-    }
-
-    boot_ctrl->slot_info[slot] = entry;
-  }
-  boot_ctrl->recovery_tries_remaining = 0;
-
-  boot_ctrl->crc32_le = BootloaderControlLECRC(boot_ctrl);
-}
-
-// Return the index of the slot suffix passed or -1 if not a valid slot suffix.
-int SlotSuffixToIndex(const char* suffix) {
-  for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) {
-    if (!strcmp(kSlotSuffixes[slot], suffix)) return slot;
-  }
-  return -1;
-}
-
-// Initialize the boot_control_private struct with the information from
-// the bootloader_message buffer stored in |boot_ctrl|. Returns whether the
-// initialization succeeded.
-bool BootControl_lazyInitialization(boot_control_private_t* module) {
-  if (module->initialized) return true;
-
-  // Initialize the current_slot from the read-only property. If the property
-  // was not set (from either the command line or the device tree), we can later
-  // initialize it from the bootloader_control struct.
-  std::string suffix_prop = android::base::GetProperty("ro.boot.slot_suffix", "");
-  module->current_slot = SlotSuffixToIndex(suffix_prop.c_str());
-
-  std::string err;
-  std::string device = get_bootloader_message_blk_device(&err);
-  if (device.empty()) return false;
-
-  bootloader_control boot_ctrl;
-  if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) return false;
-
-  // Note that since there isn't a module unload function this memory is leaked.
-  module->misc_device = strdup(device.c_str());
-  module->initialized = true;
-
-  // Validate the loaded data, otherwise we will destroy it and re-initialize it
-  // with the current information.
-  uint32_t computed_crc32 = BootloaderControlLECRC(&boot_ctrl);
-  if (boot_ctrl.crc32_le != computed_crc32) {
-    LOG(WARNING) << "Invalid boot control found, expected CRC-32 0x" << std::hex << computed_crc32
-                 << " but found 0x" << std::hex << boot_ctrl.crc32_le << ". Re-initializing.";
-    InitDefaultBootloaderControl(module, &boot_ctrl);
-    UpdateAndSaveBootloaderControl(device.c_str(), &boot_ctrl);
-  }
-
-  module->num_slots = boot_ctrl.nb_slot;
-  return true;
-}
-
-void BootControl_init(boot_control_module_t* module) {
-  BootControl_lazyInitialization(reinterpret_cast<boot_control_private_t*>(module));
-}
-
-unsigned int BootControl_getNumberSlots(boot_control_module_t* module) {
-  return reinterpret_cast<boot_control_private_t*>(module)->num_slots;
-}
-
-unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) {
-  return reinterpret_cast<boot_control_private_t*>(module)->current_slot;
-}
-
-int BootControl_markBootSuccessful(boot_control_module_t* module) {
-  boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
-
-  bootloader_control bootctrl;
-  if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
-
-  bootctrl.slot_info[bootctrl_module->current_slot].successful_boot = 1;
-  // tries_remaining == 0 means that the slot is not bootable anymore, make
-  // sure we mark the current slot as bootable if it succeeds in the last
-  // attempt.
-  bootctrl.slot_info[bootctrl_module->current_slot].tries_remaining = 1;
-  if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
-  return 0;
-}
-
-int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) {
-  boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
-
-  if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
-    // Invalid slot number.
-    return -1;
-  }
-
-  bootloader_control bootctrl;
-  if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
-
-  // Set every other slot with a lower priority than the new "active" slot.
-  const unsigned int kActivePriority = 15;
-  const unsigned int kActiveTries = 6;
-  for (unsigned int i = 0; i < bootctrl_module->num_slots; ++i) {
-    if (i != slot) {
-      if (bootctrl.slot_info[i].priority >= kActivePriority)
-        bootctrl.slot_info[i].priority = kActivePriority - 1;
-    }
-  }
-
-  // Note that setting a slot as active doesn't change the successful bit.
-  // The successful bit will only be changed by setSlotAsUnbootable().
-  bootctrl.slot_info[slot].priority = kActivePriority;
-  bootctrl.slot_info[slot].tries_remaining = kActiveTries;
-
-  // Setting the current slot as active is a way to revert the operation that
-  // set *another* slot as active at the end of an updater. This is commonly
-  // used to cancel the pending update. We should only reset the verity_corrpted
-  // bit when attempting a new slot, otherwise the verity bit on the current
-  // slot would be flip.
-  if (slot != bootctrl_module->current_slot) bootctrl.slot_info[slot].verity_corrupted = 0;
-
-  if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
-  return 0;
-}
-
-int BootControl_setSlotAsUnbootable(struct boot_control_module* module, unsigned int slot) {
-  boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
-
-  if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
-    // Invalid slot number.
-    return -1;
-  }
-
-  bootloader_control bootctrl;
-  if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
-
-  // The only way to mark a slot as unbootable, regardless of the priority is to
-  // set the tries_remaining to 0.
-  bootctrl.slot_info[slot].successful_boot = 0;
-  bootctrl.slot_info[slot].tries_remaining = 0;
-  if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
-  return 0;
-}
-
-int BootControl_isSlotBootable(struct boot_control_module* module, unsigned int slot) {
-  boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
-
-  if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
-    // Invalid slot number.
-    return -1;
-  }
-
-  bootloader_control bootctrl;
-  if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
-
-  return bootctrl.slot_info[slot].tries_remaining;
-}
-
-int BootControl_isSlotMarkedSuccessful(struct boot_control_module* module, unsigned int slot) {
-  boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module);
-
-  if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) {
-    // Invalid slot number.
-    return -1;
-  }
-
-  bootloader_control bootctrl;
-  if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1;
-
-  return bootctrl.slot_info[slot].successful_boot && bootctrl.slot_info[slot].tries_remaining;
-}
-
-const char* BootControl_getSuffix(boot_control_module_t* module, unsigned int slot) {
-  if (slot >= kMaxNumSlots || slot >= reinterpret_cast<boot_control_private_t*>(module)->num_slots) {
-    return NULL;
-  }
-  return kSlotSuffixes[slot];
-}
-
-static int BootControl_open(const hw_module_t* module __unused, const char* id __unused,
-                            hw_device_t** device __unused) {
-  /* Nothing to do currently. */
-  return 0;
-}
-
-struct hw_module_methods_t BootControl_methods = {
-  .open = BootControl_open,
-};
-
-}  // namespace
-
-boot_control_private_t HAL_MODULE_INFO_SYM = {
-  .base =
-      {
-          .common =
-              {
-                  .tag = HARDWARE_MODULE_TAG,
-                  .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
-                  .hal_api_version = HARDWARE_HAL_API_VERSION,
-                  .id = BOOT_CONTROL_HARDWARE_MODULE_ID,
-                  .name = "AOSP reference bootctrl HAL",
-                  .author = "The Android Open Source Project",
-                  .methods = &BootControl_methods,
-              },
-          .init = BootControl_init,
-          .getNumberSlots = BootControl_getNumberSlots,
-          .getCurrentSlot = BootControl_getCurrentSlot,
-          .markBootSuccessful = BootControl_markBootSuccessful,
-          .setActiveBootSlot = BootControl_setActiveBootSlot,
-          .setSlotAsUnbootable = BootControl_setSlotAsUnbootable,
-          .isSlotBootable = BootControl_isSlotBootable,
-          .getSuffix = BootControl_getSuffix,
-          .isSlotMarkedSuccessful = BootControl_isSlotMarkedSuccessful,
-      },
-  .initialized = false,
-  .misc_device = nullptr,
-  .num_slots = 0,
-  .current_slot = 0,
-};
diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp
index 450dad0..6443a07 100644
--- a/bootloader_message/Android.bp
+++ b/bootloader_message/Android.bp
@@ -36,6 +36,18 @@
         "libbootloader_message_defaults",
     ],
     recovery_available: true,
+    host_supported: true,
+
+    target: {
+        host: {
+            shared_libs: [
+                "libcutils", // for strlcpy
+            ],
+        },
+        darwin: {
+            enabled: false,
+        },
+    }
 }
 
 cc_library_static {
@@ -44,4 +56,5 @@
         "libbootloader_message_defaults",
     ],
     vendor: true,
+    recovery_available: true,
 }
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index c1ebeaa..b70d54e 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -20,6 +20,7 @@
 #include <fcntl.h>
 #include <string.h>
 
+#include <optional>
 #include <string>
 #include <string_view>
 #include <vector>
@@ -30,19 +31,23 @@
 #include <android-base/unique_fd.h>
 #include <fstab/fstab.h>
 
+#ifndef __ANDROID__
+#include <cutils/memory.h>  // for strlcpy
+#endif
+
 using android::fs_mgr::Fstab;
 using android::fs_mgr::ReadDefaultFstab;
 
-static std::string g_misc_device_for_test;
+static std::optional<std::string> g_misc_device_for_test;
 
 // Exposed for test purpose.
 void SetMiscBlockDeviceForTest(std::string_view misc_device) {
   g_misc_device_for_test = misc_device;
 }
 
-static std::string get_misc_blk_device(std::string* err) {
-  if (!g_misc_device_for_test.empty()) {
-    return g_misc_device_for_test;
+std::string get_misc_blk_device(std::string* err) {
+  if (g_misc_device_for_test.has_value() && !g_misc_device_for_test->empty()) {
+    return *g_misc_device_for_test;
   }
   Fstab fstab;
   if (!ReadDefaultFstab(&fstab)) {
@@ -106,8 +111,8 @@
   return true;
 }
 
-static bool write_misc_partition(const void* p, size_t size, const std::string& misc_blk_device,
-                                 size_t offset, std::string* err) {
+bool write_misc_partition(const void* p, size_t size, const std::string& misc_blk_device,
+                          size_t offset, std::string* err) {
   android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY));
   if (fd == -1) {
     *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(),
@@ -179,6 +184,14 @@
   return write_bootloader_message(boot, err);
 }
 
+bool write_bootloader_message_to(const std::vector<std::string>& options,
+                                 const std::string& misc_blk_device, std::string* err) {
+  bootloader_message boot = {};
+  update_bootloader_message_in_struct(&boot, options);
+
+  return write_bootloader_message_to(boot, misc_blk_device, err);
+}
+
 bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) {
   bootloader_message boot;
   if (!read_bootloader_message(&boot, err)) {
@@ -197,13 +210,15 @@
   memset(boot->recovery, 0, sizeof(boot->recovery));
 
   strlcpy(boot->command, "boot-recovery", sizeof(boot->command));
-  strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery));
+
+  std::string recovery = "recovery\n";
   for (const auto& s : options) {
-    strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery));
+    recovery += s;
     if (s.back() != '\n') {
-      strlcat(boot->recovery, "\n", sizeof(boot->recovery));
+      recovery += '\n';
     }
   }
+  strlcpy(boot->recovery, recovery.c_str(), sizeof(boot->recovery));
   return true;
 }
 
@@ -235,41 +250,60 @@
   if (misc_blk_device.empty()) {
     return false;
   }
+  static constexpr size_t kMaximumWipePackageSize =
+      SYSTEM_SPACE_OFFSET_IN_MISC - WIPE_PACKAGE_OFFSET_IN_MISC;
+  if (package_data.size() > kMaximumWipePackageSize) {
+    *err = "Wipe package size " + std::to_string(package_data.size()) + " exceeds " +
+           std::to_string(kMaximumWipePackageSize) + " bytes";
+    return false;
+  }
   return write_misc_partition(package_data.data(), package_data.size(), misc_blk_device,
                               WIPE_PACKAGE_OFFSET_IN_MISC, err);
 }
 
-static bool OffsetAndSizeInVendorSpace(size_t offset, size_t size) {
-  auto total_size = WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC;
-  return size <= total_size && offset <= total_size - size;
+static bool ValidateSystemSpaceRegion(size_t offset, size_t size, std::string* err) {
+  if (size <= SYSTEM_SPACE_SIZE_IN_MISC && offset <= (SYSTEM_SPACE_SIZE_IN_MISC - size)) {
+    return true;
+  }
+  *err = android::base::StringPrintf("Out of bound access (offset %zu size %zu)", offset, size);
+  return false;
 }
 
-bool ReadMiscPartitionVendorSpace(void* data, size_t size, size_t offset, std::string* err) {
-  if (!OffsetAndSizeInVendorSpace(offset, size)) {
-    *err = android::base::StringPrintf("Out of bound read (offset %zu size %zu)", offset, size);
+static bool ReadMiscPartitionSystemSpace(void* data, size_t size, size_t offset, std::string* err) {
+  if (!ValidateSystemSpaceRegion(offset, size, err)) {
     return false;
   }
   auto misc_blk_device = get_misc_blk_device(err);
   if (misc_blk_device.empty()) {
     return false;
   }
-  return read_misc_partition(data, size, misc_blk_device, VENDOR_SPACE_OFFSET_IN_MISC + offset,
+  return read_misc_partition(data, size, misc_blk_device, SYSTEM_SPACE_OFFSET_IN_MISC + offset,
                              err);
 }
 
-bool WriteMiscPartitionVendorSpace(const void* data, size_t size, size_t offset, std::string* err) {
-  if (!OffsetAndSizeInVendorSpace(offset, size)) {
-    *err = android::base::StringPrintf("Out of bound write (offset %zu size %zu)", offset, size);
+static bool WriteMiscPartitionSystemSpace(const void* data, size_t size, size_t offset,
+                                          std::string* err) {
+  if (!ValidateSystemSpaceRegion(offset, size, err)) {
     return false;
   }
   auto misc_blk_device = get_misc_blk_device(err);
   if (misc_blk_device.empty()) {
     return false;
   }
-  return write_misc_partition(data, size, misc_blk_device, VENDOR_SPACE_OFFSET_IN_MISC + offset,
+  return write_misc_partition(data, size, misc_blk_device, SYSTEM_SPACE_OFFSET_IN_MISC + offset,
                               err);
 }
 
+bool ReadMiscVirtualAbMessage(misc_virtual_ab_message* message, std::string* err) {
+  return ReadMiscPartitionSystemSpace(message, sizeof(*message),
+                                      offsetof(misc_system_space_layout, virtual_ab_message), err);
+}
+
+bool WriteMiscVirtualAbMessage(const misc_virtual_ab_message& message, std::string* err) {
+  return WriteMiscPartitionSystemSpace(&message, sizeof(message),
+                                       offsetof(misc_system_space_layout, virtual_ab_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 95dd8f4..e4cf09b 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -25,12 +25,15 @@
 // 0   - 2K     For bootloader_message
 // 2K  - 16K    Used by Vendor's bootloader (the 2K - 4K range may be optionally used
 //              as bootloader_message_ab struct)
-// 16K - 64K    Used by uncrypt and recovery to store wipe_package for A/B devices
+// 16K - 32K    Used by uncrypt and recovery to store wipe_package for A/B devices
+// 32K - 64K    System space, used for miscellanious AOSP features. See below.
 // Note that these offsets are admitted by bootloader,recovery and uncrypt, so they
 // are not configurable without changing all of them.
 constexpr size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0;
 constexpr size_t VENDOR_SPACE_OFFSET_IN_MISC = 2 * 1024;
 constexpr size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024;
+constexpr size_t SYSTEM_SPACE_OFFSET_IN_MISC = 32 * 1024;
+constexpr size_t SYSTEM_SPACE_SIZE_IN_MISC = 32 * 1024;
 
 /* Bootloader Message (2-KiB)
  *
@@ -80,116 +83,47 @@
     char reserved[1184];
 };
 
-/**
- * We must be cautious when changing the bootloader_message struct size,
- * because A/B-specific fields may end up with different offsets.
- */
-#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus)
-static_assert(sizeof(struct bootloader_message) == 2048,
-              "struct bootloader_message size changes, which may break A/B devices");
-#endif
-
-/**
- * The A/B-specific bootloader message structure (4-KiB).
- *
- * We separate A/B boot control metadata from the regular bootloader
- * message struct and keep it here. Everything that's A/B-specific
- * stays after struct bootloader_message, which should be managed by
- * the A/B-bootloader or boot control HAL.
- *
- * The slot_suffix field is used for A/B implementations where the
- * bootloader does not set the androidboot.ro.boot.slot_suffix kernel
- * commandline parameter. This is used by fs_mgr to mount /system and
- * other partitions with the slotselect flag set in fstab. A/B
- * implementations are free to use all 32 bytes and may store private
- * data past the first NUL-byte in this field. It is encouraged, but
- * not mandatory, to use 'struct bootloader_control' described below.
- *
- * The update_channel field is used to store the Omaha update channel
- * if update_engine is compiled with Omaha support.
- */
-struct bootloader_message_ab {
-    struct bootloader_message message;
-    char slot_suffix[32];
-    char update_channel[128];
-
-    // Round up the entire struct to 4096-byte.
-    char reserved[1888];
-};
-
-/**
- * Be cautious about the struct size change, in case we put anything post
- * bootloader_message_ab struct (b/29159185).
- */
-#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus)
-static_assert(sizeof(struct bootloader_message_ab) == 4096,
-              "struct bootloader_message_ab size changes");
-#endif
-
-#define BOOT_CTRL_MAGIC   0x42414342 /* Bootloader Control AB */
-#define BOOT_CTRL_VERSION 1
-
-struct slot_metadata {
-    // Slot priority with 15 meaning highest priority, 1 lowest
-    // priority and 0 the slot is unbootable.
-    uint8_t priority : 4;
-    // Number of times left attempting to boot this slot.
-    uint8_t tries_remaining : 3;
-    // 1 if this slot has booted successfully, 0 otherwise.
-    uint8_t successful_boot : 1;
-    // 1 if this slot is corrupted from a dm-verity corruption, 0
-    // otherwise.
-    uint8_t verity_corrupted : 1;
-    // Reserved for further use.
-    uint8_t reserved : 7;
+// Holds Virtual A/B merge status information. Current version is 1. New fields
+// must be added to the end.
+struct misc_virtual_ab_message {
+  uint8_t version;
+  uint32_t magic;
+  uint8_t merge_status;  // IBootControl 1.1, MergeStatus enum.
+  uint8_t source_slot;   // Slot number when merge_status was written.
+  uint8_t reserved[57];
 } __attribute__((packed));
 
-/* Bootloader Control AB
- *
- * This struct can be used to manage A/B metadata. It is designed to
- * be put in the 'slot_suffix' field of the 'bootloader_message'
- * structure described above. It is encouraged to use the
- * 'bootloader_control' structure to store the A/B metadata, but not
- * mandatory.
- */
-struct bootloader_control {
-    // NUL terminated active slot suffix.
-    char slot_suffix[4];
-    // Bootloader Control AB magic number (see BOOT_CTRL_MAGIC).
-    uint32_t magic;
-    // Version of struct being used (see BOOT_CTRL_VERSION).
-    uint8_t version;
-    // Number of slots being managed.
-    uint8_t nb_slot : 3;
-    // Number of times left attempting to boot recovery.
-    uint8_t recovery_tries_remaining : 3;
-    // Ensure 4-bytes alignment for slot_info field.
-    uint8_t reserved0[2];
-    // Per-slot information.  Up to 4 slots.
-    struct slot_metadata slot_info[4];
-    // Reserved for further use.
-    uint8_t reserved1[8];
-    // CRC32 of all 28 bytes preceding this field (little endian
-    // format).
-    uint32_t crc32_le;
-} __attribute__((packed));
+#define MISC_VIRTUAL_AB_MESSAGE_VERSION 2
+#define MISC_VIRTUAL_AB_MAGIC_HEADER 0x56740AB0
 
 #if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus)
-static_assert(sizeof(struct bootloader_control) ==
-              sizeof(((struct bootloader_message_ab *)0)->slot_suffix),
-              "struct bootloader_control has wrong size");
+static_assert(sizeof(struct misc_virtual_ab_message) == 64,
+              "struct misc_virtual_ab_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;
+} __attribute__((packed));
+
 #ifdef __cplusplus
 
 #include <string>
 #include <vector>
 
+// Gets the block device name of /misc partition.
+std::string get_misc_blk_device(std::string* err);
 // Return the block device name for the bootloader message partition and waits
 // for the device for up to 10 seconds. In case of error returns the empty
 // string.
 std::string get_bootloader_message_blk_device(std::string* err);
 
+// Writes |size| bytes of data from buffer |p| to |misc_blk_device| at |offset|. If the write fails,
+// sets the error message in |err|.
+bool write_misc_partition(const void* p, size_t size, const std::string& misc_blk_device,
+                          size_t offset, std::string* err);
+
 // Read bootloader message into boot. Error message will be set in err.
 bool read_bootloader_message(bootloader_message* boot, std::string* err);
 
@@ -208,6 +142,11 @@
 // set the command and recovery fields, and reset the rest.
 bool write_bootloader_message(const std::vector<std::string>& options, std::string* err);
 
+// Write bootloader message (boots into recovery with the options) to the specific BCB device. Will
+// set the command and recovery fields, and reset the rest.
+bool write_bootloader_message_to(const std::vector<std::string>& options,
+                                 const std::string& misc_blk_device, std::string* err);
+
 // Update bootloader message (boots into recovery with the options) to BCB. Will
 // only update the command and recovery fields.
 bool update_bootloader_message(const std::vector<std::string>& options, std::string* err);
@@ -229,13 +168,9 @@
 // Write the wipe package into BCB (to offset WIPE_PACKAGE_OFFSET_IN_MISC).
 bool write_wipe_package(const std::string& package_data, std::string* err);
 
-// Reads data from the vendor space in /misc partition, with the given offset and size. Note that
-// offset is in relative to the start of vendor space.
-bool ReadMiscPartitionVendorSpace(void* data, size_t size, size_t offset, std::string* err);
-
-// Writes the given data to the vendor space in /misc partition, at the given offset. Note that
-// offset is in relative to the start of the vendor space.
-bool WriteMiscPartitionVendorSpace(const void* data, size_t size, size_t offset, std::string* err);
+// Read or write the Virtual A/B message from system space in /misc.
+bool ReadMiscVirtualAbMessage(misc_virtual_ab_message* message, std::string* err);
+bool WriteMiscVirtualAbMessage(const misc_virtual_ab_message& message, std::string* err);
 
 #else
 
diff --git a/common.h b/common.h
deleted file mode 100644
index a524a41..0000000
--- a/common.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <string>
-
-// Not using the command-line defined macro here because this header could be included by
-// device-specific recovery libraries. We static assert the value consistency in recovery.cpp.
-static constexpr int kRecoveryApiVersion = 3;
-
-class RecoveryUI;
-struct selabel_handle;
-
-extern struct selabel_handle* sehandle;
-extern RecoveryUI* ui;
-extern bool has_cache;
-
-// The current stage, e.g. "1/2".
-extern std::string stage;
-
-// The reason argument provided in "--reason=".
-extern const char* reason;
-
-bool is_ro_debuggable();
diff --git a/edify/Android.bp b/edify/Android.bp
index 42947eb..0ab53d6 100644
--- a/edify/Android.bp
+++ b/edify/Android.bp
@@ -16,6 +16,8 @@
     name: "libedify",
 
     host_supported: true,
+    vendor_available: true,
+    recovery_available: true,
 
     srcs: [
         "expr.cpp",
diff --git a/edify/expr.cpp b/edify/expr.cpp
index c090eb2..e5e0e24 100644
--- a/edify/expr.cpp
+++ b/edify/expr.cpp
@@ -421,5 +421,5 @@
   return nullptr;
 }
 
-State::State(const std::string& script, void* cookie)
-    : script(script), cookie(cookie), error_code(kNoError), cause_code(kNoCause) {}
+State::State(const std::string& script, UpdaterInterface* interface)
+    : script(script), updater(interface), error_code(kNoError), cause_code(kNoCause) {}
diff --git a/edify/include/edify/expr.h b/edify/include/edify/expr.h
index 5cbd5e1..3ddf7f5 100644
--- a/edify/include/edify/expr.h
+++ b/edify/include/edify/expr.h
@@ -23,19 +23,20 @@
 #include <string>
 #include <vector>
 
+#include "edify/updater_interface.h"
+
 // Forward declaration to avoid including "otautil/error_code.h".
 enum ErrorCode : int;
 enum CauseCode : int;
 
 struct State {
-  State(const std::string& script, void* cookie);
+  State(const std::string& script, UpdaterInterface* cookie);
 
   // The source of the original script.
   const std::string& script;
 
-  // Optional pointer to app-specific data; the core of edify never
-  // uses this value.
-  void* cookie;
+  // A pointer to app-specific data; the libedify doesn't use this value.
+  UpdaterInterface* updater;
 
   // The error message (if any) returned if the evaluation aborts.
   // Should be empty initially, will be either empty or a string that
@@ -59,7 +60,7 @@
     BLOB = 2,
   };
 
-  Value(Type type, const std::string& str) : type(type), data(str) {}
+  Value(Type type, std::string str) : type(type), data(std::move(str)) {}
 
   Type type;
   std::string data;
diff --git a/edify/include/edify/updater_interface.h b/edify/include/edify/updater_interface.h
new file mode 100644
index 0000000..aa977e3
--- /dev/null
+++ b/edify/include/edify/updater_interface.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <string>
+#include <string_view>
+
+struct ZipArchive;
+typedef ZipArchive* ZipArchiveHandle;
+
+class UpdaterRuntimeInterface;
+
+class UpdaterInterface {
+ public:
+  virtual ~UpdaterInterface() = default;
+
+  // Writes the message to command pipe, adds a new line in the end.
+  virtual void WriteToCommandPipe(const std::string_view message, bool flush = false) const = 0;
+
+  // Sends over the message to recovery to print it on the screen.
+  virtual void UiPrint(const std::string_view message) const = 0;
+
+  // Given the name of the block device, returns |name| for updates on the device; or the file path
+  // to the fake block device for simulations.
+  virtual std::string FindBlockDeviceName(const std::string_view name) const = 0;
+
+  virtual UpdaterRuntimeInterface* GetRuntime() const = 0;
+  virtual ZipArchiveHandle GetPackageHandle() const = 0;
+  virtual std::string GetResult() const = 0;
+  virtual uint8_t* GetMappedPackageAddress() const = 0;
+  virtual size_t GetMappedPackageLength() const = 0;
+};
diff --git a/edify/include/edify/updater_runtime_interface.h b/edify/include/edify/updater_runtime_interface.h
new file mode 100644
index 0000000..d3d26da
--- /dev/null
+++ b/edify/include/edify/updater_runtime_interface.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+// This class serves as the base to updater runtime. It wraps the runtime dependent functions; and
+// updates on device and host simulations can have different implementations. e.g. block devices
+// during host simulation merely a temporary file. With this class, the caller side in registered
+// updater's functions will stay the same for both update and simulation.
+class UpdaterRuntimeInterface {
+ public:
+  virtual ~UpdaterRuntimeInterface() = default;
+
+  // Returns true if it's a runtime instance for simulation.
+  virtual bool IsSimulator() const = 0;
+
+  // Returns the value of system property |key|. If the property doesn't exist, returns
+  // |default_value|.
+  virtual std::string GetProperty(const std::string_view key,
+                                  const std::string_view default_value) const = 0;
+
+  // Given the name of the block device, returns |name| for updates on the device; or the file path
+  // to the fake block device for simulations.
+  virtual std::string FindBlockDeviceName(const std::string_view name) const = 0;
+
+  // Mounts the |location| on |mount_point|. Returns 0 on success.
+  virtual int Mount(const std::string_view location, const std::string_view mount_point,
+                    const std::string_view fs_type, const std::string_view mount_options) = 0;
+
+  // Returns true if |mount_point| is mounted.
+  virtual bool IsMounted(const std::string_view mount_point) const = 0;
+
+  // Unmounts the |mount_point|. Returns a pair of results with the first value indicating
+  // if the |mount_point| is mounted, and the second value indicating the result of umount(2).
+  virtual std::pair<bool, int> Unmount(const std::string_view mount_point) = 0;
+
+  // Reads |filename| and puts its value to |content|.
+  virtual bool ReadFileToString(const std::string_view filename, std::string* content) const = 0;
+
+  // Updates the content of |filename| with |content|.
+  virtual bool WriteStringToFile(const std::string_view content,
+                                 const std::string_view filename) const = 0;
+
+  // Wipes the first |len| bytes of block device in |filename|.
+  virtual int WipeBlockDevice(const std::string_view filename, size_t len) const = 0;
+
+  // Starts a child process and runs the program with |args|. Uses vfork(2) if |is_vfork| is true.
+  virtual int RunProgram(const std::vector<std::string>& args, bool is_vfork) const = 0;
+
+  // Runs tune2fs with arguments |args|.
+  virtual int Tune2Fs(const std::vector<std::string>& args) const = 0;
+
+  // Dynamic partition related functions.
+  virtual bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) = 0;
+  virtual bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) = 0;
+  virtual bool UpdateDynamicPartitions(const std::string_view op_list_value) = 0;
+};
diff --git a/edify/parser.yy b/edify/parser.yy
index 3a63c37..37bcdd0 100644
--- a/edify/parser.yy
+++ b/edify/parser.yy
@@ -72,7 +72,7 @@
 
 %parse-param {std::unique_ptr<Expr>* root}
 %parse-param {int* error_count}
-%error-verbose
+%define parse.error verbose
 
 /* declarations in increasing order of precedence */
 %left ';'
diff --git a/etc/init.rc b/etc/init.rc
index 0822aba..3ec45db 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -4,6 +4,10 @@
     # Set the security context of /postinstall if present.
     restorecon /postinstall
 
+    # Copy prebuilt ld.config.txt into linkerconfig directory
+    copy /system/etc/ld.config.txt /linkerconfig/ld.config.txt
+    chmod 444 /linkerconfig/ld.config.txt
+
     start ueventd
 
     setprop sys.usb.configfs 0
@@ -44,10 +48,6 @@
 
     class_start default
 
-# Load properties from /system/ + /factory after fs mount.
-on load_system_props_action
-    load_system_props
-
 on firmware_mounts_complete
    rm /dev/.booting
 
@@ -58,11 +58,6 @@
     trigger post-fs
     trigger post-fs-data
 
-    # Load properties from /system/ + /factory after fs mount. Place
-    # this in another action so that the load will be scheduled after the prior
-    # issued fs triggers have completed.
-    trigger load_system_props_action
-
     # Remove a file to wake up anything waiting for firmware
     trigger firmware_mounts_complete
 
@@ -95,10 +90,6 @@
 on property:service.adb.root=1
     restart adbd
 
-# Always start adbd on userdebug and eng builds
-on fs && property:ro.debuggable=1
-    setprop sys.usb.config adb
-
 on fs && property:sys.usb.configfs=1
     mount configfs none /config
     mkdir /config/usb_gadget/g1 0770 shell shell
@@ -134,7 +125,7 @@
 
 on property:sys.usb.config=none && property:sys.usb.configfs=0
     stop adbd
-    stop fastboot
+    stop fastbootd
     write /sys/class/android_usb/android0/enable 0
     setprop sys.usb.state ${sys.usb.config}
 
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 14f5e4b..2023349 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -30,10 +30,10 @@
 #include "recovery_ui/ui.h"
 
 static const std::vector<std::pair<std::string, Device::BuiltinAction>> kFastbootMenuActions{
-  { "Reboot system now", Device::REBOOT },
+  { "Reboot system now", Device::REBOOT_FROM_FASTBOOT },
   { "Enter recovery", Device::ENTER_RECOVERY },
   { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
-  { "Power off", Device::SHUTDOWN },
+  { "Power off", Device::SHUTDOWN_FROM_FASTBOOT },
 };
 
 Device::BuiltinAction StartFastboot(Device* device, const std::vector<std::string>& /* args */) {
diff --git a/fsck_unshare_blocks.cpp b/fsck_unshare_blocks.cpp
deleted file mode 100644
index e74f8ba..0000000
--- a/fsck_unshare_blocks.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "fsck_unshare_blocks.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <spawn.h>
-#include <string.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/unique_fd.h>
-#include <fstab/fstab.h>
-
-#include "otautil/roots.h"
-
-static constexpr const char* SYSTEM_E2FSCK_BIN = "/system/bin/e2fsck_static";
-static constexpr const char* TMP_E2FSCK_BIN = "/tmp/e2fsck.bin";
-
-static bool copy_file(const char* source, const char* dest) {
-  android::base::unique_fd source_fd(open(source, O_RDONLY));
-  if (source_fd < 0) {
-    PLOG(ERROR) << "open %s failed" << source;
-    return false;
-  }
-
-  android::base::unique_fd dest_fd(open(dest, O_CREAT | O_WRONLY, S_IRWXU));
-  if (dest_fd < 0) {
-    PLOG(ERROR) << "open %s failed" << dest;
-    return false;
-  }
-
-  for (;;) {
-    char buf[4096];
-    ssize_t rv = read(source_fd, buf, sizeof(buf));
-    if (rv < 0) {
-      PLOG(ERROR) << "read failed";
-      return false;
-    }
-    if (rv == 0) {
-      break;
-    }
-    if (write(dest_fd, buf, rv) != rv) {
-      PLOG(ERROR) << "write failed";
-      return false;
-    }
-  }
-  return true;
-}
-
-static bool run_e2fsck(const std::string& partition) {
-  Volume* volume = volume_for_mount_point(partition);
-  if (!volume) {
-    LOG(INFO) << "No fstab entry for " << partition << ", skipping.";
-    return true;
-  }
-
-  LOG(INFO) << "Running e2fsck on device " << volume->blk_device;
-
-  std::vector<std::string> args = { TMP_E2FSCK_BIN, "-p", "-E", "unshare_blocks",
-                                    volume->blk_device };
-  std::vector<char*> argv(args.size());
-  std::transform(args.cbegin(), args.cend(), argv.begin(),
-                 [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
-  argv.push_back(nullptr);
-
-  pid_t child;
-  char* env[] = { nullptr };
-  if (posix_spawn(&child, argv[0], nullptr, nullptr, argv.data(), env)) {
-    PLOG(ERROR) << "posix_spawn failed";
-    return false;
-  }
-
-  int status = 0;
-  int ret = TEMP_FAILURE_RETRY(waitpid(child, &status, 0));
-  if (ret < 0) {
-    PLOG(ERROR) << "waitpid failed";
-    return false;
-  }
-  if (!WIFEXITED(status)) {
-    LOG(ERROR) << "e2fsck exited abnormally: " << status;
-    return false;
-  }
-  int return_code = WEXITSTATUS(status);
-  if (return_code >= 8) {
-    LOG(ERROR) << "e2fsck could not unshare blocks: " << return_code;
-    return false;
-  }
-
-  LOG(INFO) << "Successfully unshared blocks on " << partition;
-  return true;
-}
-
-bool do_fsck_unshare_blocks() {
-  // List of partitions we will try to e2fsck -E unshare_blocks.
-  std::vector<std::string> partitions = { "/odm", "/oem", "/product", "/vendor" };
-
-  // Temporarily mount system so we can copy e2fsck_static.
-  std::string system_root = get_system_root();
-  bool mounted = ensure_path_mounted_at(system_root, "/mnt/system") != -1;
-  partitions.push_back(system_root);
-
-  if (!mounted) {
-    LOG(ERROR) << "Failed to mount system image.";
-    return false;
-  }
-  if (!copy_file(SYSTEM_E2FSCK_BIN, TMP_E2FSCK_BIN)) {
-    LOG(ERROR) << "Could not copy e2fsck to /tmp.";
-    return false;
-  }
-  if (umount("/mnt/system") < 0) {
-    PLOG(ERROR) << "umount failed";
-    return false;
-  }
-
-  bool ok = true;
-  for (const auto& partition : partitions) {
-    ok &= run_e2fsck(partition);
-  }
-
-  if (ok) {
-    LOG(INFO) << "Finished running e2fsck.";
-  } else {
-    LOG(ERROR) << "Finished running e2fsck, but not all partitions succceeded.";
-  }
-  return ok;
-}
diff --git a/fsck_unshare_blocks.h b/fsck_unshare_blocks.h
deleted file mode 100644
index 9de8ef9..0000000
--- a/fsck_unshare_blocks.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _FILESYSTEM_CMDS_H
-#define _FILESYSTEM_CMDS_H
-
-bool do_fsck_unshare_blocks();
-
-#endif  // _FILESYSTEM_CMDS_H
diff --git a/fuse_sideload/Android.bp b/fuse_sideload/Android.bp
index 8548548..9bf19eb 100644
--- a/fuse_sideload/Android.bp
+++ b/fuse_sideload/Android.bp
@@ -34,6 +34,10 @@
         "include",
     ],
 
+    static_libs: [
+        "libotautil",
+    ],
+
     shared_libs: [
         "libbase",
         "libcrypto",
diff --git a/fuse_sideload/fuse_provider.cpp b/fuse_sideload/fuse_provider.cpp
index 58786f5..8fa1b5c 100644
--- a/fuse_sideload/fuse_provider.cpp
+++ b/fuse_sideload/fuse_provider.cpp
@@ -27,8 +27,11 @@
 #include <functional>
 
 #include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
 
 #include "fuse_sideload.h"
+#include "otautil/sysutil.h"
 
 FuseFileDataProvider::FuseFileDataProvider(const std::string& path, uint32_t block_size) {
   struct stat sb;
@@ -46,6 +49,11 @@
   fuse_block_size_ = block_size;
 }
 
+std::unique_ptr<FuseDataProvider> FuseFileDataProvider::CreateFromFile(const std::string& path,
+                                                                       uint32_t block_size) {
+  return std::make_unique<FuseFileDataProvider>(path, block_size);
+}
+
 bool FuseFileDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
                                                 uint32_t start_block) const {
   uint64_t offset = static_cast<uint64_t>(start_block) * fuse_block_size_;
@@ -69,3 +77,79 @@
 void FuseFileDataProvider::Close() {
   fd_.reset();
 }
+
+FuseBlockDataProvider::FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size,
+                                             android::base::unique_fd&& fd,
+                                             uint32_t source_block_size, RangeSet ranges)
+    : FuseDataProvider(file_size, fuse_block_size),
+      fd_(std::move(fd)),
+      source_block_size_(source_block_size),
+      ranges_(std::move(ranges)) {
+  // Make sure the offset is also aligned with the blocks on the block device when we call
+  // ReadBlockAlignedData().
+  CHECK_EQ(0, fuse_block_size_ % source_block_size_);
+}
+
+bool FuseBlockDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                                                 uint32_t start_block) const {
+  uint64_t offset = static_cast<uint64_t>(start_block) * fuse_block_size_;
+  if (fetch_size > file_size_ || offset > file_size_ - fetch_size) {
+    LOG(ERROR) << "Out of bound read, offset: " << offset << ", fetch size: " << fetch_size
+               << ", file size " << file_size_;
+    return false;
+  }
+
+  auto read_ranges =
+      ranges_.GetSubRanges(offset / source_block_size_, fetch_size / source_block_size_);
+  if (!read_ranges) {
+    return false;
+  }
+
+  uint8_t* next_out = buffer;
+  for (const auto& [range_start, range_end] : read_ranges.value()) {
+    uint64_t bytes_start = static_cast<uint64_t>(range_start) * source_block_size_;
+    uint64_t bytes_to_read = static_cast<uint64_t>(range_end - range_start) * source_block_size_;
+    if (!android::base::ReadFullyAtOffset(fd_, next_out, bytes_to_read, bytes_start)) {
+      PLOG(ERROR) << "Failed to read " << bytes_to_read << " bytes at offset " << bytes_start;
+      return false;
+    }
+
+    next_out += bytes_to_read;
+  }
+
+  if (uint64_t tailing_bytes = fetch_size % source_block_size_; tailing_bytes != 0) {
+    // Calculate the offset to last partial block.
+    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_;
+    if (!android::base::ReadFullyAtOffset(fd_, next_out, tailing_bytes, tailing_offset)) {
+      PLOG(ERROR) << "Failed to read tailing " << tailing_bytes << " bytes at offset "
+                  << tailing_offset;
+      return false;
+    }
+  }
+  return true;
+}
+
+std::unique_ptr<FuseDataProvider> FuseBlockDataProvider::CreateFromBlockMap(
+    const std::string& block_map_path, uint32_t fuse_block_size) {
+  auto block_map = BlockMapData::ParseBlockMapFile(block_map_path);
+  if (!block_map) {
+    return nullptr;
+  }
+
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_map.path().c_str(), O_RDONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open " << block_map.path();
+    return nullptr;
+  }
+
+  return std::unique_ptr<FuseBlockDataProvider>(
+      new FuseBlockDataProvider(block_map.file_size(), fuse_block_size, std::move(fd),
+                                block_map.block_size(), block_map.block_ranges()));
+}
+
+void FuseBlockDataProvider::Close() {
+  fd_.reset();
+}
diff --git a/fuse_sideload/include/fuse_provider.h b/fuse_sideload/include/fuse_provider.h
index 59059cf..3cdaef3 100644
--- a/fuse_sideload/include/fuse_provider.h
+++ b/fuse_sideload/include/fuse_provider.h
@@ -18,10 +18,13 @@
 
 #include <stdint.h>
 
+#include <memory>
 #include <string>
 
 #include <android-base/unique_fd.h>
 
+#include "otautil/rangeset.h"
+
 // This is the base class to read data from source and provide the data to FUSE.
 class FuseDataProvider {
  public:
@@ -41,6 +44,8 @@
   virtual bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
                                     uint32_t start_block) const = 0;
 
+  virtual bool Valid() const = 0;
+
   virtual void Close() {}
 
  protected:
@@ -57,10 +62,13 @@
  public:
   FuseFileDataProvider(const std::string& path, uint32_t block_size);
 
+  static std::unique_ptr<FuseDataProvider> CreateFromFile(const std::string& path,
+                                                          uint32_t block_size);
+
   bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
                             uint32_t start_block) const override;
 
-  bool Valid() const {
+  bool Valid() const override {
     return fd_ != -1;
   }
 
@@ -70,3 +78,34 @@
   // The underlying source to read data from.
   android::base::unique_fd fd_;
 };
+
+// This class parses a block map and reads data from the underlying block device.
+class FuseBlockDataProvider : public FuseDataProvider {
+ public:
+  // Constructs the fuse provider from the block map.
+  static std::unique_ptr<FuseDataProvider> CreateFromBlockMap(const std::string& block_map_path,
+                                                              uint32_t fuse_block_size);
+
+  RangeSet ranges() const {
+    return ranges_;
+  }
+
+  bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                            uint32_t start_block) const override;
+
+  bool Valid() const override {
+    return fd_ != -1;
+  }
+
+  void Close() override;
+
+ private:
+  FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size, android::base::unique_fd&& fd,
+                        uint32_t source_block_size, RangeSet ranges);
+  // The underlying block device to read data from.
+  android::base::unique_fd fd_;
+  // The block size of the source block device.
+  uint32_t source_block_size_;
+  // The block ranges from the source block device that consist of the file
+  RangeSet ranges_;
+};
diff --git a/install/Android.bp b/install/Android.bp
index ea893a0..bed3bc5 100644
--- a/install/Android.bp
+++ b/install/Android.bp
@@ -19,10 +19,6 @@
         "recovery_defaults",
     ],
 
-    header_libs: [
-        "libminadbd_headers",
-    ],
-
     shared_libs: [
         "libbase",
         "libbootloader_message",
@@ -32,7 +28,6 @@
         "libfusesideload",
         "libhidl-gen-utils",
         "libhidlbase",
-        "libhidltransport",
         "liblog",
         "libselinux",
         "libtinyxml2",
@@ -42,12 +37,12 @@
     ],
 
     static_libs: [
+        "librecovery_utils",
         "libotautil",
+        "libsnapshot_nobinder",
 
         // external dependencies
-        "libvintf_recovery",
         "libvintf",
-        "libfstab",
     ],
 }
 
@@ -62,11 +57,17 @@
     srcs: [
         "adb_install.cpp",
         "asn1_decoder.cpp",
-        "fuse_sdcard_install.cpp",
+        "fuse_install.cpp",
         "install.cpp",
         "package.cpp",
+        "snapshot_utils.cpp",
         "verifier.cpp",
         "wipe_data.cpp",
+        "wipe_device.cpp",
+    ],
+
+    header_libs: [
+        "libminadbd_headers",
     ],
 
     shared_libs: [
diff --git a/install/adb_install.cpp b/install/adb_install.cpp
index 9497df5..ee79a32 100644
--- a/install/adb_install.cpp
+++ b/install/adb_install.cpp
@@ -44,7 +44,7 @@
 #include "fuse_sideload.h"
 #include "install/install.h"
 #include "install/wipe_data.h"
-#include "minadbd_types.h"
+#include "minadbd/types.h"
 #include "otautil/sysutil.h"
 #include "recovery_ui/device.h"
 #include "recovery_ui/ui.h"
@@ -90,7 +90,7 @@
 
 // Installs the package from FUSE. Returns the installation result and whether it should continue
 // waiting for new commands.
-static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) {
+static auto AdbInstallPackageHandler(RecoveryUI* ui, 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
@@ -110,7 +110,11 @@
         break;
       }
     }
-    *result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0, ui);
+
+    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);
     break;
   }
 
@@ -120,7 +124,7 @@
   return std::make_pair(*result == INSTALL_SUCCESS, should_continue);
 }
 
-static auto AdbRebootHandler(MinadbdCommand command, int* result,
+static auto AdbRebootHandler(MinadbdCommand command, InstallResult* result,
                              Device::BuiltinAction* reboot_action) {
   // Use Device::REBOOT_{FASTBOOT,RECOVERY,RESCUE}, instead of the ones with ENTER_. This allows
   // rebooting back into fastboot/recovery/rescue mode through bootloader, which may use a newly
@@ -331,7 +335,7 @@
   signal(SIGPIPE, SIG_DFL);
 }
 
-int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) {
+InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) {
   // Save the usb state to restore after the sideload operation.
   std::string usb_state = android::base::GetProperty("sys.usb.state", "none");
   // Clean up state and stop adbd.
@@ -342,7 +346,7 @@
 
   RecoveryUI* ui = device->GetUI();
 
-  int install_result = INSTALL_ERROR;
+  InstallResult install_result = INSTALL_ERROR;
   std::map<MinadbdCommand, CommandFunction> command_map{
     { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) },
     { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid,
diff --git a/install/fuse_sdcard_install.cpp b/install/fuse_install.cpp
similarity index 73%
rename from install/fuse_sdcard_install.cpp
rename to install/fuse_install.cpp
index 1aa8768..143b5d3 100644
--- a/install/fuse_sdcard_install.cpp
+++ b/install/fuse_install.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "install/fuse_sdcard_install.h"
+#include "install/fuse_install.h"
 
 #include <dirent.h>
 #include <signal.h>
@@ -27,6 +27,7 @@
 #include <algorithm>
 #include <functional>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include <android-base/logging.h>
@@ -36,7 +37,7 @@
 #include "fuse_provider.h"
 #include "fuse_sideload.h"
 #include "install/install.h"
-#include "otautil/roots.h"
+#include "recovery_utils/roots.h"
 
 static constexpr const char* SDCARD_ROOT = "/sdcard";
 // How long (in seconds) we wait for the fuse-provided package file to
@@ -74,7 +75,8 @@
       // Skip "." and ".." entries.
       if (name == "." || name == "..") continue;
       dirs.push_back(name + "/");
-    } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) {
+    } else if (de->d_type == DT_REG && (android::base::EndsWithIgnoreCase(name, ".zip") ||
+                                        android::base::EndsWithIgnoreCase(name, ".map"))) {
       entries.push_back(name);
     }
   }
@@ -119,49 +121,44 @@
   // Unreachable.
 }
 
-static bool StartSdcardFuse(const std::string& path) {
-  auto file_data_reader = std::make_unique<FuseFileDataProvider>(path, 65536);
-
-  if (!file_data_reader->Valid()) {
+static bool StartInstallPackageFuse(std::string_view path) {
+  if (path.empty()) {
     return false;
   }
 
-  // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so
-  // that our open file continues to work but new references see it as unmounted.
-  umount2("/sdcard", MNT_DETACH);
+  constexpr auto FUSE_BLOCK_SIZE = 65536;
+  bool is_block_map = android::base::ConsumePrefix(&path, "@");
+  auto fuse_data_provider =
+      is_block_map ? FuseBlockDataProvider::CreateFromBlockMap(std::string(path), FUSE_BLOCK_SIZE)
+                   : FuseFileDataProvider::CreateFromFile(std::string(path), FUSE_BLOCK_SIZE);
 
-  return run_fuse_sideload(std::move(file_data_reader)) == 0;
+  if (!fuse_data_provider || !fuse_data_provider->Valid()) {
+    LOG(ERROR) << "Failed to create fuse data provider.";
+    return false;
+  }
+
+  if (android::base::StartsWith(path, SDCARD_ROOT)) {
+    // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so
+    // that our open file continues to work but new references see it as unmounted.
+    umount2(SDCARD_ROOT, MNT_DETACH);
+  }
+
+  return run_fuse_sideload(std::move(fuse_data_provider)) == 0;
 }
 
-int ApplyFromSdcard(Device* device, RecoveryUI* ui) {
-  if (ensure_path_mounted(SDCARD_ROOT) != 0) {
-    LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n";
-    return INSTALL_ERROR;
-  }
-
-  std::string path = BrowseDirectory(SDCARD_ROOT, device, ui);
-  if (path.empty()) {
-    LOG(ERROR) << "\n-- No package file selected.\n";
-    ensure_path_unmounted(SDCARD_ROOT);
-    return INSTALL_ERROR;
-  }
-
-  ui->Print("\n-- Install %s ...\n", path.c_str());
-  SetSdcardUpdateBootloaderMessage();
-
+InstallResult InstallWithFuseFromPath(std::string_view path, RecoveryUI* ui) {
   // 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)
   pid_t child;
   if ((child = fork()) == 0) {
-    bool status = StartSdcardFuse(path);
+    bool status = StartInstallPackageFuse(path);
 
     _exit(status ? EXIT_SUCCESS : EXIT_FAILURE);
   }
 
-  // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child
-  // process is ready.
-  int result = INSTALL_ERROR;
+  // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child process is ready.
+  InstallResult result = INSTALL_ERROR;
   int status;
   bool waited = false;
   for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) {
@@ -183,8 +180,11 @@
         break;
       }
     }
-
-    result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /*retry_count*/, ui);
+    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);
     break;
   }
 
@@ -201,6 +201,32 @@
     LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status);
   }
 
+  return result;
+}
+
+InstallResult ApplyFromSdcard(Device* device) {
+  auto ui = device->GetUI();
+  if (ensure_path_mounted(SDCARD_ROOT) != 0) {
+    LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n";
+    return INSTALL_ERROR;
+  }
+
+  std::string path = BrowseDirectory(SDCARD_ROOT, device, ui);
+  if (path.empty()) {
+    LOG(ERROR) << "\n-- No package file selected.\n";
+    ensure_path_unmounted(SDCARD_ROOT);
+    return INSTALL_ERROR;
+  }
+
+  // Hint the install function to read from a block map file.
+  if (android::base::EndsWithIgnoreCase(path, ".map")) {
+    path = "@" + path;
+  }
+
+  ui->Print("\n-- Install %s ...\n", path.c_str());
+  SetSdcardUpdateBootloaderMessage();
+
+  auto result = InstallWithFuseFromPath(path, ui);
   ensure_path_unmounted(SDCARD_ROOT);
   return result;
 }
diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h
index 3a0a817..8800223 100644
--- a/install/include/install/adb_install.h
+++ b/install/include/install/adb_install.h
@@ -16,9 +16,10 @@
 
 #pragma once
 
-#include <recovery_ui/device.h>
+#include "install/install.h"
+#include "recovery_ui/device.h"
 
-// Applies a package via `adb sideload` or `adb rescue`. Returns the install result (in `enum
-// InstallResult`). When a reboot has been requested, INSTALL_REBOOT will be the return value, with
-// the reboot target set in reboot_action.
-int ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action);
+// Applies a package via `adb sideload` or `adb rescue`. Returns the install result. When a reboot
+// has been requested, INSTALL_REBOOT will be the return value, with the reboot target set in
+// reboot_action.
+InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action);
diff --git a/install/include/install/fuse_sdcard_install.h b/install/include/install/fuse_install.h
similarity index 60%
copy from install/include/install/fuse_sdcard_install.h
copy to install/include/install/fuse_install.h
index d9214ca..63b116a 100644
--- a/install/include/install/fuse_sdcard_install.h
+++ b/install/include/install/fuse_install.h
@@ -16,7 +16,15 @@
 
 #pragma once
 
+#include <string_view>
+
+#include "install/install.h"
 #include "recovery_ui/device.h"
 #include "recovery_ui/ui.h"
 
-int ApplyFromSdcard(Device* device, RecoveryUI* ui);
+// 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 ApplyFromSdcard(Device* device);
diff --git a/install/include/install/install.h b/install/include/install/install.h
index ed0f6c7..bef23e9 100644
--- a/install/include/install/install.h
+++ b/install/include/install/install.h
@@ -44,11 +44,12 @@
   BRICK,
 };
 
-// Installs the given update package. This function should also wipe the cache partition after a
-// successful installation if |should_wipe_cache| is true or an updater command asks to wipe the
-// cache.
-int install_package(const std::string& package, bool should_wipe_cache, bool needs_mount,
-                    int retry_count, RecoveryUI* ui);
+// Installs the given update package. The package_id is a string provided by the caller (e.g. the
+// package path) to identify the package and log to last_install. This function should also wipe the
+// 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);
 
 // Verifies the package by ota keys. Returns true if the package is verified successfully,
 // otherwise returns false.
@@ -58,17 +59,10 @@
 // result to |metadata|. Return true if succeed, otherwise return false.
 bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata);
 
-// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe.
-std::vector<std::string> GetWipePartitionList(Package* wipe_package);
-
-// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
-// entry doesn't exist.
-bool verify_package_compatibility(ZipArchiveHandle package_zip);
-
-// Checks if the the metadata in the OTA package has expected values. Returns 0 on success.
-// Mandatory checks: ota-type, pre-device and serial number(if presents)
-// AB OTA specific checks: pre-build version, fingerprint, timestamp.
-int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
+// Checks if the metadata in the OTA package has expected values. Mandatory checks: ota-type,
+// pre-device and serial number (if presents). A/B OTA specific checks: pre-build version,
+// fingerprint, timestamp.
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
 
 // Ensures the path to the update package is mounted. Also set the |should_use_fuse| to true if the
 // package stays on a removable media.
diff --git a/install/include/install/package.h b/install/include/install/package.h
index cd44d10..0b42332 100644
--- a/install/include/install/package.h
+++ b/install/include/install/package.h
@@ -28,6 +28,11 @@
 
 #include "verifier.h"
 
+enum class PackageType {
+  kMemory,
+  kFile,
+};
+
 // This class serves as a wrapper for an OTA update package. It aims to provide the common
 // interface for both packages loaded in memory and packages read from fd.
 class Package : public VerifierInterface {
@@ -41,6 +46,10 @@
 
   virtual ~Package() = default;
 
+  virtual PackageType GetType() const = 0;
+
+  virtual std::string GetPath() const = 0;
+
   // Opens the package as a zip file and returns the ZipArchiveHandle.
   virtual ZipArchiveHandle GetZipArchiveHandle() = 0;
 
diff --git a/install/include/install/fuse_sdcard_install.h b/install/include/install/snapshot_utils.h
similarity index 62%
copy from install/include/install/fuse_sdcard_install.h
copy to install/include/install/snapshot_utils.h
index d9214ca..f4b978d 100644
--- a/install/include/install/fuse_sdcard_install.h
+++ b/install/include/install/snapshot_utils.h
@@ -17,6 +17,14 @@
 #pragma once
 
 #include "recovery_ui/device.h"
-#include "recovery_ui/ui.h"
 
-int ApplyFromSdcard(Device* device, RecoveryUI* ui);
+bool FinishPendingSnapshotMerges(Device* device);
+
+/*
+ * This function tries to create the snapshotted devices in the case a Virtual
+ * A/B device is updating.
+ * The function returns false in case of critical failure that would prevent
+ * the further mountings of devices, or true in case of success, if either the
+ * devices were created or there was no need to.
+ */
+bool CreateSnapshotPartitions();
diff --git a/install/include/install/fuse_sdcard_install.h b/install/include/install/wipe_device.h
similarity index 63%
rename from install/include/install/fuse_sdcard_install.h
rename to install/include/install/wipe_device.h
index d9214ca..c60b999 100644
--- a/install/include/install/fuse_sdcard_install.h
+++ b/install/include/install/wipe_device.h
@@ -16,7 +16,14 @@
 
 #pragma once
 
-#include "recovery_ui/device.h"
-#include "recovery_ui/ui.h"
+#include <string>
+#include <vector>
 
-int ApplyFromSdcard(Device* device, RecoveryUI* ui);
+#include "install/package.h"
+#include "recovery_ui/device.h"
+
+// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE.
+bool WipeAbDevice(Device* device, size_t wipe_package_size);
+
+// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe.
+std::vector<std::string> GetWipePartitionList(Package* wipe_package);
diff --git a/install/include/private/setup_commands.h b/install/include/private/setup_commands.h
index 7fdc741..dcff761 100644
--- a/install/include/private/setup_commands.h
+++ b/install/include/private/setup_commands.h
@@ -27,13 +27,13 @@
 // |zip| located at |package|. Stores the command line that should be called into |cmd|. The
 // |status_fd| is the file descriptor the child process should use to report back the progress of
 // the update.
-int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
-                             int status_fd, std::vector<std::string>* cmd);
+bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
+                              int status_fd, std::vector<std::string>* cmd);
 
 // Sets up the commands for an A/B update. Extracts the needed entries from the open zip archive
 // |zip| located at |package|. Stores the command line that should be called into |cmd|. The
 // |status_fd| is the file descriptor the child process should use to report back the progress of
 // the update. Note that since this applies to the sideloading flow only, it takes one less
 // parameter |retry_count| than the non-A/B version.
-int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
-                          std::vector<std::string>* cmd);
+bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+                           std::vector<std::string>* cmd);
diff --git a/install/install.cpp b/install/install.cpp
index 9203ef0..1c9bf2f 100644
--- a/install/install.cpp
+++ b/install/install.cpp
@@ -45,23 +45,23 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <vintf/VintfObjectRecovery.h>
 
 #include "install/package.h"
 #include "install/verifier.h"
 #include "install/wipe_data.h"
 #include "otautil/error_code.h"
 #include "otautil/paths.h"
-#include "otautil/roots.h"
 #include "otautil/sysutil.h"
-#include "otautil/thermalutil.h"
 #include "private/setup_commands.h"
 #include "recovery_ui/ui.h"
+#include "recovery_utils/roots.h"
+#include "recovery_utils/thermalutil.h"
 
 using namespace std::chrono_literals;
 
 static constexpr int kRecoveryApiVersion = 3;
-// Assert the version defined in code and in Android.mk are consistent.
+// We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed
+// into target_files.zip. Assert the version defined in code and in Android.mk are consistent.
 static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions.");
 
 // Default allocation of progress bar segments to operations
@@ -74,9 +74,8 @@
   CHECK(metadata != nullptr);
 
   static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
-  ZipString path(METADATA_PATH);
   ZipEntry entry;
-  if (FindEntry(zip, path, &entry) != 0) {
+  if (FindEntry(zip, METADATA_PATH, &entry) != 0) {
     LOG(ERROR) << "Failed to find " << METADATA_PATH;
     return false;
   }
@@ -140,14 +139,14 @@
 // Checks the build version, fingerprint and timestamp in the metadata of the A/B package.
 // Downgrading is not allowed unless explicitly enabled in the package and only for
 // incremental packages.
-static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
+static bool CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
   // Incremental updates should match the current build.
   auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", "");
   auto pkg_pre_build = get_value(metadata, "pre-build-incremental");
   if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) {
     LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected "
                << device_pre_build;
-    return INSTALL_ERROR;
+    return false;
   }
 
   auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", "");
@@ -155,7 +154,7 @@
   if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) {
     LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected "
                << device_fingerprint;
-    return INSTALL_ERROR;
+    return false;
   }
 
   // Check for downgrade version.
@@ -173,36 +172,36 @@
                     "newer than timestamp "
                  << build_timestamp << " but package has timestamp " << pkg_post_timestamp
                  << " and downgrade not allowed.";
-      return INSTALL_ERROR;
+      return false;
     }
     if (pkg_pre_build_fingerprint.empty()) {
       LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
-      return INSTALL_ERROR;
+      return false;
     }
   }
 
-  return 0;
+  return true;
 }
 
-int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
   auto package_ota_type = get_value(metadata, "ota-type");
   auto expected_ota_type = OtaTypeToString(ota_type);
   if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) {
     LOG(INFO) << "Skip package metadata check for ota type " << expected_ota_type;
-    return 0;
+    return true;
   }
 
   if (package_ota_type != expected_ota_type) {
     LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual "
                << package_ota_type;
-    return INSTALL_ERROR;
+    return false;
   }
 
   auto device = android::base::GetProperty("ro.product.device", "");
   auto pkg_device = get_value(metadata, "pre-device");
   if (pkg_device != device || pkg_device.empty()) {
     LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device;
-    return INSTALL_ERROR;
+    return false;
   }
 
   // We allow the package to not have any serialno; and we also allow it to carry multiple serial
@@ -219,7 +218,7 @@
     }
     if (!serial_number_match) {
       LOG(ERROR) << "Package is for serial " << pkg_serial_no;
-      return INSTALL_ERROR;
+      return false;
     }
   }
 
@@ -227,21 +226,20 @@
     return CheckAbSpecificMetadata(metadata);
   }
 
-  return 0;
+  return true;
 }
 
-int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
-                          std::vector<std::string>* cmd) {
+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.
   static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
-  ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES);
   ZipEntry properties_entry;
-  if (FindEntry(zip, property_name, &properties_entry) != 0) {
+  if (FindEntry(zip, AB_OTA_PAYLOAD_PROPERTIES, &properties_entry) != 0) {
     LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES;
-    return INSTALL_CORRUPT;
+    return false;
   }
   uint32_t properties_entry_length = properties_entry.uncompressed_length;
   std::vector<uint8_t> payload_properties(properties_entry_length);
@@ -249,15 +247,14 @@
       ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length);
   if (err != 0) {
     LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err);
-    return INSTALL_CORRUPT;
+    return false;
   }
 
   static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
-  ZipString payload_name(AB_OTA_PAYLOAD);
   ZipEntry payload_entry;
-  if (FindEntry(zip, payload_name, &payload_entry) != 0) {
+  if (FindEntry(zip, AB_OTA_PAYLOAD, &payload_entry) != 0) {
     LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD;
-    return INSTALL_CORRUPT;
+    return false;
   }
   long payload_offset = payload_entry.offset;
   *cmd = {
@@ -267,20 +264,19 @@
     "--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
     android::base::StringPrintf("--status_fd=%d", status_fd),
   };
-  return 0;
+  return true;
 }
 
-int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
-                             int status_fd, std::vector<std::string>* cmd) {
+bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
+                              int status_fd, std::vector<std::string>* cmd) {
   CHECK(cmd != nullptr);
 
   // In non-A/B updates we extract the update binary from the package.
   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
-  ZipString binary_name(UPDATE_BINARY_NAME);
   ZipEntry binary_entry;
-  if (FindEntry(zip, binary_name, &binary_entry) != 0) {
+  if (FindEntry(zip, UPDATE_BINARY_NAME, &binary_entry) != 0) {
     LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
-    return INSTALL_CORRUPT;
+    return false;
   }
 
   const std::string binary_path = Paths::Get().temporary_update_binary();
@@ -289,13 +285,12 @@
       open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755));
   if (fd == -1) {
     PLOG(ERROR) << "Failed to create " << binary_path;
-    return INSTALL_ERROR;
+    return false;
   }
 
-  int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
-  if (error != 0) {
+  if (auto error = ExtractEntryToFile(zip, &binary_entry, fd); error != 0) {
     LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
-    return INSTALL_ERROR;
+    return false;
   }
 
   // When executing the update binary contained in the package, the arguments passed are:
@@ -312,7 +307,7 @@
   if (retry_count > 0) {
     cmd->push_back("retry");
   }
-  return 0;
+  return true;
 }
 
 static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) {
@@ -326,21 +321,25 @@
 }
 
 // If the package contains an update binary, extract it and run it.
-static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
-                             std::vector<std::string>* log_buffer, int retry_count,
-                             int* max_temperature, RecoveryUI* ui) {
+static InstallResult TryUpdateBinary(Package* package, bool* wipe_cache,
+                                     std::vector<std::string>* log_buffer, int retry_count,
+                                     int* max_temperature, RecoveryUI* ui) {
   std::map<std::string, std::string> metadata;
+  auto zip = package->GetZipArchiveHandle();
   if (!ReadMetadataFromPackage(zip, &metadata)) {
     LOG(ERROR) << "Failed to parse metadata in the zip file";
     return INSTALL_CORRUPT;
   }
 
   bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
-  // Verifies against the metadata in the package first.
-  if (int check_status = is_ab ? CheckPackageMetadata(metadata, OtaType::AB) : 0;
-      check_status != 0) {
+  if (is_ab) {
+    CHECK(package->GetType() == PackageType::kFile);
+  }
+
+  // Verify against the metadata in the package first.
+  if (is_ab && !CheckPackageMetadata(metadata, OtaType::AB)) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
-    return check_status;
+    return INSTALL_ERROR;
   }
 
   ReadSourceTargetBuild(metadata, log_buffer);
@@ -386,13 +385,15 @@
   //       updater requests logging the string (e.g. cause of the failure).
   //
 
+  std::string package_path = package->GetPath();
+
   std::vector<std::string> args;
-  if (int update_status =
-          is_ab ? SetUpAbUpdateCommands(package, zip, pipe_write.get(), &args)
-                : SetUpNonAbUpdateCommands(package, zip, retry_count, pipe_write.get(), &args);
-      update_status != 0) {
+  if (auto setup_result =
+          is_ab ? SetUpAbUpdateCommands(package_path, zip, pipe_write.get(), &args)
+                : SetUpNonAbUpdateCommands(package_path, zip, retry_count, pipe_write.get(), &args);
+      !setup_result) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
-    return update_status;
+    return INSTALL_CORRUPT;
   }
 
   pid_t pid = fork();
@@ -491,11 +492,11 @@
   }
   if (WIFEXITED(status)) {
     if (WEXITSTATUS(status) != EXIT_SUCCESS) {
-      LOG(ERROR) << "Error in " << package << " (status " << WEXITSTATUS(status) << ")";
+      LOG(ERROR) << "Error in " << package_path << " (status " << WEXITSTATUS(status) << ")";
       return INSTALL_ERROR;
     }
   } else if (WIFSIGNALED(status)) {
-    LOG(ERROR) << "Error in " << package << " (killed by signal " << WTERMSIG(status) << ")";
+    LOG(ERROR) << "Error in " << package_path << " (killed by signal " << WTERMSIG(status) << ")";
     return INSTALL_ERROR;
   } else {
     LOG(FATAL) << "Invalid status code " << status;
@@ -504,153 +505,57 @@
   return INSTALL_SUCCESS;
 }
 
-// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the
-// entry doesn't exist. Note that the compatibility info is packed in a zip file inside the OTA
-// package.
-bool verify_package_compatibility(ZipArchiveHandle package_zip) {
-  LOG(INFO) << "Verifying package compatibility...";
-
-  static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip";
-  ZipString compatibility_entry_name(COMPATIBILITY_ZIP_ENTRY);
-  ZipEntry compatibility_entry;
-  if (FindEntry(package_zip, compatibility_entry_name, &compatibility_entry) != 0) {
-    LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry";
-    return true;
-  }
-
-  std::string zip_content(compatibility_entry.uncompressed_length, '\0');
-  int32_t ret;
-  if ((ret = ExtractToMemory(package_zip, &compatibility_entry,
-                             reinterpret_cast<uint8_t*>(&zip_content[0]),
-                             compatibility_entry.uncompressed_length)) != 0) {
-    LOG(ERROR) << "Failed to read " << COMPATIBILITY_ZIP_ENTRY << ": " << ErrorCodeString(ret);
-    return false;
-  }
-
-  ZipArchiveHandle zip_handle;
-  ret = OpenArchiveFromMemory(static_cast<void*>(const_cast<char*>(zip_content.data())),
-                              zip_content.size(), COMPATIBILITY_ZIP_ENTRY, &zip_handle);
-  if (ret != 0) {
-    LOG(ERROR) << "Failed to OpenArchiveFromMemory: " << ErrorCodeString(ret);
-    return false;
-  }
-
-  // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents.
-  void* cookie;
-  ret = StartIteration(zip_handle, &cookie, nullptr, nullptr);
-  if (ret != 0) {
-    LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret);
-    CloseArchive(zip_handle);
-    return false;
-  }
-  std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
-
-  std::vector<std::string> compatibility_info;
-  ZipEntry info_entry;
-  ZipString info_name;
-  while (Next(cookie, &info_entry, &info_name) == 0) {
-    std::string content(info_entry.uncompressed_length, '\0');
-    int32_t ret = ExtractToMemory(zip_handle, &info_entry, reinterpret_cast<uint8_t*>(&content[0]),
-                                  info_entry.uncompressed_length);
-    if (ret != 0) {
-      LOG(ERROR) << "Failed to read " << info_name.name << ": " << ErrorCodeString(ret);
-      CloseArchive(zip_handle);
-      return false;
-    }
-    compatibility_info.emplace_back(std::move(content));
-  }
-  CloseArchive(zip_handle);
-
-  // VintfObjectRecovery::CheckCompatibility returns zero on success.
-  std::string err;
-  int result = android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err);
-  if (result == 0) {
-    return true;
-  }
-
-  LOG(ERROR) << "Failed to verify package compatibility (result " << result << "): " << err;
-  return false;
-}
-
-static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
-                                  std::vector<std::string>* log_buffer, int retry_count,
-                                  int* max_temperature, RecoveryUI* ui) {
+static InstallResult VerifyAndInstallPackage(Package* package, bool* wipe_cache,
+                                             std::vector<std::string>* log_buffer, int retry_count,
+                                             int* max_temperature, RecoveryUI* ui) {
   ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
-  ui->Print("Finding update package...\n");
   // Give verification half the progress bar...
   ui->SetProgressType(RecoveryUI::DETERMINATE);
   ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
-  LOG(INFO) << "Update location: " << path;
-
-  // Map the update package into memory.
-  ui->Print("Opening update package...\n");
-
-  if (needs_mount) {
-    if (path[0] == '@') {
-      ensure_path_mounted(path.substr(1));
-    } else {
-      ensure_path_mounted(path);
-    }
-  }
-
-  auto package = Package::CreateMemoryPackage(
-      path, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
-  if (!package) {
-    log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
-    return INSTALL_CORRUPT;
-  }
 
   // Verify package.
-  if (!verify_package(package.get(), ui)) {
+  if (!verify_package(package, ui)) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
     return INSTALL_CORRUPT;
   }
 
-  // Try to open the package.
-  ZipArchiveHandle zip = package->GetZipArchiveHandle();
-  if (!zip) {
-    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
-    return INSTALL_CORRUPT;
-  }
-
-  // Additionally verify the compatibility of the package if it's a fresh install.
-  if (retry_count == 0 && !verify_package_compatibility(zip)) {
-    log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
-    return INSTALL_CORRUPT;
-  }
-
   // Verify and install the contents of the package.
   ui->Print("Installing update...\n");
   if (retry_count > 0) {
     ui->Print("Retry attempt: %d\n", retry_count);
   }
   ui->SetEnableReboot(false);
-  int result =
-      try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
+  auto result = TryUpdateBinary(package, wipe_cache, log_buffer, retry_count, max_temperature, ui);
   ui->SetEnableReboot(true);
   ui->Print("\n");
 
   return result;
 }
 
-int install_package(const std::string& path, bool should_wipe_cache, bool needs_mount,
-                    int retry_count, RecoveryUI* ui) {
-  CHECK(!path.empty());
-
+InstallResult InstallPackage(Package* package, const std::string_view package_id,
+                             bool should_wipe_cache, int retry_count, RecoveryUI* ui) {
   auto start = std::chrono::system_clock::now();
 
   int start_temperature = GetMaxValueFromThermalZone();
   int max_temperature = start_temperature;
 
-  int result;
+  InstallResult result;
   std::vector<std::string> log_buffer;
-  if (setup_install_mounts() != 0) {
+
+  ui->Print("Supported API: %d\n", kRecoveryApiVersion);
+
+  ui->Print("Finding update package...\n");
+  LOG(INFO) << "Update package id: " << package_id;
+  if (!package) {
+    log_buffer.push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
+    result = INSTALL_CORRUPT;
+  } else if (setup_install_mounts() != 0) {
     LOG(ERROR) << "failed to set up expected mounts for install; aborting";
     result = INSTALL_ERROR;
   } else {
     bool updater_wipe_cache = false;
-    result = really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer,
-                                    retry_count, &max_temperature, ui);
+    result = VerifyAndInstallPackage(package, &updater_wipe_cache, &log_buffer, retry_count,
+                                     &max_temperature, ui);
     should_wipe_cache = should_wipe_cache || updater_wipe_cache;
   }
 
@@ -678,7 +583,7 @@
 
   // The first two lines need to be the package name and install result.
   std::vector<std::string> log_header = {
-    path,
+    std::string(package_id),
     result == INSTALL_SUCCESS ? "1" : "0",
     "time_total: " + std::to_string(time_total),
     "retry: " + std::to_string(retry_count),
diff --git a/install/package.cpp b/install/package.cpp
index 4402f48..86fc064 100644
--- a/install/package.cpp
+++ b/install/package.cpp
@@ -40,12 +40,20 @@
 
   ~MemoryPackage() override;
 
+  PackageType GetType() const override {
+    return PackageType::kMemory;
+  }
+
   // Memory maps the package file if necessary. Initializes the start address and size of the
   // package.
   uint64_t GetPackageSize() const override {
     return package_size_;
   }
 
+  std::string GetPath() const override {
+    return path_;
+  }
+
   bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override;
 
   ZipArchiveHandle GetZipArchiveHandle() override;
@@ -82,10 +90,18 @@
 
   ~FilePackage() override;
 
+  PackageType GetType() const override {
+    return PackageType::kFile;
+  }
+
   uint64_t GetPackageSize() const override {
     return package_size_;
   }
 
+  std::string GetPath() const override {
+    return path_;
+  }
+
   bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override;
 
   ZipArchiveHandle GetZipArchiveHandle() override;
@@ -253,7 +269,7 @@
     return zip_handle_;
   }
 
-  if (auto err = OpenArchiveFd(fd_.get(), path_.c_str(), &zip_handle_); err != 0) {
+  if (auto err = OpenArchiveFd(fd_.get(), path_.c_str(), &zip_handle_, false); err != 0) {
     LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err);
     return nullptr;
   }
diff --git a/install/snapshot_utils.cpp b/install/snapshot_utils.cpp
new file mode 100644
index 0000000..7235e67
--- /dev/null
+++ b/install/snapshot_utils.cpp
@@ -0,0 +1,74 @@
+
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <libsnapshot/snapshot.h>
+
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+#include "recovery_utils/roots.h"
+
+using android::snapshot::CreateResult;
+using android::snapshot::SnapshotManager;
+
+bool FinishPendingSnapshotMerges(Device* device) {
+  if (!android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) {
+    return true;
+  }
+
+  RecoveryUI* ui = device->GetUI();
+  auto sm = SnapshotManager::NewForFirstStageMount();
+  if (!sm) {
+    ui->Print("Could not create SnapshotManager.\n");
+    return false;
+  }
+
+  auto callback = [&]() -> void {
+    double progress;
+    sm->GetUpdateState(&progress);
+    ui->Print("Waiting for merge to complete: %.2f\n", progress);
+  };
+  if (!sm->HandleImminentDataWipe(callback)) {
+    ui->Print("Unable to check merge status and/or complete update merge.\n");
+    return false;
+  }
+  return true;
+}
+
+bool CreateSnapshotPartitions() {
+  if (!android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) {
+    // If the device does not support Virtual A/B, there's no need to create
+    // snapshot devices.
+    return true;
+  }
+
+  auto sm = SnapshotManager::NewForFirstStageMount();
+  if (!sm) {
+    // SnapshotManager could not be created. The device is still in a
+    // consistent state and can continue with the mounting of the existing
+    // devices, but cannot initialize snapshot devices.
+    LOG(WARNING) << "Could not create SnapshotManager";
+    return true;
+  }
+
+  auto ret = sm->RecoveryCreateSnapshotDevices();
+  if (ret == CreateResult::ERROR) {
+    return false;
+  }
+  return true;
+}
diff --git a/install/verifier.cpp b/install/verifier.cpp
index 6ba1d77..ab75044 100644
--- a/install/verifier.cpp
+++ b/install/verifier.cpp
@@ -311,8 +311,7 @@
 
 static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) {
   void* cookie;
-  ZipString suffix("x509.pem");
-  int32_t iter_status = StartIteration(handle, &cookie, nullptr, &suffix);
+  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);
@@ -321,22 +320,21 @@
 
   std::vector<Certificate> result;
 
-  ZipString name;
+  std::string_view name;
   ZipEntry entry;
   while ((iter_status = Next(cookie, &entry, &name)) == 0) {
     std::vector<uint8_t> pem_content(entry.uncompressed_length);
     if (int32_t extract_status =
             ExtractToMemory(handle, &entry, pem_content.data(), pem_content.size());
         extract_status != 0) {
-      LOG(ERROR) << "Failed to extract " << std::string(name.name, name.name + name.name_length);
+      LOG(ERROR) << "Failed to extract " << name;
       return {};
     }
 
     Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
     // Aborts the parsing if we fail to load one of the key file.
     if (!LoadCertificateFromBuffer(pem_content, &cert)) {
-      LOG(ERROR) << "Failed to load keys from "
-                 << std::string(name.name, name.name + name.name_length);
+      LOG(ERROR) << "Failed to load keys from " << name;
       return {};
     }
 
diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp
index 765a815..2872085 100644
--- a/install/wipe_data.cpp
+++ b/install/wipe_data.cpp
@@ -27,10 +27,11 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 
+#include "install/snapshot_utils.h"
 #include "otautil/dirutil.h"
-#include "otautil/logging.h"
-#include "otautil/roots.h"
 #include "recovery_ui/ui.h"
+#include "recovery_utils/logging.h"
+#include "recovery_utils/roots.h"
 
 constexpr const char* CACHE_ROOT = "/cache";
 constexpr const char* DATA_ROOT = "/data";
@@ -104,6 +105,12 @@
 bool WipeData(Device* device, bool convert_fbe) {
   RecoveryUI* ui = device->GetUI();
   ui->Print("\n-- Wiping data...\n");
+
+  if (!FinishPendingSnapshotMerges(device)) {
+    ui->Print("Unable to check update status or complete merge, cannot wipe partitions.\n");
+    return false;
+  }
+
   bool success = device->PreWipeData();
   if (success) {
     success &= EraseVolume(DATA_ROOT, ui, convert_fbe);
@@ -120,4 +127,4 @@
   }
   ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
   return success;
-}
\ No newline at end of file
+}
diff --git a/install/wipe_device.cpp b/install/wipe_device.cpp
new file mode 100644
index 0000000..89d5d31
--- /dev/null
+++ b/install/wipe_device.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "install/wipe_device.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ziparchive/zip_archive.h>
+
+#include "bootloader_message/bootloader_message.h"
+#include "install/install.h"
+#include "install/package.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+std::vector<std::string> GetWipePartitionList(Package* wipe_package) {
+  ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
+  if (!zip) {
+    LOG(ERROR) << "Failed to get ZipArchiveHandle";
+    return {};
+  }
+
+  constexpr char RECOVERY_WIPE_ENTRY_NAME[] = "recovery.wipe";
+
+  std::string partition_list_content;
+  ZipEntry entry;
+  if (FindEntry(zip, RECOVERY_WIPE_ENTRY_NAME, &entry) == 0) {
+    uint32_t length = entry.uncompressed_length;
+    partition_list_content = std::string(length, '\0');
+    if (auto err = ExtractToMemory(
+            zip, &entry, reinterpret_cast<uint8_t*>(partition_list_content.data()), length);
+        err != 0) {
+      LOG(ERROR) << "Failed to extract " << RECOVERY_WIPE_ENTRY_NAME << ": "
+                 << ErrorCodeString(err);
+      return {};
+    }
+  } else {
+    LOG(INFO) << "Failed to find " << RECOVERY_WIPE_ENTRY_NAME
+              << ", falling back to use the partition list on device.";
+
+    constexpr char RECOVERY_WIPE_ON_DEVICE[] = "/etc/recovery.wipe";
+    if (!android::base::ReadFileToString(RECOVERY_WIPE_ON_DEVICE, &partition_list_content)) {
+      PLOG(ERROR) << "failed to read \"" << RECOVERY_WIPE_ON_DEVICE << "\"";
+      return {};
+    }
+  }
+
+  std::vector<std::string> result;
+  auto lines = android::base::Split(partition_list_content, "\n");
+  for (const auto& line : lines) {
+    auto partition = android::base::Trim(line);
+    // Ignore '#' comment or empty lines.
+    if (android::base::StartsWith(partition, "#") || partition.empty()) {
+      continue;
+    }
+    result.push_back(line);
+  }
+
+  return result;
+}
+
+// Secure-wipes a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with
+// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT.
+static bool SecureWipePartition(const std::string& partition) {
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open \"" << partition << "\"";
+    return false;
+  }
+
+  uint64_t range[2] = { 0, 0 };
+  if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
+    PLOG(ERROR) << "Failed to get partition size";
+    return false;
+  }
+  LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1];
+
+  LOG(INFO) << "  Trying BLKSECDISCARD...";
+  if (ioctl(fd, BLKSECDISCARD, &range) == -1) {
+    PLOG(WARNING) << "  Failed";
+
+    // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
+    unsigned int zeroes;
+    if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
+      LOG(INFO) << "  Trying BLKDISCARD...";
+      if (ioctl(fd, BLKDISCARD, &range) == -1) {
+        PLOG(ERROR) << "  Failed";
+        return false;
+      }
+    } else {
+      LOG(INFO) << "  Trying BLKZEROOUT...";
+      if (ioctl(fd, BLKZEROOUT, &range) == -1) {
+        PLOG(ERROR) << "  Failed";
+        return false;
+      }
+    }
+  }
+
+  LOG(INFO) << "  Done";
+  return true;
+}
+
+static std::unique_ptr<Package> ReadWipePackage(size_t wipe_package_size) {
+  if (wipe_package_size == 0) {
+    LOG(ERROR) << "wipe_package_size is zero";
+    return nullptr;
+  }
+
+  std::string wipe_package;
+  if (std::string err_str; !read_wipe_package(&wipe_package, wipe_package_size, &err_str)) {
+    PLOG(ERROR) << "Failed to read wipe package" << err_str;
+    return nullptr;
+  }
+
+  return Package::CreateMemoryPackage(
+      std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr);
+}
+
+// Checks if the wipe package matches expectation. If the check passes, reads the list of
+// partitions to wipe from the package. Checks include
+// 1. verify the package.
+// 2. check metadata (ota-type, pre-device and serial number if having one).
+static bool CheckWipePackage(Package* wipe_package, RecoveryUI* ui) {
+  if (!verify_package(wipe_package, ui)) {
+    LOG(ERROR) << "Failed to verify package";
+    return false;
+  }
+
+  ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
+  if (!zip) {
+    LOG(ERROR) << "Failed to get ZipArchiveHandle";
+    return false;
+  }
+
+  std::map<std::string, std::string> metadata;
+  if (!ReadMetadataFromPackage(zip, &metadata)) {
+    LOG(ERROR) << "Failed to parse metadata in the zip file";
+    return false;
+  }
+
+  return CheckPackageMetadata(metadata, OtaType::BRICK);
+}
+
+bool WipeAbDevice(Device* device, size_t wipe_package_size) {
+  auto ui = device->GetUI();
+  ui->SetBackground(RecoveryUI::ERASING);
+  ui->SetProgressType(RecoveryUI::INDETERMINATE);
+
+  auto wipe_package = ReadWipePackage(wipe_package_size);
+  if (!wipe_package) {
+    LOG(ERROR) << "Failed to open wipe package";
+    return false;
+  }
+
+  if (!CheckWipePackage(wipe_package.get(), ui)) {
+    LOG(ERROR) << "Failed to verify wipe package";
+    return false;
+  }
+
+  auto partition_list = GetWipePartitionList(wipe_package.get());
+  if (partition_list.empty()) {
+    LOG(ERROR) << "Empty wipe ab partition list";
+    return false;
+  }
+
+  for (const auto& partition : partition_list) {
+    // Proceed anyway even if it fails to wipe some partition.
+    SecureWipePartition(partition);
+  }
+  return true;
+}
diff --git a/minadbd/Android.bp b/minadbd/Android.bp
index 007e505..c39c734 100644
--- a/minadbd/Android.bp
+++ b/minadbd/Android.bp
@@ -26,6 +26,10 @@
     include_dirs: [
         "system/core/adb",
     ],
+
+    header_libs: [
+        "libminadbd_headers",
+    ],
 }
 
 // `libminadbd_services` is analogous to the `libadbd_services` for regular `adbd`, but providing
@@ -36,6 +40,7 @@
 
     defaults: [
         "minadbd_defaults",
+        "librecovery_utils_defaults",
     ],
 
     srcs: [
@@ -43,6 +48,11 @@
         "minadbd_services.cpp",
     ],
 
+    static_libs: [
+        "librecovery_utils",
+        "libotautil",
+    ],
+
     shared_libs: [
         "libadbd",
         "libbase",
@@ -54,9 +64,12 @@
 cc_library_headers {
     name: "libminadbd_headers",
     recovery_available: true,
-    // TODO create a include dir
     export_include_dirs: [
-        ".",
+        "include",
+    ],
+    // adb_install.cpp
+    visibility: [
+        "//bootable/recovery/install",
     ],
 }
 
@@ -78,6 +91,10 @@
         "libcrypto",
         "libminadbd_services",
     ],
+
+    required: [
+        "adbd_system_binaries_recovery",
+    ]
 }
 
 cc_test {
@@ -86,6 +103,7 @@
 
     defaults: [
         "minadbd_defaults",
+        "librecovery_utils_defaults",
     ],
 
     srcs: [
@@ -96,12 +114,14 @@
     static_libs: [
         "libminadbd_services",
         "libfusesideload",
+        "librecovery_utils",
+        "libotautil",
         "libadbd",
-        "libcrypto",
     ],
 
     shared_libs: [
         "libbase",
+        "libcrypto",
         "libcutils",
         "liblog",
     ],
diff --git a/minadbd/README.md b/minadbd/README.md
index 5a0a067..9a19583 100644
--- a/minadbd/README.md
+++ b/minadbd/README.md
@@ -1,8 +1,24 @@
-minadbd is now mostly built from libadbd. The fuse features are unique to
-minadbd, and services.c has been modified as follows:
+minadbd
+=======
 
-  - all services removed
-  - all host mode support removed
-  - `sideload_service()` added; this is the only service supported. It
-    receives a single blob of data, writes it to a fixed filename, and
-    makes the process exit.
+`minadbd` is analogous to the regular `adbd`, but providing the minimal services to support
+recovery-specific use cases. Generally speaking, `adbd` = `libadbd` + `libadbd_services`, whereas
+`minadbd` = `libadbd` + `libminadbd_services`.
+
+Although both modules may be installed into the recovery image, only one of them, or none, can be
+active at any given time.
+
+- The start / stop of `adbd` is managed via system property `sys.usb.config`, when setting to `adb`
+  or `none` respectively. Upon starting recovery mode, `adbd` is started in debuggable builds by
+  default; otherwise `adbd` will stay off at all times in user builds. See the triggers in
+  `bootable/recovery/etc/init.rc`.
+
+- `minadbd` is started by `recovery` as needed.
+  - When requested to start `minadbd`, `recovery` stops `adbd` first, if it's running; it then forks
+    and execs `minadbd` in a separate process.
+  - `minadbd` talks to host-side `adb` server to get user requests.
+    - `minadbd` handles some requests directly, e.g. querying device properties for rescue service.
+    - `minadbd` communicates with `recovery` to fulfill requests regarding package installation. See
+      the comments in `bootable/recovery/install/adb_install.cpp` for the IPC protocol between
+      `recovery` and `minadbd`.
+  - Upon exiting `minadbd`, `recovery` restarts `adbd` if it was previously running.
diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h
index c5561e5..43c07d2 100644
--- a/minadbd/fuse_adb_provider.h
+++ b/minadbd/fuse_adb_provider.h
@@ -29,6 +29,10 @@
   bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
                             uint32_t start_block) const override;
 
+  bool Valid() const override {
+    return fd_ != -1;
+  }
+
  private:
   // The underlying source to read data from (i.e. the one that talks to the host).
   int fd_;
diff --git a/minadbd/minadbd_types.h b/minadbd/include/minadbd/types.h
similarity index 100%
rename from minadbd/minadbd_types.h
rename to minadbd/include/minadbd/types.h
diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp
index c80d549..7b82faa 100644
--- a/minadbd/minadbd.cpp
+++ b/minadbd/minadbd.cpp
@@ -28,8 +28,8 @@
 #include "adb_auth.h"
 #include "transport.h"
 
+#include "minadbd/types.h"
 #include "minadbd_services.h"
-#include "minadbd_types.h"
 
 using namespace std::string_literals;
 
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
index 03341e4..eb91fb3 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -41,10 +41,10 @@
 #include "adb.h"
 #include "adb_unique_fd.h"
 #include "adb_utils.h"
-#include "fdevent.h"
 #include "fuse_adb_provider.h"
 #include "fuse_sideload.h"
-#include "minadbd_types.h"
+#include "minadbd/types.h"
+#include "recovery_utils/battery_utils.h"
 #include "services.h"
 #include "sysdeps.h"
 
@@ -161,7 +161,10 @@
 // If given an empty string, dumps all the supported properties (analogous to `adb shell getprop`)
 // in lines, e.g. "[prop]: [value]".
 static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) {
+  constexpr const char* kRescueBatteryLevelProp = "rescue.battery_level";
   static const std::set<std::string> kGetpropAllowedProps = {
+    // clang-format off
+    kRescueBatteryLevelProp,
     "ro.build.date.utc",
     "ro.build.fingerprint",
     "ro.build.flavor",
@@ -171,18 +174,28 @@
     "ro.build.version.incremental",
     "ro.product.device",
     "ro.product.vendor.device",
+    // clang-format on
   };
+
+  auto query_prop = [](const std::string& key) {
+    if (key == kRescueBatteryLevelProp) {
+      auto battery_info = GetBatteryInfo();
+      return std::to_string(battery_info.capacity);
+    }
+    return android::base::GetProperty(key, "");
+  };
+
   std::string result;
   if (prop.empty()) {
     for (const auto& key : kGetpropAllowedProps) {
-      auto value = android::base::GetProperty(key, "");
+      auto value = query_prop(key);
       if (value.empty()) {
         continue;
       }
       result += "[" + key + "]: [" + value + "]\n";
     }
   } else if (kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end()) {
-    result = android::base::GetProperty(prop, "") + "\n";
+    result = query_prop(prop) + "\n";
   }
   if (result.empty()) {
     result = "\n";
@@ -255,7 +268,7 @@
 
 unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) {
   // Common services that are supported both in sideload and rescue modes.
-  if (ConsumePrefix(&name, "reboot:")) {
+  if (android::base::ConsumePrefix(&name, "reboot:")) {
     // "reboot:<target>", where target must be one of the following.
     std::string args(name);
     if (args.empty() || args == "bootloader" || args == "rescue" || args == "recovery" ||
@@ -268,17 +281,17 @@
 
   // Rescue-specific services.
   if (rescue_mode) {
-    if (ConsumePrefix(&name, "rescue-install:")) {
+    if (android::base::ConsumePrefix(&name, "rescue-install:")) {
       // rescue-install:<file-size>:<block-size>
       std::string args(name);
       return create_service_thread(
           "rescue-install", std::bind(RescueInstallHostService, std::placeholders::_1, args));
-    } else if (ConsumePrefix(&name, "rescue-getprop:")) {
+    } else if (android::base::ConsumePrefix(&name, "rescue-getprop:")) {
       // rescue-getprop:<prop>
       std::string args(name);
       return create_service_thread(
           "rescue-getprop", std::bind(RescueGetpropHostService, std::placeholders::_1, args));
-    } else if (ConsumePrefix(&name, "rescue-wipe:")) {
+    } else if (android::base::ConsumePrefix(&name, "rescue-wipe:")) {
       // rescue-wipe:target:<message-size>
       std::string args(name);
       return create_service_thread("rescue-wipe",
@@ -293,7 +306,7 @@
     // This exit status causes recovery to print a special error message saying to use a newer adb
     // (that supports sideload-host).
     exit(kMinadbdAdbVersionError);
-  } else if (ConsumePrefix(&name, "sideload-host:")) {
+  } else if (android::base::ConsumePrefix(&name, "sideload-host:")) {
     // sideload-host:<file-size>:<block-size>
     std::string args(name);
     return create_service_thread("sideload-host",
diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp
index f878737..b694a57 100644
--- a/minadbd/minadbd_services_test.cpp
+++ b/minadbd/minadbd_services_test.cpp
@@ -35,8 +35,8 @@
 #include "adb_io.h"
 #include "fuse_adb_provider.h"
 #include "fuse_sideload.h"
+#include "minadbd/types.h"
 #include "minadbd_services.h"
-#include "minadbd_types.h"
 #include "socket.h"
 
 class MinadbdServicesTest : public ::testing::Test {
diff --git a/minui/events.cpp b/minui/events.cpp
index 7d0250e..87f8112 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -22,6 +22,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/epoll.h>
+#include <sys/inotify.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -33,6 +34,8 @@
 
 #include "minui/minui.h"
 
+constexpr const char* INPUT_DEV_DIR = "/dev/input";
+
 constexpr size_t MAX_DEVICES = 16;
 constexpr size_t MAX_MISC_FDS = 16;
 
@@ -46,6 +49,8 @@
   ev_callback cb;
 };
 
+static bool g_allow_touch_inputs = true;
+static ev_callback g_saved_input_cb;
 static android::base::unique_fd g_epoll_fd;
 static epoll_event g_polled_events[MAX_DEVICES + MAX_MISC_FDS];
 static int g_polled_events_count;
@@ -60,6 +65,80 @@
   return (array[bit / BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
 }
 
+static bool should_add_input_device(int fd, bool allow_touch_inputs) {
+  // Use unsigned long to match ioctl's parameter type.
+  unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
+
+  // Read the evbits of the input device.
+  if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+    return false;
+  }
+
+  // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
+  // allowed if allow_touch_inputs is set.
+  if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
+    if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static int inotify_cb(int fd, __unused uint32_t epevents) {
+  if (g_saved_input_cb == nullptr) return -1;
+
+  // The inotify will put one or several complete events.
+  // Should not read part of one event.
+  int event_len_int;
+  int ret = ioctl(fd, FIONREAD, &event_len_int);
+  if (ret != 0) return -1;
+  if (event_len_int < 0) return -1;
+  size_t event_len = event_len_int;
+
+  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(INPUT_DEV_DIR), closedir);
+  if (!dir) {
+    return -1;
+  }
+
+  std::vector<int8_t> buf(event_len);
+
+  ssize_t r = TEMP_FAILURE_RETRY(read(fd, buf.data(), event_len));
+  if (r != event_len) {
+    return -1;
+  }
+
+  size_t offset = 0;
+  while (offset < event_len) {
+    struct inotify_event* pevent = reinterpret_cast<struct inotify_event*>(buf.data() + offset);
+    if (offset + sizeof(inotify_event) + pevent->len > event_len) {
+      // The pevent->len is too large and buffer will over flow.
+      // In general, should not happen, just make more stable.
+      return -1;
+    }
+    offset += sizeof(inotify_event) + pevent->len;
+
+    pevent->name[pevent->len] = '\0';
+    if (strncmp(pevent->name, "event", 5)) {
+      continue;
+    }
+
+    android::base::unique_fd dfd(openat(dirfd(dir.get()), pevent->name, O_RDONLY));
+    if (dfd == -1) {
+      break;
+    }
+
+    if (!should_add_input_device(dfd, g_allow_touch_inputs)) {
+      continue;
+    }
+
+    // Only add, we assume the user will not plug out and plug in USB device again and again :)
+    ev_add_fd(std::move(dfd), g_saved_input_cb);
+  }
+
+  return 0;
+}
+
 int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
   g_epoll_fd.reset();
 
@@ -68,7 +147,16 @@
     return -1;
   }
 
-  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/dev/input"), closedir);
+  android::base::unique_fd inotify_fd(inotify_init1(IN_CLOEXEC));
+  if (inotify_fd.get() == -1) {
+    return -1;
+  }
+
+  if (inotify_add_watch(inotify_fd, INPUT_DEV_DIR, IN_CREATE) < 0) {
+    return -1;
+  }
+
+  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(INPUT_DEV_DIR), closedir);
   if (!dir) {
     return -1;
   }
@@ -80,22 +168,10 @@
     android::base::unique_fd fd(openat(dirfd(dir.get()), de->d_name, O_RDONLY | O_CLOEXEC));
     if (fd == -1) continue;
 
-    // Use unsigned long to match ioctl's parameter type.
-    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
-
-    // Read the evbits of the input device.
-    if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+    if (!should_add_input_device(fd, allow_touch_inputs)) {
       continue;
     }
 
-    // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
-    // allowed if allow_touch_inputs is set.
-    if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
-      if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
-        continue;
-      }
-    }
-
     epoll_event ev;
     ev.events = EPOLLIN | EPOLLWAKEUP;
     ev.data.ptr = &ev_fdinfo[g_ev_count];
@@ -116,6 +192,11 @@
   }
 
   g_epoll_fd.reset(epoll_fd.release());
+
+  g_saved_input_cb = input_cb;
+  g_allow_touch_inputs = allow_touch_inputs;
+  ev_add_fd(std::move(inotify_fd), inotify_cb);
+
   return 0;
 }
 
@@ -148,6 +229,7 @@
   }
   g_ev_misc_count = 0;
   g_ev_dev_count = 0;
+  g_saved_input_cb = nullptr;
   g_epoll_fd.reset();
 }
 
@@ -170,13 +252,17 @@
 }
 
 int ev_get_input(int fd, uint32_t epevents, input_event* ev) {
-    if (epevents & EPOLLIN) {
-        ssize_t r = TEMP_FAILURE_RETRY(read(fd, ev, sizeof(*ev)));
-        if (r == sizeof(*ev)) {
-            return 0;
-        }
+  if (epevents & EPOLLIN) {
+    ssize_t r = TEMP_FAILURE_RETRY(read(fd, ev, sizeof(*ev)));
+    if (r == sizeof(*ev)) {
+      return 0;
     }
-    return -1;
+  }
+  if (epevents & EPOLLHUP) {
+    // Delete this watch
+    epoll_ctl(g_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
+  }
+  return -1;
 }
 
 int ev_sync_key_state(const ev_set_key_callback& set_key_cb) {
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index 4d1f9b2..d34da56 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -209,7 +209,7 @@
 
 void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
   uint32_t r32 = r, g32 = g, b32 = b, a32 = a;
-  if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
+  if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) {
     gr_current = (a32 << 24) | (r32 << 16) | (g32 << 8) | b32;
   } else {
     gr_current = (a32 << 24) | (b32 << 16) | (g32 << 8) | r32;
@@ -348,6 +348,8 @@
     pixel_format = PixelFormat::ABGR;
   } else if (format == "RGBX_8888") {
     pixel_format = PixelFormat::RGBX;
+  } else if (format == "ARGB_8888") {
+    pixel_format = PixelFormat::ARGB;
   } else if (format == "BGRA_8888") {
     pixel_format = PixelFormat::BGRA;
   } else {
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
index 7b2eed1..95759e3 100644
--- a/minui/graphics_drm.cpp
+++ b/minui/graphics_drm.cpp
@@ -62,6 +62,8 @@
     case DRM_FORMAT_ABGR8888:
     case DRM_FORMAT_BGRA8888:
     case DRM_FORMAT_RGBX8888:
+    case DRM_FORMAT_RGBA8888:
+    case DRM_FORMAT_ARGB8888:
     case DRM_FORMAT_BGRX8888:
     case DRM_FORMAT_XBGR8888:
     case DRM_FORMAT_XRGB8888:
@@ -87,6 +89,8 @@
     format = DRM_FORMAT_ARGB8888;
   } else if (pixel_format == PixelFormat::RGBX) {
     format = DRM_FORMAT_XBGR8888;
+  } else if (pixel_format == PixelFormat::ARGB) {
+    format = DRM_FORMAT_BGRA8888;
   } else {
     format = DRM_FORMAT_RGB565;
   }
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index 36bdcf1..163e41d 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -101,6 +101,7 @@
   ABGR = 1,
   RGBX = 2,
   BGRA = 3,
+  ARGB = 4,
 };
 
 // Initializes the graphics backend and loads font file. Returns 0 on success, or -1 on error. Note
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 069a495..f635acd 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -199,7 +199,7 @@
   }
 
   PixelFormat pixel_format = gr_pixel_format();
-  if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
+  if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) {
     png_set_bgr(png_ptr);
   }
 
@@ -271,7 +271,7 @@
     surface[i] = created_surface.release();
   }
 
-  if (gr_pixel_format() == PixelFormat::ABGR || gr_pixel_format() == PixelFormat::BGRA) {
+  if (gr_pixel_format() == PixelFormat::ARGB || gr_pixel_format() == PixelFormat::BGRA) {
     png_set_bgr(png_ptr);
   }
 
@@ -317,7 +317,7 @@
   }
 
   PixelFormat pixel_format = gr_pixel_format();
-  if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
+  if (pixel_format == PixelFormat::ARGB || pixel_format == PixelFormat::BGRA) {
     png_set_bgr(png_ptr);
   }
 
@@ -347,6 +347,10 @@
   // match the locale string without the {script} section.
   // For instance, prefix == "en" matches locale == "en-US", prefix == "sr-Latn" matches locale
   // == "sr-Latn-BA", and prefix == "zh-CN" matches locale == "zh-Hans-CN".
+  if (prefix.empty()) {
+    return false;
+  }
+
   if (android::base::StartsWith(locale, prefix)) {
     return true;
   }
@@ -414,12 +418,18 @@
     __unused int len = row[4];
     char* loc = reinterpret_cast<char*>(&row[5]);
 
-    if (y + 1 + h >= height || matches_locale(loc, locale)) {
+    // We need to include one additional line for the metadata of the localized image.
+    if (y + 1 + h > height) {
+      printf("Read exceeds the image boundary, y %u, h %d, height %u\n", y, h, height);
+      return -8;
+    }
+
+    if (matches_locale(loc, locale)) {
       printf("  %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
 
       auto surface = GRSurface::Create(w, h, w, 1);
       if (!surface) {
-        return -8;
+        return -9;
       }
 
       for (int i = 0; i < h; ++i, ++y) {
@@ -428,7 +438,7 @@
       }
 
       *pSurface = surface.release();
-      break;
+      return 0;
     }
 
     for (int i = 0; i < h; ++i, ++y) {
@@ -436,7 +446,7 @@
     }
   }
 
-  return 0;
+  return -10;
 }
 
 void res_free_surface(GRSurface* surface) {
diff --git a/misc_writer/Android.bp b/misc_writer/Android.bp
deleted file mode 100644
index 567143c..0000000
--- a/misc_writer/Android.bp
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-cc_binary {
-    name: "misc_writer",
-    vendor: true,
-
-    srcs: [
-        "misc_writer.cpp",
-    ],
-
-    cpp_std: "experimental",
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-
-    shared_libs: [
-        "libbase",
-    ],
-
-    static_libs: [
-        "libbootloader_message_vendor",
-        "libfstab",
-    ],
-}
diff --git a/misc_writer/misc_writer.cpp b/misc_writer/misc_writer.cpp
deleted file mode 100644
index 1d9702e..0000000
--- a/misc_writer/misc_writer.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <errno.h>
-#include <getopt.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <string>
-#include <string_view>
-#include <vector>
-
-#include <android-base/logging.h>
-#include <android-base/parseint.h>
-#include <bootloader_message/bootloader_message.h>
-
-using namespace std::string_literals;
-
-static std::vector<uint8_t> ParseHexString(std::string_view hex_string) {
-  auto length = hex_string.size();
-  if (length % 2 != 0 || length == 0) {
-    return {};
-  }
-
-  std::vector<uint8_t> result(length / 2);
-  for (size_t i = 0; i < length / 2; i++) {
-    auto sub = "0x" + std::string(hex_string.substr(i * 2, 2));
-    if (!android::base::ParseUint(sub, &result[i])) {
-      return {};
-    }
-  }
-  return result;
-}
-
-static int Usage(std::string_view name) {
-  std::cerr << name << " usage:\n";
-  std::cerr << name << " [--vendor-space-offset <offset>] --hex-string 0xABCDEF\n";
-  std::cerr << "Writes the given hex string to the specified offset in vendor space in /misc "
-               "partition. Offset defaults to 0 if unspecified.\n";
-  return EXIT_FAILURE;
-}
-
-// misc_writer is a vendor tool that writes data to the vendor space in /misc.
-int main(int argc, char** argv) {
-  constexpr struct option OPTIONS[] = {
-    { "vendor-space-offset", required_argument, nullptr, 0 },
-    { "hex-string", required_argument, nullptr, 0 },
-    { nullptr, 0, nullptr, 0 },
-  };
-
-  // Offset defaults to 0 if unspecified.
-  size_t offset = 0;
-  std::string_view hex_string;
-
-  int arg;
-  int option_index;
-  while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
-    if (arg != 0) {
-      LOG(ERROR) << "Invalid command argument";
-      return Usage(argv[0]);
-    }
-    auto option_name = OPTIONS[option_index].name;
-    if (option_name == "vendor-space-offset"s) {
-      if (!android::base::ParseUint(optarg, &offset)) {
-        LOG(ERROR) << "Failed to parse the offset: " << optarg;
-        return Usage(argv[0]);
-      }
-    } else if (option_name == "hex-string"s) {
-      hex_string = optarg;
-    }
-  }
-
-  if (hex_string.starts_with("0x") || hex_string.starts_with("0X")) {
-    hex_string = hex_string.substr(2);
-  }
-  if (hex_string.empty()) {
-    LOG(ERROR) << "Invalid input hex string: " << hex_string;
-    return Usage(argv[0]);
-  }
-
-  auto data = ParseHexString(hex_string);
-  if (data.empty()) {
-    LOG(ERROR) << "Failed to parse the input hex string: " << hex_string;
-    return EXIT_FAILURE;
-  }
-  if (std::string err; !WriteMiscPartitionVendorSpace(data.data(), data.size(), offset, &err)) {
-    LOG(ERROR) << "Failed to write to misc partition: " << err;
-    return EXIT_FAILURE;
-  }
-  return EXIT_SUCCESS;
-}
diff --git a/otautil/Android.bp b/otautil/Android.bp
index 0a21731..3b3f9cb 100644
--- a/otautil/Android.bp
+++ b/otautil/Android.bp
@@ -16,6 +16,7 @@
     name: "libotautil",
 
     host_supported: true,
+    vendor_available: true,
     recovery_available: true,
 
     defaults: [
@@ -24,44 +25,19 @@
 
     // Minimal set of files to support host build.
     srcs: [
+        "dirutil.cpp",
         "paths.cpp",
         "rangeset.cpp",
+        "sysutil.cpp",
     ],
 
     shared_libs: [
         "libbase",
+        "libcutils",
+        "libselinux",
     ],
 
     export_include_dirs: [
         "include",
     ],
-
-    target: {
-        android: {
-            srcs: [
-                "dirutil.cpp",
-                "logging.cpp",
-                "mounts.cpp",
-                "parse_install_logs.cpp",
-                "roots.cpp",
-                "sysutil.cpp",
-                "thermalutil.cpp",
-            ],
-
-            include_dirs: [
-                "system/vold",
-            ],
-
-            static_libs: [
-                "libfstab",
-            ],
-
-            shared_libs: [
-                "libcutils",
-                "libext4_utils",
-                "libfs_mgr",
-                "libselinux",
-            ],
-        },
-    },
 }
diff --git a/otautil/include/otautil/boot_state.h b/otautil/include/otautil/boot_state.h
new file mode 100644
index 0000000..6c877ba
--- /dev/null
+++ b/otautil/include/otautil/boot_state.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <string_view>
+
+class BootState {
+ public:
+  BootState(std::string_view reason, std::string_view stage) : reason_(reason), stage_(stage) {}
+
+  std::string reason() const {
+    return reason_;
+  }
+  std::string stage() const {
+    return stage_;
+  }
+
+ private:
+  std::string reason_;  // The reason argument provided in "--reason=".
+  std::string stage_;   // The current stage, e.g. "1/2".
+};
diff --git a/otautil/include/otautil/rangeset.h b/otautil/include/otautil/rangeset.h
index e91d02c..a18c30e 100644
--- a/otautil/include/otautil/rangeset.h
+++ b/otautil/include/otautil/rangeset.h
@@ -18,6 +18,7 @@
 
 #include <stddef.h>
 
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -49,6 +50,12 @@
   // bounds. For example, "3,5" contains blocks 3 and 4. So "3,5" and "5,7" are not overlapped.
   bool Overlaps(const RangeSet& other) const;
 
+  // Returns a subset of ranges starting from |start_index| with respect to the original range. The
+  // output range will have |num_of_blocks| blocks in size. Returns std::nullopt if the input is
+  // invalid. e.g. RangeSet({{0, 5}, {10, 15}}).GetSubRanges(1, 5) returns
+  // RangeSet({{1, 5}, {10, 11}}).
+  std::optional<RangeSet> GetSubRanges(size_t start_index, size_t num_of_blocks) const;
+
   // Returns a vector of RangeSets that contain the same set of blocks represented by the current
   // RangeSet. The RangeSets in the vector contain similar number of blocks, with a maximum delta
   // of 1-block between any two of them. For example, 14 blocks would be split into 4 + 4 + 3 + 3,
diff --git a/otautil/include/otautil/sysutil.h b/otautil/include/otautil/sysutil.h
index 692a99e..d0d2e67 100644
--- a/otautil/include/otautil/sysutil.h
+++ b/otautil/include/otautil/sysutil.h
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef _OTAUTIL_SYSUTIL
-#define _OTAUTIL_SYSUTIL
+#pragma once
 
 #include <sys/types.h>
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "rangeset.h"
@@ -101,13 +101,14 @@
   std::vector<MappedRange> ranges_;
 };
 
-// Wrapper function to trigger a reboot, by additionally handling quiescent reboot mode. The
-// command should start with "reboot," (e.g. "reboot,bootloader" or "reboot,").
-bool reboot(const std::string& command);
+// Reboots the device into the specified target, by additionally handling quiescent reboot mode.
+// All unknown targets reboot into Android.
+[[noreturn]] void Reboot(std::string_view target);
+
+// Triggers a shutdown.
+bool Shutdown(std::string_view target);
 
 // Returns a null-terminated char* array, where the elements point to the C-strings in the given
 // vector, plus an additional nullptr at the end. This is a helper function that facilitates
 // calling C functions (such as getopt(3)) that expect an array of C-strings.
 std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args);
-
-#endif  // _OTAUTIL_SYSUTIL
diff --git a/otautil/rangeset.cpp b/otautil/rangeset.cpp
index 5ab8e08..8ee99dd 100644
--- a/otautil/rangeset.cpp
+++ b/otautil/rangeset.cpp
@@ -184,6 +184,58 @@
   return false;
 }
 
+std::optional<RangeSet> RangeSet::GetSubRanges(size_t start_index, size_t num_of_blocks) const {
+  size_t end_index = start_index + num_of_blocks;  // The index of final block to read plus one
+  if (start_index > end_index || end_index > blocks_) {
+    LOG(ERROR) << "Failed to get the sub ranges for start_index " << start_index
+               << " num_of_blocks " << num_of_blocks
+               << " total number of blocks the range contains is " << blocks_;
+    return std::nullopt;
+  }
+
+  if (num_of_blocks == 0) {
+    LOG(WARNING) << "num_of_blocks is zero when calling GetSubRanges()";
+    return RangeSet();
+  }
+
+  RangeSet result;
+  size_t current_index = 0;
+  for (const auto& [range_start, range_end] : ranges_) {
+    CHECK_LT(range_start, range_end);
+    size_t blocks_in_range = range_end - range_start;
+    // Linear search to skip the ranges until we reach start_block.
+    if (current_index + blocks_in_range <= start_index) {
+      current_index += blocks_in_range;
+      continue;
+    }
+
+    size_t trimmed_range_start = range_start;
+    // We have found the first block range to read, trim the heading blocks.
+    if (current_index < start_index) {
+      trimmed_range_start += start_index - current_index;
+    }
+    // Trim the trailing blocks if the last range has more blocks than desired; also return the
+    // result.
+    if (current_index + blocks_in_range >= end_index) {
+      size_t trimmed_range_end = range_end - (current_index + blocks_in_range - end_index);
+      if (!result.PushBack({ trimmed_range_start, trimmed_range_end })) {
+        return std::nullopt;
+      }
+
+      return result;
+    }
+
+    if (!result.PushBack({ trimmed_range_start, range_end })) {
+      return std::nullopt;
+    }
+    current_index += blocks_in_range;
+  }
+
+  LOG(ERROR) << "Failed to construct byte ranges to read, start_block: " << start_index
+             << ", num_of_blocks: " << num_of_blocks << " total number of blocks: " << blocks_;
+  return std::nullopt;
+}
+
 // Ranges in the the set should be mutually exclusive; and they're sorted by the start block.
 SortedRangeSet::SortedRangeSet(std::vector<Range>&& pairs) : RangeSet(std::move(pairs)) {
   std::sort(ranges_.begin(), ranges_.end());
diff --git a/otautil/sysutil.cpp b/otautil/sysutil.cpp
index 8366fa0..b3ead97 100644
--- a/otautil/sysutil.cpp
+++ b/otautil/sysutil.cpp
@@ -38,7 +38,7 @@
 BlockMapData BlockMapData::ParseBlockMapFile(const std::string& block_map_path) {
   std::string content;
   if (!android::base::ReadFileToString(block_map_path, &content)) {
-    LOG(ERROR) << "Failed to read " << block_map_path;
+    PLOG(ERROR) << "Failed to read " << block_map_path;
     return {};
   }
 
@@ -94,6 +94,11 @@
     remaining_blocks -= range_blocks;
   }
 
+  if (remaining_blocks != 0) {
+    LOG(ERROR) << "Invalid ranges: remaining blocks " << remaining_blocks;
+    return {};
+  }
+
   return BlockMapData(block_dev, file_size, blksize, std::move(ranges));
 }
 
@@ -214,11 +219,22 @@
   ranges_.clear();
 }
 
-bool reboot(const std::string& command) {
-  std::string cmd = command;
-  if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
+void Reboot(std::string_view target) {
+  std::string cmd = "reboot," + std::string(target);
+  // Honor the quiescent mode if applicable.
+  if (target != "bootloader" && target != "fastboot" &&
+      android::base::GetBoolProperty("ro.boot.quiescent", false)) {
     cmd += ",quiescent";
   }
+  if (!android::base::SetProperty(ANDROID_RB_PROPERTY, cmd)) {
+    LOG(FATAL) << "Reboot failed";
+  }
+
+  while (true) pause();
+}
+
+bool Shutdown(std::string_view target) {
+  std::string cmd = "shutdown," + std::string(target);
   return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
 }
 
diff --git a/recovery-persist.cpp b/recovery-persist.cpp
index 294017a..ad101ed 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -35,16 +35,14 @@
 #include <string.h>
 #include <unistd.h>
 
-#include <limits>
 #include <string>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
-#include <metricslogger/metrics_logger.h>
 #include <private/android_logger.h> /* private pmsg functions */
 
-#include "otautil/logging.h"
-#include "otautil/parse_install_logs.h"
+#include "recovery_utils/logging.h"
+#include "recovery_utils/parse_install_logs.h"
 
 constexpr const char* LAST_LOG_FILE = "/data/misc/recovery/last_log";
 constexpr const char* LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0";
@@ -112,20 +110,6 @@
     return android::base::WriteStringToFile(buffer, destination.c_str());
 }
 
-// Parses the LAST_INSTALL file and reports the update metrics saved under recovery mode.
-static void report_metrics_from_last_install(const std::string& file_name) {
-  auto metrics = ParseLastInstall(file_name);
-  // TODO(xunchang) report the installation result.
-  for (const auto& [event, value] : metrics) {
-    if (value > std::numeric_limits<int>::max()) {
-      LOG(WARNING) << event << " (" << value << ") exceeds integer max.";
-    } else {
-      LOG(INFO) << "Uploading " << value << " to " << event;
-      android::metricslogger::LogHistogram(event, value);
-    }
-  }
-}
-
 int main(int argc, char **argv) {
 
     /* Is /cache a mount?, we have been delivered where we are not wanted */
@@ -157,7 +141,6 @@
     if (has_cache) {
       // Collects and reports the non-a/b update metrics from last_install; and removes the file
       // to avoid duplicate report.
-      report_metrics_from_last_install(LAST_INSTALL_FILE_IN_CACHE);
       if (access(LAST_INSTALL_FILE_IN_CACHE, F_OK) && unlink(LAST_INSTALL_FILE_IN_CACHE) == -1) {
         PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE_IN_CACHE;
       }
@@ -181,7 +164,6 @@
     // 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) {
-      report_metrics_from_last_install(LAST_INSTALL_FILE);
       if (access(LAST_INSTALL_FILE, F_OK) && unlink(LAST_INSTALL_FILE) == -1) {
         PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE;
       }
diff --git a/recovery-refresh.cpp b/recovery-refresh.cpp
index d41755d..42acd05 100644
--- a/recovery-refresh.cpp
+++ b/recovery-refresh.cpp
@@ -38,11 +38,12 @@
 //
 
 #include <string.h>
+
 #include <string>
 
 #include <private/android_logger.h> /* private pmsg functions */
 
-#include "otautil/logging.h"
+#include "recovery_utils/logging.h"
 
 int main(int argc, char **argv) {
     static const char filter[] = "recovery/";
diff --git a/recovery.cpp b/recovery.cpp
index e51687a..b022027 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -18,11 +18,9 @@
 
 #include <ctype.h>
 #include <errno.h>
-#include <fcntl.h>
 #include <getopt.h>
 #include <inttypes.h>
 #include <limits.h>
-#include <linux/fs.h>
 #include <linux/input.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -30,8 +28,8 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <algorithm>
 #include <functional>
+#include <iterator>
 #include <memory>
 #include <string>
 #include <vector>
@@ -42,27 +40,27 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <bootloader_message/bootloader_message.h>
 #include <cutils/properties.h> /* for property_list */
-#include <healthhalutils/HealthHalUtils.h>
+#include <fs_mgr/roots.h>
 #include <ziparchive/zip_archive.h>
 
-#include "common.h"
-#include "fsck_unshare_blocks.h"
-#include "fuse_sideload.h"
+#include "bootloader_message/bootloader_message.h"
 #include "install/adb_install.h"
-#include "install/fuse_sdcard_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/logging.h"
 #include "otautil/paths.h"
-#include "otautil/roots.h"
 #include "otautil/sysutil.h"
 #include "recovery_ui/screen_ui.h"
 #include "recovery_ui/ui.h"
+#include "recovery_utils/battery_utils.h"
+#include "recovery_utils/logging.h"
+#include "recovery_utils/roots.h"
 
 static constexpr const char* COMMAND_FILE = "/cache/recovery/command";
 static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
@@ -71,13 +69,7 @@
 
 static constexpr const char* CACHE_ROOT = "/cache";
 
-// We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed
-// into target_files.zip. Assert the version defined in code and in Android.mk are consistent.
-static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions.");
-
 static bool save_current_log = false;
-std::string stage;
-const char* reason = nullptr;
 
 /*
  * The recovery tool communicates with the main system through /cache files.
@@ -86,6 +78,8 @@
  *
  * The arguments which may be supplied in the recovery.command file:
  *   --update_package=path - verify install an OTA package file
+ *   --install_with_fuse - install the update package with FUSE. This allows installation of large
+ *       packages on LP32 builds. Since the mmap will otherwise fail due to out of memory.
  *   --wipe_data - erase user data (and cache), then reboot
  *   --prompt_and_wipe_data - prompt the user that data is corrupt, with their consent erase user
  *       data (and cache), then reboot
@@ -106,7 +100,7 @@
  *    -- after this, rebooting will restart the erase --
  * 5. erase_volume() reformats /data
  * 6. erase_volume() reformats /cache
- * 7. finish_recovery() erases BCB
+ * 7. FinishRecovery() erases BCB
  *    -- after this, rebooting will restart the main system --
  * 8. main() calls reboot() to boot main system
  *
@@ -116,27 +110,27 @@
  * 3. main system reboots into recovery
  * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
  *    -- after this, rebooting will attempt to reinstall the update --
- * 5. install_package() attempts to install the update
+ * 5. InstallPackage() attempts to install the update
  *    NOTE: the package install must itself be restartable from any point
- * 6. finish_recovery() erases BCB
+ * 6. FinishRecovery() erases BCB
  *    -- after this, rebooting will (try to) restart the main system --
  * 7. ** if install failed **
- *    7a. prompt_and_wait() shows an error icon and waits for the user
+ *    7a. PromptAndWait() shows an error icon and waits for the user
  *    7b. the user reboots (pulling the battery, etc) into the main system
  */
 
-bool is_ro_debuggable() {
-    return android::base::GetBoolProperty("ro.debuggable", false);
+static bool IsRoDebuggable() {
+  return android::base::GetBoolProperty("ro.debuggable", false);
 }
 
 // Clear the recovery command and prepare to boot a (hopefully working) system,
 // copy our log file to cache as well (for the system to read). This function is
 // idempotent: call it as many times as you like.
-static void finish_recovery() {
+static void FinishRecovery(RecoveryUI* ui) {
   std::string locale = ui->GetLocale();
   // Save the locale to cache, so if recovery is next started up without a '--locale' argument
   // (e.g., directly from the bootloader) it will use the last-known locale.
-  if (!locale.empty() && has_cache) {
+  if (!locale.empty() && HasCache()) {
     LOG(INFO) << "Saving locale \"" << locale << "\"";
     if (ensure_path_mounted(LOCALE_FILE) != 0) {
       LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
@@ -145,7 +139,7 @@
     }
   }
 
-  copy_logs(save_current_log, has_cache, sehandle);
+  copy_logs(save_current_log);
 
   // Reset to normal system boot so recovery won't cycle indefinitely.
   std::string err;
@@ -154,7 +148,7 @@
   }
 
   // Remove the command file, so recovery won't repeat indefinitely.
-  if (has_cache) {
+  if (HasCache()) {
     if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
       LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
     }
@@ -168,7 +162,7 @@
   std::vector<std::string> headers{ question1, question2 };
   std::vector<std::string> items{ " No", " Yes" };
 
-  size_t chosen_item = ui->ShowMenu(
+  size_t chosen_item = device->GetUI()->ShowMenu(
       headers, items, 0, true,
       std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
   return (chosen_item == 1);
@@ -178,7 +172,7 @@
   std::vector<std::string> headers{ "Wipe all user data?", "  THIS CAN NOT BE UNDONE!" };
   std::vector<std::string> items{ " Cancel", " Factory data reset" };
 
-  size_t chosen_item = ui->ShowPromptWipeDataConfirmationMenu(
+  size_t chosen_item = device->GetUI()->ShowPromptWipeDataConfirmationMenu(
       headers, items,
       std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
 
@@ -200,7 +194,7 @@
   };
   // clang-format on
   for (;;) {
-    size_t chosen_item = ui->ShowPromptWipeDataMenu(
+    size_t chosen_item = device->GetUI()->ShowPromptWipeDataMenu(
         wipe_data_menu_headers, wipe_data_menu_items,
         std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
     // If ShowMenu() returned RecoveryUI::KeyError::INTERRUPTED, WaitKey() was interrupted.
@@ -212,7 +206,8 @@
     }
 
     if (ask_to_wipe_data(device)) {
-      bool convert_fbe = reason && strcmp(reason, "convert_fbe") == 0;
+      CHECK(device->GetReason().has_value());
+      bool convert_fbe = device->GetReason().value() == "convert_fbe";
       if (WipeData(device, convert_fbe)) {
         return INSTALL_SUCCESS;
       } else {
@@ -222,168 +217,9 @@
   }
 }
 
-// Secure-wipe a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with
-// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT.
-static bool secure_wipe_partition(const std::string& partition) {
-  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
-  if (fd == -1) {
-    PLOG(ERROR) << "Failed to open \"" << partition << "\"";
-    return false;
-  }
-
-  uint64_t range[2] = { 0, 0 };
-  if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
-    PLOG(ERROR) << "Failed to get partition size";
-    return false;
-  }
-  LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1];
-
-  LOG(INFO) << "  Trying BLKSECDISCARD...";
-  if (ioctl(fd, BLKSECDISCARD, &range) == -1) {
-    PLOG(WARNING) << "  Failed";
-
-    // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
-    unsigned int zeroes;
-    if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
-      LOG(INFO) << "  Trying BLKDISCARD...";
-      if (ioctl(fd, BLKDISCARD, &range) == -1) {
-        PLOG(ERROR) << "  Failed";
-        return false;
-      }
-    } else {
-      LOG(INFO) << "  Trying BLKZEROOUT...";
-      if (ioctl(fd, BLKZEROOUT, &range) == -1) {
-        PLOG(ERROR) << "  Failed";
-        return false;
-      }
-    }
-  }
-
-  LOG(INFO) << "  Done";
-  return true;
-}
-
-static std::unique_ptr<Package> ReadWipePackage(size_t wipe_package_size) {
-  if (wipe_package_size == 0) {
-    LOG(ERROR) << "wipe_package_size is zero";
-    return nullptr;
-  }
-
-  std::string wipe_package;
-  std::string err_str;
-  if (!read_wipe_package(&wipe_package, wipe_package_size, &err_str)) {
-    PLOG(ERROR) << "Failed to read wipe package" << err_str;
-    return nullptr;
-  }
-
-  return Package::CreateMemoryPackage(
-      std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr);
-}
-
-// Checks if the wipe package matches expectation. If the check passes, reads the list of
-// partitions to wipe from the package. Checks include
-// 1. verify the package.
-// 2. check metadata (ota-type, pre-device and serial number if having one).
-static bool CheckWipePackage(Package* wipe_package) {
-  if (!verify_package(wipe_package, ui)) {
-    LOG(ERROR) << "Failed to verify package";
-    return false;
-  }
-
-  ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
-  if (!zip) {
-    LOG(ERROR) << "Failed to get ZipArchiveHandle";
-    return false;
-  }
-
-  std::map<std::string, std::string> metadata;
-  if (!ReadMetadataFromPackage(zip, &metadata)) {
-    LOG(ERROR) << "Failed to parse metadata in the zip file";
-    return false;
-  }
-
-  return CheckPackageMetadata(metadata, OtaType::BRICK) == 0;
-}
-
-std::vector<std::string> GetWipePartitionList(Package* wipe_package) {
-  ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
-  if (!zip) {
-    LOG(ERROR) << "Failed to get ZipArchiveHandle";
-    return {};
-  }
-
-  static constexpr const char* RECOVERY_WIPE_ENTRY_NAME = "recovery.wipe";
-
-  std::string partition_list_content;
-  ZipString path(RECOVERY_WIPE_ENTRY_NAME);
-  ZipEntry entry;
-  if (FindEntry(zip, path, &entry) == 0) {
-    uint32_t length = entry.uncompressed_length;
-    partition_list_content = std::string(length, '\0');
-    if (auto err = ExtractToMemory(
-            zip, &entry, reinterpret_cast<uint8_t*>(partition_list_content.data()), length);
-        err != 0) {
-      LOG(ERROR) << "Failed to extract " << RECOVERY_WIPE_ENTRY_NAME << ": "
-                 << ErrorCodeString(err);
-      return {};
-    }
-  } else {
-    LOG(INFO) << "Failed to find " << RECOVERY_WIPE_ENTRY_NAME
-              << ", falling back to use the partition list on device.";
-
-    static constexpr const char* RECOVERY_WIPE_ON_DEVICE = "/etc/recovery.wipe";
-    if (!android::base::ReadFileToString(RECOVERY_WIPE_ON_DEVICE, &partition_list_content)) {
-      PLOG(ERROR) << "failed to read \"" << RECOVERY_WIPE_ON_DEVICE << "\"";
-      return {};
-    }
-  }
-
-  std::vector<std::string> result;
-  std::vector<std::string> lines = android::base::Split(partition_list_content, "\n");
-  for (const std::string& line : lines) {
-    std::string partition = android::base::Trim(line);
-    // Ignore '#' comment or empty lines.
-    if (android::base::StartsWith(partition, "#") || partition.empty()) {
-      continue;
-    }
-    result.push_back(line);
-  }
-
-  return result;
-}
-
-// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE.
-static bool wipe_ab_device(size_t wipe_package_size) {
-  ui->SetBackground(RecoveryUI::ERASING);
-  ui->SetProgressType(RecoveryUI::INDETERMINATE);
-
-  auto wipe_package = ReadWipePackage(wipe_package_size);
-  if (!wipe_package) {
-    LOG(ERROR) << "Failed to open wipe package";
-    return false;
-  }
-
-  if (!CheckWipePackage(wipe_package.get())) {
-    LOG(ERROR) << "Failed to verify wipe package";
-    return false;
-  }
-
-  auto partition_list = GetWipePartitionList(wipe_package.get());
-  if (partition_list.empty()) {
-    LOG(ERROR) << "Empty wipe ab partition list";
-    return false;
-  }
-
-  for (const auto& partition : partition_list) {
-    // Proceed anyway even if it fails to wipe some partition.
-    secure_wipe_partition(partition);
-  }
-  return true;
-}
-
 static void choose_recovery_file(Device* device) {
   std::vector<std::string> entries;
-  if (has_cache) {
+  if (HasCache()) {
     for (int i = 0; i < KEEP_LOG_COUNT; i++) {
       auto add_to_entries = [&](const char* filename) {
         std::string log_file(filename);
@@ -417,7 +253,7 @@
 
   size_t chosen_item = 0;
   while (true) {
-    chosen_item = ui->ShowMenu(
+    chosen_item = device->GetUI()->ShowMenu(
         headers, entries, chosen_item, true,
         std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
 
@@ -427,11 +263,11 @@
     }
     if (entries[chosen_item] == "Back") break;
 
-    ui->ShowFile(entries[chosen_item]);
+    device->GetUI()->ShowFile(entries[chosen_item]);
   }
 }
 
-static void run_graphics_test() {
+static void run_graphics_test(RecoveryUI* ui) {
   // Switch to graphics screen.
   ui->ShowText(false);
 
@@ -474,14 +310,64 @@
   ui->ShowText(true);
 }
 
-// Returns REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default,
-// which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery.
-static Device::BuiltinAction prompt_and_wait(Device* device, int status) {
+static void WriteUpdateInProgress() {
+  std::string err;
+  if (!update_bootloader_message({ "--reason=update_in_progress" }, &err)) {
+    LOG(ERROR) << "Failed to WriteUpdateInProgress: " << err;
+  }
+}
+
+static bool AskToReboot(Device* device, Device::BuiltinAction chosen_action) {
+  bool is_non_ab = android::base::GetProperty("ro.boot.slot_suffix", "").empty();
+  bool is_virtual_ab = android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
+  if (!is_non_ab && !is_virtual_ab) {
+    // Only prompt for non-A/B or Virtual A/B devices.
+    return true;
+  }
+
+  std::string header_text;
+  std::string item_text;
+  switch (chosen_action) {
+    case Device::REBOOT:
+      header_text = "reboot";
+      item_text = " Reboot system now";
+      break;
+    case Device::SHUTDOWN:
+      header_text = "power off";
+      item_text = " Power off";
+      break;
+    default:
+      LOG(FATAL) << "Invalid chosen action " << chosen_action;
+      break;
+  }
+
+  std::vector<std::string> headers{ "WARNING: Previous installation has failed.",
+                                    "  Your device may fail to boot if you " + header_text +
+                                        " now.",
+                                    "  Confirm reboot?" };
+  std::vector<std::string> items{ " Cancel", item_text };
+
+  size_t chosen_item = device->GetUI()->ShowMenu(
+      headers, items, 0, true /* menu_only */,
+      std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+  return (chosen_item == 1);
+}
+
+// Shows the recovery UI and waits for user input. Returns one of the device builtin actions, such
+// as REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, which
+// is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery.
+static Device::BuiltinAction PromptAndWait(Device* device, InstallResult status) {
+  auto ui = device->GetUI();
+  bool update_in_progress = (device->GetReason().value_or("") == "update_in_progress");
   for (;;) {
-    finish_recovery();
+    FinishRecovery(ui);
     switch (status) {
       case INSTALL_SUCCESS:
       case INSTALL_NONE:
+      case INSTALL_SKIPPED:
+      case INSTALL_RETRY:
+      case INSTALL_KEY_INTERRUPTED:
         ui->SetBackground(RecoveryUI::NO_COMMAND);
         break;
 
@@ -489,11 +375,23 @@
       case INSTALL_CORRUPT:
         ui->SetBackground(RecoveryUI::ERROR);
         break;
+
+      case INSTALL_REBOOT:
+        // All the reboots should have been handled prior to entering PromptAndWait() or immediately
+        // after installing a package.
+        LOG(FATAL) << "Invalid status code of INSTALL_REBOOT";
+        break;
     }
     ui->SetProgressType(RecoveryUI::EMPTY);
 
+    std::vector<std::string> headers;
+    if (update_in_progress) {
+      headers = { "WARNING: Previous installation has failed.",
+                  "  Your device may fail to boot if you reboot or power off now." };
+    }
+
     size_t chosen_item = ui->ShowMenu(
-        {}, device->GetMenuItems(), 0, false,
+        headers, device->GetMenuItems(), 0, false,
         std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
     // Handle Interrupt key
     if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
@@ -507,19 +405,34 @@
             : device->InvokeMenuItem(chosen_item);
 
     switch (chosen_action) {
+      case Device::REBOOT_FROM_FASTBOOT:    // Can not happen
+      case Device::SHUTDOWN_FROM_FASTBOOT:  // Can not happen
       case Device::NO_ACTION:
         break;
 
       case Device::ENTER_FASTBOOT:
       case Device::ENTER_RECOVERY:
-      case Device::REBOOT:
       case Device::REBOOT_BOOTLOADER:
       case Device::REBOOT_FASTBOOT:
       case Device::REBOOT_RECOVERY:
       case Device::REBOOT_RESCUE:
-      case Device::SHUTDOWN:
         return chosen_action;
 
+      case Device::REBOOT:
+      case Device::SHUTDOWN:
+        if (!ui->IsTextVisible()) {
+          return Device::REBOOT;
+        }
+        // okay to reboot; no need to ask.
+        if (!update_in_progress) {
+          return Device::REBOOT;
+        }
+        // An update might have been failed. Ask if user really wants to reboot.
+        if (AskToReboot(device, chosen_action)) {
+          return Device::REBOOT;
+        }
+        break;
+
       case Device::WIPE_DATA:
         save_current_log = true;
         if (ui->IsTextVisible()) {
@@ -547,6 +460,9 @@
       case Device::ENTER_RESCUE: {
         save_current_log = true;
 
+        update_in_progress = true;
+        WriteUpdateInProgress();
+
         bool adb = true;
         Device::BuiltinAction reboot_action;
         if (chosen_action == Device::ENTER_RESCUE) {
@@ -557,7 +473,7 @@
           status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action);
         } else {
           adb = false;
-          status = ApplyFromSdcard(device, ui);
+          status = ApplyFromSdcard(device);
         }
 
         ui->Print("\nInstall from %s completed with status %d.\n", adb ? "ADB" : "SD card", status);
@@ -565,12 +481,15 @@
           return reboot_action;
         }
 
-        if (status != INSTALL_SUCCESS) {
+        if (status == INSTALL_SUCCESS) {
+          update_in_progress = false;
+          if (!ui->IsTextVisible()) {
+            return Device::NO_ACTION;  // reboot if logs aren't visible
+          }
+        } else {
           ui->SetBackground(RecoveryUI::ERROR);
           ui->Print("Installation aborted.\n");
-          copy_logs(save_current_log, has_cache, sehandle);
-        } else if (!ui->IsTextVisible()) {
-          return Device::NO_ACTION;  // reboot if logs aren't visible
+          copy_logs(save_current_log);
         }
         break;
       }
@@ -580,7 +499,7 @@
         break;
 
       case Device::RUN_GRAPHICS_TEST:
-        run_graphics_test();
+        run_graphics_test(ui);
         break;
 
       case Device::RUN_LOCALE_TEST: {
@@ -588,9 +507,14 @@
         screen_ui->CheckBackgroundTextImages();
         break;
       }
+
       case Device::MOUNT_SYSTEM:
-        // the system partition is mounted at /mnt/system
-        if (ensure_path_mounted_at(get_system_root(), "/mnt/system") != -1) {
+        // For Virtual A/B, set up the snapshot devices (if exist).
+        if (!CreateSnapshotPartitions()) {
+          ui->Print("Virtual A/B: snapshot partitions creation failed.\n");
+          break;
+        }
+        if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) {
           ui->Print("Mounted /system.\n");
         }
         break;
@@ -605,74 +529,17 @@
   printf("%s=%s\n", key, name);
 }
 
-static bool is_battery_ok(int* required_battery_level) {
-  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;
+static bool IsBatteryOk(int* required_battery_level) {
+  // GmsCore enters recovery mode to install package when having enough battery percentage.
+  // Normally, the threshold is 40% without charger and 20% with charger. So we check the battery
+  // level against a slightly lower limit.
+  constexpr int BATTERY_OK_PERCENTAGE = 20;
+  constexpr int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15;
 
-  android::sp<IHealth> health = get_health_service();
-
-  static constexpr int BATTERY_READ_TIMEOUT_IN_SEC = 10;
-  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
-    }
-
-    // Treat unknown status as charged.
-    bool charged = (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
-    }
-
-    LOG(INFO) << "charge_status " << toString(charge_status) << ", charged " << charged
-              << ", status " << toString(res) << ", capacity " << capacity;
-    // 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 (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) {
-        sleep(1);
-        wait_second++;
-        continue;
-      }
-    }
-    // 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) {
-      capacity = 100;
-    }
-
-    // GmsCore enters recovery mode to install package when having enough battery percentage.
-    // Normally, the threshold is 40% without charger and 20% with charger. So we should check
-    // battery with a slightly lower limitation.
-    static constexpr int BATTERY_OK_PERCENTAGE = 20;
-    static constexpr int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15;
-    *required_battery_level = charged ? BATTERY_WITH_CHARGER_OK_PERCENTAGE : BATTERY_OK_PERCENTAGE;
-    return capacity >= *required_battery_level;
-  }
+  auto battery_info = GetBatteryInfo();
+  *required_battery_level =
+      battery_info.charging ? BATTERY_WITH_CHARGER_OK_PERCENTAGE : BATTERY_OK_PERCENTAGE;
+  return battery_info.capacity >= *required_battery_level;
 }
 
 // Set the retry count to |retry_count| in BCB.
@@ -726,7 +593,7 @@
 Device::BuiltinAction start_recovery(Device* device, const std::vector<std::string>& args) {
   static constexpr struct option OPTIONS[] = {
     { "fastboot", no_argument, nullptr, 0 },
-    { "fsck_unshare_blocks", no_argument, nullptr, 0 },
+    { "install_with_fuse", no_argument, nullptr, 0 },
     { "just_exit", no_argument, nullptr, 'x' },
     { "locale", required_argument, nullptr, 0 },
     { "prompt_and_wipe_data", no_argument, nullptr, 0 },
@@ -747,6 +614,7 @@
   };
 
   const char* update_package = nullptr;
+  bool install_with_fuse = false;  // memory map the update package by default.
   bool should_wipe_data = false;
   bool should_prompt_and_wipe_data = false;
   bool should_wipe_cache = false;
@@ -757,7 +625,6 @@
   bool rescue = false;
   bool just_exit = false;
   bool shutdown_after = false;
-  bool fsck_unshare_blocks = false;
   int retry_count = 0;
   bool security_update = false;
   std::string locale;
@@ -780,14 +647,12 @@
         break;
       case 0: {
         std::string option = OPTIONS[option_index].name;
-        if (option == "fsck_unshare_blocks") {
-          fsck_unshare_blocks = true;
-        } else if (option == "locale" || option == "fastboot") {
+        if (option == "install_with_fuse") {
+          install_with_fuse = true;
+        } else if (option == "locale" || option == "fastboot" || option == "reason") {
           // Handled in recovery_main.cpp
         } else if (option == "prompt_and_wipe_data") {
           should_prompt_and_wipe_data = true;
-        } else if (option == "reason") {
-          reason = optarg;
         } else if (option == "rescue") {
           rescue = true;
         } else if (option == "retry_count") {
@@ -821,15 +686,18 @@
   }
   optind = 1;
 
-  printf("stage is [%s]\n", stage.c_str());
-  printf("reason is [%s]\n", reason);
+  printf("stage is [%s]\n", device->GetStage().value_or("").c_str());
+  printf("reason is [%s]\n", device->GetReason().value_or("").c_str());
+
+  auto ui = device->GetUI();
 
   // Set background string to "installing security update" for security update,
   // otherwise set it to "installing system update".
   ui->SetSystemUpdateText(security_update);
 
   int st_cur, st_max;
-  if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
+  if (!device->GetStage().has_value() &&
+      sscanf(device->GetStage().value().c_str(), "%d/%d", &st_cur, &st_max) == 2) {
     ui->SetStage(st_cur, st_max);
   }
 
@@ -850,9 +718,7 @@
   property_list(print_property, nullptr);
   printf("\n");
 
-  ui->Print("Supported API: %d\n", kRecoveryApiVersion);
-
-  int status = INSTALL_SUCCESS;
+  InstallResult status = INSTALL_SUCCESS;
   // next_action indicates the next target to reboot into upon finishing the install. It could be
   // overridden to a different reboot target per user request.
   Device::BuiltinAction next_action = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
@@ -862,12 +728,10 @@
     // to log the update attempt since update_package is non-NULL.
     save_current_log = true;
 
-    int required_battery_level;
-    if (retry_count == 0 && !is_battery_ok(&required_battery_level)) {
+    if (int required_battery_level; retry_count == 0 && !IsBatteryOk(&required_battery_level)) {
       ui->Print("battery capacity is not enough for installing package: %d%% needed\n",
                 required_battery_level);
-      // Log the error code to last_install when installation skips due to
-      // low battery.
+      // Log the error code to last_install when installation skips due to low battery.
       log_failure_code(kLowBattery, update_package);
       status = INSTALL_SKIPPED;
     } else if (retry_count == 0 && bootreason_in_blacklist()) {
@@ -882,29 +746,33 @@
         set_retry_bootloader_message(retry_count + 1, args);
       }
 
+      if (update_package[0] == '@') {
+        ensure_path_mounted(update_package + 1);
+      } else {
+        ensure_path_mounted(update_package);
+      }
+
       bool should_use_fuse = false;
       if (!SetupPackageMount(update_package, &should_use_fuse)) {
         LOG(INFO) << "Failed to set up the package access, skipping installation";
         status = INSTALL_ERROR;
-      } else if (should_use_fuse) {
+      } else if (install_with_fuse || should_use_fuse) {
         LOG(INFO) << "Installing package " << update_package << " with fuse";
-        auto file_data_reader = std::make_unique<FuseFileDataProvider>(update_package, 65536);
-        status = run_fuse_sideload(std::move(file_data_reader));
+        status = InstallWithFuseFromPath(update_package, ui);
       } else if (auto memory_package = Package::CreateMemoryPackage(
                      update_package,
                      std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
                  memory_package != nullptr) {
-        status = install_package(update_package, should_wipe_cache, true, retry_count, ui);
+        status = InstallPackage(memory_package.get(), update_package, should_wipe_cache,
+                                retry_count, ui);
       } 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";
-        auto file_data_reader = std::make_unique<FuseFileDataProvider>(update_package, 65536);
-        status = run_fuse_sideload(std::move(file_data_reader));
+        status = InstallWithFuseFromPath(update_package, ui);
       }
-
       if (status != INSTALL_SUCCESS) {
         ui->Print("Installation aborted.\n");
 
@@ -912,32 +780,27 @@
         // RETRY_LIMIT times before we abandon this OTA update.
         static constexpr int RETRY_LIMIT = 4;
         if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
-          copy_logs(save_current_log, has_cache, sehandle);
+          copy_logs(save_current_log);
           retry_count += 1;
           set_retry_bootloader_message(retry_count, args);
           // Print retry count on screen.
           ui->Print("Retry attempt %d\n", retry_count);
 
-          // Reboot and retry the update
-          if (!reboot("reboot,recovery")) {
-            ui->Print("Reboot failed\n");
-          } else {
-            while (true) {
-              pause();
-            }
-          }
+          // Reboot back into recovery to retry the update.
+          Reboot("recovery");
         }
         // If this is an eng or userdebug build, then automatically
         // turn the text display on if the script fails so the error
         // message is visible.
-        if (is_ro_debuggable()) {
+        if (IsRoDebuggable()) {
           ui->ShowText(true);
         }
       }
     }
   } else if (should_wipe_data) {
     save_current_log = true;
-    bool convert_fbe = reason && strcmp(reason, "convert_fbe") == 0;
+    CHECK(device->GetReason().has_value());
+    bool convert_fbe = device->GetReason().value() == "convert_fbe";
     if (!WipeData(device, convert_fbe)) {
       status = INSTALL_ERROR;
     }
@@ -957,7 +820,7 @@
       status = INSTALL_ERROR;
     }
   } else if (should_wipe_ab) {
-    if (!wipe_ab_device(wipe_package_size)) {
+    if (!WipeAbDevice(device, wipe_package_size)) {
       status = INSTALL_ERROR;
     }
   } else if (sideload) {
@@ -979,15 +842,11 @@
     save_current_log = true;
     status = ApplyFromAdb(device, true /* rescue_mode */, &next_action);
     ui->Print("\nInstall from ADB complete (status: %d).\n", status);
-  } else if (fsck_unshare_blocks) {
-    if (!do_fsck_unshare_blocks()) {
-      status = INSTALL_ERROR;
-    }
   } else if (!just_exit) {
     // If this is an eng or userdebug build, automatically turn on the text display if no command
     // is specified. Note that this should be called before setting the background to avoid
     // flickering the background image.
-    if (is_ro_debuggable()) {
+    if (IsRoDebuggable()) {
       ui->ShowText(true);
     }
     status = INSTALL_NONE;  // No command specified
@@ -1012,7 +871,7 @@
   //    for 5s followed by an automatic reboot.
   if (status != INSTALL_REBOOT) {
     if (status == INSTALL_NONE || ui->IsTextVisible()) {
-      Device::BuiltinAction temp = prompt_and_wait(device, status);
+      auto temp = PromptAndWait(device, status);
       if (temp != Device::NO_ACTION) {
         next_action = temp;
       }
@@ -1020,7 +879,7 @@
   }
 
   // Save logs and clean up before rebooting or shutting down.
-  finish_recovery();
+  FinishRecovery(ui);
 
   return next_action;
 }
diff --git a/recovery_main.cpp b/recovery_main.cpp
index de8ac1f..30a1fc0 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -41,34 +41,37 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <bootloader_message/bootloader_message.h>
-#include <cutils/android_reboot.h>
 #include <cutils/sockets.h>
+#include <fs_mgr/roots.h>
 #include <private/android_logger.h> /* private pmsg functions */
 #include <selinux/android.h>
 #include <selinux/label.h>
 #include <selinux/selinux.h>
 
-#include "common.h"
 #include "fastboot/fastboot.h"
 #include "install/wipe_data.h"
-#include "otautil/logging.h"
+#include "otautil/boot_state.h"
 #include "otautil/paths.h"
-#include "otautil/roots.h"
 #include "otautil/sysutil.h"
 #include "recovery.h"
 #include "recovery_ui/device.h"
 #include "recovery_ui/stub_ui.h"
 #include "recovery_ui/ui.h"
+#include "recovery_utils/logging.h"
+#include "recovery_utils/roots.h"
 
 static constexpr const char* COMMAND_FILE = "/cache/recovery/command";
 static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale";
 
-static constexpr const char* CACHE_ROOT = "/cache";
+static RecoveryUI* ui = nullptr;
 
-bool has_cache = false;
+static bool IsRoDebuggable() {
+  return android::base::GetBoolProperty("ro.debuggable", false);
+}
 
-RecoveryUI* ui = nullptr;
-struct selabel_handle* sehandle;
+static bool IsDeviceUnlocked() {
+  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 */,
@@ -81,11 +84,12 @@
   }
 }
 
+// Parses the command line argument from various sources; and reads the stage field from BCB.
 // command line args come from, in decreasing precedence:
 //   - the actual command line
 //   - the bootloader control block (one per line, after "recovery")
 //   - the contents of COMMAND_FILE (one per line)
-static std::vector<std::string> get_args(const int argc, char** const argv) {
+static std::vector<std::string> get_args(const int argc, char** const argv, std::string* stage) {
   CHECK_GT(argc, 0);
 
   bootloader_message boot = {};
@@ -95,7 +99,9 @@
     // If fails, leave a zeroed bootloader_message.
     boot = {};
   }
-  stage = std::string(boot.stage);
+  if (stage) {
+    *stage = std::string(boot.stage);
+  }
 
   std::string boot_command;
   if (boot.command[0] != 0) {
@@ -131,7 +137,7 @@
   }
 
   // --- if that doesn't work, try the command file (if we have /cache).
-  if (args.size() == 1 && has_cache) {
+  if (args.size() == 1 && HasCache()) {
     std::string content;
     if (ensure_path_mounted(COMMAND_FILE) == 0 &&
         android::base::ReadFileToString(COMMAND_FILE, &content)) {
@@ -148,7 +154,7 @@
 
   // Write the arguments (excluding the filename in args[0]) back into the
   // bootloader control block. So the device will always boot into recovery to
-  // finish the pending work, until finish_recovery() is called.
+  // finish the pending work, until FinishRecovery() is called.
   std::vector<std::string> options(args.cbegin() + 1, args.cend());
   if (!update_bootloader_message(options, &err)) {
     LOG(ERROR) << "Failed to set BCB message: " << err;
@@ -331,14 +337,15 @@
   redirect_stdio(Paths::Get().temporary_log_file().c_str());
 
   load_volume_table();
-  has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
 
-  std::vector<std::string> args = get_args(argc, argv);
+  std::string stage;
+  std::vector<std::string> args = get_args(argc, argv, &stage);
   auto args_to_parse = StringVectorToNullTerminatedArray(args);
 
   static constexpr struct option OPTIONS[] = {
     { "fastboot", no_argument, nullptr, 0 },
     { "locale", required_argument, nullptr, 0 },
+    { "reason", required_argument, nullptr, 0 },
     { "show_text", no_argument, nullptr, 't' },
     { nullptr, 0, nullptr, 0 },
   };
@@ -346,6 +353,13 @@
   bool show_text = false;
   bool fastboot = false;
   std::string locale;
+  std::string reason;
+
+  // The code here is only interested in the options that signal the intent to start fastbootd or
+  // recovery. Unrecognized options are likely meant for recovery, which will be processed later in
+  // start_recovery(). Suppress the warnings for such -- even if some flags were indeed invalid, the
+  // code in start_recovery() will capture and report them.
+  opterr = 0;
 
   int arg;
   int option_index;
@@ -359,6 +373,8 @@
         std::string option = OPTIONS[option_index].name;
         if (option == "locale") {
           locale = optarg;
+        } else if (option == "reason") {
+          reason = optarg;
         } else if (option == "fastboot" &&
                    android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
           fastboot = true;
@@ -368,14 +384,14 @@
     }
   }
   optind = 1;
+  opterr = 1;
 
   if (locale.empty()) {
-    if (has_cache) {
+    if (HasCache()) {
       locale = load_locale_from_cache();
     }
 
     if (locale.empty()) {
-      static constexpr const char* DEFAULT_LOCALE = "en-US";
       locale = DEFAULT_LOCALE;
     }
   }
@@ -415,9 +431,12 @@
       device->ResetUI(new StubRecoveryUI());
     }
   }
+
+  BootState boot_state(reason, stage);  // recovery_main owns the state of boot.
+  device->SetBootState(&boot_state);
   ui = device->GetUI();
 
-  if (!has_cache) {
+  if (!HasCache()) {
     device->RemoveMenuItemForAction(Device::WIPE_CACHE);
   }
 
@@ -425,7 +444,7 @@
     device->RemoveMenuItemForAction(Device::ENTER_FASTBOOT);
   }
 
-  if (!is_ro_debuggable()) {
+  if (!IsRoDebuggable()) {
     device->RemoveMenuItemForAction(Device::ENTER_RESCUE);
   }
 
@@ -435,7 +454,7 @@
   LOG(INFO) << "Starting recovery (pid " << getpid() << ") on " << ctime(&start);
   LOG(INFO) << "locale is [" << locale << "]";
 
-  sehandle = selinux_android_file_context_handle();
+  auto sehandle = selinux_android_file_context_handle();
   selinux_android_set_sehandle(sehandle);
   if (!sehandle) {
     ui->Print("Warning: No file_contexts\n");
@@ -448,7 +467,9 @@
   listener_thread.detach();
 
   while (true) {
-    std::string usb_config = fastboot ? "fastboot" : is_ro_debuggable() ? "adb" : "none";
+    // We start adbd in recovery for the device with userdebug build or a unlocked bootloader.
+    std::string usb_config =
+        fastboot ? "fastboot" : IsRoDebuggable() || IsDeviceUnlocked() ? "adb" : "none";
     std::string usb_state = android::base::GetProperty("sys.usb.state", "none");
     if (usb_config != usb_state) {
       if (!SetUsbConfig("none")) {
@@ -472,27 +493,31 @@
     switch (ret) {
       case Device::SHUTDOWN:
         ui->Print("Shutting down...\n");
-        // TODO: Move all the reboots to reboot(), which should conditionally set quiescent flag.
-        android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
+        Shutdown("userrequested,recovery");
+        break;
+
+      case Device::SHUTDOWN_FROM_FASTBOOT:
+        ui->Print("Shutting down...\n");
+        Shutdown("userrequested,fastboot");
         break;
 
       case Device::REBOOT_BOOTLOADER:
         ui->Print("Rebooting to bootloader...\n");
-        android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
+        Reboot("bootloader");
         break;
 
       case Device::REBOOT_FASTBOOT:
         ui->Print("Rebooting to recovery/fastboot...\n");
-        android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot");
+        Reboot("fastboot");
         break;
 
       case Device::REBOOT_RECOVERY:
         ui->Print("Rebooting to recovery...\n");
-        reboot("reboot,recovery");
+        Reboot("recovery");
         break;
 
       case Device::REBOOT_RESCUE: {
-        // Not using `reboot("reboot,rescue")`, as it requires matching support in kernel and/or
+        // Not using `Reboot("rescue")`, as it requires matching support in kernel and/or
         // bootloader.
         bootloader_message boot = {};
         strlcpy(boot.command, "boot-rescue", sizeof(boot.command));
@@ -503,14 +528,14 @@
           continue;
         }
         ui->Print("Rebooting to recovery/rescue...\n");
-        reboot("reboot,recovery");
+        Reboot("recovery");
         break;
       }
 
       case Device::ENTER_FASTBOOT:
-        if (logical_partitions_mapped()) {
+        if (android::fs_mgr::LogicalPartitionsMapped()) {
           ui->Print("Partitions may be mounted - rebooting to enter fastboot.");
-          android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot");
+          Reboot("fastboot");
         } else {
           LOG(INFO) << "Entering fastboot";
           fastboot = true;
@@ -522,9 +547,19 @@
         fastboot = false;
         break;
 
+      case Device::REBOOT:
+        ui->Print("Rebooting...\n");
+        Reboot("userrequested,recovery");
+        break;
+
+      case Device::REBOOT_FROM_FASTBOOT:
+        ui->Print("Rebooting...\n");
+        Reboot("userrequested,fastboot");
+        break;
+
       default:
         ui->Print("Rebooting...\n");
-        reboot("reboot,");
+        Reboot("unknown" + std::to_string(ret));
         break;
     }
   }
diff --git a/recovery_ui/Android.bp b/recovery_ui/Android.bp
index ee3149d..149ef8a 100644
--- a/recovery_ui/Android.bp
+++ b/recovery_ui/Android.bp
@@ -23,6 +23,7 @@
     srcs: [
         "device.cpp",
         "screen_ui.cpp",
+        "stub_ui.cpp",
         "ui.cpp",
         "vr_ui.cpp",
         "wear_ui.cpp",
diff --git a/recovery_ui/device.cpp b/recovery_ui/device.cpp
index e7ae1a3..d46df92 100644
--- a/recovery_ui/device.cpp
+++ b/recovery_ui/device.cpp
@@ -23,6 +23,7 @@
 
 #include <android-base/logging.h>
 
+#include "otautil/boot_state.h"
 #include "recovery_ui/ui.h"
 
 static std::vector<std::pair<std::string, Device::BuiltinAction>> g_menu_actions{
@@ -95,3 +96,15 @@
       return ui_->HasThreeButtons() ? kNoAction : kHighlightDown;
   }
 }
+
+void Device::SetBootState(const BootState* state) {
+  boot_state_ = state;
+}
+
+std::optional<std::string> Device::GetReason() const {
+  return boot_state_ ? std::make_optional(boot_state_->reason()) : std::nullopt;
+}
+
+std::optional<std::string> Device::GetStage() const {
+  return boot_state_ ? std::make_optional(boot_state_->stage()) : std::nullopt;
+}
diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h
index 7c76cdb..f4f9936 100644
--- a/recovery_ui/include/recovery_ui/device.h
+++ b/recovery_ui/include/recovery_ui/device.h
@@ -20,12 +20,15 @@
 #include <stddef.h>
 
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
 // Forward declaration to avoid including "ui.h".
 class RecoveryUI;
 
+class BootState;
+
 class Device {
  public:
   static constexpr const int kNoAction = -1;
@@ -58,6 +61,8 @@
     REBOOT_FASTBOOT = 17,
     REBOOT_RECOVERY = 18,
     REBOOT_RESCUE = 19,
+    REBOOT_FROM_FASTBOOT = 20,
+    SHUTDOWN_FROM_FASTBOOT = 21,
   };
 
   explicit Device(RecoveryUI* ui);
@@ -124,9 +129,16 @@
     return true;
   }
 
+  void SetBootState(const BootState* state);
+  // The getters for reason and stage may return std::nullopt until StartRecovery() is called. It's
+  // the caller's responsibility to perform the check and handle the exception.
+  std::optional<std::string> GetReason() const;
+  std::optional<std::string> GetStage() const;
+
  private:
   // The RecoveryUI object that should be used to display the user interface for this device.
   std::unique_ptr<RecoveryUI> ui_;
+  const BootState* boot_state_{ nullptr };
 };
 
 // Disable name mangling, as this function will be loaded via dlsym(3).
diff --git a/recovery_ui/include/recovery_ui/screen_ui.h b/recovery_ui/include/recovery_ui/screen_ui.h
index 5cda2a2..92b3c25 100644
--- a/recovery_ui/include/recovery_ui/screen_ui.h
+++ b/recovery_ui/include/recovery_ui/screen_ui.h
@@ -286,6 +286,9 @@
   // selected.
   virtual int SelectMenu(int sel);
 
+  // Returns the help message displayed on top of the menu.
+  virtual std::vector<std::string> GetMenuHelpMessage() const;
+
   virtual void draw_background_locked();
   virtual void draw_foreground_locked();
   virtual void draw_screen_locked();
diff --git a/recovery_ui/include/recovery_ui/stub_ui.h b/recovery_ui/include/recovery_ui/stub_ui.h
index fb1d8c7..511b131 100644
--- a/recovery_ui/include/recovery_ui/stub_ui.h
+++ b/recovery_ui/include/recovery_ui/stub_ui.h
@@ -62,11 +62,9 @@
 
   // menu display
   size_t ShowMenu(const std::vector<std::string>& /* headers */,
-                  const std::vector<std::string>& /* items */, size_t initial_selection,
+                  const std::vector<std::string>& /* items */, size_t /* initial_selection */,
                   bool /* menu_only */,
-                  const std::function<int(int, bool)>& /* key_handler */) override {
-    return initial_selection;
-  }
+                  const std::function<int(int, bool)>& /* key_handler */) override;
 
   size_t ShowPromptWipeDataMenu(const std::vector<std::string>& /* backup_headers */,
                                 const std::vector<std::string>& /* backup_items */,
diff --git a/recovery_ui/include/recovery_ui/ui.h b/recovery_ui/include/recovery_ui/ui.h
index d55322c..08ec1d7 100644
--- a/recovery_ui/include/recovery_ui/ui.h
+++ b/recovery_ui/include/recovery_ui/ui.h
@@ -27,6 +27,8 @@
 #include <thread>
 #include <vector>
 
+static constexpr const char* DEFAULT_LOCALE = "en-US";
+
 // Abstract class for controlling the user interface during recovery.
 class RecoveryUI {
  public:
@@ -116,7 +118,7 @@
 
   // Returns true if you have the volume up/down and power trio typical of phones and tablets, false
   // otherwise.
-  virtual bool HasThreeButtons();
+  virtual bool HasThreeButtons() const;
 
   // Returns true if it has a power key.
   virtual bool HasPowerKey() const;
@@ -228,20 +230,23 @@
 
   bool InitScreensaver();
   void SetScreensaverState(ScreensaverState state);
+
   // Key event input queue
   std::mutex key_queue_mutex;
   std::condition_variable key_queue_cond;
   bool key_interrupted_;
   int key_queue[256], key_queue_len;
-  char key_pressed[KEY_MAX + 1];  // under key_queue_mutex
-  int key_last_down;              // under key_queue_mutex
-  bool key_long_press;            // under key_queue_mutex
-  int key_down_count;             // under key_queue_mutex
-  bool enable_reboot;             // under key_queue_mutex
-  int rel_sum;
 
+  // key press events
+  std::mutex key_press_mutex;
+  char key_pressed[KEY_MAX + 1];
+  int key_last_down;
+  bool key_long_press;
+  int key_down_count;
+  bool enable_reboot;
+
+  int rel_sum;
   int consecutive_power_keys;
-  int last_key;
 
   bool has_power_key;
   bool has_up_key;
diff --git a/recovery_ui/screen_ui.cpp b/recovery_ui/screen_ui.cpp
index 870db62..6dcb161 100644
--- a/recovery_ui/screen_ui.cpp
+++ b/recovery_ui/screen_ui.cpp
@@ -448,7 +448,9 @@
     int frame_height = gr_get_height(frame);
     int frame_x = (ScreenWidth() - frame_width) / 2;
     int frame_y = GetAnimationBaseline();
-    DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
+    if (frame_x >= 0 && frame_y >= 0 && (frame_x + frame_width) < ScreenWidth() &&
+        (frame_y + frame_height) < ScreenHeight())
+      DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
   }
 
   if (progressBarType != EMPTY) {
@@ -673,6 +675,19 @@
   title_lines_ = lines;
 }
 
+std::vector<std::string> ScreenRecoveryUI::GetMenuHelpMessage() const {
+  // clang-format off
+  static std::vector<std::string> REGULAR_HELP{
+    "Use volume up/down and power.",
+  };
+  static std::vector<std::string> LONG_PRESS_HELP{
+    "Any button cycles highlight.",
+    "Long-press activates.",
+  };
+  // clang-format on
+  return HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP;
+}
+
 // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
 // locked.
 void ScreenRecoveryUI::draw_screen_locked() {
@@ -685,16 +700,7 @@
   gr_color(0, 0, 0, 255);
   gr_clear();
 
-  // clang-format off
-  static std::vector<std::string> REGULAR_HELP{
-    "Use volume up/down and power.",
-  };
-  static std::vector<std::string> LONG_PRESS_HELP{
-    "Any button cycles highlight.",
-    "Long-press activates.",
-  };
-  // clang-format on
-  draw_menu_and_text_buffer_locked(HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
+  draw_menu_and_text_buffer_locked(GetMenuHelpMessage());
 }
 
 // Draws the menu and text buffer on the screen. Should only be called with updateMutex locked.
@@ -817,12 +823,22 @@
 
 std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) {
   GRSurface* surface;
-  if (auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface);
-      result < 0) {
-    LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")";
-    return nullptr;
+  auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface);
+  if (result == 0) {
+    return std::unique_ptr<GRSurface>(surface);
   }
-  return std::unique_ptr<GRSurface>(surface);
+  // TODO(xunchang) create a error code enum to refine the retry condition.
+  LOG(WARNING) << "Failed to load bitmap " << filename << " for locale " << locale_ << " (error "
+               << result << "). Falling back to use default locale.";
+
+  result = res_create_localized_alpha_surface(filename.c_str(), DEFAULT_LOCALE, &surface);
+  if (result == 0) {
+    return std::unique_ptr<GRSurface>(surface);
+  }
+
+  LOG(ERROR) << "Failed to load bitmap " << filename << " for locale " << DEFAULT_LOCALE
+             << " (error " << result << ")";
+  return nullptr;
 }
 
 static char** Alloc2d(size_t rows, size_t cols) {
@@ -1253,7 +1269,7 @@
     return initial_selection;
   }
 
-  return ShowMenu(CreateMenu(headers, items, initial_selection), menu_only, key_handler);
+  return ShowMenu(std::move(menu), menu_only, key_handler);
 }
 
 size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers,
diff --git a/recovery_ui/stub_ui.cpp b/recovery_ui/stub_ui.cpp
new file mode 100644
index 0000000..a56b3f7
--- /dev/null
+++ b/recovery_ui/stub_ui.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "recovery_ui/stub_ui.h"
+
+#include <android-base/logging.h>
+
+#include "recovery_ui/device.h"
+
+size_t StubRecoveryUI::ShowMenu(const std::vector<std::string>& /* headers */,
+                                const std::vector<std::string>& /* items */,
+                                size_t /* initial_selection */, bool /* menu_only */,
+                                const std::function<int(int, bool)>& /*key_handler*/) {
+  while (true) {
+    int key = WaitKey();
+    // Exit the loop in the case of interruption or time out.
+    if (key == static_cast<int>(KeyError::INTERRUPTED) ||
+        key == static_cast<int>(KeyError::TIMED_OUT)) {
+      return static_cast<size_t>(key);
+    }
+  }
+  LOG(FATAL) << "Unreachable key selected in ShowMenu of stub UI";
+}
diff --git a/recovery_ui/ui.cpp b/recovery_ui/ui.cpp
index b7107ff..3307217 100644
--- a/recovery_ui/ui.cpp
+++ b/recovery_ui/ui.cpp
@@ -70,7 +70,6 @@
       key_down_count(0),
       enable_reboot(true),
       consecutive_power_keys(0),
-      last_key(-1),
       has_power_key(false),
       has_up_key(false),
       has_down_key(false),
@@ -346,7 +345,7 @@
   bool long_press = false;
 
   {
-    std::lock_guard<std::mutex> lg(key_queue_mutex);
+    std::lock_guard<std::mutex> lg(key_press_mutex);
     key_pressed[key_code] = updown;
     if (updown) {
       ++key_down_count;
@@ -375,10 +374,7 @@
 
       case RecoveryUI::REBOOT:
         if (reboot_enabled) {
-          reboot("reboot,");
-          while (true) {
-            pause();
-          }
+          Reboot("userrequested,recovery,ui");
         }
         break;
 
@@ -393,7 +389,7 @@
   std::this_thread::sleep_for(750ms);  // 750 ms == "long"
   bool long_press = false;
   {
-    std::lock_guard<std::mutex> lg(key_queue_mutex);
+    std::lock_guard<std::mutex> lg(key_press_mutex);
     if (key_last_down == key_code && key_down_count == count) {
       long_press = key_long_press = true;
     }
@@ -419,7 +415,7 @@
         LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
                   << "%)";
       } else {
-        LOG(ERROR) << "Unable to set brightness to normal";
+        LOG(WARNING) << "Unable to set brightness to normal";
       }
       break;
     case ScreensaverState::DIMMED:
@@ -429,7 +425,7 @@
                   << "%)";
         screensaver_state_ = ScreensaverState::DIMMED;
       } else {
-        LOG(ERROR) << "Unable to set brightness to dim";
+        LOG(WARNING) << "Unable to set brightness to dim";
       }
       break;
     case ScreensaverState::OFF:
@@ -437,7 +433,7 @@
         LOG(INFO) << "Brightness: 0 (off)";
         screensaver_state_ = ScreensaverState::OFF;
       } else {
-        LOG(ERROR) << "Unable to set brightness to off";
+        LOG(WARNING) << "Unable to set brightness to off";
       }
       break;
     default:
@@ -518,18 +514,18 @@
 }
 
 bool RecoveryUI::IsKeyPressed(int key) {
-  std::lock_guard<std::mutex> lg(key_queue_mutex);
+  std::lock_guard<std::mutex> lg(key_press_mutex);
   int pressed = key_pressed[key];
   return pressed;
 }
 
 bool RecoveryUI::IsLongPress() {
-  std::lock_guard<std::mutex> lg(key_queue_mutex);
+  std::lock_guard<std::mutex> lg(key_press_mutex);
   bool result = key_long_press;
   return result;
 }
 
-bool RecoveryUI::HasThreeButtons() {
+bool RecoveryUI::HasThreeButtons() const {
   return has_power_key && has_up_key && has_down_key;
 }
 
@@ -548,7 +544,7 @@
 
 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
   {
-    std::lock_guard<std::mutex> lg(key_queue_mutex);
+    std::lock_guard<std::mutex> lg(key_press_mutex);
     key_long_press = false;
   }
 
@@ -585,13 +581,12 @@
     consecutive_power_keys = 0;
   }
 
-  last_key = key;
   return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE;
 }
 
 void RecoveryUI::KeyLongPress(int) {}
 
 void RecoveryUI::SetEnableReboot(bool enabled) {
-  std::lock_guard<std::mutex> lg(key_queue_mutex);
+  std::lock_guard<std::mutex> lg(key_press_mutex);
   enable_reboot = enabled;
 }
diff --git a/recovery_utils/Android.bp b/recovery_utils/Android.bp
new file mode 100644
index 0000000..bf79a2e
--- /dev/null
+++ b/recovery_utils/Android.bp
@@ -0,0 +1,81 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+    name: "librecovery_utils_defaults",
+
+    defaults: [
+        "recovery_defaults",
+    ],
+
+    shared_libs: [
+        "android.hardware.health@2.0",
+        "libbase",
+        "libext4_utils",
+        "libfs_mgr",
+        "libhidlbase",
+        "libselinux",
+        "libutils",
+    ],
+
+    static_libs: [
+        "libotautil",
+
+        // External dependencies.
+        "libfstab",
+        "libhealthhalutils",
+    ],
+}
+
+// A utility lib that's local to recovery (in contrast, libotautil is exposed to device-specific
+// recovery_ui lib as well as device-specific updater).
+cc_library_static {
+    name: "librecovery_utils",
+
+    recovery_available: true,
+
+    defaults: [
+        "librecovery_utils_defaults",
+    ],
+
+    srcs: [
+        "battery_utils.cpp",
+        "logging.cpp",
+        "parse_install_logs.cpp",
+        "roots.cpp",
+        "thermalutil.cpp",
+    ],
+
+    header_libs: [
+        "libvold_headers",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    export_static_lib_headers: [
+        // roots.h includes <fstab/fstab.h>.
+        "libfstab",
+    ],
+
+    // Should avoid exposing to the libs that might be used in device-specific codes (e.g.
+    // libedify, libotautil, librecovery_ui).
+    visibility: [
+        "//bootable/recovery",
+        "//bootable/recovery/install",
+        "//bootable/recovery/minadbd",
+        "//bootable/recovery/tests",
+    ],
+}
diff --git a/recovery_utils/battery_utils.cpp b/recovery_utils/battery_utils.cpp
new file mode 100644
index 0000000..323f525
--- /dev/null
+++ b/recovery_utils/battery_utils.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "recovery_utils/battery_utils.h"
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <android-base/logging.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;
+
+  android::sp<IHealth> health = get_health_service();
+
+  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
+    }
+
+    // Treat unknown status as on charger. See hardware/interfaces/health/1.0/types.hal 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
+    }
+
+    LOG(INFO) << "charge_status " << toString(charge_status) << ", charging " << charging
+              << ", status " << toString(res) << ", 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 (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) {
+        sleep(1);
+        wait_second++;
+        continue;
+      }
+    }
+    // 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) {
+      capacity = 100;
+    }
+
+    return BatteryInfo{ charging, capacity };
+  }
+}
diff --git a/recovery_utils/include/recovery_utils/battery_utils.h b/recovery_utils/include/recovery_utils/battery_utils.h
new file mode 100644
index 0000000..a95f71d
--- /dev/null
+++ b/recovery_utils/include/recovery_utils/battery_utils.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+struct BatteryInfo {
+  // Whether the device is on charger. Note that the value will be `true` if the battery status is
+  // unknown (BATTERY_STATUS_UNKNOWN).
+  bool charging;
+
+  // The remaining battery capacity percentage (i.e. between 0 and 100). See getCapacity in
+  // hardware/interfaces/health/2.0/IHealth.hal. Returns 100 in case it fails to read a value from
+  // the health HAL.
+  int32_t capacity;
+};
+
+// Returns the battery status for OTA installation purpose.
+BatteryInfo GetBatteryInfo();
diff --git a/otautil/include/otautil/logging.h b/recovery_utils/include/recovery_utils/logging.h
similarity index 95%
rename from otautil/include/otautil/logging.h
rename to recovery_utils/include/recovery_utils/logging.h
index 6083497..4462eca 100644
--- a/otautil/include/otautil/logging.h
+++ b/recovery_utils/include/recovery_utils/logging.h
@@ -53,7 +53,7 @@
 void check_and_fclose(FILE* fp, const std::string& name);
 
 void copy_log_file_to_pmsg(const std::string& source, const std::string& destination);
-void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle);
+void copy_logs(bool save_current_log);
 void reset_tmplog_offset();
 
 void save_kernel_log(const char* destination);
diff --git a/otautil/include/otautil/parse_install_logs.h b/recovery_utils/include/recovery_utils/parse_install_logs.h
similarity index 100%
rename from otautil/include/otautil/parse_install_logs.h
rename to recovery_utils/include/recovery_utils/parse_install_logs.h
diff --git a/otautil/include/otautil/roots.h b/recovery_utils/include/recovery_utils/roots.h
similarity index 96%
rename from otautil/include/otautil/roots.h
rename to recovery_utils/include/recovery_utils/roots.h
index 482f3d0..92ee756 100644
--- a/otautil/include/otautil/roots.h
+++ b/recovery_utils/include/recovery_utils/roots.h
@@ -54,6 +54,5 @@
 // mounted (/tmp and /cache) are mounted.  Returns 0 on success.
 int setup_install_mounts();
 
-bool logical_partitions_mapped();
-
-std::string get_system_root();
+// Returns true if there is /cache in the volumes.
+bool HasCache();
diff --git a/otautil/include/otautil/thermalutil.h b/recovery_utils/include/recovery_utils/thermalutil.h
similarity index 100%
rename from otautil/include/otautil/thermalutil.h
rename to recovery_utils/include/recovery_utils/thermalutil.h
diff --git a/otautil/logging.cpp b/recovery_utils/logging.cpp
similarity index 95%
rename from otautil/logging.cpp
rename to recovery_utils/logging.cpp
index 484f115..52f12a8 100644
--- a/otautil/logging.cpp
+++ b/recovery_utils/logging.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "otautil/logging.h"
+#include "recovery_utils/logging.h"
 
 #include <dirent.h>
 #include <errno.h>
@@ -38,7 +38,7 @@
 
 #include "otautil/dirutil.h"
 #include "otautil/paths.h"
-#include "otautil/roots.h"
+#include "recovery_utils/roots.h"
 
 constexpr const char* LOG_FILE = "/cache/recovery/log";
 constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install";
@@ -178,9 +178,8 @@
   tmplog_offset = 0;
 }
 
-static void copy_log_file(const std::string& source, const std::string& destination, bool append,
-                          const selabel_handle* sehandle) {
-  FILE* dest_fp = fopen_path(destination, append ? "ae" : "we", sehandle);
+static void copy_log_file(const std::string& source, const std::string& destination, bool append) {
+  FILE* dest_fp = fopen_path(destination, append ? "ae" : "we", logging_sehandle);
   if (dest_fp == nullptr) {
     PLOG(ERROR) << "Can't open " << destination;
   } else {
@@ -203,7 +202,7 @@
   }
 }
 
-void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle) {
+void copy_logs(bool save_current_log) {
   // We only rotate and record the log of the current session if explicitly requested. This usually
   // happens after wipes, installation from BCB or menu selections. This is to avoid unnecessary
   // rotation (and possible deletion) of log files, if it does not do anything loggable.
@@ -216,7 +215,7 @@
   copy_log_file_to_pmsg(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE);
 
   // We can do nothing for now if there's no /cache partition.
-  if (!has_cache) {
+  if (!HasCache()) {
     return;
   }
 
@@ -225,9 +224,9 @@
   rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);
 
   // Copy logs to cache so the system can find out what happened.
-  copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true, sehandle);
-  copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false, sehandle);
-  copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false, sehandle);
+  copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true);
+  copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false);
+  copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false);
   save_kernel_log(LAST_KMSG_FILE);
   chmod(LOG_FILE, 0600);
   chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM);
@@ -319,7 +318,7 @@
   // Reset the pointer so we copy from the beginning of the temp
   // log.
   reset_tmplog_offset();
-  copy_logs(true /* save_current_log */, true /* has_cache */, logging_sehandle);
+  copy_logs(true /* save_current_log */);
 
   return true;
 }
diff --git a/otautil/parse_install_logs.cpp b/recovery_utils/parse_install_logs.cpp
similarity index 98%
rename from otautil/parse_install_logs.cpp
rename to recovery_utils/parse_install_logs.cpp
index 13a7299..c863176 100644
--- a/otautil/parse_install_logs.cpp
+++ b/recovery_utils/parse_install_logs.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "otautil/parse_install_logs.h"
+#include "recovery_utils/parse_install_logs.h"
 
 #include <unistd.h>
 
diff --git a/otautil/roots.cpp b/recovery_utils/roots.cpp
similarity index 84%
rename from otautil/roots.cpp
rename to recovery_utils/roots.cpp
index 815d644..58a3139 100644
--- a/otautil/roots.cpp
+++ b/recovery_utils/roots.cpp
@@ -14,15 +14,12 @@
  * limitations under the License.
  */
 
-#include "otautil/roots.h"
+#include "recovery_utils/roots.h"
 
-#include <ctype.h>
 #include <fcntl.h>
-#include <inttypes.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -40,9 +37,7 @@
 #include <ext4_utils/wipe.h>
 #include <fs_mgr.h>
 #include <fs_mgr/roots.h>
-#include <fs_mgr_dm_linear.h>
 
-#include "otautil/mounts.h"
 #include "otautil/sysutil.h"
 
 using android::fs_mgr::Fstab;
@@ -51,6 +46,8 @@
 
 static Fstab fstab;
 
+constexpr const char* CACHE_ROOT = "/cache";
+
 void load_volume_table() {
   if (!ReadDefaultFstab(&fstab)) {
     LOG(ERROR) << "Failed to read default fstab";
@@ -58,7 +55,11 @@
   }
 
   fstab.emplace_back(FstabEntry{
-      .mount_point = "/tmp", .fs_type = "ramdisk", .blk_device = "ramdisk", .length = 0 });
+      .blk_device = "ramdisk",
+      .mount_point = "/tmp",
+      .fs_type = "ramdisk",
+      .length = 0,
+  });
 
   std::cout << "recovery filesystem table" << std::endl << "=========================" << std::endl;
   for (size_t i = 0; i < fstab.size(); ++i) {
@@ -152,6 +153,14 @@
     return -1;
   }
 
+  bool needs_casefold = false;
+  bool needs_projid = false;
+
+  if (volume == "/data") {
+    needs_casefold = android::base::GetBoolProperty("ro.emulated_storage.casefold", false);
+    needs_projid = android::base::GetBoolProperty("ro.emulated_storage.projid", 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] == '/') {
@@ -187,6 +196,21 @@
       "/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");
+    }
+
+    if (v->fs_mgr_flags.ext_meta_csum) {
+      mke2fs_args.push_back("-O");
+      mke2fs_args.push_back("metadata_csum");
+      mke2fs_args.push_back("-O");
+      mke2fs_args.push_back("64bit");
+      mke2fs_args.push_back("-O");
+      mke2fs_args.push_back("extent");
+    }
+
     int raid_stride = v->logical_blk_size / kBlockSize;
     int raid_stripe_width = v->erase_blk_size / kBlockSize;
     // stride should be the max of 8KB and logical block size
@@ -224,8 +248,18 @@
     "/system/bin/make_f2fs",
     "-g",
     "android",
-    v->blk_device,
   };
+  if (needs_projid) {
+    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");
+    make_f2fs_cmd.push_back("-C");
+    make_f2fs_cmd.push_back("utf8");
+  }
+  make_f2fs_cmd.push_back(v->blk_device);
   if (length >= kSectorSize) {
     make_f2fs_cmd.push_back(std::to_string(length / kSectorSize));
   }
@@ -276,10 +310,8 @@
   return 0;
 }
 
-bool logical_partitions_mapped() {
-  return android::fs_mgr::LogicalPartitionsMapped();
-}
-
-std::string get_system_root() {
-  return android::fs_mgr::GetSystemRoot();
+bool HasCache() {
+  CHECK(!fstab.empty());
+  static bool has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
+  return has_cache;
 }
diff --git a/otautil/thermalutil.cpp b/recovery_utils/thermalutil.cpp
similarity index 97%
rename from otautil/thermalutil.cpp
rename to recovery_utils/thermalutil.cpp
index 4660e05..5436355 100644
--- a/otautil/thermalutil.cpp
+++ b/recovery_utils/thermalutil.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "otautil/thermalutil.h"
+#include "recovery_utils/thermalutil.h"
 
 #include <dirent.h>
 #include <stdio.h>
diff --git a/tests/Android.bp b/tests/Android.bp
index a0d82d5..a9a088a 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -39,6 +39,7 @@
         android: {
             shared_libs: [
                 "libutils",
+                "libvndksupport",
             ],
         },
 
@@ -50,13 +51,11 @@
     },
 }
 
-// libapplypatch, libapplypatch_modes, libimgdiff, libimgpatch
+// libapplypatch, libapplypatch_modes
 libapplypatch_static_libs = [
     "libapplypatch_modes",
     "libapplypatch",
     "libedify",
-    "libimgdiff",
-    "libimgpatch",
     "libotautil",
     "libbsdiff",
     "libbspatch",
@@ -66,7 +65,6 @@
     "libbase",
     "libbrotli",
     "libbz",
-    "libcrypto",
     "libz",
     "libziparchive",
 ]
@@ -79,35 +77,51 @@
     "libinstall",
     "librecovery_ui",
     "libminui",
+    "libfusesideload",
+    "libbootloader_message",
     "libotautil",
 
     "libhealthhalutils",
-    "libvintf_recovery",
     "libvintf",
 
     "android.hardware.health@2.0",
     "android.hardware.health@1.0",
-    "libbootloader_message",
     "libext4_utils",
     "libfs_mgr",
-    "libfusesideload",
     "libhidl-gen-utils",
     "libhidlbase",
-    "libhidltransport",
-    "libhwbinder_noltopgo",
-    "libbinderthreadstate",
     "liblp",
-    "libvndksupport",
     "libtinyxml2",
     "libc++fs",
 ]
 
+// recovery image for unittests.
+// ========================================================
+genrule {
+    name: "recovery_image",
+    cmd: "cat $(location testdata/recovery_head) <(cat $(location testdata/recovery_body) | $(location minigzip)) $(location testdata/recovery_tail) > $(out)",
+    srcs: [
+        "testdata/recovery_head",
+        "testdata/recovery_body",
+        "testdata/recovery_tail",
+    ],
+    tools: [
+        "minigzip",
+    ],
+    out: [
+        "testdata/recovery.img",
+    ],
+}
+
 cc_test {
     name: "recovery_unit_test",
     isolated: true,
+    require_root: true,
 
     defaults: [
         "recovery_test_defaults",
+        "libupdater_defaults",
+        "libupdater_device_defaults",
     ],
 
     test_suites: ["device-tests"],
@@ -116,16 +130,25 @@
         "unit/*.cpp",
     ],
 
-    static_libs: libapplypatch_static_libs + [
-        "libinstall",
+    static_libs: libapplypatch_static_libs + librecovery_static_libs + [
         "librecovery_ui",
+        "libfusesideload",
         "libminui",
+        "librecovery_utils",
         "libotautil",
-        "libupdater",
+        "libupdater_device",
+        "libupdater_core",
+        "libupdate_verifier",
+
         "libgtest_prod",
+        "libprotobuf-cpp-lite",
     ],
 
-    data: ["testdata/*"],
+    data: [
+        "testdata/*",
+        ":recovery_image",
+        ":res-testdata",
+    ],
 }
 
 cc_test {
@@ -143,8 +166,8 @@
     ],
 }
 
-cc_test {
-    name: "recovery_component_test",
+cc_test_host {
+    name: "recovery_host_test",
     isolated: true,
 
     defaults: [
@@ -152,57 +175,28 @@
         "libupdater_defaults",
     ],
 
-    test_suites: ["device-tests"],
-
     srcs: [
-        "component/*.cpp",
-    ],
-
-    static_libs: libapplypatch_static_libs + librecovery_static_libs + [
-        "libupdater",
-        "libupdate_verifier",
-        "libprotobuf-cpp-lite",
-    ],
-
-    data: [
-        "testdata/*",
-        ":res-testdata",
-    ],
-}
-
-cc_test_host {
-    name: "recovery_host_test",
-    isolated: true,
-
-    defaults: [
-        "recovery_test_defaults",
-    ],
-
-    srcs: [
-        "component/imgdiff_test.cpp",
+        "unit/host/*",
     ],
 
     static_libs: [
+        "libupdater_host",
+        "libupdater_core",
         "libimgdiff",
-        "libimgpatch",
-        "libotautil",
         "libbsdiff",
-        "libbspatch",
-        "libziparchive",
-        "libutils",
-        "libcrypto",
-        "libbrotli",
-        "libbz",
         "libdivsufsort64",
         "libdivsufsort",
-        "libz",
+        "libfstab",
+        "libc++fs",
     ],
 
+    test_suites: ["general-tests"],
+
     data: ["testdata/*"],
 
     target: {
         darwin: {
-            // libimgdiff is not available on the Mac.
+            // libapplypatch in "libupdater_defaults" is not available on the Mac.
             enabled: false,
         },
     },
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
deleted file mode 100644
index 6b86085..0000000
--- a/tests/AndroidTest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for recovery_component_test and recovery_unit_test">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="recovery_component_test->/data/local/tmp/recovery_component_test/recovery_component_test" />
-        <option name="push" value="testdata->/data/local/tmp/recovery_component_test/testdata" />
-        <option name="push" value="recovery_unit_test->/data/local/tmp/recovery_unit_test/recovery_unit_test" />
-        <option name="push" value="testdata->/data/local/tmp/recovery_unit_test/testdata" />
-    </target_preparer>
-    <option name="test-suite-tag" value="apct" />
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp/recovery_component_test" />
-        <option name="module-name" value="recovery_component_test" />
-    </test>
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp/recovery_unit_test" />
-        <option name="module-name" value="recovery_unit_test" />
-    </test>
-</configuration>
diff --git a/tests/component/resources_test.cpp b/tests/component/resources_test.cpp
deleted file mode 100644
index d7fdb8f..0000000
--- a/tests/component/resources_test.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <dirent.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/strings.h>
-#include <gtest/gtest.h>
-#include <png.h>
-
-#include "minui/minui.h"
-#include "private/resources.h"
-
-static const std::string kLocale = "zu";
-
-static const std::vector<std::string> kResourceImagesDirs{
-  "res-mdpi/images/",   "res-hdpi/images/",    "res-xhdpi/images/",
-  "res-xxhdpi/images/", "res-xxxhdpi/images/",
-};
-
-static int png_filter(const dirent* de) {
-  if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
-    return 0;
-  }
-  return 1;
-}
-
-// Finds out all the PNG files to test, which stay under the same dir with the executabl..
-static std::vector<std::string> add_files() {
-  std::vector<std::string> files;
-  for (const std::string& images_dir : kResourceImagesDirs) {
-    static std::string exec_dir = android::base::GetExecutableDirectory();
-    std::string dir_path = exec_dir + "/" + images_dir;
-    dirent** namelist;
-    int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
-    if (n == -1) {
-      printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno));
-      continue;
-    }
-    if (n == 0) {
-      printf("No file is added for test in %s\n", dir_path.c_str());
-    }
-
-    while (n--) {
-      std::string file_path = dir_path + namelist[n]->d_name;
-      files.push_back(file_path);
-      free(namelist[n]);
-    }
-    free(namelist);
-  }
-  return files;
-}
-
-class ResourcesTest : public testing::TestWithParam<std::string> {
- public:
-  static std::vector<std::string> png_list;
-
- protected:
-  void SetUp() override {
-    png_ = std::make_unique<PngHandler>(GetParam());
-    ASSERT_TRUE(png_);
-
-    ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file.";
-    ASSERT_LT(static_cast<png_uint_32>(5), png_->width());
-    ASSERT_LT(static_cast<png_uint_32>(0), png_->height());
-    ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file.";
-  }
-
-  std::unique_ptr<PngHandler> png_{ nullptr };
-};
-
-// Parses a png file and tests if it's qualified for the background text image under recovery.
-TEST_P(ResourcesTest, ValidateLocale) {
-  std::vector<unsigned char> row(png_->width());
-  for (png_uint_32 y = 0; y < png_->height(); ++y) {
-    png_read_row(png_->png_ptr(), row.data(), nullptr);
-    int w = (row[1] << 8) | row[0];
-    int h = (row[3] << 8) | row[2];
-    int len = row[4];
-    EXPECT_LT(0, w);
-    EXPECT_LT(0, h);
-    EXPECT_LT(0, len) << "Locale string should be non-empty.";
-    EXPECT_NE(0, row[5]) << "Locale string is missing.";
-
-    ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
-    char* loc = reinterpret_cast<char*>(&row[5]);
-    if (matches_locale(loc, kLocale.c_str())) {
-      EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
-      break;
-    }
-    for (int i = 0; i < h; ++i, ++y) {
-      png_read_row(png_->png_ptr(), row.data(), nullptr);
-    }
-  }
-}
-
-std::vector<std::string> ResourcesTest::png_list = add_files();
-
-INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest,
-                        ::testing::ValuesIn(ResourcesTest::png_list.cbegin(),
-                                            ResourcesTest::png_list.cend()));
diff --git a/tests/testdata/recovery.img b/tests/testdata/recovery.img
deleted file mode 100644
index b862e6f..0000000
--- a/tests/testdata/recovery.img
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/recovery_body b/tests/testdata/recovery_body
new file mode 100644
index 0000000..48d7c10
--- /dev/null
+++ b/tests/testdata/recovery_body
Binary files differ
diff --git a/tests/testdata/recovery_head b/tests/testdata/recovery_head
new file mode 100644
index 0000000..7f494d0
--- /dev/null
+++ b/tests/testdata/recovery_head
Binary files differ
diff --git a/tests/testdata/recovery_tail b/tests/testdata/recovery_tail
new file mode 100644
index 0000000..7fe2c6c
--- /dev/null
+++ b/tests/testdata/recovery_tail
Binary files differ
diff --git a/tests/component/applypatch_modes_test.cpp b/tests/unit/applypatch_modes_test.cpp
similarity index 100%
rename from tests/component/applypatch_modes_test.cpp
rename to tests/unit/applypatch_modes_test.cpp
diff --git a/tests/unit/applypatch_test.cpp b/tests/unit/applypatch_test.cpp
index 794f2c1..218a224 100644
--- a/tests/unit/applypatch_test.cpp
+++ b/tests/unit/applypatch_test.cpp
@@ -141,7 +141,7 @@
   ASSERT_TRUE(LoadFileContents(from_testdata_base("bonus.file"), &bonus_fc));
   Value bonus(Value::Type::BLOB, std::string(bonus_fc.data.cbegin(), bonus_fc.data.cend()));
 
-  ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus));
+  ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus, false));
 }
 
 // Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has
@@ -151,7 +151,7 @@
   ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot-with-bonus.p"), &patch_fc));
   Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
 
-  ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr));
+  ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr, false));
 }
 
 class FreeCacheTest : public ::testing::Test {
diff --git a/tests/unit/battery_utils_test.cpp b/tests/unit/battery_utils_test.cpp
new file mode 100644
index 0000000..55639fd
--- /dev/null
+++ b/tests/unit/battery_utils_test.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agree to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+#include "recovery_utils/battery_utils.h"
+
+TEST(BatteryInfoTest, GetBatteryInfo) {
+  auto info = GetBatteryInfo();
+  // 0 <= capacity <= 100
+  ASSERT_LE(0, info.capacity);
+  ASSERT_LE(info.capacity, 100);
+}
diff --git a/tests/component/bootloader_message_test.cpp b/tests/unit/bootloader_message_test.cpp
similarity index 75%
rename from tests/component/bootloader_message_test.cpp
rename to tests/unit/bootloader_message_test.cpp
index 95d875e..731c8fe 100644
--- a/tests/component/bootloader_message_test.cpp
+++ b/tests/unit/bootloader_message_test.cpp
@@ -118,37 +118,3 @@
   ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
             std::string(boot.reserved, sizeof(boot.reserved)));
 }
-
-TEST(BootloaderMessageTest, WriteMiscPartitionVendorSpace) {
-  TemporaryFile temp_misc;
-  ASSERT_TRUE(android::base::WriteStringToFile(std::string(4096, '\x00'), temp_misc.path));
-  SetMiscBlockDeviceForTest(temp_misc.path);
-
-  constexpr std::string_view kTestMessage = "kTestMessage";
-  std::string err;
-  ASSERT_TRUE(WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(), 0, &err));
-
-  std::string message;
-  message.resize(kTestMessage.size());
-  ASSERT_TRUE(ReadMiscPartitionVendorSpace(message.data(), message.size(), 0, &err));
-  ASSERT_EQ(kTestMessage, message);
-
-  // Write with an offset.
-  ASSERT_TRUE(WriteMiscPartitionVendorSpace("\x00\x00", 2, 5, &err));
-  ASSERT_TRUE(ReadMiscPartitionVendorSpace(message.data(), message.size(), 0, &err));
-  ASSERT_EQ("kTest\x00\x00ssage"s, message);
-
-  // Write with the right size.
-  auto start_offset =
-      WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC - kTestMessage.size();
-  ASSERT_TRUE(
-      WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(), start_offset, &err));
-
-  // Out-of-bound write.
-  ASSERT_FALSE(WriteMiscPartitionVendorSpace(kTestMessage.data(), kTestMessage.size(),
-                                             start_offset + 1, &err));
-
-  // Message won't fit.
-  std::string long_message(WIPE_PACKAGE_OFFSET_IN_MISC - VENDOR_SPACE_OFFSET_IN_MISC + 1, 'a');
-  ASSERT_FALSE(WriteMiscPartitionVendorSpace(long_message.data(), long_message.size(), 0, &err));
-}
diff --git a/tests/component/edify_test.cpp b/tests/unit/edify_test.cpp
similarity index 100%
rename from tests/component/edify_test.cpp
rename to tests/unit/edify_test.cpp
diff --git a/tests/unit/fuse_provider_test.cpp b/tests/unit/fuse_provider_test.cpp
new file mode 100644
index 0000000..37f99f9
--- /dev/null
+++ b/tests/unit/fuse_provider_test.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+#include "fuse_provider.h"
+#include "fuse_sideload.h"
+#include "install/install.h"
+
+TEST(FuseBlockMapTest, CreateFromBlockMap_smoke) {
+  TemporaryFile fake_block_device;
+  std::vector<std::string> lines = {
+    fake_block_device.path, "10000 4096", "3", "10 11", "20 21", "22 23",
+  };
+
+  TemporaryFile temp_file;
+  android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path);
+  auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096);
+
+  ASSERT_TRUE(block_map_data);
+  ASSERT_EQ(10000, block_map_data->file_size());
+  ASSERT_EQ(4096, block_map_data->fuse_block_size());
+  ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 21 }, { 22, 23 } }),
+            static_cast<FuseBlockDataProvider*>(block_map_data.get())->ranges());
+}
+
+TEST(FuseBlockMapTest, ReadBlockAlignedData_smoke) {
+  std::string content;
+  content.reserve(40960);
+  for (char c = 0; c < 10; c++) {
+    content += std::string(4096, c);
+  }
+  TemporaryFile fake_block_device;
+  ASSERT_TRUE(android::base::WriteStringToFile(content, fake_block_device.path));
+
+  std::vector<std::string> lines = {
+    fake_block_device.path,
+    "20000 4096",
+    "1",
+    "0 5",
+  };
+  TemporaryFile temp_file;
+  android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path);
+  auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096);
+
+  std::vector<uint8_t> result(2000);
+  ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 2000, 1));
+  ASSERT_EQ(std::vector<uint8_t>(content.begin() + 4096, content.begin() + 6096), result);
+
+  result.resize(20000);
+  ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 0));
+  ASSERT_EQ(std::vector<uint8_t>(content.begin(), content.begin() + 20000), result);
+}
+
+TEST(FuseBlockMapTest, ReadBlockAlignedData_large_fuse_block) {
+  std::string content;
+  for (char c = 0; c < 10; c++) {
+    content += std::string(4096, c);
+  }
+
+  TemporaryFile temp_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+
+  std::vector<std::string> lines = {
+    temp_file.path, "36384 4096", "2", "0 5", "6 10",
+  };
+  TemporaryFile block_map;
+  ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(lines, '\n'), block_map.path));
+
+  auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(block_map.path, 16384);
+  ASSERT_TRUE(block_map_data);
+
+  std::vector<uint8_t> result(20000);
+  // Out of bound read
+  ASSERT_FALSE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 2));
+  ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 1));
+  // expected source block contains: 4, 6-9
+  std::string expected = content.substr(16384, 4096) + content.substr(24576, 15904);
+  ASSERT_EQ(std::vector<uint8_t>(expected.begin(), expected.end()), result);
+}
diff --git a/tests/component/sideload_test.cpp b/tests/unit/fuse_sideload_test.cpp
similarity index 97%
rename from tests/component/sideload_test.cpp
rename to tests/unit/fuse_sideload_test.cpp
index 6add99f..ea89503 100644
--- a/tests/component/sideload_test.cpp
+++ b/tests/unit/fuse_sideload_test.cpp
@@ -40,6 +40,10 @@
   bool ReadBlockAlignedData(uint8_t*, uint32_t, uint32_t) const override {
     return true;
   }
+
+  bool Valid() const override {
+    return true;
+  }
 };
 
 TEST(SideloadTest, run_fuse_sideload_wrong_parameters) {
diff --git a/tests/component/imgdiff_test.cpp b/tests/unit/host/imgdiff_test.cpp
similarity index 100%
rename from tests/component/imgdiff_test.cpp
rename to tests/unit/host/imgdiff_test.cpp
diff --git a/tests/unit/host/update_simulator_test.cpp b/tests/unit/host/update_simulator_test.cpp
new file mode 100644
index 0000000..fb12178
--- /dev/null
+++ b/tests/unit/host/update_simulator_test.cpp
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <bsdiff/bsdiff.h>
+#include <gtest/gtest.h>
+#include <ziparchive/zip_writer.h>
+
+#include "otautil/paths.h"
+#include "otautil/print_sha1.h"
+#include "updater/blockimg.h"
+#include "updater/build_info.h"
+#include "updater/install.h"
+#include "updater/simulator_runtime.h"
+#include "updater/target_files.h"
+#include "updater/updater.h"
+
+using std::string;
+
+// echo -n "system.img" > system.img && img2simg system.img sparse_system_string_.img 4096 &&
+// hexdump -v -e '"    " 12/1 "0x%02x, " "\n"' sparse_system_string_.img
+// The total size of the result sparse image is 4136 bytes; and we can append 0s in the end to get
+// the full image.
+constexpr uint8_t SPARSE_SYSTEM_HEADER[] = {
+  0x3a, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10, 0x00,
+  0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xca,
+  0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x10, 0x00, 0x00, 0x73, 0x79, 0x73, 0x74, 0x65,
+  0x6d, 0x2e, 0x69, 0x6d, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static void AddZipEntries(int fd, const std::map<string, string>& entries) {
+  FILE* zip_file = fdopen(fd, "w");
+  ZipWriter writer(zip_file);
+  for (const auto& pair : entries) {
+    ASSERT_EQ(0, writer.StartEntry(pair.first.c_str(), 0));
+    ASSERT_EQ(0, writer.WriteBytes(pair.second.data(), pair.second.size()));
+    ASSERT_EQ(0, writer.FinishEntry());
+  }
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+}
+
+static string CalculateSha1(const string& data) {
+  uint8_t digest[SHA_DIGEST_LENGTH];
+  SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
+  return print_sha1(digest);
+}
+
+static void CreateBsdiffPatch(const string& src, const string& tgt, string* patch) {
+  TemporaryFile patch_file;
+  ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src.data()), src.size(),
+                              reinterpret_cast<const uint8_t*>(tgt.data()), tgt.size(),
+                              patch_file.path, nullptr));
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, patch));
+}
+
+static void RunSimulation(std::string_view src_tf, std::string_view ota_package, bool expected) {
+  TemporaryFile cmd_pipe;
+  TemporaryFile temp_saved_source;
+  TemporaryFile temp_last_command;
+  TemporaryDir temp_stash_base;
+
+  Paths::Get().set_cache_temp_source(temp_saved_source.path);
+  Paths::Get().set_last_command_file(temp_last_command.path);
+  Paths::Get().set_stash_directory_base(temp_stash_base.path);
+
+  // Configure edify's functions.
+  RegisterBuiltins();
+  RegisterInstallFunctions();
+  RegisterBlockImageFunctions();
+
+  // Run the update simulation and check the result.
+  TemporaryDir work_dir;
+  BuildInfo build_info(work_dir.path, false);
+  ASSERT_TRUE(build_info.ParseTargetFile(src_tf, false));
+  Updater updater(std::make_unique<SimulatorRuntime>(&build_info));
+  ASSERT_TRUE(updater.Init(cmd_pipe.release(), ota_package, false));
+  ASSERT_EQ(expected, updater.RunUpdate());
+  // TODO(xunchang) check the recovery&system has the expected contents.
+}
+
+class UpdateSimulatorTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    std::vector<string> props = {
+      "import /oem/oem.prop oem*",
+      "# begin build properties",
+      "# autogenerated by buildinfo.sh",
+      "ro.build.id=OPR1.170510.001",
+      "ro.build.display.id=OPR1.170510.001 dev-keys",
+      "ro.build.version.incremental=3993052",
+      "ro.build.version.release=O",
+      "ro.build.date=Wed May 10 11:10:29 UTC 2017",
+      "ro.build.date.utc=1494414629",
+      "ro.build.type=user",
+      "ro.build.tags=dev-keys",
+      "ro.build.flavor=angler-user",
+      "ro.product.system.brand=google",
+      "ro.product.system.name=angler",
+      "ro.product.system.device=angler",
+    };
+    build_prop_string_ = android::base::Join(props, "\n");
+
+    fstab_content_ = R"(
+#<src>         <mnt_point>  <type>  <mnt_flags and options> <fs_mgr_flags>
+# More comments.....
+
+/dev/block/by-name/system     /system     ext4    ro,barrier=1    wait
+/dev/block/by-name/vendor     /vendor     ext4    ro              wait,verify=/dev/metadata
+/dev/block/by-name/cache      /cache      ext4    noatime,errors=panic      wait,check
+/dev/block/by-name/modem      /firmware   vfat    ro,uid=1000,gid=1000,     wait
+/dev/block/by-name/boot       /boot       emmc    defaults        defaults
+/dev/block/by-name/recovery   /recovery   emmc    defaults        defaults
+/dev/block/by-name/misc       /misc       emmc    defaults
+/dev/block/by-name/modem      /modem      emmc    defaults        defaults)";
+
+    raw_system_string_ = "system.img" + string(4086, '\0');  // raw image is 4096 bytes in total
+    sparse_system_string_ = string(SPARSE_SYSTEM_HEADER, std::end(SPARSE_SYSTEM_HEADER)) +
+                            string(4136 - sizeof(SPARSE_SYSTEM_HEADER), '\0');
+  }
+
+  string build_prop_string_;
+  string fstab_content_;
+  string raw_system_string_;
+  string sparse_system_string_;
+};
+
+TEST_F(UpdateSimulatorTest, TargetFile_ExtractImage) {
+  TemporaryFile zip_file;
+  AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" },
+                                      { "IMAGES/system.img", sparse_system_string_ } });
+  TargetFile target_file(zip_file.path, false);
+  ASSERT_TRUE(target_file.Open());
+
+  TemporaryDir temp_dir;
+  TemporaryFile raw_image;
+  ASSERT_TRUE(target_file.ExtractImage(
+      "IMAGES/system.img", FstabInfo("/dev/system", "system", "ext4"), temp_dir.path, &raw_image));
+
+  // Check the raw image has expected contents.
+  string content;
+  ASSERT_TRUE(android::base::ReadFileToString(raw_image.path, &content));
+  string expected_content = "system.img" + string(4086, '\0');
+  ASSERT_EQ(expected_content, content);
+}
+
+TEST_F(UpdateSimulatorTest, TargetFile_ParseFstabInfo) {
+  TemporaryFile zip_file;
+  AddZipEntries(zip_file.release(),
+                { { "META/misc_info.txt", "" },
+                  { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ } });
+  TargetFile target_file(zip_file.path, false);
+  ASSERT_TRUE(target_file.Open());
+
+  std::vector<FstabInfo> fstab_info;
+  EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info));
+
+  std::vector<std::vector<string>> transformed;
+  std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed),
+                 [](const FstabInfo& info) {
+                   return std::vector<string>{ info.blockdev_name, info.mount_point, info.fs_type };
+                 });
+
+  std::vector<std::vector<string>> expected = {
+    { "/dev/block/by-name/system", "/system", "ext4" },
+    { "/dev/block/by-name/vendor", "/vendor", "ext4" },
+    { "/dev/block/by-name/cache", "/cache", "ext4" },
+    { "/dev/block/by-name/boot", "/boot", "emmc" },
+    { "/dev/block/by-name/recovery", "/recovery", "emmc" },
+    { "/dev/block/by-name/misc", "/misc", "emmc" },
+    { "/dev/block/by-name/modem", "/modem", "emmc" },
+  };
+  EXPECT_EQ(expected, transformed);
+}
+
+TEST_F(UpdateSimulatorTest, BuildInfo_ParseTargetFile) {
+  std::map<string, string> entries = {
+    { "META/misc_info.txt", "" },
+    { "SYSTEM/build.prop", build_prop_string_ },
+    { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ },
+    { "IMAGES/recovery.img", "" },
+    { "IMAGES/boot.img", "" },
+    { "IMAGES/misc.img", "" },
+    { "IMAGES/system.map", "" },
+    { "IMAGES/system.img", sparse_system_string_ },
+  };
+
+  TemporaryFile zip_file;
+  AddZipEntries(zip_file.release(), entries);
+
+  TemporaryDir temp_dir;
+  BuildInfo build_info(temp_dir.path, false);
+  ASSERT_TRUE(build_info.ParseTargetFile(zip_file.path, false));
+
+  std::map<string, string> expected_result = {
+    { "ro.build.id", "OPR1.170510.001" },
+    { "ro.build.display.id", "OPR1.170510.001 dev-keys" },
+    { "ro.build.version.incremental", "3993052" },
+    { "ro.build.version.release", "O" },
+    { "ro.build.date", "Wed May 10 11:10:29 UTC 2017" },
+    { "ro.build.date.utc", "1494414629" },
+    { "ro.build.type", "user" },
+    { "ro.build.tags", "dev-keys" },
+    { "ro.build.flavor", "angler-user" },
+    { "ro.product.brand", "google" },
+    { "ro.product.name", "angler" },
+    { "ro.product.device", "angler" },
+  };
+
+  for (const auto& [key, value] : expected_result) {
+    ASSERT_EQ(value, build_info.GetProperty(key, ""));
+  }
+
+  // Check that the temp files for each block device are created successfully.
+  for (auto name : { "/dev/block/by-name/system", "/dev/block/by-name/recovery",
+                     "/dev/block/by-name/boot", "/dev/block/by-name/misc" }) {
+    ASSERT_EQ(0, access(build_info.FindBlockDeviceName(name).c_str(), R_OK));
+  }
+}
+
+TEST_F(UpdateSimulatorTest, RunUpdateSmoke) {
+  string recovery_img_string = "recovery.img";
+  string boot_img_string = "boot.img";
+
+  std::map<string, string> src_entries{
+    { "META/misc_info.txt", "extfs_sparse_flag=-s" },
+    { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
+    { "SYSTEM/build.prop", build_prop_string_ },
+    { "IMAGES/recovery.img", "" },
+    { "IMAGES/boot.img", boot_img_string },
+    { "IMAGES/system.img", sparse_system_string_ },
+  };
+
+  // Construct the source target-files.
+  TemporaryFile src_tf;
+  AddZipEntries(src_tf.release(), src_entries);
+
+  string recovery_from_boot;
+  CreateBsdiffPatch(boot_img_string, recovery_img_string, &recovery_from_boot);
+
+  // Set up the apply patch commands to patch the recovery image.
+  string recovery_sha1 = CalculateSha1(recovery_img_string);
+  string boot_sha1 = CalculateSha1(boot_img_string);
+  string apply_patch_source_string = android::base::StringPrintf(
+      "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str());
+  string apply_patch_target_string = android::base::StringPrintf(
+      "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str());
+  string check_command = android::base::StringPrintf(
+      R"(patch_partition_check("%s", "%s") || abort("check failed");)",
+      apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
+  string patch_command = android::base::StringPrintf(
+      R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("patch failed");)",
+      apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
+
+  // Add the commands to update the system image. Test common commands:
+  // * getprop
+  // * ui_print
+  // * patch_partition
+  // * package_extract_file (single argument)
+  // * block_image_verify, block_image_update
+  string tgt_system_string = string(4096, 'a');
+  string system_patch;
+  CreateBsdiffPatch(raw_system_string_, tgt_system_string, &system_patch);
+
+  string tgt_system_hash = CalculateSha1(tgt_system_string);
+  string src_system_hash = CalculateSha1(raw_system_string_);
+
+  std::vector<std::string> transfer_list = {
+    "4",
+    "1",
+    "0",
+    "0",
+    android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,1 1 2,0,1", system_patch.size(),
+                                src_system_hash.c_str(), tgt_system_hash.c_str()),
+  };
+
+  // Construct the updater_script.
+  std::vector<string> updater_commands = {
+    R"(getprop("ro.product.device") == "angler" || abort("This package is for \"angler\"");)",
+    R"(ui_print("Source: angler/OPR1.170510.001");)",
+    check_command,
+    patch_command,
+    R"(block_image_verify("/dev/block/by-name/system", )"
+    R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )"
+    R"(abort("Failed to verify system.");)",
+    R"(block_image_update("/dev/block/by-name/system", )"
+    R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )"
+    R"(abort("Failed to verify system.");)",
+  };
+  string updater_script = android::base::Join(updater_commands, '\n');
+
+  // Construct the ota update package.
+  std::map<string, string> ota_entries{
+    { "system.new.dat", "" },
+    { "system.patch.dat", system_patch },
+    { "system.transfer.list", android::base::Join(transfer_list, '\n') },
+    { "META-INF/com/google/android/updater-script", updater_script },
+    { "patch.p", recovery_from_boot },
+  };
+
+  TemporaryFile ota_package;
+  AddZipEntries(ota_package.release(), ota_entries);
+
+  RunSimulation(src_tf.path, ota_package.path, true);
+}
+
+TEST_F(UpdateSimulatorTest, RunUpdateUnrecognizedFunction) {
+  std::map<string, string> src_entries{
+    { "META/misc_info.txt", "extfs_sparse_flag=-s" },
+    { "IMAGES/system.img", sparse_system_string_ },
+    { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
+    { "SYSTEM/build.prop", build_prop_string_ },
+  };
+
+  TemporaryFile src_tf;
+  AddZipEntries(src_tf.release(), src_entries);
+
+  std::map<string, string> ota_entries{
+    { "system.new.dat", "" },
+    { "system.patch.dat", "" },
+    { "system.transfer.list", "" },
+    { "META-INF/com/google/android/updater-script", R"(bad_function("");)" },
+  };
+
+  TemporaryFile ota_package;
+  AddZipEntries(ota_package.release(), ota_entries);
+
+  RunSimulation(src_tf.path, ota_package.path, false);
+}
+
+TEST_F(UpdateSimulatorTest, RunUpdateApplyPatchFailed) {
+  string recovery_img_string = "recovery.img";
+  string boot_img_string = "boot.img";
+
+  std::map<string, string> src_entries{
+    { "META/misc_info.txt", "extfs_sparse_flag=-s" },
+    { "IMAGES/recovery.img", "" },
+    { "IMAGES/boot.img", boot_img_string },
+    { "IMAGES/system.img", sparse_system_string_ },
+    { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
+    { "SYSTEM/build.prop", build_prop_string_ },
+  };
+
+  TemporaryFile src_tf;
+  AddZipEntries(src_tf.release(), src_entries);
+
+  string recovery_sha1 = CalculateSha1(recovery_img_string);
+  string boot_sha1 = CalculateSha1(boot_img_string);
+  string apply_patch_source_string = android::base::StringPrintf(
+      "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str());
+  string apply_patch_target_string = android::base::StringPrintf(
+      "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str());
+  string check_command = android::base::StringPrintf(
+      R"(patch_partition_check("%s", "%s") || abort("check failed");)",
+      apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
+  string patch_command = android::base::StringPrintf(
+      R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("failed");)",
+      apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
+
+  // Give an invalid recovery patch and expect the apply patch to fail.
+  // TODO(xunchang) check the cause code.
+  std::vector<string> updater_commands = {
+    R"(ui_print("Source: angler/OPR1.170510.001");)",
+    check_command,
+    patch_command,
+  };
+
+  string updater_script = android::base::Join(updater_commands, '\n');
+  std::map<string, string> ota_entries{
+    { "system.new.dat", "" },
+    { "system.patch.dat", "" },
+    { "system.transfer.list", "" },
+    { "META-INF/com/google/android/updater-script", updater_script },
+    { "patch.p", "random string" },
+  };
+
+  TemporaryFile ota_package;
+  AddZipEntries(ota_package.release(), ota_entries);
+
+  RunSimulation(src_tf.path, ota_package.path, false);
+}
diff --git a/tests/component/install_test.cpp b/tests/unit/install_test.cpp
similarity index 76%
rename from tests/component/install_test.cpp
rename to tests/unit/install_test.cpp
index c1f0ca8..ee75349 100644
--- a/tests/component/install_test.cpp
+++ b/tests/unit/install_test.cpp
@@ -28,14 +28,14 @@
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <gtest/gtest.h>
-#include <vintf/VintfObjectRecovery.h>
 #include <ziparchive/zip_archive.h>
 #include <ziparchive/zip_writer.h>
 
 #include "install/install.h"
+#include "install/wipe_device.h"
 #include "otautil/paths.h"
-#include "otautil/roots.h"
 #include "private/setup_commands.h"
+#include "recovery_utils/roots.h"
 
 static void BuildZipArchive(const std::map<std::string, std::string>& file_map, int fd,
                             int compression_type) {
@@ -50,29 +50,6 @@
   ASSERT_EQ(0, fclose(zip_file));
 }
 
-TEST(InstallTest, verify_package_compatibility_no_entry) {
-  TemporaryFile temp_file;
-  // The archive must have something to be opened correctly.
-  BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
-
-  // Doesn't contain compatibility zip entry.
-  ZipArchiveHandle zip;
-  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  ASSERT_TRUE(verify_package_compatibility(zip));
-  CloseArchive(zip);
-}
-
-TEST(InstallTest, verify_package_compatibility_invalid_entry) {
-  TemporaryFile temp_file;
-  BuildZipArchive({ { "compatibility.zip", "" } }, temp_file.release(), kCompressStored);
-
-  // Empty compatibility zip entry.
-  ZipArchiveHandle zip;
-  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  ASSERT_FALSE(verify_package_compatibility(zip));
-  CloseArchive(zip);
-}
-
 TEST(InstallTest, read_metadata_from_package_smoke) {
   TemporaryFile temp_file;
   const std::string content("abc=defg");
@@ -134,64 +111,6 @@
   ASSERT_EQ(expected, read_partition_list);
 }
 
-TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
-  TemporaryFile compatibility_zip_file;
-  std::string malformed_xml = "malformed";
-  BuildZipArchive({ { "system_manifest.xml", malformed_xml } }, compatibility_zip_file.release(),
-                  kCompressDeflated);
-
-  TemporaryFile temp_file;
-  std::string compatibility_zip_content;
-  ASSERT_TRUE(
-      android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
-  BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(),
-                  kCompressStored);
-
-  ZipArchiveHandle zip;
-  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  std::vector<std::string> compatibility_info;
-  compatibility_info.push_back(malformed_xml);
-  // Malformed compatibility zip is expected to be rejected by libvintf. But we defer that to
-  // libvintf.
-  std::string err;
-  bool result =
-      android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
-  ASSERT_EQ(result, verify_package_compatibility(zip));
-  CloseArchive(zip);
-}
-
-TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml) {
-  static constexpr const char* system_manifest_xml_path = "/system/manifest.xml";
-  if (access(system_manifest_xml_path, R_OK) == -1) {
-    GTEST_LOG_(INFO) << "Test skipped on devices w/o /system/manifest.xml.";
-    return;
-  }
-  std::string system_manifest_xml_content;
-  ASSERT_TRUE(
-      android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
-  TemporaryFile compatibility_zip_file;
-  BuildZipArchive({ { "system_manifest.xml", system_manifest_xml_content } },
-                  compatibility_zip_file.release(), kCompressDeflated);
-
-  TemporaryFile temp_file;
-  std::string compatibility_zip_content;
-  ASSERT_TRUE(
-      android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
-  BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(),
-                  kCompressStored);
-
-  ZipArchiveHandle zip;
-  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  std::vector<std::string> compatibility_info;
-  compatibility_info.push_back(system_manifest_xml_content);
-  std::string err;
-  bool result =
-      android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
-  // Make sure the result is consistent with libvintf library.
-  ASSERT_EQ(result, verify_package_compatibility(zip));
-  CloseArchive(zip);
-}
-
 TEST(InstallTest, SetUpNonAbUpdateCommands) {
   TemporaryFile temp_file;
   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
@@ -205,7 +124,7 @@
   std::string binary_path = std::string(td.path) + "/update_binary";
   Paths::Get().set_temporary_update_binary(binary_path);
   std::vector<std::string> cmd;
-  ASSERT_EQ(0, SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
+  ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
   ASSERT_EQ(4U, cmd.size());
   ASSERT_EQ(binary_path, cmd[0]);
   ASSERT_EQ("3", cmd[1]);  // RECOVERY_API_VERSION
@@ -217,7 +136,7 @@
 
   // With non-zero retry count. update_binary will be removed automatically.
   cmd.clear();
-  ASSERT_EQ(0, SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd));
+  ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd));
   ASSERT_EQ(5U, cmd.size());
   ASSERT_EQ(binary_path, cmd[0]);
   ASSERT_EQ("3", cmd[1]);  // RECOVERY_API_VERSION
@@ -244,7 +163,7 @@
   TemporaryDir td;
   Paths::Get().set_temporary_update_binary(std::string(td.path) + "/update_binary");
   std::vector<std::string> cmd;
-  ASSERT_EQ(INSTALL_CORRUPT, SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
+  ASSERT_FALSE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
   CloseArchive(zip);
 }
 
@@ -271,19 +190,18 @@
 
   ZipArchiveHandle zip;
   ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  ZipString payload_name("payload.bin");
   ZipEntry payload_entry;
-  ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry));
+  ASSERT_EQ(0, FindEntry(zip, "payload.bin", &payload_entry));
 
   std::map<std::string, std::string> metadata;
   ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
   if (success) {
-    ASSERT_EQ(0, CheckPackageMetadata(metadata, OtaType::AB));
+    ASSERT_TRUE(CheckPackageMetadata(metadata, OtaType::AB));
 
     int status_fd = 10;
     std::string package = "/path/to/update.zip";
     std::vector<std::string> cmd;
-    ASSERT_EQ(0, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
+    ASSERT_TRUE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
     ASSERT_EQ(5U, cmd.size());
     ASSERT_EQ("/system/bin/update_engine_sideload", cmd[0]);
     ASSERT_EQ("--payload=file://" + package, cmd[1]);
@@ -291,7 +209,7 @@
     ASSERT_EQ("--headers=" + properties, cmd[3]);
     ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
   } else {
-    ASSERT_EQ(INSTALL_ERROR, CheckPackageMetadata(metadata, OtaType::AB));
+    ASSERT_FALSE(CheckPackageMetadata(metadata, OtaType::AB));
   }
   CloseArchive(zip);
 }
@@ -326,7 +244,7 @@
   int status_fd = 10;
   std::string package = "/path/to/update.zip";
   std::vector<std::string> cmd;
-  ASSERT_EQ(INSTALL_CORRUPT, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
+  ASSERT_FALSE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
   CloseArchive(zip);
 }
 
@@ -359,8 +277,8 @@
   VerifyAbUpdateCommands(long_serialno);
 }
 
-static void test_check_package_metadata(const std::string& metadata_string, OtaType ota_type,
-                                        int exptected_result) {
+static void TestCheckPackageMetadata(const std::string& metadata_string, OtaType ota_type,
+                                     bool exptected_result) {
   TemporaryFile temp_file;
   BuildZipArchive(
       {
@@ -388,7 +306,7 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 
   // Checks if ota-type matches
   metadata = android::base::Join(
@@ -398,9 +316,9 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, 0);
+  TestCheckPackageMetadata(metadata, OtaType::AB, true);
 
-  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
 }
 
 TEST(InstallTest, CheckPackageMetadata_device_type) {
@@ -410,7 +328,7 @@
           "ota-type=BRICK",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
 
   // device type mismatches
   metadata = android::base::Join(
@@ -419,7 +337,7 @@
           "pre-device=dummy_device_type",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
 }
 
 TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) {
@@ -433,7 +351,7 @@
           "pre-device=" + device,
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, 0);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
 
   // Serial number mismatches
   metadata = android::base::Join(
@@ -443,7 +361,7 @@
           "serialno=dummy_serial",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
 
   std::string serialno = android::base::GetProperty("ro.serialno", "");
   ASSERT_NE("", serialno);
@@ -454,7 +372,7 @@
           "serialno=" + serialno,
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, 0);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
 }
 
 TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) {
@@ -478,7 +396,7 @@
           "serialno=" + android::base::Join(serial_numbers, '|'),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
 
   serial_numbers.emplace_back(serialno);
   std::shuffle(serial_numbers.begin(), serial_numbers.end(), std::default_random_engine());
@@ -489,7 +407,7 @@
           "serialno=" + android::base::Join(serial_numbers, '|'),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, 0);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
 }
 
 TEST(InstallTest, CheckPackageMetadata_ab_build_version) {
@@ -507,7 +425,7 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, 0);
+  TestCheckPackageMetadata(metadata, OtaType::AB, true);
 
   metadata = android::base::Join(
       std::vector<std::string>{
@@ -517,7 +435,7 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 }
 
 TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) {
@@ -535,7 +453,7 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, 0);
+  TestCheckPackageMetadata(metadata, OtaType::AB, true);
 
   metadata = android::base::Join(
       std::vector<std::string>{
@@ -545,7 +463,7 @@
           "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 }
 
 TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) {
@@ -559,7 +477,7 @@
           "pre-device=" + device,
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 
   // post timestamp should be larger than the timestamp on device.
   metadata = android::base::Join(
@@ -569,7 +487,7 @@
           "post-timestamp=0",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 
   // fingerprint is required for downgrade
   metadata = android::base::Join(
@@ -580,7 +498,7 @@
           "ota-downgrade=yes",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+  TestCheckPackageMetadata(metadata, OtaType::AB, false);
 
   std::string finger_print = android::base::GetProperty("ro.build.fingerprint", "");
   ASSERT_NE("", finger_print);
@@ -594,7 +512,7 @@
           "ota-downgrade=yes",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, 0);
+  TestCheckPackageMetadata(metadata, OtaType::AB, true);
 }
 
 TEST(InstallTest, SetupPackageMount_package_path) {
diff --git a/tests/unit/locale_test.cpp b/tests/unit/locale_test.cpp
index cdaba0e..c69434c 100644
--- a/tests/unit/locale_test.cpp
+++ b/tests/unit/locale_test.cpp
@@ -27,7 +27,7 @@
   EXPECT_FALSE(matches_locale("en-GB", "en"));
   EXPECT_FALSE(matches_locale("en-GB", "en-US"));
   EXPECT_FALSE(matches_locale("en-US", ""));
-  // Empty locale prefix in the PNG file will match the input locale.
-  EXPECT_TRUE(matches_locale("", "en-US"));
+  // Empty locale prefix in the PNG file should not match the input locale.
+  EXPECT_FALSE(matches_locale("", "en-US"));
   EXPECT_TRUE(matches_locale("sr-Latn", "sr-Latn-BA"));
 }
diff --git a/tests/unit/package_test.cpp b/tests/unit/package_test.cpp
index a735a69..5e31f7f 100644
--- a/tests/unit/package_test.cpp
+++ b/tests/unit/package_test.cpp
@@ -105,10 +105,9 @@
     ASSERT_TRUE(zip);
 
     // Check that we can extract one zip entry.
-    std::string entry_name = "dir1/file3.txt";
-    ZipString path(entry_name.c_str());
+    std::string_view entry_name = "dir1/file3.txt";
     ZipEntry entry;
-    ASSERT_EQ(0, FindEntry(zip, path, &entry));
+    ASSERT_EQ(0, FindEntry(zip, entry_name, &entry));
 
     std::vector<uint8_t> extracted(entry_name.size());
     ASSERT_EQ(0, ExtractToMemory(zip, &entry, extracted.data(), extracted.size()));
diff --git a/tests/unit/parse_install_logs_test.cpp b/tests/unit/parse_install_logs_test.cpp
index 72169a0..052f71c 100644
--- a/tests/unit/parse_install_logs_test.cpp
+++ b/tests/unit/parse_install_logs_test.cpp
@@ -22,7 +22,7 @@
 #include <android-base/strings.h>
 #include <gtest/gtest.h>
 
-#include "otautil/parse_install_logs.h"
+#include "recovery_utils/parse_install_logs.h"
 
 TEST(ParseInstallLogsTest, EmptyFile) {
   TemporaryFile last_install;
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
index fc72f2f..699f933 100644
--- a/tests/unit/rangeset_test.cpp
+++ b/tests/unit/rangeset_test.cpp
@@ -18,6 +18,7 @@
 #include <sys/types.h>
 
 #include <limits>
+#include <optional>
 #include <vector>
 
 #include <gtest/gtest.h>
@@ -248,6 +249,29 @@
   ASSERT_EQ("6,1,3,4,6,15,22", RangeSet::Parse("6,1,3,4,6,15,22").ToString());
 }
 
+TEST(RangeSetTest, GetSubRanges_invalid) {
+  RangeSet range0({ { 1, 11 }, { 20, 30 } });
+  ASSERT_FALSE(range0.GetSubRanges(0, 21));  // too many blocks
+  ASSERT_FALSE(range0.GetSubRanges(21, 1));  // start block OOB
+}
+
+TEST(RangeSetTest, GetSubRanges_empty) {
+  RangeSet range0({ { 1, 11 }, { 20, 30 } });
+  ASSERT_EQ(RangeSet{}, range0.GetSubRanges(1, 0));  // empty num_of_blocks
+}
+
+TEST(RangeSetTest, GetSubRanges_smoke) {
+  RangeSet range0({ { 10, 11 } });
+  ASSERT_EQ(RangeSet({ { 10, 11 } }), range0.GetSubRanges(0, 1));
+
+  RangeSet range1({ { 10, 11 }, { 20, 21 }, { 30, 31 } });
+  ASSERT_EQ(range1, range1.GetSubRanges(0, 3));
+  ASSERT_EQ(RangeSet({ { 20, 21 } }), range1.GetSubRanges(1, 1));
+
+  RangeSet range2({ { 1, 11 }, { 20, 25 }, { 30, 35 } });
+  ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 25 }, { 30, 31 } }), range2.GetSubRanges(9, 7));
+}
+
 TEST(SortedRangeSetTest, Insert) {
   SortedRangeSet rs({ { 2, 3 }, { 4, 6 }, { 8, 14 } });
   rs.Insert({ 1, 2 });
diff --git a/tests/unit/resources_test.cpp b/tests/unit/resources_test.cpp
index c3f7271..3027443 100644
--- a/tests/unit/resources_test.cpp
+++ b/tests/unit/resources_test.cpp
@@ -14,12 +14,62 @@
  * limitations under the License.
  */
 
-#include <string>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
 
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
 #include <gtest/gtest.h>
+#include <png.h>
 
 #include "common/test_constants.h"
 #include "minui/minui.h"
+#include "private/resources.h"
+
+static const std::string kLocale = "zu";
+
+static const std::vector<std::string> kResourceImagesDirs{
+  "res-mdpi/images/",   "res-hdpi/images/",    "res-xhdpi/images/",
+  "res-xxhdpi/images/", "res-xxxhdpi/images/",
+};
+
+static int png_filter(const dirent* de) {
+  if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
+    return 0;
+  }
+  return 1;
+}
+
+// Finds out all the PNG files to test, which stay under the same dir with the executabl..
+static std::vector<std::string> add_files() {
+  std::vector<std::string> files;
+  for (const std::string& images_dir : kResourceImagesDirs) {
+    static std::string exec_dir = android::base::GetExecutableDirectory();
+    std::string dir_path = exec_dir + "/" + images_dir;
+    dirent** namelist;
+    int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
+    if (n == -1) {
+      printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno));
+      continue;
+    }
+    if (n == 0) {
+      printf("No file is added for test in %s\n", dir_path.c_str());
+    }
+
+    while (n--) {
+      std::string file_path = dir_path + namelist[n]->d_name;
+      files.push_back(file_path);
+      free(namelist[n]);
+    }
+    free(namelist);
+  }
+  return files;
+}
 
 TEST(ResourcesTest, res_create_multi_display_surface) {
   GRSurface** frames;
@@ -35,3 +85,52 @@
   }
   free(frames);
 }
+
+class ResourcesTest : public testing::TestWithParam<std::string> {
+ public:
+  static std::vector<std::string> png_list;
+
+ protected:
+  void SetUp() override {
+    png_ = std::make_unique<PngHandler>(GetParam());
+    ASSERT_TRUE(png_);
+
+    ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file.";
+    ASSERT_LT(static_cast<png_uint_32>(5), png_->width());
+    ASSERT_LT(static_cast<png_uint_32>(0), png_->height());
+    ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file.";
+  }
+
+  std::unique_ptr<PngHandler> png_{ nullptr };
+};
+
+// Parses a png file and tests if it's qualified for the background text image under recovery.
+TEST_P(ResourcesTest, ValidateLocale) {
+  std::vector<unsigned char> row(png_->width());
+  for (png_uint_32 y = 0; y < png_->height(); ++y) {
+    png_read_row(png_->png_ptr(), row.data(), nullptr);
+    int w = (row[1] << 8) | row[0];
+    int h = (row[3] << 8) | row[2];
+    int len = row[4];
+    EXPECT_LT(0, w);
+    EXPECT_LT(0, h);
+    EXPECT_LT(0, len) << "Locale string should be non-empty.";
+    EXPECT_NE(0, row[5]) << "Locale string is missing.";
+
+    ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
+    char* loc = reinterpret_cast<char*>(&row[5]);
+    if (matches_locale(loc, kLocale.c_str())) {
+      EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
+      break;
+    }
+    for (int i = 0; i < h; ++i, ++y) {
+      png_read_row(png_->png_ptr(), row.data(), nullptr);
+    }
+  }
+}
+
+std::vector<std::string> ResourcesTest::png_list = add_files();
+
+INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest,
+                        ::testing::ValuesIn(ResourcesTest::png_list.cbegin(),
+                                            ResourcesTest::png_list.cend()));
diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp
index 3466e8e..64b8956 100644
--- a/tests/unit/sysutil_test.cpp
+++ b/tests/unit/sysutil_test.cpp
@@ -67,7 +67,7 @@
     "/dev/abc",
     "42949672950 4294967295",
     "1",
-    "0 9",
+    "0 10",
   };
 
   TemporaryFile temp_file;
diff --git a/tests/component/uncrypt_test.cpp b/tests/unit/uncrypt_test.cpp
similarity index 100%
rename from tests/component/uncrypt_test.cpp
rename to tests/unit/uncrypt_test.cpp
diff --git a/tests/component/update_verifier_test.cpp b/tests/unit/update_verifier_test.cpp
similarity index 100%
rename from tests/component/update_verifier_test.cpp
rename to tests/unit/update_verifier_test.cpp
diff --git a/tests/component/updater_test.cpp b/tests/unit/updater_test.cpp
similarity index 92%
rename from tests/component/updater_test.cpp
rename to tests/unit/updater_test.cpp
index a0a7b66..8993dd8 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/unit/updater_test.cpp
@@ -52,21 +52,20 @@
 #include "updater/blockimg.h"
 #include "updater/install.h"
 #include "updater/updater.h"
+#include "updater/updater_runtime.h"
 
 using namespace std::string_literals;
 
 using PackageEntries = std::unordered_map<std::string, std::string>;
 
-struct selabel_handle* sehandle = nullptr;
-
 static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code,
-                   UpdaterInfo* info = nullptr) {
+                   Updater* updater) {
   std::unique_ptr<Expr> e;
   int error_count = 0;
   ASSERT_EQ(0, ParseString(expr_str, &e, &error_count));
   ASSERT_EQ(0, error_count);
 
-  State state(expr_str, info);
+  State state(expr_str, updater);
 
   std::string result;
   bool status = Evaluate(&state, e, &result);
@@ -85,6 +84,11 @@
   ASSERT_EQ(cause_code, state.cause_code);
 }
 
+static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code) {
+  Updater updater(std::make_unique<UpdaterRuntime>(nullptr));
+  expect(expected, expr_str, cause_code, &updater);
+}
+
 static void BuildUpdatePackage(const PackageEntries& entries, int fd) {
   FILE* zip_file_ptr = fdopen(fd, "wb");
   ZipWriter zip_writer(zip_file_ptr);
@@ -102,38 +106,6 @@
   ASSERT_EQ(0, fclose(zip_file_ptr));
 }
 
-static void RunBlockImageUpdate(bool is_verify, const PackageEntries& entries,
-                                const std::string& image_file, const std::string& result,
-                                CauseCode cause_code = kNoCause) {
-  CHECK(entries.find("transfer_list") != entries.end());
-
-  // Build the update package.
-  TemporaryFile zip_file;
-  BuildUpdatePackage(entries, zip_file.release());
-
-  MemMapping map;
-  ASSERT_TRUE(map.MapFile(zip_file.path));
-  ZipArchiveHandle handle;
-  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
-
-  // Set up the handler, command_pipe, patch offset & length.
-  UpdaterInfo updater_info;
-  updater_info.package_zip = handle;
-  TemporaryFile temp_pipe;
-  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
-  updater_info.package_zip_addr = map.addr;
-  updater_info.package_zip_len = map.length;
-
-  std::string new_data = entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data";
-  std::string script = is_verify ? "block_image_verify" : "block_image_update";
-  script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data +
-            R"(", "patch_data"))";
-  expect(result.c_str(), script, cause_code, &updater_info);
-
-  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
-  CloseArchive(handle);
-}
-
 static std::string GetSha1(std::string_view content) {
   uint8_t digest[SHA_DIGEST_LENGTH];
   SHA1(reinterpret_cast<const uint8_t*>(content.data()), content.size(), digest);
@@ -159,29 +131,26 @@
   return args[0].release();
 }
 
-class UpdaterTest : public ::testing::Test {
+class UpdaterTestBase {
  protected:
-  void SetUp() override {
+  UpdaterTestBase() : updater_(std::make_unique<UpdaterRuntime>(nullptr)) {}
+
+  void SetUp() {
     RegisterBuiltins();
     RegisterInstallFunctions();
     RegisterBlockImageFunctions();
 
-    RegisterFunction("blob_to_string", BlobToString);
-
     // Each test is run in a separate process (isolated mode). Shared temporary files won't cause
     // conflicts.
     Paths::Get().set_cache_temp_source(temp_saved_source_.path);
     Paths::Get().set_last_command_file(temp_last_command_.path);
     Paths::Get().set_stash_directory_base(temp_stash_base_.path);
 
-    // Enable a special command "abort" to simulate interruption.
-    Command::abort_allowed_ = true;
-
     last_command_file_ = temp_last_command_.path;
     image_file_ = image_temp_file_.path;
   }
 
-  void TearDown() override {
+  void TearDown() {
     // Clean up the last_command_file if any.
     ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
 
@@ -191,16 +160,80 @@
     ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
   }
 
+  void RunBlockImageUpdate(bool is_verify, PackageEntries entries, const std::string& image_file,
+                           const std::string& result, CauseCode cause_code = kNoCause) {
+    CHECK(entries.find("transfer_list") != entries.end());
+    std::string new_data =
+        entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data";
+    std::string script = is_verify ? "block_image_verify" : "block_image_update";
+    script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data +
+              R"(", "patch_data"))";
+    entries.emplace(Updater::SCRIPT_NAME, script);
+
+    // Build the update package.
+    TemporaryFile zip_file;
+    BuildUpdatePackage(entries, zip_file.release());
+
+    // Set up the handler, command_pipe, patch offset & length.
+    TemporaryFile temp_pipe;
+    ASSERT_TRUE(updater_.Init(temp_pipe.release(), zip_file.path, false));
+    ASSERT_TRUE(updater_.RunUpdate());
+    ASSERT_EQ(result, updater_.GetResult());
+
+    // Parse the cause code written to the command pipe.
+    int received_cause_code = kNoCause;
+    std::string pipe_content;
+    ASSERT_TRUE(android::base::ReadFileToString(temp_pipe.path, &pipe_content));
+    auto lines = android::base::Split(pipe_content, "\n");
+    for (std::string_view line : lines) {
+      if (android::base::ConsumePrefix(&line, "log cause: ")) {
+        ASSERT_TRUE(android::base::ParseInt(line.data(), &received_cause_code));
+      }
+    }
+    ASSERT_EQ(cause_code, received_cause_code);
+  }
+
   TemporaryFile temp_saved_source_;
   TemporaryDir temp_stash_base_;
   std::string last_command_file_;
   std::string image_file_;
 
+  Updater updater_;
+
  private:
   TemporaryFile temp_last_command_;
   TemporaryFile image_temp_file_;
 };
 
+class UpdaterTest : public UpdaterTestBase, public ::testing::Test {
+ protected:
+  void SetUp() override {
+    UpdaterTestBase::SetUp();
+
+    RegisterFunction("blob_to_string", BlobToString);
+    // Enable a special command "abort" to simulate interruption.
+    Command::abort_allowed_ = true;
+  }
+
+  void TearDown() override {
+    UpdaterTestBase::TearDown();
+  }
+
+  void SetUpdaterCmdPipe(int fd) {
+    FILE* cmd_pipe = fdopen(fd, "w");
+    ASSERT_NE(nullptr, cmd_pipe);
+    updater_.cmd_pipe_.reset(cmd_pipe);
+  }
+
+  void SetUpdaterOtaPackageHandle(ZipArchiveHandle handle) {
+    updater_.package_handle_ = handle;
+  }
+
+  void FlushUpdaterCommandPipe() const {
+    fflush(updater_.cmd_pipe_.get());
+  }
+};
+
 TEST_F(UpdaterTest, getprop) {
     expect(android::base::GetProperty("ro.product.device", "").c_str(),
            "getprop(\"ro.product.device\")",
@@ -317,13 +350,12 @@
   ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
 
   // Need to set up the ziphandle.
-  UpdaterInfo updater_info;
-  updater_info.package_zip = handle;
+  SetUpdaterOtaPackageHandle(handle);
 
   // Two-argument version.
   TemporaryFile temp_file1;
   std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")");
-  expect("t", script, kNoCause, &updater_info);
+  expect("t", script, kNoCause, &updater_);
 
   // Verify the extracted entry.
   std::string data;
@@ -332,32 +364,30 @@
 
   // Now extract another entry to the same location, which should overwrite.
   script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")";
-  expect("t", script, kNoCause, &updater_info);
+  expect("t", script, kNoCause, &updater_);
 
   ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
   ASSERT_EQ(kBTxtContents, data);
 
   // Missing zip entry. The two-argument version doesn't abort.
   script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")";
-  expect("", script, kNoCause, &updater_info);
+  expect("", script, kNoCause, &updater_);
 
   // Extract to /dev/full should fail.
   script = "package_extract_file(\"a.txt\", \"/dev/full\")";
-  expect("", script, kNoCause, &updater_info);
+  expect("", script, kNoCause, &updater_);
 
   // One-argument version. package_extract_file() gives a VAL_BLOB, which needs to be converted to
   // VAL_STRING for equality test.
   script = "blob_to_string(package_extract_file(\"a.txt\")) == \"" + kATxtContents + "\"";
-  expect("t", script, kNoCause, &updater_info);
+  expect("t", script, kNoCause, &updater_);
 
   script = "blob_to_string(package_extract_file(\"b.txt\")) == \"" + kBTxtContents + "\"";
-  expect("t", script, kNoCause, &updater_info);
+  expect("t", script, kNoCause, &updater_);
 
   // Missing entry. The one-argument version aborts the evaluation.
   script = "package_extract_file(\"doesntexist\")";
-  expect(nullptr, script, kPackageExtractFileFailure, &updater_info);
-
-  CloseArchive(handle);
+  expect(nullptr, script, kPackageExtractFileFailure, &updater_);
 }
 
 TEST_F(UpdaterTest, read_file) {
@@ -563,17 +593,15 @@
   expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure);
 
   TemporaryFile tf;
-  UpdaterInfo updater_info;
-  updater_info.cmd_pipe = fdopen(tf.release(), "w");
-  expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
-  fflush(updater_info.cmd_pipe);
+  SetUpdaterCmdPipe(tf.release());
+  expect(".52", "set_progress(\".52\")", kNoCause, &updater_);
+  FlushUpdaterCommandPipe();
 
   std::string cmd;
   ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
   ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd);
   // recovery-updater protocol expects 2 tokens ("set_progress <frac>").
   ASSERT_EQ(2U, android::base::Split(cmd, " ").size());
-  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
 }
 
 TEST_F(UpdaterTest, show_progress) {
@@ -588,17 +616,15 @@
   expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure);
 
   TemporaryFile tf;
-  UpdaterInfo updater_info;
-  updater_info.cmd_pipe = fdopen(tf.release(), "w");
-  expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
-  fflush(updater_info.cmd_pipe);
+  SetUpdaterCmdPipe(tf.release());
+  expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_);
+  FlushUpdaterCommandPipe();
 
   std::string cmd;
   ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
   ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd);
   // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
   ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
-  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
 }
 
 TEST_F(UpdaterTest, block_image_update_parsing_error) {
@@ -993,44 +1019,20 @@
   ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
 }
 
-class ResumableUpdaterTest : public testing::TestWithParam<size_t> {
+class ResumableUpdaterTest : public UpdaterTestBase, public testing::TestWithParam<size_t> {
  protected:
   void SetUp() override {
-    RegisterBuiltins();
-    RegisterInstallFunctions();
-    RegisterBlockImageFunctions();
-
-    Paths::Get().set_cache_temp_source(temp_saved_source_.path);
-    Paths::Get().set_last_command_file(temp_last_command_.path);
-    Paths::Get().set_stash_directory_base(temp_stash_base_.path);
-
+    UpdaterTestBase::SetUp();
     // Enable a special command "abort" to simulate interruption.
     Command::abort_allowed_ = true;
-
     index_ = GetParam();
-    image_file_ = image_temp_file_.path;
-    last_command_file_ = temp_last_command_.path;
   }
 
   void TearDown() override {
-    // Clean up the last_command_file if any.
-    ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
-
-    // Clear partition updated marker if any.
-    std::string updated_marker{ temp_stash_base_.path };
-    updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED";
-    ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
+    UpdaterTestBase::TearDown();
   }
 
-  TemporaryFile temp_saved_source_;
-  TemporaryDir temp_stash_base_;
-  std::string last_command_file_;
-  std::string image_file_;
   size_t index_;
-
- private:
-  TemporaryFile temp_last_command_;
-  TemporaryFile image_temp_file_;
 };
 
 static std::string g_source_image;
diff --git a/tests/component/verifier_test.cpp b/tests/unit/verifier_test.cpp
similarity index 100%
rename from tests/component/verifier_test.cpp
rename to tests/unit/verifier_test.cpp
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index dfe617e..0753d64 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -37,10 +37,9 @@
   ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_path.c_str(), &handle));
 
   static constexpr const char* BINARY_PATH = "META-INF/com/google/android/update-binary";
-  ZipString binary_path(BINARY_PATH);
   ZipEntry binary_entry;
   // Make sure the package opens correctly and its entry can be read.
-  ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry));
+  ASSERT_EQ(0, FindEntry(handle, BINARY_PATH, &binary_entry));
 
   TemporaryFile tmp_binary;
   ASSERT_NE(-1, tmp_binary.fd);
diff --git a/tools/image_generator/Android.bp b/tools/image_generator/Android.bp
index 2afdd5a..8300040 100644
--- a/tools/image_generator/Android.bp
+++ b/tools/image_generator/Android.bp
@@ -19,7 +19,7 @@
 
     static_libs: [
         "commons-cli-1.2",
-        "icu4j-host",
+        "icu4j",
     ],
 
     srcs: [
diff --git a/tools/recovery_l10n/res/values-ar/strings.xml b/tools/recovery_l10n/res/values-ar/strings.xml
index a9cd2d1..2af36d6 100644
--- a/tools/recovery_l10n/res/values-ar/strings.xml
+++ b/tools/recovery_l10n/res/values-ar/strings.xml
@@ -6,9 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"ليس هناك أي أمر"</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_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_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>
 </resources>
diff --git a/tools/recovery_l10n/res/values-gl/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml
index e6f2ffd..e51b36d 100644
--- a/tools/recovery_l10n/res/values-gl/strings.xml
+++ b/tools/recovery_l10n/res/values-gl/strings.xml
@@ -6,9 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Non hai ningún comando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Erro"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instalando actualización de seguranza"</string>
-    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos do usuario almacenados neste dispositivo."</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos de usuario almacenados neste dispositivo."</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"Tentar de novo"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Restablecemento dos datos de fábrica"</string>
-    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Queres borrar todos os datos do usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER."</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Queres borrar todos os datos de usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER."</string>
     <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancelar"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-hy/strings.xml b/tools/recovery_l10n/res/values-hy/strings.xml
index 76c28a7..35a0ab1 100644
--- a/tools/recovery_l10n/res/values-hy/strings.xml
+++ b/tools/recovery_l10n/res/values-hy/strings.xml
@@ -9,6 +9,6 @@
     <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_wipe_data_confirmation" msgid="5439823343348043954">"Մաքրե՞լ օգտատիրոջ բոլոր տվյալները։\n\n ԱՅՍ ԳՈՐԾՈՂՈՒԹՅՈՒՆԸ ՀՆԱՐԱՎՈՐ ՉԻ ԼԻՆԻ ՀԵՏԱՐԿԵԼ"</string>
     <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Չեղարկել"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-in/strings.xml b/tools/recovery_l10n/res/values-in/strings.xml
index 43c9deb..15a78ec 100644
--- a/tools/recovery_l10n/res/values-in/strings.xml
+++ b/tools/recovery_l10n/res/values-in/strings.xml
@@ -9,6 +9,6 @@
     <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Tidak dapat memuat sistem Android. Data Anda mungkin rusak. Jika terus mendapatkan pesan ini, Anda mungkin perlu melakukan reset ke setelan pabrik dan menghapus semua data pengguna yang disimpan di perangkat ini."</string>
     <string name="recovery_try_again" msgid="7168248750158873496">"Coba lagi"</string>
     <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Reset ke setelan pabrik"</string>
-    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Hapus total semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Wipe semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!"</string>
     <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Batal"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml
index 2d6c0ab..3d66372 100644
--- a/tools/recovery_l10n/res/values-ja/strings.xml
+++ b/tools/recovery_l10n/res/values-ja/strings.xml
@@ -6,7 +6,7 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"コマンドが指定されていません"</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_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>
diff --git a/tools/recovery_l10n/res/values-ky/strings.xml b/tools/recovery_l10n/res/values-ky/strings.xml
index 837cf7d..1cd69ea 100644
--- a/tools/recovery_l10n/res/values-ky/strings.xml
+++ b/tools/recovery_l10n/res/values-ky/strings.xml
@@ -6,7 +6,7 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Буйрук берилген жок"</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_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>
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index d04c455..a042f90 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -371,6 +371,10 @@
         return reboot_device();
       }
       LOG(INFO) << "Marked slot " << current_slot << " as booted successfully.";
+      // Clears the warm reset flag for next reboot.
+      if (!android::base::SetProperty("ota.warm_reset", "0")) {
+        LOG(WARNING) << "Failed to reset the warm reset flag";
+      }
     } else {
       LOG(INFO) << "Deferred marking slot " << current_slot << " as booted successfully.";
     }
diff --git a/updater/Android.bp b/updater/Android.bp
index b80cdb3..f00a192 100644
--- a/updater/Android.bp
+++ b/updater/Android.bp
@@ -13,11 +13,7 @@
 // limitations under the License.
 
 cc_defaults {
-    name: "libupdater_defaults",
-
-    defaults: [
-        "recovery_defaults",
-    ],
+    name: "libupdater_static_libs",
 
     static_libs: [
         "libapplypatch",
@@ -29,8 +25,8 @@
         "libdm",
         "libfec",
         "libfec_rs",
+        "libavb",
         "libverity_tree",
-        "libfs_mgr",
         "libgtest_prod",
         "liblog",
         "liblp",
@@ -42,10 +38,30 @@
         "libziparchive",
         "libz",
         "libbase",
-        "libcrypto",
         "libcrypto_utils",
         "libcutils",
         "libutils",
+    ],
+}
+
+cc_defaults {
+    name: "libupdater_defaults",
+
+    defaults: [
+        "recovery_defaults",
+        "libupdater_static_libs",
+    ],
+
+    shared_libs: [
+        "libcrypto",
+    ],
+}
+
+cc_defaults {
+    name: "libupdater_device_defaults",
+
+    static_libs: [
+        "libfs_mgr",
         "libtune2fs",
 
         "libext2_com_err",
@@ -58,7 +74,9 @@
 }
 
 cc_library_static {
-    name: "libupdater",
+    name: "libupdater_core",
+
+    host_supported: true,
 
     defaults: [
         "recovery_defaults",
@@ -68,8 +86,39 @@
     srcs: [
         "blockimg.cpp",
         "commands.cpp",
-        "dynamic_partitions.cpp",
         "install.cpp",
+        "mounts.cpp",
+        "updater.cpp",
+    ],
+
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+
+    export_include_dirs: [
+        "include",
+    ],
+}
+
+cc_library_static {
+    name: "libupdater_device",
+
+    defaults: [
+        "recovery_defaults",
+        "libupdater_defaults",
+        "libupdater_device_defaults",
+    ],
+
+    srcs: [
+        "dynamic_partitions.cpp",
+        "updater_runtime.cpp",
+        "updater_runtime_dynamic_partitions.cpp",
+    ],
+
+    static_libs: [
+        "libupdater_core",
     ],
 
     include_dirs: [
@@ -80,3 +129,61 @@
         "include",
     ],
 }
+
+cc_library_host_static {
+    name: "libupdater_host",
+
+    defaults: [
+        "recovery_defaults",
+        "libupdater_defaults",
+    ],
+
+    srcs: [
+        "build_info.cpp",
+        "dynamic_partitions.cpp",
+        "simulator_runtime.cpp",
+        "target_files.cpp",
+    ],
+
+    static_libs: [
+        "libupdater_core",
+        "libfstab",
+        "libc++fs",
+    ],
+
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+
+    export_include_dirs: [
+        "include",
+    ],
+}
+
+cc_binary_host {
+    name: "update_host_simulator",
+    defaults: ["libupdater_static_libs"],
+
+    srcs: ["update_simulator_main.cpp"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    static_libs: [
+        "libupdater_host",
+        "libupdater_core",
+        "libcrypto_static",
+        "libfstab",
+        "libc++fs",
+    ],
+
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+}
diff --git a/updater/Android.mk b/updater/Android.mk
index c7a6ba9..46300d9 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -32,8 +32,8 @@
     libdm \
     libfec \
     libfec_rs \
+    libavb \
     libverity_tree \
-    libfs_mgr \
     libgtest_prod \
     liblog \
     liblp \
@@ -45,12 +45,27 @@
     libziparchive \
     libz \
     libbase \
-    libcrypto \
+    libcrypto_static \
     libcrypto_utils \
     libcutils \
-    libutils \
-    libtune2fs \
-    $(tune2fs_static_libraries)
+    libutils
+
+
+# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
+# named "Register_<libname>()".  Here we emit a little C function that
+# gets #included by updater.cpp.  It calls all those registration
+# functions.
+# $(1): the path to the register.inc file
+# $(2): a list of TARGET_RECOVERY_UPDATER_LIBS
+define generate-register-inc
+    $(hide) mkdir -p $(dir $(1))
+    $(hide) echo "" > $(1)
+    $(hide) $(foreach lib,$(2),echo "extern void Register_$(lib)(void);" >> $(1);)
+    $(hide) echo "void RegisterDeviceExtensions() {" >> $(1)
+    $(hide) $(foreach lib,$(2),echo "  Register_$(lib)();" >> $(1);)
+    $(hide) echo "}" >> $(1)
+endef
+
 
 # updater (static executable)
 # ===============================
@@ -59,7 +74,7 @@
 LOCAL_MODULE := updater
 
 LOCAL_SRC_FILES := \
-    updater.cpp
+    updater_main.cpp
 
 LOCAL_C_INCLUDES := \
     $(LOCAL_PATH)/include
@@ -69,33 +84,26 @@
     -Werror
 
 LOCAL_STATIC_LIBRARIES := \
-    libupdater \
+    libupdater_device \
+    libupdater_core \
     $(TARGET_RECOVERY_UPDATER_LIBS) \
     $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \
-    $(updater_common_static_libraries)
+    $(updater_common_static_libraries) \
+    libfs_mgr \
+    libtune2fs \
+    $(tune2fs_static_libraries)
 
-# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
-# named "Register_<libname>()".  Here we emit a little C function that
-# gets #included by updater.c.  It calls all those registration
-# functions.
+LOCAL_MODULE_CLASS := EXECUTABLES
+inc := $(call local-generated-sources-dir)/register.inc
 
 # Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS.
 # These libs are also linked in with updater, but we don't try to call
 # any sort of registration function for these.  Use this variable for
 # any subsidiary static libraries required for your registered
 # extension libs.
-
-LOCAL_MODULE_CLASS := EXECUTABLES
-inc := $(call local-generated-sources-dir)/register.inc
-
 $(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS)
 $(inc) :
-	$(hide) mkdir -p $(dir $@)
-	$(hide) echo "" > $@
-	$(hide) $(foreach lib,$(libs),echo "extern void Register_$(lib)(void);" >> $@;)
-	$(hide) echo "void RegisterDeviceExtensions() {" >> $@
-	$(hide) $(foreach lib,$(libs),echo "  Register_$(lib)();" >> $@;)
-	$(hide) echo "}" >> $@
+	$(call generate-register-inc,$@,$(libs))
 
 LOCAL_GENERATED_SOURCES := $(inc)
 
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 07c3c7b..2d41f61 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -42,17 +42,18 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <applypatch/applypatch.h>
 #include <brotli/decode.h>
 #include <fec/io.h>
 #include <openssl/sha.h>
-#include <private/android_filesystem_config.h>
 #include <verity/hash_tree_builder.h>
 #include <ziparchive/zip_archive.h>
 
 #include "edify/expr.h"
+#include "edify/updater_interface.h"
 #include "otautil/dirutil.h"
 #include "otautil/error_code.h"
 #include "otautil/paths.h"
@@ -60,12 +61,16 @@
 #include "otautil/rangeset.h"
 #include "private/commands.h"
 #include "updater/install.h"
-#include "updater/updater.h"
 
-// Set this to 0 to interpret 'erase' transfers to mean do a
-// BLKDISCARD ioctl (the normal behavior).  Set to 1 to interpret
-// erase to mean fill the region with zeroes.
+#ifdef __ANDROID__
+#include <private/android_filesystem_config.h>
+// Set this to 0 to interpret 'erase' transfers to mean do a BLKDISCARD ioctl (the normal behavior).
+// Set to 1 to interpret erase to mean fill the region with zeroes.
 #define DEBUG_ERASE  0
+#else
+#define DEBUG_ERASE 1
+#define AID_SYSTEM -1
+#endif  // __ANDROID__
 
 static constexpr size_t BLOCKSIZE = 4096;
 static constexpr mode_t STASH_DIRECTORY_MODE = 0700;
@@ -1668,42 +1673,43 @@
     return StringValue("");
   }
 
-  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
-  if (ui == nullptr) {
+  auto updater = state->updater;
+  auto block_device_path = updater->FindBlockDeviceName(blockdev_filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name
+               << " failed.";
     return StringValue("");
   }
 
-  FILE* cmd_pipe = ui->cmd_pipe;
-  ZipArchiveHandle za = ui->package_zip;
-
-  if (cmd_pipe == nullptr || za == nullptr) {
+  ZipArchiveHandle za = updater->GetPackageHandle();
+  if (za == nullptr) {
     return StringValue("");
   }
 
-  ZipString path_data(patch_data_fn->data.c_str());
+  std::string_view path_data(patch_data_fn->data);
   ZipEntry patch_entry;
   if (FindEntry(za, path_data, &patch_entry) != 0) {
     LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package";
     return StringValue("");
   }
+  params.patch_start = updater->GetMappedPackageAddress() + patch_entry.offset;
 
-  params.patch_start = ui->package_zip_addr + patch_entry.offset;
-  ZipString new_data(new_data_fn->data.c_str());
+  std::string_view new_data(new_data_fn->data);
   ZipEntry new_entry;
   if (FindEntry(za, new_data, &new_entry) != 0) {
     LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package";
     return StringValue("");
   }
 
-  params.fd.reset(TEMP_FAILURE_RETRY(open(blockdev_filename->data.c_str(), O_RDWR)));
+  params.fd.reset(TEMP_FAILURE_RETRY(open(block_device_path.c_str(), O_RDWR)));
   if (params.fd == -1) {
     failure_type = errno == EIO ? kEioFailure : kFileOpenFailure;
-    PLOG(ERROR) << "open \"" << blockdev_filename->data << "\" failed";
+    PLOG(ERROR) << "open \"" << block_device_path << "\" failed";
     return StringValue("");
   }
 
   uint8_t digest[SHA_DIGEST_LENGTH];
-  if (!Sha1DevicePath(blockdev_filename->data, digest)) {
+  if (!Sha1DevicePath(block_device_path, digest)) {
     return StringValue("");
   }
   params.stashbase = print_sha1(digest);
@@ -1716,8 +1722,7 @@
     struct stat sb;
     int result = stat(updated_marker.c_str(), &sb);
     if (result == 0) {
-      LOG(INFO) << "Skipping already updated partition " << blockdev_filename->data
-                << " based on marker";
+      LOG(INFO) << "Skipping already updated partition " << block_device_path << " based on marker";
       return StringValue("t");
     }
   } else {
@@ -1887,8 +1892,10 @@
         LOG(WARNING) << "Failed to update the last command file.";
       }
 
-      fprintf(cmd_pipe, "set_progress %.4f\n", static_cast<double>(params.written) / total_blocks);
-      fflush(cmd_pipe);
+      updater->WriteToCommandPipe(
+          android::base::StringPrintf("set_progress %.4f",
+                                      static_cast<double>(params.written) / total_blocks),
+          true);
     }
   }
 
@@ -1913,13 +1920,15 @@
       LOG(INFO) << "stashed " << params.stashed << " blocks";
       LOG(INFO) << "max alloc needed was " << params.buffer.size();
 
-      const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
+      const char* partition = strrchr(block_device_path.c_str(), '/');
       if (partition != nullptr && *(partition + 1) != 0) {
-        fprintf(cmd_pipe, "log bytes_written_%s: %" PRIu64 "\n", partition + 1,
-                static_cast<uint64_t>(params.written) * BLOCKSIZE);
-        fprintf(cmd_pipe, "log bytes_stashed_%s: %" PRIu64 "\n", partition + 1,
-                static_cast<uint64_t>(params.stashed) * BLOCKSIZE);
-        fflush(cmd_pipe);
+        updater->WriteToCommandPipe(
+            android::base::StringPrintf("log bytes_written_%s: %" PRIu64, partition + 1,
+                                        static_cast<uint64_t>(params.written) * BLOCKSIZE));
+        updater->WriteToCommandPipe(
+            android::base::StringPrintf("log bytes_stashed_%s: %" PRIu64, partition + 1,
+                                        static_cast<uint64_t>(params.stashed) * BLOCKSIZE),
+            true);
       }
       // Delete stash only after successfully completing the update, as it may contain blocks needed
       // to complete the update later.
@@ -2019,7 +2028,7 @@
     // clang-format off
     { Command::Type::ABORT,             PerformCommandAbort },
     { Command::Type::BSDIFF,            PerformCommandDiff },
-    { Command::Type::COMPUTE_HASH_TREE, PerformCommandComputeHashTree },
+    { Command::Type::COMPUTE_HASH_TREE, nullptr },
     { Command::Type::ERASE,             nullptr },
     { Command::Type::FREE,              PerformCommandFree },
     { Command::Type::IMGDIFF,           PerformCommandDiff },
@@ -2079,10 +2088,17 @@
     return StringValue("");
   }
 
-  android::base::unique_fd fd(open(blockdev_filename->data.c_str(), O_RDWR));
+  auto block_device_path = state->updater->FindBlockDeviceName(blockdev_filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name
+               << " failed.";
+    return StringValue("");
+  }
+
+  android::base::unique_fd fd(open(block_device_path.c_str(), O_RDWR));
   if (fd == -1) {
     CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
-    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", blockdev_filename->data.c_str(),
+    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2096,7 +2112,7 @@
   std::vector<uint8_t> buffer(BLOCKSIZE);
   for (const auto& [begin, end] : rs) {
     if (!check_lseek(fd, static_cast<off64_t>(begin) * BLOCKSIZE, SEEK_SET)) {
-      ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data.c_str(),
+      ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", block_device_path.c_str(),
                  strerror(errno));
       return StringValue("");
     }
@@ -2104,7 +2120,7 @@
     for (size_t j = begin; j < end; ++j) {
       if (!android::base::ReadFully(fd, buffer.data(), BLOCKSIZE)) {
         CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure;
-        ErrorAbort(state, cause_code, "failed to read %s: %s", blockdev_filename->data.c_str(),
+        ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(),
                    strerror(errno));
         return StringValue("");
       }
@@ -2143,10 +2159,17 @@
     return StringValue("");
   }
 
-  android::base::unique_fd fd(open(arg_filename->data.c_str(), O_RDONLY));
+  auto block_device_path = state->updater->FindBlockDeviceName(arg_filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << arg_filename->data << " not found. " << name
+               << " failed.";
+    return StringValue("");
+  }
+
+  android::base::unique_fd fd(open(block_device_path.c_str(), O_RDONLY));
   if (fd == -1) {
     CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure;
-    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", arg_filename->data.c_str(),
+    ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2156,7 +2179,7 @@
 
   if (ReadBlocks(blk0, &block0_buffer, fd) == -1) {
     CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure;
-    ErrorAbort(state, cause_code, "failed to read %s: %s", arg_filename->data.c_str(),
+    ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2172,8 +2195,10 @@
   uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]);
 
   if (mount_count > 0) {
-    uiPrintf(state, "Device was remounted R/W %" PRIu16 " times", mount_count);
-    uiPrintf(state, "Last remount happened on %s", ctime(&mount_time));
+    state->updater->UiPrint(
+        android::base::StringPrintf("Device was remounted R/W %" PRIu16 " times", mount_count));
+    state->updater->UiPrint(
+        android::base::StringPrintf("Last remount happened on %s", ctime(&mount_time)));
   }
 
   return StringValue("t");
@@ -2209,14 +2234,21 @@
     return StringValue("");
   }
 
+  auto block_device_path = state->updater->FindBlockDeviceName(filename->data);
+  if (block_device_path.empty()) {
+    LOG(ERROR) << "Block device path for " << filename->data << " not found. " << name
+               << " failed.";
+    return StringValue("");
+  }
+
   // Output notice to log when recover is attempted
-  LOG(INFO) << filename->data << " image corrupted, attempting to recover...";
+  LOG(INFO) << block_device_path << " image corrupted, attempting to recover...";
 
   // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read
-  fec::io fh(filename->data, O_RDWR);
+  fec::io fh(block_device_path, O_RDWR);
 
   if (!fh) {
-    ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(),
+    ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", block_device_path.c_str(),
                strerror(errno));
     return StringValue("");
   }
@@ -2242,7 +2274,7 @@
 
       if (fh.pread(buffer, BLOCKSIZE, static_cast<off64_t>(j) * BLOCKSIZE) != BLOCKSIZE) {
         ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s",
-                   filename->data.c_str(), j, strerror(errno));
+                   block_device_path.c_str(), j, strerror(errno));
         return StringValue("");
       }
 
@@ -2258,7 +2290,7 @@
       //     read and check if the errors field value has increased.
     }
   }
-  LOG(INFO) << "..." << filename->data << " image recovered successfully.";
+  LOG(INFO) << "..." << block_device_path << " image recovered successfully.";
   return StringValue("t");
 }
 
diff --git a/updater/build_info.cpp b/updater/build_info.cpp
new file mode 100644
index 0000000..f168008
--- /dev/null
+++ b/updater/build_info.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/build_info.h"
+
+#include <stdio.h>
+
+#include <set>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "updater/target_files.h"
+
+bool BuildInfo::ParseTargetFile(const std::string_view target_file_path, bool extracted_input) {
+  TargetFile target_file(std::string(target_file_path), extracted_input);
+  if (!target_file.Open()) {
+    return false;
+  }
+
+  if (!target_file.GetBuildProps(&build_props_)) {
+    return false;
+  }
+
+  std::vector<FstabInfo> fstab_info_list;
+  if (!target_file.ParseFstabInfo(&fstab_info_list)) {
+    return false;
+  }
+
+  for (const auto& fstab_info : fstab_info_list) {
+    for (const auto& directory : { "IMAGES", "RADIO" }) {
+      std::string entry_name = directory + fstab_info.mount_point + ".img";
+      if (!target_file.EntryExists(entry_name)) {
+        LOG(WARNING) << "Failed to find the image entry in the target file: " << entry_name;
+        continue;
+      }
+
+      temp_files_.emplace_back(work_dir_);
+      auto& image_file = temp_files_.back();
+      if (!target_file.ExtractImage(entry_name, fstab_info, work_dir_, &image_file)) {
+        LOG(ERROR) << "Failed to set up source image files.";
+        return false;
+      }
+
+      std::string mapped_path = image_file.path;
+      // Rename the images to more readable ones if we want to keep the image.
+      if (keep_images_) {
+        mapped_path = work_dir_ + fstab_info.mount_point + ".img";
+        image_file.release();
+        if (rename(image_file.path, mapped_path.c_str()) != 0) {
+          PLOG(ERROR) << "Failed to rename " << image_file.path << " to " << mapped_path;
+          return false;
+        }
+      }
+
+      LOG(INFO) << "Mounted " << fstab_info.mount_point << "\nMapping: " << fstab_info.blockdev_name
+                << " to " << mapped_path;
+
+      blockdev_map_.emplace(
+          fstab_info.blockdev_name,
+          FakeBlockDevice(fstab_info.blockdev_name, fstab_info.mount_point, mapped_path));
+      break;
+    }
+  }
+
+  return true;
+}
+
+std::string BuildInfo::GetProperty(const std::string_view key,
+                                   const std::string_view default_value) const {
+  // The logic to parse the ro.product properties should be in line with the generation script.
+  // More details in common.py BuildInfo.GetBuildProp.
+  // TODO(xunchang) handle the oem property and the source order defined in
+  // ro.product.property_source_order
+  const std::set<std::string, std::less<>> ro_product_props = {
+    "ro.product.brand", "ro.product.device", "ro.product.manufacturer", "ro.product.model",
+    "ro.product.name"
+  };
+  const std::vector<std::string> source_order = {
+    "product", "odm", "vendor", "system_ext", "system",
+  };
+  if (ro_product_props.find(key) != ro_product_props.end()) {
+    std::string_view key_suffix(key);
+    CHECK(android::base::ConsumePrefix(&key_suffix, "ro.product"));
+    for (const auto& source : source_order) {
+      std::string resolved_key = "ro.product." + source + std::string(key_suffix);
+      if (auto entry = build_props_.find(resolved_key); entry != build_props_.end()) {
+        return entry->second;
+      }
+    }
+    LOG(WARNING) << "Failed to find property: " << key;
+    return std::string(default_value);
+  } else if (key == "ro.build.fingerprint") {
+    // clang-format off
+    return android::base::StringPrintf("%s/%s/%s:%s/%s/%s:%s/%s",
+        GetProperty("ro.product.brand", "").c_str(),
+        GetProperty("ro.product.name", "").c_str(),
+        GetProperty("ro.product.device", "").c_str(),
+        GetProperty("ro.build.version.release", "").c_str(),
+        GetProperty("ro.build.id", "").c_str(),
+        GetProperty("ro.build.version.incremental", "").c_str(),
+        GetProperty("ro.build.type", "").c_str(),
+        GetProperty("ro.build.tags", "").c_str());
+    // clang-format on
+  }
+
+  auto entry = build_props_.find(key);
+  if (entry == build_props_.end()) {
+    LOG(WARNING) << "Failed to find property: " << key;
+    return std::string(default_value);
+  }
+
+  return entry->second;
+}
+
+std::string BuildInfo::FindBlockDeviceName(const std::string_view name) const {
+  auto entry = blockdev_map_.find(name);
+  if (entry == blockdev_map_.end()) {
+    LOG(WARNING) << "Failed to find path to block device " << name;
+    return "";
+  }
+
+  return entry->second.mounted_file_path;
+}
diff --git a/updater/dynamic_partitions.cpp b/updater/dynamic_partitions.cpp
index b50dd75..a340116 100644
--- a/updater/dynamic_partitions.cpp
+++ b/updater/dynamic_partitions.cpp
@@ -19,46 +19,20 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <algorithm>
-#include <chrono>
-#include <iterator>
 #include <memory>
-#include <optional>
 #include <string>
-#include <type_traits>
 #include <vector>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
-#include <android-base/parseint.h>
 #include <android-base/strings.h>
-#include <fs_mgr.h>
-#include <fs_mgr_dm_linear.h>
-#include <libdm/dm.h>
-#include <liblp/builder.h>
 
 #include "edify/expr.h"
+#include "edify/updater_runtime_interface.h"
 #include "otautil/error_code.h"
 #include "otautil/paths.h"
 #include "private/utils.h"
 
-using android::base::ParseUint;
-using android::dm::DeviceMapper;
-using android::dm::DmDeviceState;
-using android::fs_mgr::CreateLogicalPartition;
-using android::fs_mgr::DestroyLogicalPartition;
-using android::fs_mgr::LpMetadata;
-using android::fs_mgr::MetadataBuilder;
-using android::fs_mgr::Partition;
-using android::fs_mgr::PartitionOpener;
-
-static constexpr std::chrono::milliseconds kMapTimeout{ 1000 };
-static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED";
-
-static std::string GetSuperDevice() {
-  return "/dev/block/by-name/" + fs_mgr_get_super_partition_name();
-}
-
 static std::vector<std::string> ReadStringArgs(const char* name, State* state,
                                                const std::vector<std::unique_ptr<Expr>>& argv,
                                                const std::vector<std::string>& arg_names) {
@@ -89,40 +63,14 @@
   return ret;
 }
 
-static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
-  auto state = DeviceMapper::Instance().GetState(partition_name);
-  if (state == DmDeviceState::INVALID) {
-    return true;
-  }
-  if (state == DmDeviceState::ACTIVE) {
-    return DestroyLogicalPartition(partition_name, kMapTimeout);
-  }
-  LOG(ERROR) << "Unknown device mapper state: "
-             << static_cast<std::underlying_type_t<DmDeviceState>>(state);
-  return false;
-}
-
-static bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) {
-  auto state = DeviceMapper::Instance().GetState(partition_name);
-  if (state == DmDeviceState::INVALID) {
-    return CreateLogicalPartition(GetSuperDevice(), 0 /* metadata slot */, partition_name,
-                                  true /* force writable */, kMapTimeout, path);
-  }
-
-  if (state == DmDeviceState::ACTIVE) {
-    return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path);
-  }
-  LOG(ERROR) << "Unknown device mapper state: "
-             << static_cast<std::underlying_type_t<DmDeviceState>>(state);
-  return false;
-}
-
 Value* UnmapPartitionFn(const char* name, State* state,
                         const std::vector<std::unique_ptr<Expr>>& argv) {
   auto args = ReadStringArgs(name, state, argv, { "name" });
   if (args.empty()) return StringValue("");
 
-  return UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t") : StringValue("");
+  auto updater_runtime = state->updater->GetRuntime();
+  return updater_runtime->UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t")
+                                                                : StringValue("");
 }
 
 Value* MapPartitionFn(const char* name, State* state,
@@ -131,207 +79,12 @@
   if (args.empty()) return StringValue("");
 
   std::string path;
-  bool result = MapPartitionOnDeviceMapper(args[0], &path);
+  auto updater_runtime = state->updater->GetRuntime();
+  bool result = updater_runtime->MapPartitionOnDeviceMapper(args[0], &path);
   return result ? StringValue(path) : StringValue("");
 }
 
-namespace {  // Ops
-
-struct OpParameters {
-  std::vector<std::string> tokens;
-  MetadataBuilder* builder;
-
-  bool ExpectArgSize(size_t size) const {
-    CHECK(!tokens.empty());
-    auto actual = tokens.size() - 1;
-    if (actual != size) {
-      LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual;
-      return false;
-    }
-    return true;
-  }
-  const std::string& op() const {
-    CHECK(!tokens.empty());
-    return tokens[0];
-  }
-  const std::string& arg(size_t pos) const {
-    CHECK_LE(pos + 1, tokens.size());
-    return tokens[pos + 1];
-  }
-  std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const {
-    auto str = arg(pos);
-    uint64_t ret;
-    if (!ParseUint(str, &ret)) {
-      LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str;
-      return std::nullopt;
-    }
-    return ret;
-  }
-};
-
-using OpFunction = std::function<bool(const OpParameters&)>;
-using OpMap = std::map<std::string, OpFunction>;
-
-bool PerformOpResize(const OpParameters& params) {
-  if (!params.ExpectArgSize(2)) return false;
-  const auto& partition_name = params.arg(0);
-  auto size = params.uint_arg(1, "size");
-  if (!size.has_value()) return false;
-
-  auto partition = params.builder->FindPartition(partition_name);
-  if (partition == nullptr) {
-    LOG(ERROR) << "Failed to find partition " << partition_name
-               << " in dynamic partition metadata.";
-    return false;
-  }
-  if (!UnmapPartitionOnDeviceMapper(partition_name)) {
-    LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing.";
-    return false;
-  }
-  if (!params.builder->ResizePartition(partition, size.value())) {
-    LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << ".";
-    return false;
-  }
-  return true;
-}
-
-bool PerformOpRemove(const OpParameters& params) {
-  if (!params.ExpectArgSize(1)) return false;
-  const auto& partition_name = params.arg(0);
-
-  if (!UnmapPartitionOnDeviceMapper(partition_name)) {
-    LOG(ERROR) << "Cannot unmap " << partition_name << " before removing.";
-    return false;
-  }
-  params.builder->RemovePartition(partition_name);
-  return true;
-}
-
-bool PerformOpAdd(const OpParameters& params) {
-  if (!params.ExpectArgSize(2)) return false;
-  const auto& partition_name = params.arg(0);
-  const auto& group_name = params.arg(1);
-
-  if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) ==
-      nullptr) {
-    LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << ".";
-    return false;
-  }
-  return true;
-}
-
-bool PerformOpMove(const OpParameters& params) {
-  if (!params.ExpectArgSize(2)) return false;
-  const auto& partition_name = params.arg(0);
-  const auto& new_group = params.arg(1);
-
-  auto partition = params.builder->FindPartition(partition_name);
-  if (partition == nullptr) {
-    LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group
-               << " because it is not found.";
-    return false;
-  }
-
-  auto old_group = partition->group_name();
-  if (old_group != new_group) {
-    if (!params.builder->ChangePartitionGroup(partition, new_group)) {
-      LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group
-                 << " to group " << new_group << ".";
-      return false;
-    }
-  }
-  return true;
-}
-
-bool PerformOpAddGroup(const OpParameters& params) {
-  if (!params.ExpectArgSize(2)) return false;
-  const auto& group_name = params.arg(0);
-  auto maximum_size = params.uint_arg(1, "maximum_size");
-  if (!maximum_size.has_value()) return false;
-
-  auto group = params.builder->FindGroup(group_name);
-  if (group != nullptr) {
-    LOG(ERROR) << "Cannot add group " << group_name << " because it already exists.";
-    return false;
-  }
-
-  if (maximum_size.value() == 0) {
-    LOG(WARNING) << "Adding group " << group_name << " with no size limits.";
-  }
-
-  if (!params.builder->AddGroup(group_name, maximum_size.value())) {
-    LOG(ERROR) << "Failed to add group " << group_name << " with maximum size "
-               << maximum_size.value() << ".";
-    return false;
-  }
-  return true;
-}
-
-bool PerformOpResizeGroup(const OpParameters& params) {
-  if (!params.ExpectArgSize(2)) return false;
-  const auto& group_name = params.arg(0);
-  auto new_size = params.uint_arg(1, "maximum_size");
-  if (!new_size.has_value()) return false;
-
-  auto group = params.builder->FindGroup(group_name);
-  if (group == nullptr) {
-    LOG(ERROR) << "Cannot resize group " << group_name << " because it is not found.";
-    return false;
-  }
-
-  auto old_size = group->maximum_size();
-  if (old_size != new_size.value()) {
-    if (!params.builder->ChangeGroupSize(group_name, new_size.value())) {
-      LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to "
-                 << new_size.value() << ".";
-      return false;
-    }
-  }
-  return true;
-}
-
-std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder,
-                                                   const std::string& group_name) {
-  auto partitions = builder->ListPartitionsInGroup(group_name);
-  std::vector<std::string> partition_names;
-  std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names),
-                 [](Partition* partition) { return partition->name(); });
-  return partition_names;
-}
-
-bool PerformOpRemoveGroup(const OpParameters& params) {
-  if (!params.ExpectArgSize(1)) return false;
-  const auto& group_name = params.arg(0);
-
-  auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
-  if (!partition_names.empty()) {
-    LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions ["
-               << android::base::Join(partition_names, ", ") << "]";
-    return false;
-  }
-  params.builder->RemoveGroupAndPartitions(group_name);
-  return true;
-}
-
-bool PerformOpRemoveAllGroups(const OpParameters& params) {
-  if (!params.ExpectArgSize(0)) return false;
-
-  auto group_names = params.builder->ListGroups();
-  for (const auto& group_name : group_names) {
-    auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
-    for (const auto& partition_name : partition_names) {
-      if (!UnmapPartitionOnDeviceMapper(partition_name)) {
-        LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name
-                   << ".";
-        return false;
-      }
-    }
-    params.builder->RemoveGroupAndPartitions(group_name);
-  }
-  return true;
-}
-
-}  // namespace
+static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED";
 
 Value* UpdateDynamicPartitionsFn(const char* name, State* state,
                                  const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -367,56 +120,8 @@
     }
   }
 
-  auto super_device = GetSuperDevice();
-  auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0);
-  if (builder == nullptr) {
-    LOG(ERROR) << "Failed to load dynamic partition metadata.";
-    return StringValue("");
-  }
-
-  static const OpMap op_map{
-    // clang-format off
-    {"resize",                PerformOpResize},
-    {"remove",                PerformOpRemove},
-    {"add",                   PerformOpAdd},
-    {"move",                  PerformOpMove},
-    {"add_group",             PerformOpAddGroup},
-    {"resize_group",          PerformOpResizeGroup},
-    {"remove_group",          PerformOpRemoveGroup},
-    {"remove_all_groups",     PerformOpRemoveAllGroups},
-    // clang-format on
-  };
-
-  std::vector<std::string> lines = android::base::Split(op_list_value->data, "\n");
-  for (const auto& line : lines) {
-    auto comment_idx = line.find('#');
-    auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx);
-    op_and_args = android::base::Trim(op_and_args);
-    if (op_and_args.empty()) continue;
-
-    auto tokens = android::base::Split(op_and_args, " ");
-    const auto& op = tokens[0];
-    auto it = op_map.find(op);
-    if (it == op_map.end()) {
-      LOG(ERROR) << "Unknown operation in op_list: " << op;
-      return StringValue("");
-    }
-    OpParameters params;
-    params.tokens = tokens;
-    params.builder = builder.get();
-    if (!it->second(params)) {
-      return StringValue("");
-    }
-  }
-
-  auto metadata = builder->Export();
-  if (metadata == nullptr) {
-    LOG(ERROR) << "Failed to export metadata.";
-    return StringValue("");
-  }
-
-  if (!UpdatePartitionTable(super_device, *metadata, 0)) {
-    LOG(ERROR) << "Failed to write metadata.";
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->UpdateDynamicPartitions(op_list_value->data)) {
     return StringValue("");
   }
 
diff --git a/updater/include/private/commands.h b/updater/include/private/commands.h
index 79f9154..7a23bb7 100644
--- a/updater/include/private/commands.h
+++ b/updater/include/private/commands.h
@@ -307,7 +307,7 @@
       : type_(type),
         index_(index),
         cmdline_(std::move(cmdline)),
-        patch_(std::move(patch)),
+        patch_(patch),
         target_(std::move(target)),
         source_(std::move(source)),
         stash_(std::move(stash)) {}
diff --git a/updater/include/updater/build_info.h b/updater/include/updater/build_info.h
new file mode 100644
index 0000000..0073bfa
--- /dev/null
+++ b/updater/include/updater/build_info.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <list>
+#include <map>
+#include <string>
+#include <string_view>
+
+#include <android-base/file.h>
+
+// This class serves as the aggregation of the fake block device information during update
+// simulation on host. In specific, it has the name of the block device, its mount point, and the
+// path to the temporary file that fakes this block device.
+class FakeBlockDevice {
+ public:
+  FakeBlockDevice(std::string block_device, std::string mount_point, std::string temp_file_path)
+      : blockdev_name(std::move(block_device)),
+        mount_point(std::move(mount_point)),
+        mounted_file_path(std::move(temp_file_path)) {}
+
+  std::string blockdev_name;
+  std::string mount_point;
+  std::string mounted_file_path;  // path to the temp file that mocks the block device
+};
+
+// This class stores the information of the source build. For example, it creates and maintains
+// the temporary files to simulate the block devices on host. Therefore, the simulator runtime can
+// query the information and run the update on host.
+class BuildInfo {
+ public:
+  BuildInfo(const std::string_view work_dir, bool keep_images)
+      : work_dir_(work_dir), keep_images_(keep_images) {}
+  // Returns the value of the build properties.
+  std::string GetProperty(const std::string_view key, const std::string_view default_value) const;
+  // Returns the path to the mock block device.
+  std::string FindBlockDeviceName(const std::string_view name) const;
+  // Parses the given target-file, initializes the build properties and extracts the images.
+  bool ParseTargetFile(const std::string_view target_file_path, bool extracted_input);
+
+  std::string GetOemSettings() const {
+    return oem_settings_;
+  }
+  void SetOemSettings(const std::string_view oem_settings) {
+    oem_settings_ = oem_settings;
+  }
+
+ private:
+  // A map to store the system properties during simulation.
+  std::map<std::string, std::string, std::less<>> build_props_;
+  // A file that contains the oem properties.
+  std::string oem_settings_;
+  // A map from the blockdev_name to the FakeBlockDevice object, which contains the path to the
+  // temporary file.
+  std::map<std::string, FakeBlockDevice, std::less<>> blockdev_map_;
+
+  std::list<TemporaryFile> temp_files_;
+  std::string work_dir_;  // A temporary directory to store the extracted image files
+  bool keep_images_;
+};
diff --git a/updater/include/updater/install.h b/updater/include/updater/install.h
index 8d6ca47..9fe2031 100644
--- a/updater/include/updater/install.h
+++ b/updater/include/updater/install.h
@@ -14,15 +14,6 @@
  * limitations under the License.
  */
 
-#ifndef _UPDATER_INSTALL_H_
-#define _UPDATER_INSTALL_H_
-
-struct State;
+#pragma once
 
 void RegisterInstallFunctions();
-
-// uiPrintf function prints msg to screen as well as logs
-void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...)
-    __attribute__((__format__(printf, 2, 3)));
-
-#endif
diff --git a/updater/include/updater/simulator_runtime.h b/updater/include/updater/simulator_runtime.h
new file mode 100644
index 0000000..9f7847b
--- /dev/null
+++ b/updater/include/updater/simulator_runtime.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "edify/updater_runtime_interface.h"
+#include "updater/build_info.h"
+
+class SimulatorRuntime : public UpdaterRuntimeInterface {
+ public:
+  explicit SimulatorRuntime(BuildInfo* source) : source_(source) {}
+
+  bool IsSimulator() const override {
+    return true;
+  }
+
+  std::string GetProperty(const std::string_view key,
+                          const std::string_view default_value) const override;
+
+  int Mount(const std::string_view location, const std::string_view mount_point,
+            const std::string_view fs_type, const std::string_view mount_options) override;
+  bool IsMounted(const std::string_view mount_point) const override;
+  std::pair<bool, int> Unmount(const std::string_view mount_point) override;
+
+  bool ReadFileToString(const std::string_view filename, std::string* content) const override;
+  bool WriteStringToFile(const std::string_view content,
+                         const std::string_view filename) const override;
+
+  int WipeBlockDevice(const std::string_view filename, size_t len) const override;
+  int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
+  int Tune2Fs(const std::vector<std::string>& args) const override;
+
+  bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override;
+  bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override;
+  bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
+
+ private:
+  std::string FindBlockDeviceName(const std::string_view name) const override;
+
+  BuildInfo* source_;
+  std::map<std::string, std::string, std::less<>> mounted_partitions_;
+};
diff --git a/updater/include/updater/target_files.h b/updater/include/updater/target_files.h
new file mode 100644
index 0000000..860d47a
--- /dev/null
+++ b/updater/include/updater/target_files.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <android-base/file.h>
+#include <ziparchive/zip_archive.h>
+
+// This class represents the mount information for each line in a fstab file.
+class FstabInfo {
+ public:
+  FstabInfo(std::string blockdev_name, std::string mount_point, std::string fs_type)
+      : blockdev_name(std::move(blockdev_name)),
+        mount_point(std::move(mount_point)),
+        fs_type(std::move(fs_type)) {}
+
+  std::string blockdev_name;
+  std::string mount_point;
+  std::string fs_type;
+};
+
+// This class parses a target file from a zip file or an extracted directory. It also provides the
+// function to read the its content for simulation.
+class TargetFile {
+ public:
+  TargetFile(std::string path, bool extracted_input)
+      : path_(std::move(path)), extracted_input_(extracted_input) {}
+
+  // Opens the input target file (or extracted directory) and parses the misc_info.txt.
+  bool Open();
+  // Parses the build properties in all possible locations and save them in |props_map|
+  bool GetBuildProps(std::map<std::string, std::string, std::less<>>* props_map) const;
+  // Parses the fstab and save the information about each partition to mount into |fstab_info_list|.
+  bool ParseFstabInfo(std::vector<FstabInfo>* fstab_info_list) const;
+  // Returns true if the given entry exists in the target file.
+  bool EntryExists(const std::string_view name) const;
+  // Extracts the image file |entry_name|. Returns true on success.
+  bool ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info,
+                    const std::string_view work_dir, TemporaryFile* image_file) const;
+
+ private:
+  // Wrapper functions to read the entry from either the zipped target-file, or the extracted input
+  // directory.
+  bool ReadEntryToString(const std::string_view name, std::string* content) const;
+  bool ExtractEntryToTempFile(const std::string_view name, TemporaryFile* temp_file) const;
+
+  std::string path_;      // Path to the zipped target-file or an extracted directory.
+  bool extracted_input_;  // True if the target-file has been extracted.
+  ZipArchiveHandle handle_{ nullptr };
+
+  // The properties under META/misc_info.txt
+  std::map<std::string, std::string, std::less<>> misc_info_;
+};
diff --git a/updater/include/updater/updater.h b/updater/include/updater/updater.h
index f4a2fe8..8676b60 100644
--- a/updater/include/updater/updater.h
+++ b/updater/include/updater/updater.h
@@ -14,22 +14,83 @@
  * limitations under the License.
  */
 
-#ifndef _UPDATER_UPDATER_H_
-#define _UPDATER_UPDATER_H_
+#pragma once
 
+#include <stdint.h>
 #include <stdio.h>
+
+#include <memory>
+#include <string>
+#include <string_view>
+
 #include <ziparchive/zip_archive.h>
 
-typedef struct {
-    FILE* cmd_pipe;
-    ZipArchiveHandle package_zip;
-    int version;
+#include "edify/expr.h"
+#include "edify/updater_interface.h"
+#include "otautil/error_code.h"
+#include "otautil/sysutil.h"
 
-    uint8_t* package_zip_addr;
-    size_t package_zip_len;
-} UpdaterInfo;
+class Updater : public UpdaterInterface {
+ public:
+  explicit Updater(std::unique_ptr<UpdaterRuntimeInterface> run_time)
+      : runtime_(std::move(run_time)) {}
 
-struct selabel_handle;
-extern struct selabel_handle *sehandle;
+  ~Updater() override;
 
-#endif
+  // Memory-maps the OTA package and opens it as a zip file. Also sets up the command pipe and
+  // UpdaterRuntime.
+  bool Init(int fd, const std::string_view package_filename, bool is_retry);
+
+  // Parses and evaluates the updater-script in the OTA package. Reports the error code if the
+  // evaluation fails.
+  bool RunUpdate();
+
+  // Writes the message to command pipe, adds a new line in the end.
+  void WriteToCommandPipe(const std::string_view message, bool flush = false) const override;
+
+  // Sends over the message to recovery to print it on the screen.
+  void UiPrint(const std::string_view message) const override;
+
+  std::string FindBlockDeviceName(const std::string_view name) const override;
+
+  UpdaterRuntimeInterface* GetRuntime() const override {
+    return runtime_.get();
+  }
+  ZipArchiveHandle GetPackageHandle() const override {
+    return package_handle_;
+  }
+  std::string GetResult() const override {
+    return result_;
+  }
+  uint8_t* GetMappedPackageAddress() const override {
+    return mapped_package_.addr;
+  }
+  size_t GetMappedPackageLength() const override {
+    return mapped_package_.length;
+  }
+
+ private:
+  friend class UpdaterTestBase;
+  friend class UpdaterTest;
+  // Where in the package we expect to find the edify script to execute.
+  // (Note it's "updateR-script", not the older "update-script".)
+  static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script";
+
+  // Reads the entry |name| in the zip archive and put the result in |content|.
+  bool ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, std::string* content);
+
+  // Parses the error code embedded in state->errmsg; and reports the error code and cause code.
+  void ParseAndReportErrorCode(State* state);
+
+  std::unique_ptr<UpdaterRuntimeInterface> runtime_;
+
+  MemMapping mapped_package_;
+  ZipArchiveHandle package_handle_{ nullptr };
+  std::string updater_script_;
+
+  bool is_retry_{ false };
+  std::unique_ptr<FILE, decltype(&fclose)> cmd_pipe_{ nullptr, fclose };
+
+  std::string result_;
+  std::vector<std::string> skipped_functions_;
+};
diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h
new file mode 100644
index 0000000..8fc066f
--- /dev/null
+++ b/updater/include/updater/updater_runtime.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "edify/updater_runtime_interface.h"
+
+struct selabel_handle;
+
+class UpdaterRuntime : public UpdaterRuntimeInterface {
+ public:
+  explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {}
+  ~UpdaterRuntime() override = default;
+
+  bool IsSimulator() const override {
+    return false;
+  }
+
+  std::string GetProperty(const std::string_view key,
+                          const std::string_view default_value) const override;
+
+  std::string FindBlockDeviceName(const std::string_view name) const override;
+
+  int Mount(const std::string_view location, const std::string_view mount_point,
+            const std::string_view fs_type, const std::string_view mount_options) override;
+  bool IsMounted(const std::string_view mount_point) const override;
+  std::pair<bool, int> Unmount(const std::string_view mount_point) override;
+
+  bool ReadFileToString(const std::string_view filename, std::string* content) const override;
+  bool WriteStringToFile(const std::string_view content,
+                         const std::string_view filename) const override;
+
+  int WipeBlockDevice(const std::string_view filename, size_t len) const override;
+  int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
+  int Tune2Fs(const std::vector<std::string>& args) const override;
+
+  bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override;
+  bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override;
+  bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
+
+ private:
+  struct selabel_handle* sehandle_{ nullptr };
+};
diff --git a/updater/install.cpp b/updater/install.cpp
index 20a204a..7608dc3 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -53,45 +53,30 @@
 #include <openssl/sha.h>
 #include <selinux/label.h>
 #include <selinux/selinux.h>
-#include <tune2fs.h>
 #include <ziparchive/zip_archive.h>
 
 #include "edify/expr.h"
+#include "edify/updater_interface.h"
+#include "edify/updater_runtime_interface.h"
 #include "otautil/dirutil.h"
 #include "otautil/error_code.h"
-#include "otautil/mounts.h"
 #include "otautil/print_sha1.h"
 #include "otautil/sysutil.h"
-#include "updater/updater.h"
 
-// Send over the buffer to recovery though the command pipe.
-static void uiPrint(State* state, const std::string& buffer) {
-  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
+#ifndef __ANDROID__
+#include <cutils/memory.h>  // for strlcpy
+#endif
 
-  // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
-  // So skip sending empty strings to UI.
-  std::vector<std::string> lines = android::base::Split(buffer, "\n");
-  for (auto& line : lines) {
-    if (!line.empty()) {
-      fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str());
-    }
+static bool UpdateBlockDeviceNameForPartition(UpdaterInterface* updater, Partition* partition) {
+  CHECK(updater);
+  std::string name = updater->FindBlockDeviceName(partition->name);
+  if (name.empty()) {
+    LOG(ERROR) << "Failed to find the block device " << partition->name;
+    return false;
   }
 
-  // On the updater side, we need to dump the contents to stderr (which has
-  // been redirected to the log file). Because the recovery will only print
-  // the contents to screen when processing pipe command ui_print.
-  LOG(INFO) << buffer;
-}
-
-void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) {
-  std::string error_msg;
-
-  va_list ap;
-  va_start(ap, format);
-  android::base::StringAppendV(&error_msg, format, ap);
-  va_end(ap);
-
-  uiPrint(state, error_msg);
+  partition->name = std::move(name);
+  return true;
 }
 
 // This is the updater side handler for ui_print() in edify script. Contents will be sent over to
@@ -103,7 +88,7 @@
   }
 
   std::string buffer = android::base::Join(args, "");
-  uiPrint(state, buffer);
+  state->updater->UiPrint(buffer);
   return StringValue(buffer);
 }
 
@@ -127,16 +112,22 @@
                         argv.size());
     }
     const std::string& zip_path = args[0];
-    const std::string& dest_path = args[1];
+    std::string dest_path = args[1];
 
-    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
-    ZipString zip_string_path(zip_path.c_str());
+    ZipArchiveHandle za = state->updater->GetPackageHandle();
     ZipEntry entry;
-    if (FindEntry(za, zip_string_path, &entry) != 0) {
+    if (FindEntry(za, zip_path, &entry) != 0) {
       LOG(ERROR) << name << ": no " << zip_path << " in package";
       return StringValue("");
     }
 
+    // Update the destination of package_extract_file if it's a block device. During simulation the
+    // destination will map to a fake file.
+    if (std::string block_device_name = state->updater->FindBlockDeviceName(dest_path);
+        !block_device_name.empty()) {
+      dest_path = block_device_name;
+    }
+
     android::base::unique_fd fd(TEMP_FAILURE_RETRY(
         open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)));
     if (fd == -1) {
@@ -173,10 +164,9 @@
     }
     const std::string& zip_path = args[0];
 
-    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
-    ZipString zip_string_path(zip_path.c_str());
+    ZipArchiveHandle za = state->updater->GetPackageHandle();
     ZipEntry entry;
-    if (FindEntry(za, zip_string_path, &entry) != 0) {
+    if (FindEntry(za, zip_path, &entry) != 0) {
       return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
                         zip_path.c_str());
     }
@@ -229,6 +219,11 @@
                       args[1].c_str(), err.c_str());
   }
 
+  if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
+      !UpdateBlockDeviceNameForPartition(state->updater, &target)) {
+    return StringValue("");
+  }
+
   bool result = PatchPartitionCheck(target, source);
   return StringValue(result ? "t" : "");
 }
@@ -270,7 +265,12 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name);
   }
 
-  bool result = PatchPartition(target, source, *values[0], nullptr);
+  if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
+      !UpdateBlockDeviceNameForPartition(state->updater, &target)) {
+    return StringValue("");
+  }
+
+  bool result = PatchPartition(target, source, *values[0], nullptr, true);
   return StringValue(result ? "t" : "");
 }
 
@@ -313,26 +313,11 @@
                       name);
   }
 
-  {
-    char* secontext = nullptr;
-
-    if (sehandle) {
-      selabel_lookup(sehandle, &secontext, mount_point.c_str(), 0755);
-      setfscreatecon(secontext);
-    }
-
-    mkdir(mount_point.c_str(), 0755);
-
-    if (secontext) {
-      freecon(secontext);
-      setfscreatecon(nullptr);
-    }
-  }
-
-  if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(),
-            MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) {
-    uiPrintf(state, "%s: Failed to mount %s at %s: %s", name, location.c_str(), mount_point.c_str(),
-             strerror(errno));
+  auto updater = state->updater;
+  if (updater->GetRuntime()->Mount(location, mount_point, fs_type, mount_options) != 0) {
+    updater->UiPrint(android::base::StringPrintf("%s: Failed to mount %s at %s: %s", name,
+                                                 location.c_str(), mount_point.c_str(),
+                                                 strerror(errno)));
     return StringValue("");
   }
 
@@ -355,9 +340,8 @@
                       "mount_point argument to unmount() can't be empty");
   }
 
-  scan_mounted_volumes();
-  MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
-  if (vol == nullptr) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->IsMounted(mount_point)) {
     return StringValue("");
   }
 
@@ -378,39 +362,20 @@
                       "mount_point argument to unmount() can't be empty");
   }
 
-  scan_mounted_volumes();
-  MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str());
-  if (vol == nullptr) {
-    uiPrintf(state, "Failed to unmount %s: No such volume", mount_point.c_str());
+  auto updater = state->updater;
+  auto [mounted, result] = updater->GetRuntime()->Unmount(mount_point);
+  if (!mounted) {
+    updater->UiPrint(
+        android::base::StringPrintf("Failed to unmount %s: No such volume", mount_point.c_str()));
     return nullptr;
-  } else {
-    int ret = unmount_mounted_volume(vol);
-    if (ret != 0) {
-      uiPrintf(state, "Failed to unmount %s: %s", mount_point.c_str(), strerror(errno));
-    }
+  } else if (result != 0) {
+    updater->UiPrint(android::base::StringPrintf("Failed to unmount %s: %s", mount_point.c_str(),
+                                                 strerror(errno)));
   }
 
   return StringValue(mount_point);
 }
 
-static int exec_cmd(const std::vector<std::string>& args) {
-  CHECK(!args.empty());
-  auto argv = StringVectorToNullTerminatedArray(args);
-
-  pid_t child;
-  if ((child = vfork()) == 0) {
-    execv(argv[0], argv.data());
-    _exit(EXIT_FAILURE);
-  }
-
-  int status;
-  waitpid(child, &status, 0);
-  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-    LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status);
-  }
-  return WEXITSTATUS(status);
-}
-
 // format(fs_type, partition_type, location, fs_size, mount_point)
 //
 //    fs_type="ext4"  partition_type="EMMC"  location=device  fs_size=<bytes> mount_point=<location>
@@ -455,6 +420,7 @@
                       fs_size.c_str());
   }
 
+  auto updater_runtime = state->updater->GetRuntime();
   if (fs_type == "ext4") {
     std::vector<std::string> mke2fs_args = {
       "/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location
@@ -463,12 +429,13 @@
       mke2fs_args.push_back(std::to_string(size / 4096LL));
     }
 
-    if (auto status = exec_cmd(mke2fs_args); status != 0) {
+    if (auto status = updater_runtime->RunProgram(mke2fs_args, true); status != 0) {
       LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location;
       return StringValue("");
     }
 
-    if (auto status = exec_cmd({ "/system/bin/e2fsdroid", "-e", "-a", mount_point, location });
+    if (auto status = updater_runtime->RunProgram(
+            { "/system/bin/e2fsdroid", "-e", "-a", mount_point, location }, true);
         status != 0) {
       LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location;
       return StringValue("");
@@ -487,12 +454,13 @@
     if (size >= 512) {
       f2fs_args.push_back(std::to_string(size / 512));
     }
-    if (auto status = exec_cmd(f2fs_args); status != 0) {
+    if (auto status = updater_runtime->RunProgram(f2fs_args, true); status != 0) {
       LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location;
       return StringValue("");
     }
 
-    if (auto status = exec_cmd({ "/system/bin/sload_f2fs", "-t", mount_point, location });
+    if (auto status = updater_runtime->RunProgram(
+            { "/system/bin/sload_f2fs", "-t", mount_point, location }, true);
         status != 0) {
       LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location;
       return StringValue("");
@@ -531,8 +499,7 @@
                       sec_str.c_str());
   }
 
-  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
-  fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
+  state->updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec));
 
   return StringValue(frac_str);
 }
@@ -555,8 +522,7 @@
                       frac_str.c_str());
   }
 
-  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
-  fprintf(ui->cmd_pipe, "set_progress %f\n", frac);
+  state->updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac));
 
   return StringValue(frac_str);
 }
@@ -569,7 +535,9 @@
   if (!Evaluate(state, argv[0], &key)) {
     return nullptr;
   }
-  std::string value = android::base::GetProperty(key, "");
+
+  auto updater_runtime = state->updater->GetRuntime();
+  std::string value = updater_runtime->GetProperty(key, "");
 
   return StringValue(value);
 }
@@ -594,7 +562,8 @@
   const std::string& key = args[1];
 
   std::string buffer;
-  if (!android::base::ReadFileToString(filename, &buffer)) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->ReadFileToString(filename, &buffer)) {
     ErrorAbort(state, kFreadFailure, "%s: failed to read %s", name, filename.c_str());
     return nullptr;
   }
@@ -655,7 +624,8 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
                       argv.size());
   }
-  fprintf(static_cast<UpdaterInfo*>(state->cookie)->cmd_pipe, "wipe_cache\n");
+
+  state->updater->WriteToCommandPipe("wipe_cache");
   return StringValue("t");
 }
 
@@ -669,26 +639,8 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
   }
 
-  auto exec_args = StringVectorToNullTerminatedArray(args);
-  LOG(INFO) << "about to run program [" << exec_args[0] << "] with " << argv.size() << " args";
-
-  pid_t child = fork();
-  if (child == 0) {
-    execv(exec_args[0], exec_args.data());
-    PLOG(ERROR) << "run_program: execv failed";
-    _exit(EXIT_FAILURE);
-  }
-
-  int status;
-  waitpid(child, &status, 0);
-  if (WIFEXITED(status)) {
-    if (WEXITSTATUS(status) != 0) {
-      LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
-    }
-  } else if (WIFSIGNALED(status)) {
-    LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
-  }
-
+  auto updater_runtime = state->updater->GetRuntime();
+  auto status = updater_runtime->RunProgram(args, false);
   return StringValue(std::to_string(status));
 }
 
@@ -706,7 +658,8 @@
   const std::string& filename = args[0];
 
   std::string contents;
-  if (android::base::ReadFileToString(filename, &contents)) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (updater_runtime->ReadFileToString(filename, &contents)) {
     return new Value(Value::Type::STRING, std::move(contents));
   }
 
@@ -735,12 +688,12 @@
   }
 
   const std::string& value = args[0];
-  if (!android::base::WriteStringToFile(value, filename)) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (!updater_runtime->WriteStringToFile(value, filename)) {
     PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\"";
     return StringValue("");
-  } else {
-    return StringValue("t");
   }
+  return StringValue("t");
 }
 
 // Immediately reboot the device.  Recovery is not finished normally,
@@ -778,9 +731,8 @@
     return StringValue("");
   }
 
-  reboot("reboot," + property);
+  Reboot(property);
 
-  sleep(5);
   return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
 }
 
@@ -866,16 +818,10 @@
   if (!android::base::ParseUint(len_str.c_str(), &len)) {
     return nullptr;
   }
-  android::base::unique_fd fd(open(filename.c_str(), O_WRONLY));
-  if (fd == -1) {
-    PLOG(ERROR) << "Failed to open " << filename;
-    return StringValue("");
-  }
 
-  // The wipe_block_device function in ext4_utils returns 0 on success and 1
-  // for failure.
-  int status = wipe_block_device(fd, len);
-  return StringValue((status == 0) ? "t" : "");
+  auto updater_runtime = state->updater->GetRuntime();
+  int status = updater_runtime->WipeBlockDevice(filename, len);
+  return StringValue(status == 0 ? "t" : "");
 }
 
 Value* EnableRebootFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -883,8 +829,7 @@
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
                       argv.size());
   }
-  UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
-  fprintf(ui->cmd_pipe, "enable_reboot\n");
+  state->updater->WriteToCommandPipe("enable_reboot");
   return StringValue("t");
 }
 
@@ -900,10 +845,8 @@
 
   // tune2fs expects the program name as its first arg.
   args.insert(args.begin(), "tune2fs");
-  auto tune2fs_args = StringVectorToNullTerminatedArray(args);
-
-  // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
-  if (auto result = tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data()); result != 0) {
+  auto updater_runtime = state->updater->GetRuntime();
+  if (auto result = updater_runtime->Tune2Fs(args); result != 0) {
     return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
   }
   return StringValue("t");
diff --git a/otautil/mounts.cpp b/updater/mounts.cpp
similarity index 98%
rename from otautil/mounts.cpp
rename to updater/mounts.cpp
index 951311b..943d35c 100644
--- a/otautil/mounts.cpp
+++ b/updater/mounts.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "otautil/mounts.h"
+#include "mounts.h"
 
 #include <errno.h>
 #include <fcntl.h>
diff --git a/otautil/include/otautil/mounts.h b/updater/mounts.h
similarity index 100%
rename from otautil/include/otautil/mounts.h
rename to updater/mounts.h
diff --git a/updater/simulator_runtime.cpp b/updater/simulator_runtime.cpp
new file mode 100644
index 0000000..3ed7bf3
--- /dev/null
+++ b/updater/simulator_runtime.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/simulator_runtime.h"
+
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/wipe.h>
+#include <selinux/label.h>
+
+#include "mounts.h"
+#include "otautil/sysutil.h"
+
+std::string SimulatorRuntime::GetProperty(const std::string_view key,
+                                          const std::string_view default_value) const {
+  return source_->GetProperty(key, default_value);
+}
+
+int SimulatorRuntime::Mount(const std::string_view location, const std::string_view mount_point,
+                            const std::string_view /* fs_type */,
+                            const std::string_view /* mount_options */) {
+  if (auto mounted_location = mounted_partitions_.find(mount_point);
+      mounted_location != mounted_partitions_.end() && mounted_location->second != location) {
+    LOG(ERROR) << mount_point << " has been mounted at " << mounted_location->second;
+    return -1;
+  }
+
+  mounted_partitions_.emplace(mount_point, location);
+  return 0;
+}
+
+bool SimulatorRuntime::IsMounted(const std::string_view mount_point) const {
+  return mounted_partitions_.find(mount_point) != mounted_partitions_.end();
+}
+
+std::pair<bool, int> SimulatorRuntime::Unmount(const std::string_view mount_point) {
+  if (!IsMounted(mount_point)) {
+    return { false, -1 };
+  }
+
+  mounted_partitions_.erase(std::string(mount_point));
+  return { true, 0 };
+}
+
+std::string SimulatorRuntime::FindBlockDeviceName(const std::string_view name) const {
+  return source_->FindBlockDeviceName(name);
+}
+
+// TODO(xunchang) implement the utility functions in simulator.
+int SimulatorRuntime::RunProgram(const std::vector<std::string>& args, bool /* is_vfork */) const {
+  LOG(INFO) << "Running program with args " << android::base::Join(args, " ");
+  return 0;
+}
+
+int SimulatorRuntime::Tune2Fs(const std::vector<std::string>& args) const {
+  LOG(INFO) << "Running Tune2Fs with args " << android::base::Join(args, " ");
+  return 0;
+}
+
+int SimulatorRuntime::WipeBlockDevice(const std::string_view filename, size_t /* len */) const {
+  LOG(INFO) << "SKip wiping block device " << filename;
+  return 0;
+}
+
+bool SimulatorRuntime::ReadFileToString(const std::string_view filename,
+                                        std::string* content) const {
+  if (android::base::EndsWith(filename, "oem.prop")) {
+    return android::base::ReadFileToString(source_->GetOemSettings(), content);
+  }
+
+  LOG(INFO) << "SKip reading filename " << filename;
+  return true;
+}
+
+bool SimulatorRuntime::WriteStringToFile(const std::string_view content,
+                                         const std::string_view filename) const {
+  LOG(INFO) << "SKip writing " << content.size() << " bytes to file " << filename;
+  return true;
+}
+
+bool SimulatorRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name,
+                                                  std::string* path) {
+  *path = partition_name;
+  return true;
+}
+
+bool SimulatorRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+  LOG(INFO) << "Skip unmapping " << partition_name;
+  return true;
+}
+
+bool SimulatorRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) {
+  const std::unordered_set<std::string> commands{
+    "resize",    "remove",       "add",          "move",
+    "add_group", "resize_group", "remove_group", "remove_all_groups",
+  };
+
+  std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n");
+  for (const auto& line : lines) {
+    if (line.empty() || line[0] == '#') continue;
+    auto tokens = android::base::Split(line, " ");
+    if (commands.find(tokens[0]) == commands.end()) {
+      LOG(ERROR) << "Unknown operation in op_list: " << line;
+      return false;
+    }
+  }
+  return true;
+}
diff --git a/updater/target_files.cpp b/updater/target_files.cpp
new file mode 100644
index 0000000..919ec4e
--- /dev/null
+++ b/updater/target_files.cpp
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/target_files.h"
+
+#include <unistd.h>
+
+#include <algorithm>
+#include <filesystem>
+#include <memory>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <sparse/sparse.h>
+
+static bool SimgToImg(int input_fd, int output_fd) {
+  if (lseek64(input_fd, 0, SEEK_SET) == -1) {
+    PLOG(ERROR) << "Failed to lseek64 on the input sparse image";
+    return false;
+  }
+
+  if (lseek64(output_fd, 0, SEEK_SET) == -1) {
+    PLOG(ERROR) << "Failed to lseek64 on the output raw image";
+    return false;
+  }
+
+  std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)> s_file(
+      sparse_file_import(input_fd, true, false), sparse_file_destroy);
+  if (!s_file) {
+    LOG(ERROR) << "Failed to import the sparse image.";
+    return false;
+  }
+
+  if (sparse_file_write(s_file.get(), output_fd, false, false, false) < 0) {
+    PLOG(ERROR) << "Failed to output the raw image file.";
+    return false;
+  }
+
+  return true;
+}
+
+static bool ParsePropertyFile(const std::string_view prop_content,
+                              std::map<std::string, std::string, std::less<>>* props_map) {
+  LOG(INFO) << "Start parsing build property\n";
+  std::vector<std::string> lines = android::base::Split(std::string(prop_content), "\n");
+  for (const auto& line : lines) {
+    if (line.empty() || line[0] == '#') continue;
+    auto pos = line.find('=');
+    if (pos == std::string::npos) continue;
+    std::string key = line.substr(0, pos);
+    std::string value = line.substr(pos + 1);
+    LOG(INFO) << key << ": " << value;
+    props_map->emplace(key, value);
+  }
+
+  return true;
+}
+
+static bool ParseFstab(const std::string_view fstab, std::vector<FstabInfo>* fstab_info_list) {
+  LOG(INFO) << "parsing fstab\n";
+  std::vector<std::string> lines = android::base::Split(std::string(fstab), "\n");
+  for (const auto& line : lines) {
+    if (line.empty() || line[0] == '#') continue;
+
+    // <block_device>  <mount_point>  <fs_type>  <mount_flags>  optional:<fs_mgr_flags>
+    std::vector<std::string> tokens = android::base::Split(line, " ");
+    tokens.erase(std::remove(tokens.begin(), tokens.end(), ""), tokens.end());
+    if (tokens.size() != 4 && tokens.size() != 5) {
+      LOG(ERROR) << "Unexpected token size: " << tokens.size() << std::endl
+                 << "Error parsing fstab line: " << line;
+      return false;
+    }
+
+    const auto& blockdev = tokens[0];
+    const auto& mount_point = tokens[1];
+    const auto& fs_type = tokens[2];
+    if (!android::base::StartsWith(mount_point, "/")) {
+      LOG(WARNING) << "mount point '" << mount_point << "' does not start with '/'";
+      continue;
+    }
+
+    // The simulator only supports ext4 and emmc for now.
+    if (fs_type != "ext4" && fs_type != "emmc") {
+      LOG(WARNING) << "Unsupported fs_type in " << line;
+      continue;
+    }
+
+    fstab_info_list->emplace_back(blockdev, mount_point, fs_type);
+  }
+
+  return true;
+}
+
+bool TargetFile::EntryExists(const std::string_view name) const {
+  if (extracted_input_) {
+    std::string entry_path = path_ + "/" + std::string(name);
+    if (access(entry_path.c_str(), O_RDONLY) != 0) {
+      PLOG(WARNING) << "Failed to access " << entry_path;
+      return false;
+    }
+    return true;
+  }
+
+  CHECK(handle_);
+  ZipEntry img_entry;
+  return FindEntry(handle_, name, &img_entry) == 0;
+}
+
+bool TargetFile::ReadEntryToString(const std::string_view name, std::string* content) const {
+  if (extracted_input_) {
+    std::string entry_path = path_ + "/" + std::string(name);
+    return android::base::ReadFileToString(entry_path, content);
+  }
+
+  CHECK(handle_);
+  ZipEntry entry;
+  if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) {
+    LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err);
+    return false;
+  }
+
+  if (entry.uncompressed_length == 0) {
+    content->clear();
+    return true;
+  }
+
+  content->resize(entry.uncompressed_length);
+  if (auto extract_err = ExtractToMemory(
+          handle_, &entry, reinterpret_cast<uint8_t*>(&content->at(0)), entry.uncompressed_length);
+      extract_err != 0) {
+    LOG(ERROR) << "failed to read " << name << " from package: " << ErrorCodeString(extract_err);
+    return false;
+  }
+
+  return true;
+}
+
+bool TargetFile::ExtractEntryToTempFile(const std::string_view name,
+                                        TemporaryFile* temp_file) const {
+  if (extracted_input_) {
+    std::string entry_path = path_ + "/" + std::string(name);
+    return std::filesystem::copy_file(entry_path, temp_file->path,
+                                      std::filesystem::copy_options::overwrite_existing);
+  }
+
+  CHECK(handle_);
+  ZipEntry entry;
+  if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) {
+    LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err);
+    return false;
+  }
+
+  if (auto status = ExtractEntryToFile(handle_, &entry, temp_file->fd); status != 0) {
+    LOG(ERROR) << "Failed to extract zip entry " << name << " : " << ErrorCodeString(status);
+    return false;
+  }
+  return true;
+}
+
+bool TargetFile::Open() {
+  if (!extracted_input_) {
+    if (auto ret = OpenArchive(path_.c_str(), &handle_); ret != 0) {
+      LOG(ERROR) << "failed to open source target file " << path_ << ": " << ErrorCodeString(ret);
+      return false;
+    }
+  }
+
+  // Parse the misc info.
+  std::string misc_info_content;
+  if (!ReadEntryToString("META/misc_info.txt", &misc_info_content)) {
+    return false;
+  }
+  if (!ParsePropertyFile(misc_info_content, &misc_info_)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool TargetFile::GetBuildProps(std::map<std::string, std::string, std::less<>>* props_map) const {
+  props_map->clear();
+  // Parse the source zip to mock the system props and block devices. We try all the possible
+  // locations for build props.
+  constexpr std::string_view kPropLocations[] = {
+    "SYSTEM/build.prop",
+    "VENDOR/build.prop",
+    "PRODUCT/build.prop",
+    "SYSTEM_EXT/build.prop",
+    "SYSTEM/vendor/build.prop",
+    "SYSTEM/product/build.prop",
+    "SYSTEM/system_ext/build.prop",
+    "ODM/build.prop",  // legacy
+    "ODM/etc/build.prop",
+    "VENDOR/odm/build.prop",  // legacy
+    "VENDOR/odm/etc/build.prop",
+  };
+  for (const auto& name : kPropLocations) {
+    std::string build_prop_content;
+    if (!ReadEntryToString(name, &build_prop_content)) {
+      continue;
+    }
+    std::map<std::string, std::string, std::less<>> props;
+    if (!ParsePropertyFile(build_prop_content, &props)) {
+      LOG(ERROR) << "Failed to parse build prop in " << name;
+      return false;
+    }
+    for (const auto& [key, value] : props) {
+      if (auto it = props_map->find(key); it != props_map->end() && it->second != value) {
+        LOG(WARNING) << "Property " << key << " has different values in property files, we got "
+                     << it->second << " and " << value;
+      }
+      props_map->emplace(key, value);
+    }
+  }
+
+  return true;
+}
+
+bool TargetFile::ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info,
+                              const std::string_view work_dir, TemporaryFile* image_file) const {
+  if (!EntryExists(entry_name)) {
+    return false;
+  }
+
+  // We don't need extra work for 'emmc'; use the image file as the block device.
+  if (fstab_info.fs_type == "emmc" || misc_info_.find("extfs_sparse_flag") == misc_info_.end()) {
+    if (!ExtractEntryToTempFile(entry_name, image_file)) {
+      return false;
+    }
+  } else {  // treated as ext4 sparse image
+    TemporaryFile sparse_image{ std::string(work_dir) };
+    if (!ExtractEntryToTempFile(entry_name, &sparse_image)) {
+      return false;
+    }
+
+    // Convert the sparse image to raw.
+    if (!SimgToImg(sparse_image.fd, image_file->fd)) {
+      LOG(ERROR) << "Failed to convert " << fstab_info.mount_point << " to raw.";
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool TargetFile::ParseFstabInfo(std::vector<FstabInfo>* fstab_info_list) const {
+  // Parse the fstab file and extract the image files. The location of the fstab actually depends
+  // on some flags e.g. "no_recovery", "recovery_as_boot". Here we just try all possibilities.
+  constexpr std::string_view kRecoveryFstabLocations[] = {
+    "RECOVERY/RAMDISK/system/etc/recovery.fstab",
+    "RECOVERY/RAMDISK/etc/recovery.fstab",
+    "BOOT/RAMDISK/system/etc/recovery.fstab",
+    "BOOT/RAMDISK/etc/recovery.fstab",
+  };
+  std::string fstab_content;
+  for (const auto& name : kRecoveryFstabLocations) {
+    if (std::string content; ReadEntryToString(name, &content)) {
+      fstab_content = std::move(content);
+      break;
+    }
+  }
+  if (fstab_content.empty()) {
+    LOG(ERROR) << "Failed to parse the recovery fstab file";
+    return false;
+  }
+
+  // Extract the images and convert them to raw.
+  if (!ParseFstab(fstab_content, fstab_info_list)) {
+    LOG(ERROR) << "Failed to mount the block devices for source build.";
+    return false;
+  }
+
+  return true;
+}
diff --git a/updater/update_simulator_main.cpp b/updater/update_simulator_main.cpp
new file mode 100644
index 0000000..6c6989b
--- /dev/null
+++ b/updater/update_simulator_main.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <string>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "edify/expr.h"
+#include "otautil/error_code.h"
+#include "otautil/paths.h"
+#include "updater/blockimg.h"
+#include "updater/build_info.h"
+#include "updater/dynamic_partitions.h"
+#include "updater/install.h"
+#include "updater/simulator_runtime.h"
+#include "updater/updater.h"
+
+using namespace std::string_literals;
+
+void Usage(std::string_view name) {
+  LOG(INFO) << "Usage: " << name << "[--oem_settings <oem_property_file>]"
+            << "[--skip_functions <skip_function_file>]"
+            << " --source <source_target_file>"
+            << " --ota_package <ota_package>";
+}
+
+Value* SimulatorPlaceHolderFn(const char* name, State* /* state */,
+                              const std::vector<std::unique_ptr<Expr>>& /* argv */) {
+  LOG(INFO) << "Skip function " << name << " in host simulation";
+  return StringValue("t");
+}
+
+int main(int argc, char** argv) {
+  // Write the logs to stdout.
+  android::base::InitLogging(argv, &android::base::StderrLogger);
+
+  std::string oem_settings;
+  std::string skip_function_file;
+  std::string source_target_file;
+  std::string package_name;
+  std::string work_dir;
+  bool keep_images = false;
+
+  constexpr struct option OPTIONS[] = {
+    { "keep_images", no_argument, nullptr, 0 },
+    { "oem_settings", required_argument, nullptr, 0 },
+    { "ota_package", required_argument, nullptr, 0 },
+    { "skip_functions", required_argument, nullptr, 0 },
+    { "source", required_argument, nullptr, 0 },
+    { "work_dir", required_argument, nullptr, 0 },
+    { nullptr, 0, nullptr, 0 },
+  };
+
+  int arg;
+  int option_index;
+  while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
+    if (arg != 0) {
+      LOG(ERROR) << "Invalid command argument";
+      Usage(argv[0]);
+      return EXIT_FAILURE;
+    }
+    auto option_name = OPTIONS[option_index].name;
+    // The same oem property file used during OTA generation. It's needed for file_getprop() to
+    // return the correct value for the source build.
+    if (option_name == "oem_settings"s) {
+      oem_settings = optarg;
+    } else if (option_name == "skip_functions"s) {
+      skip_function_file = optarg;
+    } else if (option_name == "source"s) {
+      source_target_file = optarg;
+    } else if (option_name == "ota_package"s) {
+      package_name = optarg;
+    } else if (option_name == "keep_images"s) {
+      keep_images = true;
+    } else if (option_name == "work_dir"s) {
+      work_dir = optarg;
+    } else {
+      Usage(argv[0]);
+      return EXIT_FAILURE;
+    }
+  }
+
+  if (source_target_file.empty() || package_name.empty()) {
+    Usage(argv[0]);
+    return EXIT_FAILURE;
+  }
+
+  // Configure edify's functions.
+  RegisterBuiltins();
+  RegisterInstallFunctions();
+  RegisterBlockImageFunctions();
+  RegisterDynamicPartitionsFunctions();
+
+  if (!skip_function_file.empty()) {
+    std::string content;
+    if (!android::base::ReadFileToString(skip_function_file, &content)) {
+      PLOG(ERROR) << "Failed to read " << skip_function_file;
+      return EXIT_FAILURE;
+    }
+
+    auto lines = android::base::Split(content, "\n");
+    for (const auto& line : lines) {
+      if (line.empty() || android::base::StartsWith(line, "#")) {
+        continue;
+      }
+      RegisterFunction(line, SimulatorPlaceHolderFn);
+    }
+  }
+
+  TemporaryFile temp_saved_source;
+  TemporaryFile temp_last_command;
+  TemporaryDir temp_stash_base;
+
+  Paths::Get().set_cache_temp_source(temp_saved_source.path);
+  Paths::Get().set_last_command_file(temp_last_command.path);
+  Paths::Get().set_stash_directory_base(temp_stash_base.path);
+
+  TemporaryFile cmd_pipe;
+  TemporaryDir source_temp_dir;
+  if (work_dir.empty()) {
+    work_dir = source_temp_dir.path;
+  }
+
+  BuildInfo source_build_info(work_dir, keep_images);
+  if (!source_build_info.ParseTargetFile(source_target_file, false)) {
+    LOG(ERROR) << "Failed to parse the target file " << source_target_file;
+    return EXIT_FAILURE;
+  }
+
+  if (!oem_settings.empty()) {
+    CHECK_EQ(0, access(oem_settings.c_str(), R_OK));
+    source_build_info.SetOemSettings(oem_settings);
+  }
+
+  Updater updater(std::make_unique<SimulatorRuntime>(&source_build_info));
+  if (!updater.Init(cmd_pipe.release(), package_name, false)) {
+    return EXIT_FAILURE;
+  }
+
+  if (!updater.RunUpdate()) {
+    return EXIT_FAILURE;
+  }
+
+  LOG(INFO) << "\nscript succeeded, result: " << updater.GetResult();
+
+  return 0;
+}
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 7b5a3f9..8f4a6ed 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -16,8 +16,6 @@
 
 #include "updater/updater.h"
 
-#include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -25,198 +23,162 @@
 
 #include <android-base/logging.h>
 #include <android-base/strings.h>
-#include <selinux/android.h>
-#include <selinux/label.h>
-#include <selinux/selinux.h>
-#include <ziparchive/zip_archive.h>
 
-#include "edify/expr.h"
-#include "otautil/dirutil.h"
-#include "otautil/error_code.h"
-#include "otautil/sysutil.h"
-#include "updater/blockimg.h"
-#include "updater/dynamic_partitions.h"
-#include "updater/install.h"
+#include "edify/updater_runtime_interface.h"
 
-// Generated by the makefile, this function defines the
-// RegisterDeviceExtensions() function, which calls all the
-// registration functions for device-specific extensions.
-#include "register.inc"
-
-// Where in the package we expect to find the edify script to execute.
-// (Note it's "updateR-script", not the older "update-script".)
-static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script";
-
-struct selabel_handle *sehandle;
-
-static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
-                          const char* /* tag */, const char* /* file */, unsigned int /* line */,
-                          const char* message) {
-  fprintf(stdout, "%s\n", message);
+Updater::~Updater() {
+  if (package_handle_) {
+    CloseArchive(package_handle_);
+  }
 }
 
-int main(int argc, char** argv) {
-  // Various things log information to stdout or stderr more or less
-  // at random (though we've tried to standardize on stdout).  The
-  // log file makes more sense if buffering is turned off so things
-  // appear in the right order.
-  setbuf(stdout, nullptr);
-  setbuf(stderr, nullptr);
-
-  // We don't have logcat yet under recovery. Update logs will always be written to stdout
-  // (which is redirected to recovery.log).
-  android::base::InitLogging(argv, &UpdaterLogger);
-
-  if (argc != 4 && argc != 5) {
-    LOG(ERROR) << "unexpected number of arguments: " << argc;
-    return 1;
-  }
-
-  char* version = argv[1];
-  if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
-    // We support version 1, 2, or 3.
-    LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
-    return 2;
-  }
-
+bool Updater::Init(int fd, const std::string_view package_filename, bool is_retry) {
   // Set up the pipe for sending commands back to the parent process.
-
-  int fd = atoi(argv[2]);
-  FILE* cmd_pipe = fdopen(fd, "wb");
-  setlinebuf(cmd_pipe);
-
-  // Extract the script from the package.
-
-  const char* package_filename = argv[3];
-  MemMapping map;
-  if (!map.MapFile(package_filename)) {
-    LOG(ERROR) << "failed to map package " << argv[3];
-    return 3;
-  }
-  ZipArchiveHandle za;
-  int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
-  if (open_err != 0) {
-    LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
-    CloseArchive(za);
-    return 3;
+  cmd_pipe_.reset(fdopen(fd, "wb"));
+  if (!cmd_pipe_) {
+    LOG(ERROR) << "Failed to open the command pipe";
+    return false;
   }
 
-  ZipString script_name(SCRIPT_NAME);
-  ZipEntry script_entry;
-  int find_err = FindEntry(za, script_name, &script_entry);
-  if (find_err != 0) {
-    LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
-               << ErrorCodeString(find_err);
-    CloseArchive(za);
-    return 4;
+  setlinebuf(cmd_pipe_.get());
+
+  if (!mapped_package_.MapFile(std::string(package_filename))) {
+    LOG(ERROR) << "failed to map package " << package_filename;
+    return false;
+  }
+  if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length,
+                                           std::string(package_filename).c_str(), &package_handle_);
+      open_err != 0) {
+    LOG(ERROR) << "failed to open package " << package_filename << ": "
+               << ErrorCodeString(open_err);
+    return false;
+  }
+  if (!ReadEntryToString(package_handle_, SCRIPT_NAME, &updater_script_)) {
+    return false;
   }
 
-  std::string script;
-  script.resize(script_entry.uncompressed_length);
-  int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
-                                    script_entry.uncompressed_length);
-  if (extract_err != 0) {
-    LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
-    CloseArchive(za);
-    return 5;
-  }
+  is_retry_ = is_retry;
 
-  // Configure edify's functions.
+  return true;
+}
 
-  RegisterBuiltins();
-  RegisterInstallFunctions();
-  RegisterBlockImageFunctions();
-  RegisterDynamicPartitionsFunctions();
-  RegisterDeviceExtensions();
+bool Updater::RunUpdate() {
+  CHECK(runtime_);
 
   // Parse the script.
-
   std::unique_ptr<Expr> root;
   int error_count = 0;
-  int error = ParseString(script, &root, &error_count);
+  int error = ParseString(updater_script_, &root, &error_count);
   if (error != 0 || error_count > 0) {
     LOG(ERROR) << error_count << " parse errors";
-    CloseArchive(za);
-    return 6;
-  }
-
-  sehandle = selinux_android_file_context_handle();
-  selinux_android_set_sehandle(sehandle);
-
-  if (!sehandle) {
-    fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
+    return false;
   }
 
   // Evaluate the parsed script.
+  State state(updater_script_, this);
+  state.is_retry = is_retry_;
 
-  UpdaterInfo updater_info;
-  updater_info.cmd_pipe = cmd_pipe;
-  updater_info.package_zip = za;
-  updater_info.version = atoi(version);
-  updater_info.package_zip_addr = map.addr;
-  updater_info.package_zip_len = map.length;
+  bool status = Evaluate(&state, root, &result_);
+  if (status) {
+    fprintf(cmd_pipe_.get(), "ui_print script succeeded: result was [%s]\n", result_.c_str());
+    // Even though the script doesn't abort, still log the cause code if result is empty.
+    if (result_.empty() && state.cause_code != kNoCause) {
+      fprintf(cmd_pipe_.get(), "log cause: %d\n", state.cause_code);
+    }
+    for (const auto& func : skipped_functions_) {
+      LOG(WARNING) << "Skipped executing function " << func;
+    }
+    return true;
+  }
 
-  State state(script, &updater_info);
+  ParseAndReportErrorCode(&state);
+  return false;
+}
 
-  if (argc == 5) {
-    if (strcmp(argv[4], "retry") == 0) {
-      state.is_retry = true;
-    } else {
-      printf("unexpected argument: %s", argv[4]);
+void Updater::WriteToCommandPipe(const std::string_view message, bool flush) const {
+  fprintf(cmd_pipe_.get(), "%s\n", std::string(message).c_str());
+  if (flush) {
+    fflush(cmd_pipe_.get());
+  }
+}
+
+void Updater::UiPrint(const std::string_view message) const {
+  // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
+  // so skip sending empty strings to ui.
+  std::vector<std::string> lines = android::base::Split(std::string(message), "\n");
+  for (const auto& line : lines) {
+    if (!line.empty()) {
+      fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
     }
   }
 
-  std::string result;
-  bool status = Evaluate(&state, root, &result);
+  // on the updater side, we need to dump the contents to stderr (which has
+  // been redirected to the log file). because the recovery will only print
+  // the contents to screen when processing pipe command ui_print.
+  LOG(INFO) << message;
+}
 
-  if (!status) {
-    if (state.errmsg.empty()) {
-      LOG(ERROR) << "script aborted (no error message)";
-      fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
-    } else {
-      LOG(ERROR) << "script aborted: " << state.errmsg;
-      const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n");
-      for (const std::string& line : lines) {
-        // Parse the error code in abort message.
-        // Example: "E30: This package is for bullhead devices."
-        if (!line.empty() && line[0] == 'E') {
-          if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
-            LOG(ERROR) << "Failed to parse error code: [" << line << "]";
-          }
-        }
-        fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
-      }
-    }
+std::string Updater::FindBlockDeviceName(const std::string_view name) const {
+  return runtime_->FindBlockDeviceName(name);
+}
 
-    // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
-    // a more specific code has been set in errmsg.
-    if (state.error_code == kNoError) {
-      state.error_code = kScriptExecutionFailure;
-    }
-    fprintf(cmd_pipe, "log error: %d\n", state.error_code);
-    // Cause code should provide additional information about the abort.
-    if (state.cause_code != kNoCause) {
-      fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
-      if (state.cause_code == kPatchApplicationFailure) {
-        LOG(INFO) << "Patch application failed, retry update.";
-        fprintf(cmd_pipe, "retry_update\n");
-      } else if (state.cause_code == kEioFailure) {
-        LOG(INFO) << "Update failed due to EIO, retry update.";
-        fprintf(cmd_pipe, "retry_update\n");
-      }
-    }
-
-    if (updater_info.package_zip) {
-      CloseArchive(updater_info.package_zip);
-    }
-    return 7;
+void Updater::ParseAndReportErrorCode(State* state) {
+  CHECK(state);
+  if (state->errmsg.empty()) {
+    LOG(ERROR) << "script aborted (no error message)";
+    fprintf(cmd_pipe_.get(), "ui_print script aborted (no error message)\n");
   } else {
-    fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str());
+    LOG(ERROR) << "script aborted: " << state->errmsg;
+    const std::vector<std::string> lines = android::base::Split(state->errmsg, "\n");
+    for (const std::string& line : lines) {
+      // Parse the error code in abort message.
+      // Example: "E30: This package is for bullhead devices."
+      if (!line.empty() && line[0] == 'E') {
+        if (sscanf(line.c_str(), "E%d: ", &state->error_code) != 1) {
+          LOG(ERROR) << "Failed to parse error code: [" << line << "]";
+        }
+      }
+      fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
+    }
   }
 
-  if (updater_info.package_zip) {
-    CloseArchive(updater_info.package_zip);
+  // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
+  // a more specific code has been set in errmsg.
+  if (state->error_code == kNoError) {
+    state->error_code = kScriptExecutionFailure;
+  }
+  fprintf(cmd_pipe_.get(), "log error: %d\n", state->error_code);
+  // Cause code should provide additional information about the abort.
+  if (state->cause_code != kNoCause) {
+    fprintf(cmd_pipe_.get(), "log cause: %d\n", state->cause_code);
+    if (state->cause_code == kPatchApplicationFailure) {
+      LOG(INFO) << "Patch application failed, retry update.";
+      fprintf(cmd_pipe_.get(), "retry_update\n");
+    } else if (state->cause_code == kEioFailure) {
+      LOG(INFO) << "Update failed due to EIO, retry update.";
+      fprintf(cmd_pipe_.get(), "retry_update\n");
+    }
+  }
+}
+
+bool Updater::ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name,
+                                std::string* content) {
+  ZipEntry entry;
+  int find_err = FindEntry(za, entry_name, &entry);
+  if (find_err != 0) {
+    LOG(ERROR) << "failed to find " << entry_name
+               << " in the package: " << ErrorCodeString(find_err);
+    return false;
   }
 
-  return 0;
+  content->resize(entry.uncompressed_length);
+  int extract_err = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&content->at(0)),
+                                    entry.uncompressed_length);
+  if (extract_err != 0) {
+    LOG(ERROR) << "failed to read " << entry_name
+               << " from package: " << ErrorCodeString(extract_err);
+    return false;
+  }
+
+  return true;
 }
diff --git a/updater/updater_main.cpp b/updater/updater_main.cpp
new file mode 100644
index 0000000..33d5b5b
--- /dev/null
+++ b/updater/updater_main.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <openssl/crypto.h>
+#include <selinux/android.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+
+#include "edify/expr.h"
+#include "updater/blockimg.h"
+#include "updater/dynamic_partitions.h"
+#include "updater/install.h"
+#include "updater/updater.h"
+#include "updater/updater_runtime.h"
+
+// Generated by the makefile, this function defines the
+// RegisterDeviceExtensions() function, which calls all the
+// registration functions for device-specific extensions.
+#include "register.inc"
+
+static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
+                          const char* /* tag */, const char* /* file */, unsigned int /* line */,
+                          const char* message) {
+  fprintf(stdout, "%s\n", message);
+}
+
+int main(int argc, char** argv) {
+  // Various things log information to stdout or stderr more or less
+  // at random (though we've tried to standardize on stdout).  The
+  // log file makes more sense if buffering is turned off so things
+  // appear in the right order.
+  setbuf(stdout, nullptr);
+  setbuf(stderr, nullptr);
+
+  // We don't have logcat yet under recovery. Update logs will always be written to stdout
+  // (which is redirected to recovery.log).
+  android::base::InitLogging(argv, &UpdaterLogger);
+
+  // Run the libcrypto KAT(known answer tests) based self tests.
+  if (BORINGSSL_self_test() != 1) {
+    LOG(ERROR) << "Failed to run the boringssl self tests";
+    return EXIT_FAILURE;
+  }
+
+  if (argc != 4 && argc != 5) {
+    LOG(ERROR) << "unexpected number of arguments: " << argc;
+    return EXIT_FAILURE;
+  }
+
+  char* version = argv[1];
+  if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
+    // We support version 1, 2, or 3.
+    LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
+    return EXIT_FAILURE;
+  }
+
+  int fd;
+  if (!android::base::ParseInt(argv[2], &fd)) {
+    LOG(ERROR) << "Failed to parse fd in " << argv[2];
+    return EXIT_FAILURE;
+  }
+
+  std::string package_name = argv[3];
+
+  bool is_retry = false;
+  if (argc == 5) {
+    if (strcmp(argv[4], "retry") == 0) {
+      is_retry = true;
+    } else {
+      LOG(ERROR) << "unexpected argument: " << argv[4];
+      return EXIT_FAILURE;
+    }
+  }
+
+  // Configure edify's functions.
+  RegisterBuiltins();
+  RegisterInstallFunctions();
+  RegisterBlockImageFunctions();
+  RegisterDynamicPartitionsFunctions();
+  RegisterDeviceExtensions();
+
+  auto sehandle = selinux_android_file_context_handle();
+  selinux_android_set_sehandle(sehandle);
+
+  Updater updater(std::make_unique<UpdaterRuntime>(sehandle));
+  if (!updater.Init(fd, package_name, is_retry)) {
+    return EXIT_FAILURE;
+  }
+
+  if (!updater.RunUpdate()) {
+    return EXIT_FAILURE;
+  }
+
+  return EXIT_SUCCESS;
+}
\ No newline at end of file
diff --git a/updater/updater_runtime.cpp b/updater/updater_runtime.cpp
new file mode 100644
index 0000000..c4222a5
--- /dev/null
+++ b/updater/updater_runtime.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/updater_runtime.h"
+
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/wipe.h>
+#include <selinux/label.h>
+#include <tune2fs.h>
+
+#include "mounts.h"
+#include "otautil/sysutil.h"
+
+std::string UpdaterRuntime::GetProperty(const std::string_view key,
+                                        const std::string_view default_value) const {
+  return android::base::GetProperty(std::string(key), std::string(default_value));
+}
+
+std::string UpdaterRuntime::FindBlockDeviceName(const std::string_view name) const {
+  return std::string(name);
+}
+
+int UpdaterRuntime::Mount(const std::string_view location, const std::string_view mount_point,
+                          const std::string_view fs_type, const std::string_view mount_options) {
+  std::string mount_point_string(mount_point);
+  char* secontext = nullptr;
+  if (sehandle_) {
+    selabel_lookup(sehandle_, &secontext, mount_point_string.c_str(), 0755);
+    setfscreatecon(secontext);
+  }
+
+  mkdir(mount_point_string.c_str(), 0755);
+
+  if (secontext) {
+    freecon(secontext);
+    setfscreatecon(nullptr);
+  }
+
+  return mount(std::string(location).c_str(), mount_point_string.c_str(),
+               std::string(fs_type).c_str(), MS_NOATIME | MS_NODEV | MS_NODIRATIME,
+               std::string(mount_options).c_str());
+}
+
+bool UpdaterRuntime::IsMounted(const std::string_view mount_point) const {
+  scan_mounted_volumes();
+  MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
+  return vol != nullptr;
+}
+
+std::pair<bool, int> UpdaterRuntime::Unmount(const std::string_view mount_point) {
+  scan_mounted_volumes();
+  MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
+  if (vol == nullptr) {
+    return { false, -1 };
+  }
+
+  int ret = unmount_mounted_volume(vol);
+  return { true, ret };
+}
+
+bool UpdaterRuntime::ReadFileToString(const std::string_view filename, std::string* content) const {
+  return android::base::ReadFileToString(std::string(filename), content);
+}
+
+bool UpdaterRuntime::WriteStringToFile(const std::string_view content,
+                                       const std::string_view filename) const {
+  return android::base::WriteStringToFile(std::string(content), std::string(filename));
+}
+
+int UpdaterRuntime::WipeBlockDevice(const std::string_view filename, size_t len) const {
+  android::base::unique_fd fd(open(std::string(filename).c_str(), O_WRONLY));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open " << filename;
+    return false;
+  }
+  // The wipe_block_device function in ext4_utils returns 0 on success and 1 for failure.
+  return wipe_block_device(fd, len);
+}
+
+int UpdaterRuntime::RunProgram(const std::vector<std::string>& args, bool is_vfork) const {
+  CHECK(!args.empty());
+  auto argv = StringVectorToNullTerminatedArray(args);
+  LOG(INFO) << "about to run program [" << args[0] << "] with " << argv.size() << " args";
+
+  pid_t child = is_vfork ? vfork() : fork();
+  if (child == 0) {
+    execv(argv[0], argv.data());
+    PLOG(ERROR) << "run_program: execv failed";
+    _exit(EXIT_FAILURE);
+  }
+
+  int status;
+  waitpid(child, &status, 0);
+  if (WIFEXITED(status)) {
+    if (WEXITSTATUS(status) != 0) {
+      LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
+    }
+  } else if (WIFSIGNALED(status)) {
+    LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
+  }
+
+  return status;
+}
+
+int UpdaterRuntime::Tune2Fs(const std::vector<std::string>& args) const {
+  auto tune2fs_args = StringVectorToNullTerminatedArray(args);
+  // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
+  return tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data());
+}
diff --git a/updater/updater_runtime_dynamic_partitions.cpp b/updater/updater_runtime_dynamic_partitions.cpp
new file mode 100644
index 0000000..be9250a
--- /dev/null
+++ b/updater/updater_runtime_dynamic_partitions.cpp
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "updater/updater_runtime.h"
+
+#include <algorithm>
+#include <chrono>
+#include <iterator>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
+#include <libdm/dm.h>
+#include <liblp/builder.h>
+
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::LpMetadata;
+using android::fs_mgr::MetadataBuilder;
+using android::fs_mgr::Partition;
+using android::fs_mgr::PartitionOpener;
+
+static constexpr std::chrono::milliseconds kMapTimeout{ 1000 };
+
+static std::string GetSuperDevice() {
+  return "/dev/block/by-name/" + fs_mgr_get_super_partition_name();
+}
+
+static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+  auto state = DeviceMapper::Instance().GetState(partition_name);
+  if (state == DmDeviceState::INVALID) {
+    return true;
+  }
+  if (state == DmDeviceState::ACTIVE) {
+    return DestroyLogicalPartition(partition_name);
+  }
+  LOG(ERROR) << "Unknown device mapper state: "
+             << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+  return false;
+}
+
+bool UpdaterRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name,
+                                                std::string* path) {
+  auto state = DeviceMapper::Instance().GetState(partition_name);
+  if (state == DmDeviceState::INVALID) {
+    CreateLogicalPartitionParams params = {
+      .block_device = GetSuperDevice(),
+      .metadata_slot = 0,
+      .partition_name = partition_name,
+      .force_writable = true,
+      .timeout_ms = kMapTimeout,
+    };
+    return CreateLogicalPartition(params, path);
+  }
+
+  if (state == DmDeviceState::ACTIVE) {
+    return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path);
+  }
+  LOG(ERROR) << "Unknown device mapper state: "
+             << static_cast<std::underlying_type_t<DmDeviceState>>(state);
+  return false;
+}
+
+bool UpdaterRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
+  return ::UnmapPartitionOnDeviceMapper(partition_name);
+}
+
+namespace {  // Ops
+
+struct OpParameters {
+  std::vector<std::string> tokens;
+  MetadataBuilder* builder;
+
+  bool ExpectArgSize(size_t size) const {
+    CHECK(!tokens.empty());
+    auto actual = tokens.size() - 1;
+    if (actual != size) {
+      LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual;
+      return false;
+    }
+    return true;
+  }
+  const std::string& op() const {
+    CHECK(!tokens.empty());
+    return tokens[0];
+  }
+  const std::string& arg(size_t pos) const {
+    CHECK_LE(pos + 1, tokens.size());
+    return tokens[pos + 1];
+  }
+  std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const {
+    auto str = arg(pos);
+    uint64_t ret;
+    if (!android::base::ParseUint(str, &ret)) {
+      LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str;
+      return std::nullopt;
+    }
+    return ret;
+  }
+};
+
+using OpFunction = std::function<bool(const OpParameters&)>;
+using OpMap = std::map<std::string, OpFunction>;
+
+bool PerformOpResize(const OpParameters& params) {
+  if (!params.ExpectArgSize(2)) return false;
+  const auto& partition_name = params.arg(0);
+  auto size = params.uint_arg(1, "size");
+  if (!size.has_value()) return false;
+
+  auto partition = params.builder->FindPartition(partition_name);
+  if (partition == nullptr) {
+    LOG(ERROR) << "Failed to find partition " << partition_name
+               << " in dynamic partition metadata.";
+    return false;
+  }
+  if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+    LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing.";
+    return false;
+  }
+  if (!params.builder->ResizePartition(partition, size.value())) {
+    LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << ".";
+    return false;
+  }
+  return true;
+}
+
+bool PerformOpRemove(const OpParameters& params) {
+  if (!params.ExpectArgSize(1)) return false;
+  const auto& partition_name = params.arg(0);
+
+  if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+    LOG(ERROR) << "Cannot unmap " << partition_name << " before removing.";
+    return false;
+  }
+  params.builder->RemovePartition(partition_name);
+  return true;
+}
+
+bool PerformOpAdd(const OpParameters& params) {
+  if (!params.ExpectArgSize(2)) return false;
+  const auto& partition_name = params.arg(0);
+  const auto& group_name = params.arg(1);
+
+  if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) ==
+      nullptr) {
+    LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << ".";
+    return false;
+  }
+  return true;
+}
+
+bool PerformOpMove(const OpParameters& params) {
+  if (!params.ExpectArgSize(2)) return false;
+  const auto& partition_name = params.arg(0);
+  const auto& new_group = params.arg(1);
+
+  auto partition = params.builder->FindPartition(partition_name);
+  if (partition == nullptr) {
+    LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group
+               << " because it is not found.";
+    return false;
+  }
+
+  auto old_group = partition->group_name();
+  if (old_group != new_group) {
+    if (!params.builder->ChangePartitionGroup(partition, new_group)) {
+      LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group
+                 << " to group " << new_group << ".";
+      return false;
+    }
+  }
+  return true;
+}
+
+bool PerformOpAddGroup(const OpParameters& params) {
+  if (!params.ExpectArgSize(2)) return false;
+  const auto& group_name = params.arg(0);
+  auto maximum_size = params.uint_arg(1, "maximum_size");
+  if (!maximum_size.has_value()) return false;
+
+  auto group = params.builder->FindGroup(group_name);
+  if (group != nullptr) {
+    LOG(ERROR) << "Cannot add group " << group_name << " because it already exists.";
+    return false;
+  }
+
+  if (maximum_size.value() == 0) {
+    LOG(WARNING) << "Adding group " << group_name << " with no size limits.";
+  }
+
+  if (!params.builder->AddGroup(group_name, maximum_size.value())) {
+    LOG(ERROR) << "Failed to add group " << group_name << " with maximum size "
+               << maximum_size.value() << ".";
+    return false;
+  }
+  return true;
+}
+
+bool PerformOpResizeGroup(const OpParameters& params) {
+  if (!params.ExpectArgSize(2)) return false;
+  const auto& group_name = params.arg(0);
+  auto new_size = params.uint_arg(1, "maximum_size");
+  if (!new_size.has_value()) return false;
+
+  auto group = params.builder->FindGroup(group_name);
+  if (group == nullptr) {
+    LOG(ERROR) << "Cannot resize group " << group_name << " because it is not found.";
+    return false;
+  }
+
+  auto old_size = group->maximum_size();
+  if (old_size != new_size.value()) {
+    if (!params.builder->ChangeGroupSize(group_name, new_size.value())) {
+      LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to "
+                 << new_size.value() << ".";
+      return false;
+    }
+  }
+  return true;
+}
+
+std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder,
+                                                   const std::string& group_name) {
+  auto partitions = builder->ListPartitionsInGroup(group_name);
+  std::vector<std::string> partition_names;
+  std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names),
+                 [](Partition* partition) { return partition->name(); });
+  return partition_names;
+}
+
+bool PerformOpRemoveGroup(const OpParameters& params) {
+  if (!params.ExpectArgSize(1)) return false;
+  const auto& group_name = params.arg(0);
+
+  auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
+  if (!partition_names.empty()) {
+    LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions ["
+               << android::base::Join(partition_names, ", ") << "]";
+    return false;
+  }
+  params.builder->RemoveGroupAndPartitions(group_name);
+  return true;
+}
+
+bool PerformOpRemoveAllGroups(const OpParameters& params) {
+  if (!params.ExpectArgSize(0)) return false;
+
+  auto group_names = params.builder->ListGroups();
+  for (const auto& group_name : group_names) {
+    auto partition_names = ListPartitionNamesInGroup(params.builder, group_name);
+    for (const auto& partition_name : partition_names) {
+      if (!UnmapPartitionOnDeviceMapper(partition_name)) {
+        LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name
+                   << ".";
+        return false;
+      }
+    }
+    params.builder->RemoveGroupAndPartitions(group_name);
+  }
+  return true;
+}
+
+}  // namespace
+
+bool UpdaterRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) {
+  auto super_device = GetSuperDevice();
+  auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0);
+  if (builder == nullptr) {
+    LOG(ERROR) << "Failed to load dynamic partition metadata.";
+    return false;
+  }
+
+  static const OpMap op_map{
+    // clang-format off
+    {"resize",                PerformOpResize},
+    {"remove",                PerformOpRemove},
+    {"add",                   PerformOpAdd},
+    {"move",                  PerformOpMove},
+    {"add_group",             PerformOpAddGroup},
+    {"resize_group",          PerformOpResizeGroup},
+    {"remove_group",          PerformOpRemoveGroup},
+    {"remove_all_groups",     PerformOpRemoveAllGroups},
+    // clang-format on
+  };
+
+  std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n");
+  for (const auto& line : lines) {
+    auto comment_idx = line.find('#');
+    auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx);
+    op_and_args = android::base::Trim(op_and_args);
+    if (op_and_args.empty()) continue;
+
+    auto tokens = android::base::Split(op_and_args, " ");
+    const auto& op = tokens[0];
+    auto it = op_map.find(op);
+    if (it == op_map.end()) {
+      LOG(ERROR) << "Unknown operation in op_list: " << op;
+      return false;
+    }
+    OpParameters params;
+    params.tokens = tokens;
+    params.builder = builder.get();
+    if (!it->second(params)) {
+      return false;
+    }
+  }
+
+  auto metadata = builder->Export();
+  if (metadata == nullptr) {
+    LOG(ERROR) << "Failed to export metadata.";
+    return false;
+  }
+
+  if (!UpdatePartitionTable(super_device, *metadata, 0)) {
+    LOG(ERROR) << "Failed to write metadata.";
+    return false;
+  }
+
+  return true;
+}
diff --git a/updater_sample/Android.bp b/updater_sample/Android.bp
index 845e07b..a014248 100644
--- a/updater_sample/Android.bp
+++ b/updater_sample/Android.bp
@@ -15,7 +15,6 @@
 android_app {
     name: "SystemUpdaterSample",
     sdk_version: "system_current",
-    privileged: true,
 
     srcs: ["src/**/*.java"],
 
diff --git a/updater_sample/AndroidManifest.xml b/updater_sample/AndroidManifest.xml
index 0a25116..981cd8e 100644
--- a/updater_sample/AndroidManifest.xml
+++ b/updater_sample/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27" />
 
     <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <application
         android:icon="@mipmap/ic_launcher"
diff --git a/updater_sample/OWNERS b/updater_sample/OWNERS
deleted file mode 100644
index 5c1c370..0000000
--- a/updater_sample/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-zhaojiac@google.com
-zhomart@google.com
diff --git a/updater_sample/README.md b/updater_sample/README.md
index 2070ebc..2e12a2f 100644
--- a/updater_sample/README.md
+++ b/updater_sample/README.md
@@ -191,6 +191,8 @@
     </privapp-permissions>
    ```
    to `frameworks/base/data/etc/privapp-permissions-platform.xml`
+4. Add `privileged: true` to SystemUpdaterSample
+   [building rule](https://android.googlesource.com/platform/bootable/recovery/+/refs/heads/master/updater_sample/Android.bp).
 5. Build sample app `make -j SystemUpdaterSample`.
 6. Build Android `make -j`
 7. [Flash the device](https://source.android.com/setup/build/running)
@@ -229,9 +231,9 @@
 
 1. Build `make -j SystemUpdaterSample` and `make -j SystemUpdaterSampleTests`.
 2. Install app
-   `adb install $OUT/system/priv-app/SystemUpdaterSample/SystemUpdaterSample.apk`
+   `adb install $OUT/system/app/SystemUpdaterSample/SystemUpdaterSample.apk`
 3. Install tests
-   `adb install $OUT/testcases/SystemUpdaterSampleTests/SystemUpdaterSampleTests.apk`
+   `adb install $OUT/testcases/SystemUpdaterSampleTests/arm64/SystemUpdaterSampleTests.apk`
 4. Run tests
    `adb shell am instrument -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner`
 5. Run a test file