Skip QD1A.190821.011 in stage-aosp-master

Bug: 141248619
Change-Id: I3404ae5bfb5c54df05495a62cba58cd4e818b2ff
diff --git a/Android.bp b/Android.bp
index f920782..45aafb0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,7 +58,6 @@
     ],
 
     shared_libs: [
-        "android.hardware.health@2.0",
         "libbase",
         "libbootloader_message",
         "libcrypto",
@@ -72,11 +71,8 @@
         "libinstall",
         "librecovery_fastboot",
         "libminui",
+        "librecovery_utils",
         "libotautil",
-
-        // external dependencies
-        "libhealthhalutils",
-        "libfstab",
     ],
 }
 
@@ -105,6 +101,7 @@
     defaults: [
         "libinstall_defaults",
         "librecovery_defaults",
+        "librecovery_utils_defaults",
     ],
 
     srcs: [
@@ -149,8 +146,7 @@
     ],
 
     static_libs: [
-        "libotautil",
-        "libfstab",
+        "librecovery_utils",
     ],
 
     init_rc: [
@@ -176,8 +172,7 @@
     ],
 
     static_libs: [
-        "libotautil",
-        "libfstab",
+        "librecovery_utils",
     ],
 
     init_rc: [
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..fe1c33d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,4 @@
 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..6ad4a61 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);
     }
   }
 
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
index 7720ead..b2e68df 100644
--- a/boot_control/Android.bp
+++ b/boot_control/Android.bp
@@ -14,13 +14,12 @@
 // limitations under the License.
 //
 
-cc_library_shared {
-    name: "bootctrl.default",
+cc_defaults {
+    name: "libboot_control_defaults",
+    vendor: true,
     recovery_available: true,
     relative_install_path: "hw",
 
-    srcs: ["boot_control.cpp"],
-
     cflags: [
         "-D_FILE_OFFSET_BITS=64",
         "-Werror",
@@ -29,9 +28,34 @@
     ],
 
     shared_libs: [
+        "android.hardware.boot@1.1",
         "libbase",
-        "libbootloader_message",
-        "libfs_mgr",
         "liblog",
     ],
+    static_libs: [
+        "libbootloader_message_vendor",
+        "libfstab",
+    ],
+}
+
+cc_library_static {
+    name: "libboot_control",
+    defaults: ["libboot_control_defaults"],
+    export_include_dirs: ["include"],
+
+    srcs: ["libboot_control.cpp"],
+}
+
+cc_library_shared {
+    name: "bootctrl.default",
+    defaults: ["libboot_control_defaults"],
+
+    srcs: ["legacy_boot_control.cpp"],
+
+    static_libs: [
+        "libboot_control",
+    ],
+    shared_libs: [
+        "libhardware",
+    ],
 }
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/boot_control/include/libboot_control/libboot_control.h b/boot_control/include/libboot_control/libboot_control.h
new file mode 100644
index 0000000..34a9aff
--- /dev/null
+++ b/boot_control/include/libboot_control/libboot_control.h
@@ -0,0 +1,66 @@
+//
+// 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 <android/hardware/boot/1.1/IBootControl.h>
+
+namespace android {
+namespace bootable {
+
+// Helper library to implement the IBootControl HAL using the misc partition.
+class BootControl {
+  using MergeStatus = ::android::hardware::boot::V1_1::MergeStatus;
+
+ public:
+  bool Init();
+  unsigned int GetNumberSlots();
+  unsigned int GetCurrentSlot();
+  bool MarkBootSuccessful();
+  bool SetActiveBootSlot(unsigned int slot);
+  bool SetSlotAsUnbootable(unsigned int slot);
+  bool SetSlotBootable(unsigned int slot);
+  bool IsSlotBootable(unsigned int slot);
+  const char* GetSuffix(unsigned int slot);
+  bool IsSlotMarkedSuccessful(unsigned int slot);
+  bool SetSnapshotMergeStatus(MergeStatus status);
+  MergeStatus GetSnapshotMergeStatus();
+
+  bool IsValidSlot(unsigned int slot);
+
+  const std::string& misc_device() const {
+    return misc_device_;
+  }
+
+ private:
+  // Whether this object was initialized with data from the bootloader message
+  // that doesn't change until next reboot.
+  bool initialized_ = false;
+
+  // The path to the misc_device as reported in the fstab.
+  std::string misc_device_;
+
+  // The number of slots present on the device.
+  unsigned int num_slots_ = 0;
+
+  // The slot where we are running from.
+  unsigned int current_slot_ = 0;
+};
+
+}  // namespace bootable
+}  // namespace android
diff --git a/boot_control/legacy_boot_control.cpp b/boot_control/legacy_boot_control.cpp
new file mode 100644
index 0000000..73d3a58
--- /dev/null
+++ b/boot_control/legacy_boot_control.cpp
@@ -0,0 +1,115 @@
+/*
+ * 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 <string>
+
+#include <hardware/boot_control.h>
+#include <hardware/hardware.h>
+
+#include <libboot_control/libboot_control.h>
+
+using android::bootable::BootControl;
+
+struct boot_control_private_t {
+  // The base struct needs to be first in the list.
+  boot_control_module_t base;
+
+  BootControl impl;
+};
+
+namespace {
+
+void BootControl_init(boot_control_module_t* module) {
+  auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+  impl.Init();
+}
+
+unsigned int BootControl_getNumberSlots(boot_control_module_t* module) {
+  auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+  return impl.GetNumberSlots();
+}
+
+unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) {
+  auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+  return impl.GetCurrentSlot();
+}
+
+int BootControl_markBootSuccessful(boot_control_module_t* module) {
+  auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+  return impl.MarkBootSuccessful() ? 0 : -1;
+}
+
+int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) {
+  auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+  return impl.SetActiveBootSlot(slot) ? 0 : -1;
+}
+
+int BootControl_setSlotAsUnbootable(struct boot_control_module* module, unsigned int slot) {
+  auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+  return impl.SetSlotAsUnbootable(slot) ? 0 : -1;
+}
+
+int BootControl_isSlotBootable(struct boot_control_module* module, unsigned int slot) {
+  auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+  return impl.IsSlotBootable(slot) ? 0 : -1;
+}
+
+int BootControl_isSlotMarkedSuccessful(struct boot_control_module* module, unsigned int slot) {
+  auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+  return impl.IsSlotMarkedSuccessful(slot) ? 0 : -1;
+}
+
+const char* BootControl_getSuffix(boot_control_module_t* module, unsigned int slot) {
+  auto& impl = reinterpret_cast<boot_control_private_t*>(module)->impl;
+  return impl.GetSuffix(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,
+      },
+};
diff --git a/boot_control/libboot_control.cpp b/boot_control/libboot_control.cpp
new file mode 100644
index 0000000..e3bff9f
--- /dev/null
+++ b/boot_control/libboot_control.cpp
@@ -0,0 +1,359 @@
+/*
+ * 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 <libboot_control/libboot_control.h>
+
+#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 <bootloader_message/bootloader_message.h>
+
+namespace android {
+namespace bootable {
+
+using ::android::hardware::boot::V1_1::MergeStatus;
+
+// 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 std::string& misc_device, bootloader_control* buffer) {
+  android::base::unique_fd fd(open(misc_device.c_str(), 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 std::string& misc_device, bootloader_control* buffer) {
+  buffer->crc32_le = BootloaderControlLECRC(buffer);
+  android::base::unique_fd fd(open(misc_device.c_str(), 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(BootControl* control, bootloader_control* boot_ctrl) {
+  memset(boot_ctrl, 0, sizeof(*boot_ctrl));
+
+  unsigned int current_slot = control->GetCurrentSlot();
+  if (current_slot < kMaxNumSlots) {
+    strlcpy(boot_ctrl->slot_suffix, kSlotSuffixes[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 = control->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 (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::Init() {
+  if (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", "");
+  if (suffix_prop.empty()) {
+    LOG(ERROR) << "Slot suffix property is not set";
+    return false;
+  }
+  current_slot_ = SlotSuffixToIndex(suffix_prop.c_str());
+
+  std::string err;
+  std::string device = get_bootloader_message_blk_device(&err);
+  if (device.empty()) {
+    LOG(ERROR) << "Could not find bootloader message block device: " << err;
+    return false;
+  }
+
+  bootloader_control boot_ctrl;
+  if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) {
+    LOG(ERROR) << "Failed to load bootloader control block";
+    return false;
+  }
+
+  // Note that since there isn't a module unload function this memory is leaked.
+  misc_device_ = strdup(device.c_str());
+  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(this, &boot_ctrl);
+    UpdateAndSaveBootloaderControl(device.c_str(), &boot_ctrl);
+  }
+
+  num_slots_ = boot_ctrl.nb_slot;
+  return true;
+}
+
+unsigned int BootControl::GetNumberSlots() {
+  return num_slots_;
+}
+
+unsigned int BootControl::GetCurrentSlot() {
+  return current_slot_;
+}
+
+bool BootControl::MarkBootSuccessful() {
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
+
+  bootctrl.slot_info[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[current_slot_].tries_remaining = 1;
+  return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl);
+}
+
+bool BootControl::SetActiveBootSlot(unsigned int slot) {
+  if (slot >= kMaxNumSlots || slot >= num_slots_) {
+    // Invalid slot number.
+    return false;
+  }
+
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
+
+  // 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 < 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 != current_slot_) bootctrl.slot_info[slot].verity_corrupted = 0;
+
+  return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl);
+}
+
+bool BootControl::SetSlotAsUnbootable(unsigned int slot) {
+  if (slot >= kMaxNumSlots || slot >= num_slots_) {
+    // Invalid slot number.
+    return false;
+  }
+
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
+
+  // 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;
+  return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl);
+}
+
+bool BootControl::IsSlotBootable(unsigned int slot) {
+  if (slot >= kMaxNumSlots || slot >= num_slots_) {
+    // Invalid slot number.
+    return false;
+  }
+
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
+
+  return bootctrl.slot_info[slot].tries_remaining != 0;
+}
+
+bool BootControl::IsSlotMarkedSuccessful(unsigned int slot) {
+  if (slot >= kMaxNumSlots || slot >= num_slots_) {
+    // Invalid slot number.
+    return false;
+  }
+
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
+
+  return bootctrl.slot_info[slot].successful_boot && bootctrl.slot_info[slot].tries_remaining;
+}
+
+bool BootControl::IsValidSlot(unsigned int slot) {
+  return slot < kMaxNumSlots && slot < num_slots_;
+}
+
+bool BootControl::SetSnapshotMergeStatus(MergeStatus status) {
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(misc_device_, &bootctrl)) return false;
+
+  bootctrl.merge_status = (unsigned int)status;
+  return UpdateAndSaveBootloaderControl(misc_device_, &bootctrl);
+}
+
+MergeStatus BootControl::GetSnapshotMergeStatus() {
+  bootloader_control bootctrl;
+  if (!LoadBootloaderControl(misc_device_, &bootctrl)) return MergeStatus::UNKNOWN;
+
+  return (MergeStatus)bootctrl.merge_status;
+}
+
+const char* BootControl::GetSuffix(unsigned int slot) {
+  if (slot >= kMaxNumSlots || slot >= num_slots_) {
+    return nullptr;
+  }
+  return kSlotSuffixes[slot];
+}
+
+}  // namespace bootable
+}  // namespace android
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..b15a9b9 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,10 +31,14 @@
 #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) {
@@ -41,8 +46,8 @@
 }
 
 static std::string get_misc_blk_device(std::string* err) {
-  if (!g_misc_device_for_test.empty()) {
-    return g_misc_device_for_test;
+  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)) {
@@ -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;
 }
 
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index 95dd8f4..b787830 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -163,8 +163,10 @@
     uint8_t nb_slot : 3;
     // Number of times left attempting to boot recovery.
     uint8_t recovery_tries_remaining : 3;
+    // Status of any pending snapshot merge of dynamic partitions.
+    uint8_t merge_status : 3;
     // Ensure 4-bytes alignment for slot_info field.
-    uint8_t reserved0[2];
+    uint8_t reserved0[1];
     // Per-slot information.  Up to 4 slots.
     struct slot_metadata slot_info[4];
     // Reserved for further use.
@@ -208,6 +210,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);
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..73048d2 100644
--- a/edify/Android.bp
+++ b/edify/Android.bp
@@ -16,6 +16,7 @@
     name: "libedify",
 
     host_supported: true,
+    vendor_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..cd9c701 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
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/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
index e74f8ba..e0b2d96 100644
--- a/fsck_unshare_blocks.cpp
+++ b/fsck_unshare_blocks.cpp
@@ -34,9 +34,9 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/unique_fd.h>
-#include <fstab/fstab.h>
+#include <fs_mgr/roots.h>
 
-#include "otautil/roots.h"
+#include "recovery_utils/roots.h"
 
 static constexpr const char* SYSTEM_E2FSCK_BIN = "/system/bin/e2fsck_static";
 static constexpr const char* TMP_E2FSCK_BIN = "/tmp/e2fsck.bin";
@@ -120,7 +120,7 @@
   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();
+  auto system_root = android::fs_mgr::GetSystemRoot();
   bool mounted = ensure_path_mounted_at(system_root, "/mnt/system") != -1;
   partitions.push_back(system_root);
 
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..d4606e9 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",
 
         // external dependencies
         "libvintf_recovery",
         "libvintf",
-        "libfstab",
     ],
 }
 
@@ -62,11 +57,16 @@
     srcs: [
         "adb_install.cpp",
         "asn1_decoder.cpp",
-        "fuse_sdcard_install.cpp",
+        "fuse_install.cpp",
         "install.cpp",
         "package.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 c0a8f1f..b4b3a91 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,14 +59,11 @@
 // 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);
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/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 e2d4700..9166f9c 100644
--- a/install/install.cpp
+++ b/install/install.cpp
@@ -51,16 +51,17 @@
 #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
@@ -73,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;
   }
@@ -139,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", "");
@@ -154,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.
@@ -172,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
@@ -218,7 +218,7 @@
     }
     if (!serial_number_match) {
       LOG(ERROR) << "Package is for serial " << pkg_serial_no;
-      return INSTALL_ERROR;
+      return false;
     }
   }
 
@@ -226,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);
@@ -248,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 = {
@@ -266,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();
@@ -288,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:
@@ -311,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) {
@@ -325,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);
@@ -385,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();
@@ -490,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;
@@ -503,16 +505,15 @@
   return INSTALL_SUCCESS;
 }
 
-// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the
+// Verifies 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) {
+  if (FindEntry(package_zip, COMPATIBILITY_ZIP_ENTRY, &compatibility_entry) != 0) {
     LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry";
     return true;
   }
@@ -536,7 +537,7 @@
 
   // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents.
   void* cookie;
-  ret = StartIteration(zip_handle, &cookie, nullptr, nullptr);
+  ret = StartIteration(zip_handle, &cookie);
   if (ret != 0) {
     LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret);
     CloseArchive(zip_handle);
@@ -546,13 +547,13 @@
 
   std::vector<std::string> compatibility_info;
   ZipEntry info_entry;
-  ZipString info_name;
+  std::string_view 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);
+      LOG(ERROR) << "Failed to read " << info_name << ": " << ErrorCodeString(ret);
       CloseArchive(zip_handle);
       return false;
     }
@@ -571,36 +572,16 @@
   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;
   }
@@ -624,32 +605,37 @@
     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;
   }
 
@@ -677,7 +663,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/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..82660be 100644
--- a/install/wipe_data.cpp
+++ b/install/wipe_data.cpp
@@ -28,9 +28,9 @@
 #include <android-base/stringprintf.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";
@@ -120,4 +120,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..b7075e6 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",
     ],
 }
 
@@ -86,6 +99,7 @@
 
     defaults: [
         "minadbd_defaults",
+        "librecovery_utils_defaults",
     ],
 
     srcs: [
@@ -96,12 +110,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..f331ed6 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,78 @@
   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.
+  size_t event_len;
+  int ret = ioctl(fd, FIONREAD, &event_len);
+  if (ret != 0) return -1;
+
+  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 +145,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 +166,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 +190,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 +227,7 @@
   }
   g_ev_misc_count = 0;
   g_ev_dev_count = 0;
+  g_saved_input_cb = nullptr;
   g_epoll_fd.reset();
 }
 
@@ -170,13 +250,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/resources.cpp b/minui/resources.cpp
index 069a495..00d36d5 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -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/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..326db86 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.
+bool 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..6cd46c6 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,14 +219,21 @@
   ranges_.clear();
 }
 
-bool reboot(const std::string& command) {
-  std::string cmd = command;
-  if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
+bool 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";
   }
   return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
 }
 
+bool Shutdown(std::string_view target) {
+  std::string cmd = "shutdown," + std::string(target);
+  return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
+}
+
 std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args) {
   std::vector<char*> result(args.size());
   std::transform(args.cbegin(), args.cend(), result.begin(),
diff --git a/recovery-persist.cpp b/recovery-persist.cpp
index 294017a..6dbf862 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -43,8 +43,8 @@
 #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";
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 5fc673e..f59a940 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,26 +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 "bootloader_message/bootloader_message.h"
 #include "fsck_unshare_blocks.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/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";
@@ -70,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.
@@ -85,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
@@ -105,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
  *
@@ -115,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;
@@ -144,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;
@@ -153,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;
     }
@@ -167,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);
@@ -177,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));
 
@@ -199,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.
@@ -211,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 {
@@ -221,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);
@@ -416,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));
 
@@ -426,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);
 
@@ -473,14 +310,19 @@
   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) {
+// 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();
   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;
 
@@ -488,6 +330,12 @@
       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);
 
@@ -506,6 +354,8 @@
             : 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;
 
@@ -556,7 +406,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);
@@ -567,7 +417,7 @@
         if (status != INSTALL_SUCCESS) {
           ui->SetBackground(RecoveryUI::ERROR);
           ui->Print("Installation aborted.\n");
-          copy_logs(save_current_log, has_cache, sehandle);
+          copy_logs(save_current_log);
         } else if (!ui->IsTextVisible()) {
           return Device::NO_ACTION;  // reboot if logs aren't visible
         }
@@ -579,7 +429,7 @@
         break;
 
       case Device::RUN_GRAPHICS_TEST:
-        run_graphics_test();
+        run_graphics_test(ui);
         break;
 
       case Device::RUN_LOCALE_TEST: {
@@ -588,8 +438,7 @@
         break;
       }
       case Device::MOUNT_SYSTEM:
-        // the system partition is mounted at /mnt/system
-        if (ensure_path_mounted_at(get_system_root(), "/mnt/system") != -1) {
+        if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) {
           ui->Print("Mounted /system.\n");
         }
         break;
@@ -604,74 +453,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,6 +518,7 @@
   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 },
@@ -746,6 +539,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;
@@ -781,12 +575,12 @@
         std::string option = OPTIONS[option_index].name;
         if (option == "fsck_unshare_blocks") {
           fsck_unshare_blocks = true;
-        } else if (option == "locale" || option == "fastboot") {
+        } else 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") {
@@ -820,15 +614,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);
   }
 
@@ -849,9 +646,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;
@@ -861,12 +656,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()) {
@@ -881,7 +674,29 @@
         set_retry_bootloader_message(retry_count + 1, args);
       }
 
-      status = install_package(update_package, should_wipe_cache, true, retry_count, ui);
+      if (update_package[0] == '@') {
+        ensure_path_mounted(update_package + 1);
+      } else {
+        ensure_path_mounted(update_package);
+      }
+
+      if (install_with_fuse) {
+        LOG(INFO) << "Installing package " << update_package << " with fuse";
+        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 = 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";
+        status = InstallWithFuseFromPath(update_package, ui);
+      }
       if (status != INSTALL_SUCCESS) {
         ui->Print("Installation aborted.\n");
 
@@ -889,14 +704,14 @@
         // 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")) {
+          // Reboot back into recovery to retry the update.
+          if (!Reboot("recovery")) {
             ui->Print("Reboot failed\n");
           } else {
             while (true) {
@@ -907,14 +722,15 @@
         // 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;
     }
@@ -934,7 +750,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) {
@@ -964,7 +780,7 @@
     // 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
@@ -989,7 +805,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;
       }
@@ -997,7 +813,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..89253dc 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -41,34 +41,33 @@
 #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;
-
-RecoveryUI* ui = nullptr;
-struct selabel_handle* sehandle;
+static bool IsRoDebuggable() {
+  return android::base::GetBoolProperty("ro.debuggable", false);
+}
 
 static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity,
                      const char* /* tag */, const char* /* file */, unsigned int /* line */,
@@ -81,11 +80,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 +95,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 +133,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 +150,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 +333,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 +349,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 +369,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 +380,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 +427,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 +440,7 @@
     device->RemoveMenuItemForAction(Device::ENTER_FASTBOOT);
   }
 
-  if (!is_ro_debuggable()) {
+  if (!IsRoDebuggable()) {
     device->RemoveMenuItemForAction(Device::ENTER_RESCUE);
   }
 
@@ -435,7 +450,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 +463,7 @@
   listener_thread.detach();
 
   while (true) {
-    std::string usb_config = fastboot ? "fastboot" : is_ro_debuggable() ? "adb" : "none";
+    std::string usb_config = fastboot ? "fastboot" : IsRoDebuggable() ? "adb" : "none";
     std::string usb_state = android::base::GetProperty("sys.usb.state", "none");
     if (usb_config != usb_state) {
       if (!SetUsbConfig("none")) {
@@ -472,27 +487,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 +522,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 +541,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..087fc0e 100644
--- a/recovery_ui/screen_ui.cpp
+++ b/recovery_ui/screen_ui.cpp
@@ -673,6 +673,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 +698,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 +821,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 +1267,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..6f5cbbc 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,7 +374,7 @@
 
       case RecoveryUI::REBOOT:
         if (reboot_enabled) {
-          reboot("reboot,");
+          Reboot("userrequested,recovery,ui");
           while (true) {
             pause();
           }
@@ -393,7 +392,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 +418,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 +428,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 +436,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 +517,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 +547,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 +584,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 94%
rename from otautil/roots.cpp
rename to recovery_utils/roots.cpp
index 815d644..fe3a07a 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>
@@ -33,16 +30,13 @@
 #include <vector>
 
 #include <android-base/logging.h>
-#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
 #include <cryptfs.h>
 #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 +45,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 +54,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) {
@@ -276,10 +276,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 09ef716..5b881e3 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -50,13 +50,11 @@
     },
 }
 
-// libapplypatch, libapplypatch_modes, libimgdiff, libimgpatch
+// libapplypatch, libapplypatch_modes
 libapplypatch_static_libs = [
     "libapplypatch_modes",
     "libapplypatch",
     "libedify",
-    "libimgdiff",
-    "libimgpatch",
     "libotautil",
     "libbsdiff",
     "libbspatch",
@@ -66,7 +64,6 @@
     "libbase",
     "libbrotli",
     "libbz",
-    "libcrypto",
     "libz",
     "libziparchive",
 ]
@@ -79,6 +76,8 @@
     "libinstall",
     "librecovery_ui",
     "libminui",
+    "libfusesideload",
+    "libbootloader_message",
     "libotautil",
 
     "libhealthhalutils",
@@ -87,14 +86,10 @@
 
     "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",
@@ -104,9 +99,12 @@
 cc_test {
     name: "recovery_unit_test",
     isolated: true,
+    require_root: true,
 
     defaults: [
         "recovery_test_defaults",
+        "libupdater_defaults",
+        "libupdater_device_defaults",
     ],
 
     test_suites: ["device-tests"],
@@ -115,16 +113,24 @@
         "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/*",
+        ":res-testdata",
+    ],
 }
 
 cc_test {
@@ -142,8 +148,8 @@
     ],
 }
 
-cc_test {
-    name: "recovery_component_test",
+cc_test_host {
+    name: "recovery_host_test",
     isolated: true,
 
     defaults: [
@@ -151,57 +157,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/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 100%
rename from tests/component/bootloader_message_test.cpp
rename to tests/unit/bootloader_message_test.cpp
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 90%
rename from tests/component/install_test.cpp
rename to tests/unit/install_test.cpp
index 3851329..4ec4099 100644
--- a/tests/component/install_test.cpp
+++ b/tests/unit/install_test.cpp
@@ -33,6 +33,7 @@
 #include <ziparchive/zip_writer.h>
 
 #include "install/install.h"
+#include "install/wipe_device.h"
 #include "otautil/paths.h"
 #include "private/setup_commands.h"
 
@@ -204,7 +205,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
@@ -216,7 +217,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
@@ -243,7 +244,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);
 }
 
@@ -270,19 +271,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]);
@@ -290,7 +290,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);
 }
@@ -325,7 +325,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);
 }
 
@@ -358,8 +358,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(
       {
@@ -387,7 +387,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(
@@ -397,9 +397,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) {
@@ -409,7 +409,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(
@@ -418,7 +418,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) {
@@ -432,7 +432,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(
@@ -442,7 +442,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);
@@ -453,7 +453,7 @@
           "serialno=" + serialno,
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::BRICK, 0);
+  TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
 }
 
 TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) {
@@ -477,7 +477,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());
@@ -488,7 +488,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) {
@@ -506,7 +506,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>{
@@ -516,7 +516,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) {
@@ -534,7 +534,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>{
@@ -544,7 +544,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) {
@@ -558,7 +558,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(
@@ -568,7 +568,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(
@@ -579,7 +579,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);
@@ -593,5 +593,5 @@
           "ota-downgrade=yes",
       },
       "\n");
-  test_check_package_metadata(metadata, OtaType::AB, 0);
+  TestCheckPackageMetadata(metadata, OtaType::AB, true);
 }
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/updater/Android.bp b/updater/Android.bp
index b80cdb3..8a60ef7 100644
--- a/updater/Android.bp
+++ b/updater/Android.bp
@@ -30,7 +30,6 @@
         "libfec",
         "libfec_rs",
         "libverity_tree",
-        "libfs_mgr",
         "libgtest_prod",
         "liblog",
         "liblp",
@@ -42,10 +41,21 @@
         "libziparchive",
         "libz",
         "libbase",
-        "libcrypto",
         "libcrypto_utils",
         "libcutils",
         "libutils",
+    ],
+
+    shared_libs: [
+        "libcrypto",
+    ],
+}
+
+cc_defaults {
+    name: "libupdater_device_defaults",
+
+    static_libs: [
+        "libfs_mgr",
         "libtune2fs",
 
         "libext2_com_err",
@@ -54,11 +64,13 @@
         "libext2_uuid",
         "libext2_e2p",
         "libext2fs",
-    ],
+    ]
 }
 
 cc_library_static {
-    name: "libupdater",
+    name: "libupdater_core",
+
+    host_supported: true,
 
     defaults: [
         "recovery_defaults",
@@ -68,8 +80,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 +123,35 @@
         "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",
+    ],
+}
diff --git a/updater/Android.mk b/updater/Android.mk
index c7a6ba9..6f54d89 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -33,7 +33,6 @@
     libfec \
     libfec_rs \
     libverity_tree \
-    libfs_mgr \
     libgtest_prod \
     liblog \
     liblp \
@@ -45,12 +44,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 +73,7 @@
 LOCAL_MODULE := updater
 
 LOCAL_SRC_FILES := \
-    updater.cpp
+    updater_main.cpp
 
 LOCAL_C_INCLUDES := \
     $(LOCAL_PATH)/include
@@ -69,33 +83,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)
 
@@ -104,3 +111,32 @@
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
 include $(BUILD_EXECUTABLE)
+
+# TODO(xunchang) move to bp file
+# update_host_simulator (host executable)
+# ===============================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := update_host_simulator
+LOCAL_MODULE_HOST_OS := linux
+
+LOCAL_SRC_FILES := \
+    update_simulator_main.cpp
+
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+
+LOCAL_CFLAGS := \
+    -Wall \
+    -Werror
+
+LOCAL_STATIC_LIBRARIES := \
+    libupdater_host \
+    libupdater_core \
+    $(updater_common_static_libraries) \
+    libfstab \
+    libc++fs
+
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+include $(BUILD_HOST_EXECUTABLE)
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/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..62ff87e 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,7 +731,7 @@
     return StringValue("");
   }
 
-  reboot("reboot," + property);
+  Reboot(property);
 
   sleep(5);
   return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
@@ -866,16 +819,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 +830,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 +846,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/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