Merge tag 'android-14.0.0_r50' into leaf-3.2

Android 14.0.0 Release 50 (AP2A.240605.024)

* tag 'android-14.0.0_r50' of https://android.googlesource.com/platform/bootable/recovery:
  misctrl: use libbase combined logging
  misctrl: read message, incl 16kb flag
  intro misctrl
  Reland "Only format /data in recovery if user specified a new fstype"
  Add kcmdline bootloader message
  Revert "Only format /data in recovery if user specified a new fstype"
  Only format /data in recovery if user specified a new fstype
  Fix the problem of incremental OTA upgrade failure (recovery part)
  Add recovery flag to reformat /data

Change-Id: I86ccd1ec79f307a99ca5d1b5879831a1c523e723
diff --git a/Android.bp b/Android.bp
index 9b2f80f..a23a885 100644
--- a/Android.bp
+++ b/Android.bp
@@ -105,6 +105,7 @@
         "liblog",
         "libprotobuf-cpp-lite",
         "libziparchive",
+        "libvolume_manager",
     ],
 
     static_libs: [
@@ -159,6 +160,7 @@
 
     srcs: [
         "recovery_main.cpp",
+        "volclient.cpp",
     ],
 
     shared_libs: [
diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp
index dcb6c3c..24b562f 100644
--- a/bootloader_message/Android.bp
+++ b/bootloader_message/Android.bp
@@ -25,6 +25,7 @@
 
 cc_defaults {
     name: "libbootloader_message_defaults",
+    defaults: ["bootloader_message_offset_defaults"],
     srcs: ["bootloader_message.cpp"],
     cflags: [
         "-Wall",
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index 2d743e7..d80d9d1 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -175,6 +175,11 @@
 
 bool clear_bootloader_message(std::string* err) {
   bootloader_message boot = {};
+  if (BOOTLOADER_MESSAGE_OFFSET_IN_MISC < sizeof(bootloader_message)) {
+    std::string misc_blk_device = get_misc_blk_device(err);
+    if (misc_blk_device.empty()) return false;
+    return write_misc_partition(&boot, sizeof(boot), misc_blk_device, 0 /* offset */, err);
+  }
   return write_bootloader_message(boot, err);
 }
 
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index 7cb973e..4b2b8a1 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -29,11 +29,11 @@
 // 32K - 64K    System space, used for miscellanious AOSP features. See below.
 // Note that these offsets are admitted by bootloader,recovery and uncrypt, so they
 // are not configurable without changing all of them.
-constexpr size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0;
-constexpr size_t VENDOR_SPACE_OFFSET_IN_MISC = 2 * 1024;
-constexpr size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024;
-constexpr size_t SYSTEM_SPACE_OFFSET_IN_MISC = 32 * 1024;
-constexpr size_t SYSTEM_SPACE_SIZE_IN_MISC = 32 * 1024;
+constexpr size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = BOARD_RECOVERY_BLDRMSG_OFFSET;
+constexpr size_t VENDOR_SPACE_OFFSET_IN_MISC = 2 * 1024 + BOARD_RECOVERY_BLDRMSG_OFFSET;
+constexpr size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024 + BOARD_RECOVERY_BLDRMSG_OFFSET;
+constexpr size_t SYSTEM_SPACE_OFFSET_IN_MISC = 32 * 1024 + BOARD_RECOVERY_BLDRMSG_OFFSET;
+constexpr size_t SYSTEM_SPACE_SIZE_IN_MISC = 32 * 1024 + BOARD_RECOVERY_BLDRMSG_OFFSET;
 
 /* Bootloader Message (2-KiB)
  *
diff --git a/edify/include/edify/updater_runtime_interface.h b/edify/include/edify/updater_runtime_interface.h
index bdd6aec..054b85d 100644
--- a/edify/include/edify/updater_runtime_interface.h
+++ b/edify/include/edify/updater_runtime_interface.h
@@ -20,6 +20,8 @@
 #include <string_view>
 #include <vector>
 
+struct selabel_handle;
+
 // 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
@@ -74,4 +76,8 @@
 
   // On devices supports A/B, add current slot suffix to arg. Otherwise, return |arg| as is.
   virtual std::string AddSlotSuffix(const std::string_view arg) const = 0;
+
+  virtual struct selabel_handle* sehandle() const {
+    return nullptr;
+  }
 };
diff --git a/etc/init.rc b/etc/init.rc
index bdf9ec0..4813c42 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -23,6 +23,8 @@
 
     symlink /system/bin /bin
     symlink /system/etc /etc
+    rmdir /sbin
+    symlink /system/bin /sbin
 
     mkdir /sdcard
     mkdir /system
@@ -35,6 +37,17 @@
     chown root shell /tmp
     chmod 0775 /tmp
 
+    mkdir /storage 0050 root sdcard_r
+    mount tmpfs tmpfs /storage mode=0050,uid=0,gid=1028
+
+    mkdir /mnt 0775 root system
+
+    # See storage config details at http://source.android.com/tech/storage/
+    mkdir /mnt/shell 0700 shell shell
+
+    # Directory for staging bindmounts
+    mkdir /mnt/staging 0700 root root
+
     write /proc/sys/kernel/panic_on_oops 1
     write /proc/sys/vm/max_map_count 1000000
 
@@ -49,6 +62,9 @@
     # Start essential services
     start servicemanager
 
+    # pstore/ramoops previous console log
+    mount pstore pstore /sys/fs/pstore
+
 on boot
     ifup lo
     hostname localhost
@@ -78,6 +94,18 @@
     seclabel u:r:ueventd:s0
     user root
 
+service console /system/bin/sh
+    class core
+    console
+    disabled
+    user shell
+    group shell log readproc
+    seclabel u:r:shell:s0
+    setenv HOSTNAME console
+
+on init && property:ro.debuggable=1
+    start console
+
 service charger /system/bin/charger
     critical
     seclabel u:r:charger:s0
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 42f985e..3f14e8e 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -25,6 +25,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <bootloader_message/bootloader_message.h>
 
 #include "recovery_ui/ui.h"
@@ -37,19 +38,27 @@
 };
 
 void FillDefaultFastbootLines(std::vector<std::string>& title_lines) {
-  title_lines.push_back("Android Fastboot");
+  std::string bootloader_version = android::base::GetProperty("ro.bootloader", "");
+  std::string baseband_version = android::base::GetProperty("ro.build.expect.baseband", "");
+  std::string hw_version = android::base::GetProperty(
+      "ro.boot.hardware.revision", android::base::GetProperty("ro.revision", ""));
+
   title_lines.push_back("Product name - " + android::base::GetProperty("ro.product.device", ""));
-  title_lines.push_back("Bootloader version - " + android::base::GetProperty("ro.bootloader", ""));
-  title_lines.push_back("Baseband version - " +
-                        android::base::GetProperty("ro.build.expect.baseband", ""));
+  if (!android::base::EqualsIgnoreCase(bootloader_version, "unknown")) {
+    title_lines.push_back("Bootloader version - " + bootloader_version);
+  }
+  if (!baseband_version.empty()) {
+    title_lines.push_back("Baseband version - " + baseband_version);
+  }
   title_lines.push_back("Serial number - " + android::base::GetProperty("ro.serialno", ""));
   title_lines.push_back(std::string("Secure boot - ") +
                         ((android::base::GetProperty("ro.secure", "") == "1") ? "yes" : "no"));
-  title_lines.push_back("HW version - " + android::base::GetProperty("ro.revision", ""));
+  if (!android::base::EqualsIgnoreCase(hw_version, "0")) {
+    title_lines.push_back("HW version - " + android::base::GetProperty("ro.revision", ""));
+  }
 }
 
 void FillWearableFastbootLines(std::vector<std::string>& title_lines) {
-  title_lines.push_back("Android Fastboot");
   title_lines.push_back(android::base::GetProperty("ro.product.device", "") + " - " +
                         android::base::GetProperty("ro.revision", ""));
   title_lines.push_back(android::base::GetProperty("ro.bootloader", ""));
diff --git a/install/Android.bp b/install/Android.bp
index c591714..b52ac40 100644
--- a/install/Android.bp
+++ b/install/Android.bp
@@ -119,6 +119,7 @@
 
     shared_libs: [
         "librecovery_ui",
+        "libvolume_manager",
     ],
 
     export_include_dirs: [
diff --git a/install/adb_install.cpp b/install/adb_install.cpp
index b12e529..9101e40 100644
--- a/install/adb_install.cpp
+++ b/install/adb_install.cpp
@@ -111,6 +111,7 @@
         break;
       }
     }
+    ui->CancelWaitKey();
 
     auto package =
         Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME,
@@ -277,7 +278,7 @@
 //                               b11. exit the listening loop
 //
 static void CreateMinadbdServiceAndExecuteCommands(
-    RecoveryUI* ui, const std::map<MinadbdCommand, CommandFunction>& command_map,
+    Device* device, const std::map<MinadbdCommand, CommandFunction>& command_map,
     bool rescue_mode) {
   signal(SIGPIPE, SIG_IGN);
 
@@ -317,8 +318,23 @@
     return;
   }
 
+  RecoveryUI* ui = device->GetUI();
   std::thread listener_thread(ListenAndExecuteMinadbdCommands, ui, child,
                               std::move(recovery_socket), std::ref(command_map));
+
+  if (ui->IsTextVisible()) {
+    std::vector<std::string> headers{ rescue_mode ? "Rescue mode" : "ADB Sideload" };
+    std::vector<std::string> entries{ "Cancel" };
+    size_t chosen_item = ui->ShowMenu(
+        headers, entries, 0, true,
+        std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+    if (chosen_item != Device::kDoSideload) {
+      // Kill minadbd if 'cancel' was selected, to abort sideload.
+      kill(child, SIGKILL);
+    }
+  }
+
   if (listener_thread.joinable()) {
     listener_thread.join();
   }
@@ -347,7 +363,7 @@
 
   RecoveryUI* ui = device->GetUI();
 
-  InstallResult install_result = INSTALL_ERROR;
+  InstallResult install_result = INSTALL_NONE;
   std::map<MinadbdCommand, CommandFunction> command_map{
     { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, device, &install_result) },
     { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid,
@@ -377,7 +393,7 @@
     ui->Print("\n\nWaiting for rescue commands...\n");
   }
 
-  CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode);
+  CreateMinadbdServiceAndExecuteCommands(device, command_map, rescue_mode);
 
   // Clean up before switching to the older state, for example setting the state
   // to none sets sys/class/android_usb/android0/enable to 0.
diff --git a/install/fuse_install.cpp b/install/fuse_install.cpp
index 197e1de..727419d 100644
--- a/install/fuse_install.cpp
+++ b/install/fuse_install.cpp
@@ -39,7 +39,9 @@
 #include "install/install.h"
 #include "recovery_utils/roots.h"
 
-static constexpr const char* SDCARD_ROOT = "/sdcard";
+using android::volmgr::VolumeInfo;
+using android::volmgr::VolumeManager;
+
 // How long (in seconds) we wait for the fuse-provided package file to
 // appear, before timing out.
 static constexpr int SDCARD_INSTALL_TIMEOUT = 10;
@@ -56,8 +58,6 @@
 
 // Returns the selected filename, or an empty string.
 static std::string BrowseDirectory(const std::string& path, Device* device, RecoveryUI* ui) {
-  ensure_path_mounted(path);
-
   std::unique_ptr<DIR, decltype(&closedir)> d(opendir(path.c_str()), closedir);
   if (!d) {
     PLOG(ERROR) << "error opening " << path;
@@ -99,13 +99,16 @@
     if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
       return "";
     }
-
-    const std::string& item = entries[chosen_item];
-    if (chosen_item == 0) {
-      // Go up but continue browsing (if the caller is BrowseDirectory).
+    if (chosen_item == Device::kGoHome) {
+      return "@";
+    }
+    if (chosen_item == Device::kGoBack || chosen_item == 0) {
+      // Go up but continue browsing (if the caller is browse_directory).
       return "";
     }
 
+    const std::string& item = entries[chosen_item];
+
     std::string new_path = path + "/" + item;
     if (new_path.back() == '/') {
       // Recurse down into a subdirectory.
@@ -137,12 +140,6 @@
     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;
 }
 
@@ -205,18 +202,16 @@
   return result;
 }
 
-InstallResult ApplyFromSdcard(Device* device) {
+InstallResult ApplyFromStorage(Device* device, VolumeInfo& vi) {
   auto ui = device->GetUI();
-  if (ensure_path_mounted(SDCARD_ROOT) != 0) {
-    LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n";
-    return INSTALL_ERROR;
+  if (!VolumeManager::Instance()->volumeMount(vi.mId)) {
+    return INSTALL_NONE;
   }
 
-  std::string path = BrowseDirectory(SDCARD_ROOT, device, ui);
+  std::string path = BrowseDirectory(vi.mPath, device, ui);
   if (path.empty()) {
-    LOG(ERROR) << "\n-- No package file selected.\n";
-    ensure_path_unmounted(SDCARD_ROOT);
-    return INSTALL_ERROR;
+    VolumeManager::Instance()->volumeUnmount(vi.mId);
+    return INSTALL_NONE;
   }
 
   // Hint the install function to read from a block map file.
@@ -228,6 +223,7 @@
   SetSdcardUpdateBootloaderMessage();
 
   auto result = InstallWithFuseFromPath(path, device);
-  ensure_path_unmounted(SDCARD_ROOT);
+
+  VolumeManager::Instance()->volumeUnmount(vi.mId);
   return result;
 }
diff --git a/install/include/install/fuse_install.h b/install/include/install/fuse_install.h
index 29c283f..7c38d0d 100644
--- a/install/include/install/fuse_install.h
+++ b/install/include/install/fuse_install.h
@@ -22,9 +22,13 @@
 #include "recovery_ui/device.h"
 #include "recovery_ui/ui.h"
 
+#include <volume_manager/VolumeManager.h>
+
+using android::volmgr::VolumeInfo;
+
 // 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, Device* device);
 
-InstallResult ApplyFromSdcard(Device* device);
+InstallResult ApplyFromStorage(Device* device, VolumeInfo& vi);
diff --git a/install/include/install/install.h b/install/include/install/install.h
index 0f5102f..14c538e 100644
--- a/install/include/install/install.h
+++ b/install/include/install/install.h
@@ -64,7 +64,8 @@
 // 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);
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type,
+                          RecoveryUI* ui);
 
 // Ensures the path to the update package is mounted. Also set the |should_use_fuse| to true if the
 // package stays on a removable media.
diff --git a/install/include/install/wipe_data.h b/install/include/install/wipe_data.h
index 1796bcc..47a5a80 100644
--- a/install/include/install/wipe_data.h
+++ b/install/include/install/wipe_data.h
@@ -29,3 +29,6 @@
 
 // Returns true on success.
 bool WipeData(Device* device, bool keep_memtag_mode = false, std::string_view new_fstype = "");
+
+// Returns true on success.
+bool WipeSystem(RecoveryUI* ui, const std::function<bool()>& confirm);
diff --git a/install/install.cpp b/install/install.cpp
index a9786cf..a562b5b 100644
--- a/install/install.cpp
+++ b/install/install.cpp
@@ -46,6 +46,8 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 
+#include "bootloader_message/bootloader_message.h"
+#include "install/snapshot_utils.h"
 #include "install/spl_check.h"
 #include "install/wipe_data.h"
 #include "install/wipe_device.h"
@@ -55,12 +57,17 @@
 #include "otautil/sysutil.h"
 #include "otautil/verifier.h"
 #include "private/setup_commands.h"
+#include "recovery_ui/device.h"
 #include "recovery_ui/ui.h"
 #include "recovery_utils/roots.h"
 #include "recovery_utils/thermalutil.h"
 
 using namespace std::chrono_literals;
 
+bool ask_to_ab_reboot(Device* device);
+bool ask_to_continue_unverified(Device* device);
+bool ask_to_continue_downgrade(Device* device);
+
 static constexpr int kRecoveryApiVersion = 3;
 // 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.
@@ -70,7 +77,7 @@
 static constexpr int VERIFICATION_PROGRESS_TIME = 60;
 static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
 // The charater used to separate dynamic fingerprints. e.x. sargo|aosp-sargo
-static const char* FINGERPRING_SEPARATOR = "|";
+#define FINGERPRING_SEPARATOR "|"
 static constexpr auto&& RELEASE_KEYS_TAG = "release-keys";
 // If brick packages are smaller than |MEMORY_PACKAGE_LIMIT|, read the entire package into memory
 static constexpr size_t MEMORY_PACKAGE_LIMIT = 1024 * 1024;
@@ -85,7 +92,6 @@
   static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
   ZipEntry64 entry;
   if (FindEntry(zip, METADATA_PATH, &entry) != 0) {
-    LOG(ERROR) << "Failed to find " << METADATA_PATH;
     return false;
   }
 
@@ -148,7 +154,8 @@
 // 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 bool CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
+static bool CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata,
+                                    RecoveryUI* ui) {
   // 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");
@@ -168,6 +175,7 @@
   }
 
   // Check for downgrade version.
+  bool undeclared_downgrade = false;
   int64_t build_timestamp =
       android::base::GetIntProperty("ro.build.date.utc", std::numeric_limits<int64_t>::max());
   int64_t pkg_post_timestamp = 0;
@@ -182,18 +190,23 @@
                     "newer than timestamp "
                  << build_timestamp << " but package has timestamp " << pkg_post_timestamp
                  << " and downgrade not allowed.";
-      return false;
-    }
-    if (pkg_pre_build_fingerprint.empty()) {
+      undeclared_downgrade = true;
+    } else if (pkg_pre_build_fingerprint.empty()) {
       LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
-      return false;
+      undeclared_downgrade = true;
     }
   }
 
+  if (undeclared_downgrade &&
+      !(ui->IsTextVisible() && ask_to_continue_downgrade(ui->GetDevice()))) {
+    return false;
+  }
+
   return true;
 }
 
-bool 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,
+                          RecoveryUI* ui) {
   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) {
@@ -210,7 +223,7 @@
   auto device = android::base::GetProperty("ro.product.device", "");
   auto pkg_device = get_value(metadata, "pre-device");
   // device name can be a | separated list, so need to check
-  if (pkg_device.empty() || !isInStringList(device, pkg_device, FINGERPRING_SEPARATOR)) {
+  if (pkg_device.empty() || !isInStringList(device, pkg_device, FINGERPRING_SEPARATOR ":" ",")) {
     LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device;
     return false;
   }
@@ -250,7 +263,7 @@
   }
 
   if (ota_type == OtaType::AB) {
-    return CheckAbSpecificMetadata(metadata);
+    return CheckAbSpecificMetadata(metadata, ui);
   }
 
   return true;
@@ -380,12 +393,9 @@
   auto ui = device->GetUI();
   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 has_metadata = ReadMetadataFromPackage(zip, &metadata);
 
-  const bool package_is_ab = get_value(metadata, "ota-type") == OtaTypeToString(OtaType::AB);
+  const bool package_is_ab = has_metadata && get_value(metadata, "ota-type") == OtaTypeToString(OtaType::AB);
   const bool package_is_brick = get_value(metadata, "ota-type") == OtaTypeToString(OtaType::BRICK);
   if (package_is_brick) {
     LOG(INFO) << "Installing a brick package";
@@ -400,13 +410,27 @@
     return WipeAbDevice(device, package) ? INSTALL_SUCCESS : INSTALL_ERROR;
   }
   bool device_supports_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
-  bool ab_device_supports_nonab =
-      android::base::GetBoolProperty("ro.virtual_ab.allow_non_ab", false);
+  bool ab_device_supports_nonab = true;
   bool device_only_supports_ab = device_supports_ab && !ab_device_supports_nonab;
+  bool device_supports_virtual_ab = android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
 
   const auto current_spl = android::base::GetProperty("ro.build.version.security_patch", "");
   if (ViolatesSPLDowngrade(zip, current_spl)) {
-    LOG(ERROR) << "Denying OTA because it's SPL downgrade";
+    LOG(WARNING) << "The OTA package will downgrade security patch level";
+  }
+
+  const auto reboot_to_recovery = [] {
+    if (std::string err; !clear_bootloader_message(&err)) {
+      LOG(ERROR) << "Failed to clear BCB message: " << err;
+    }
+    Reboot("recovery");
+  };
+
+  static bool ab_package_installed = false;
+  if (ab_package_installed) {
+    if (ask_to_ab_reboot(device)) {
+      reboot_to_recovery();
+    }
     return INSTALL_ERROR;
   }
 
@@ -419,12 +443,21 @@
   // Package does not declare itself as an A/B package, but device only supports A/B;
   //   still calls CheckPackageMetadata to get a meaningful error message.
   if (package_is_ab || device_only_supports_ab) {
-    if (!CheckPackageMetadata(metadata, OtaType::AB)) {
+    if (!CheckPackageMetadata(metadata, OtaType::AB, ui)) {
       log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
       return INSTALL_ERROR;
     }
   }
 
+  if (!package_is_ab && !logical_partitions_mapped()) {
+    CreateSnapshotPartitions();
+    map_logical_partitions();
+  } else if (package_is_ab && device_supports_virtual_ab && logical_partitions_mapped()) {
+    LOG(ERROR) << "Logical partitions are mapped. "
+               << "Please reboot recovery before installing an OTA update.";
+    return INSTALL_ERROR;
+  }
+
   ReadSourceTargetBuild(metadata, log_buffer);
 
   // The updater in child process writes to the pipe to communicate with recovery.
@@ -586,7 +619,11 @@
     LOG(FATAL) << "Invalid status code " << status;
   }
   if (package_is_ab) {
+    ab_package_installed = true;
     PerformPowerwashIfRequired(zip, device);
+    if (!ui->IsSideloadAutoReboot() && ask_to_ab_reboot(device)) {
+      reboot_to_recovery();
+    }
   }
 
   return INSTALL_SUCCESS;
@@ -604,7 +641,9 @@
   // Verify package.
   if (!verify_package(package, ui)) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
-    return INSTALL_CORRUPT;
+    if (!ui->IsTextVisible() || !ask_to_continue_unverified(ui->GetDevice())) {
+        return INSTALL_CORRUPT;
+    }
   }
 
   // Verify and install the contents of the package.
diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp
index 0f57384..b390a61 100644
--- a/install/wipe_data.cpp
+++ b/install/wipe_data.cpp
@@ -17,6 +17,12 @@
 #include "install/wipe_data.h"
 
 #include <string.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
 
 #include <functional>
 #include <vector>
@@ -24,6 +30,8 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <fs_mgr/roots.h>
+#include <libdm/dm.h>
 
 #include "bootloader_message/bootloader_message.h"
 #include "install/snapshot_utils.h"
@@ -48,7 +56,52 @@
 
   ui->Print("Formatting %s...\n", volume);
 
-  ensure_path_unmounted(volume);
+  Volume* vol = volume_for_mount_point(volume);
+  if (vol->fs_mgr_flags.logical) {
+    android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance();
+
+    map_logical_partitions();
+    // map_logical_partitions is non-blocking, so check for some limited time
+    // if it succeeded
+    for (int i = 0; i < 500; i++) {
+      if (vol->blk_device[0] == '/' ||
+          dm.GetState(vol->blk_device) == android::dm::DmDeviceState::ACTIVE)
+        break;
+      std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    }
+
+    if (vol->blk_device[0] != '/' && !dm.GetDmDevicePathByName(vol->blk_device, &vol->blk_device)) {
+      PLOG(ERROR) << "Failed to find dm device path for " << vol->blk_device;
+      return false;
+    }
+
+    int fd = open(vol->blk_device.c_str(), O_RDWR);
+    if (fd < 0) {
+      PLOG(ERROR) << "Failed to open " << vol->blk_device;
+      return false;
+    }
+
+    int val = 0;
+    if (ioctl(fd, BLKROSET, &val) != 0) {
+      PLOG(ERROR) << "Failed to set " << vol->blk_device << " rw";
+      close(fd);
+      return false;
+    }
+
+    close(fd);
+  }
+
+  std::string blk_device;
+
+  if (!android::base::Realpath(vol->blk_device, &blk_device)) {
+    PLOG(ERROR) << "Failed to convert \"" << vol->blk_device << "\" to absolute path";
+    return false;
+  }
+
+  if (ensure_volume_unmounted(blk_device) == -1) {
+    PLOG(ERROR) << "Failed to unmount volume!";
+    return false;
+  }
 
   int result = format_volume(volume, "", new_fstype);
 
@@ -118,3 +171,14 @@
   ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
   return success;
 }
+
+bool WipeSystem(RecoveryUI* ui, const std::function<bool()>& confirm_func) {
+  if (confirm_func && !confirm_func()) {
+    return false;
+  }
+
+  ui->Print("\n-- Wiping system...\n");
+  bool success = EraseVolume(android::fs_mgr::GetSystemRoot().c_str(), ui);
+  ui->Print("System wipe %s.\n", success ? "complete" : "failed");
+  return success;
+}
diff --git a/install/wipe_device.cpp b/install/wipe_device.cpp
index 2656580..c14009e 100644
--- a/install/wipe_device.cpp
+++ b/install/wipe_device.cpp
@@ -169,7 +169,7 @@
     return false;
   }
 
-  return CheckPackageMetadata(metadata, OtaType::BRICK);
+  return CheckPackageMetadata(metadata, OtaType::BRICK, ui);
 }
 
 bool WipeAbDevice(Device* device, size_t wipe_package_size) {
diff --git a/minui/events.cpp b/minui/events.cpp
index b307a49..b4beaa9 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -32,6 +32,7 @@
 #include <string>
 
 #include <android-base/strings.h>
+#include <android-base/properties.h>
 #include <android-base/unique_fd.h>
 
 #include "minui/minui.h"
@@ -63,6 +64,11 @@
 static size_t g_ev_dev_count = 0;
 static size_t g_ev_misc_count = 0;
 
+static bool should_skip_ev_rel() {
+  static bool prop = android::base::GetBoolProperty("ro.recovery.skip_ev_rel_input", false);
+  return prop;
+}
+
 static bool test_bit(size_t bit, unsigned long* array) { // NOLINT
   return (array[bit / BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
 }
@@ -76,9 +82,12 @@
     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)) {
+  // We assume that only EV_ABS, EV_KEY, EV_REL, and EV_SW event types are ever needed.
+  // EV_ABS is only allowed if allow_touch_inputs is set.
+  // EV_REL can be explicitly disallowed. This is needed to skip sensor inputs on some devices.
+  if (!test_bit(EV_KEY, ev_bits) &&
+      !test_bit(EV_SW, ev_bits) &&
+      (should_skip_ev_rel() || !test_bit(EV_REL, ev_bits))) {
     if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
       return false;
     }
@@ -325,7 +334,7 @@
   return 0;
 }
 
-void ev_iterate_available_keys(const std::function<void(int)>& f) {
+void ev_iterate_available_keys(const std::function<void(int)>& key_detected) {
   // Use unsigned long to match ioctl's parameter type.
   unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];    // NOLINT
   unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];  // NOLINT
@@ -348,13 +357,14 @@
 
     for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
       if (test_bit(key_code, key_bits)) {
-        f(key_code);
+        key_detected(key_code);
       }
     }
   }
 }
 
-void ev_iterate_touch_inputs(const std::function<void(int)>& action) {
+void ev_iterate_touch_inputs(const std::function<void(int)>& touch_device_detected,
+                             const std::function<void(int)>& key_detected) {
   for (size_t i = 0; i < g_ev_dev_count; ++i) {
     // Use unsigned long to match ioctl's parameter type.
     unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)] = {};  // NOLINT
@@ -370,9 +380,11 @@
       continue;
     }
 
+    touch_device_detected(ev_fdinfo[i].fd);
+
     for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
       if (test_bit(key_code, key_bits)) {
-        action(key_code);
+        key_detected(key_code);
       }
     }
   }
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index cc82094..8e2f581 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -30,6 +30,7 @@
 #include "minui/minui.h"
 
 static GRFont* gr_font = nullptr;
+static GRFont* gr_font_menu = nullptr;
 static MinuiBackend* gr_backend = nullptr;
 
 static int overscan_offset_x = 0;
@@ -40,6 +41,7 @@
 // gr_draw is owned by backends.
 static GRSurface* gr_draw = nullptr;
 static GRRotation rotation = GRRotation::NONE;
+static GRRotation touch_rotation = GRRotation::NONE;
 static PixelFormat pixel_format = PixelFormat::UNKNOWN;
 // The graphics backend list that provides fallback options for the default backend selection.
 // For example, it will fist try DRM, then try FBDEV if DRM is unavailable.
@@ -55,6 +57,10 @@
   return gr_font;
 }
 
+const GRFont* gr_menu_font() {
+  return gr_font_menu;
+}
+
 PixelFormat gr_pixel_format() {
   return pixel_format;
 }
@@ -423,6 +429,11 @@
     printf("Failed to init font: %d, continuing graphic backend initialization without font file\n",
            ret);
   }
+  ret = gr_init_font("font_menu", &gr_font_menu);
+  if (ret != 0) {
+    printf("Failed to init menu font: %d. Falling back to system font\n", ret);
+    gr_font_menu = gr_font;
+  }
 
   std::unique_ptr<MinuiBackend> minui_backend;
   for (GraphicsBackend backend : backends) {
@@ -464,6 +475,18 @@
     gr_rotate(GRRotation::NONE);
   }
 
+  std::string touch_rotation_str =
+      android::base::GetProperty("ro.minui.default_touch_rotation", "ROTATION_NONE");
+  if (touch_rotation_str == "ROTATION_RIGHT") {
+    gr_rotate_touch(GRRotation::RIGHT);
+  } else if (touch_rotation_str == "ROTATION_DOWN") {
+    gr_rotate_touch(GRRotation::DOWN);
+  } else if (touch_rotation_str == "ROTATION_LEFT") {
+    gr_rotate_touch(GRRotation::LEFT);
+  } else {  // "ROTATION_NONE" or unknown string
+    gr_rotate_touch(GRRotation::NONE);
+  }
+
   if (gr_draw->pixel_bytes != 4) {
     printf("gr_init: Only 4-byte pixel formats supported\n");
   }
@@ -491,6 +514,28 @@
              : gr_draw->height - 2 * overscan_offset_y;
 }
 
+int gr_fb_width_real() {
+  return (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT) ? gr_draw->height
+                                                                         : gr_draw->width;
+}
+
+int gr_fb_height_real() {
+  return (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT) ? gr_draw->width
+                                                                         : gr_draw->height;
+}
+
+int gr_overscan_offset_x() {
+  return overscan_offset_x;
+}
+
+int gr_overscan_offset_y() {
+  return overscan_offset_y;
+}
+
+GRRotation gr_touch_rotation() {
+  return touch_rotation;
+}
+
 void gr_fb_blank(bool blank) {
   gr_backend->Blank(blank);
 }
@@ -507,6 +552,10 @@
   return rotation;
 }
 
+void gr_rotate_touch(GRRotation rot) {
+  touch_rotation = rot;
+}
+
 bool gr_has_multiple_connectors() {
   return gr_backend->HasMultipleConnectors();
 }
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index 6a71ad3..2eaa970 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -125,6 +125,11 @@
 
 int gr_fb_width();
 int gr_fb_height();
+int gr_fb_width_real();
+int gr_fb_height_real();
+int gr_overscan_offset_x();
+int gr_overscan_offset_y();
+GRRotation gr_touch_rotation();
 
 void gr_flip();
 void gr_fb_blank(bool blank);
@@ -139,6 +144,7 @@
 void gr_texticon(int x, int y, const GRSurface* icon);
 
 const GRFont* gr_sys_font();
+const GRFont* gr_menu_font();
 int gr_init_font(const char* name, GRFont** dest);
 void gr_text(const GRFont* font, int x, int y, const char* s, bool bold);
 // Returns -1 if font is nullptr.
@@ -156,6 +162,9 @@
 // Get current rotation
 GRRotation gr_get_rotation();
 
+// Sets touch rotation
+void gr_rotate_touch(GRRotation rotation);
+
 // Returns the current PixelFormat being used.
 PixelFormat gr_pixel_format();
 
@@ -172,8 +181,9 @@
 int ev_init(ev_callback input_cb, bool allow_touch_inputs = false);
 void ev_exit();
 int ev_add_fd(android::base::unique_fd&& fd, ev_callback cb);
-void ev_iterate_available_keys(const std::function<void(int)>& f);
-void ev_iterate_touch_inputs(const std::function<void(int)>& action);
+void ev_iterate_available_keys(const std::function<void(int)>& key_detected);
+void ev_iterate_touch_inputs(const std::function<void(int)>& touch_device_detected,
+                             const std::function<void(int)>& key_detected);
 int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
 int ev_sync_sw_state(const ev_set_sw_callback& set_sw_cb);
 
diff --git a/otautil/Android.bp b/otautil/Android.bp
index 4b043ad..6411c5d 100644
--- a/otautil/Android.bp
+++ b/otautil/Android.bp
@@ -41,6 +41,7 @@
         "rangeset.cpp",
         "sysutil.cpp",
         "verifier.cpp",
+        "ziputil.cpp",
     ],
 
     shared_libs: [
diff --git a/otautil/dirutil.cpp b/otautil/dirutil.cpp
index ae1cd5c..1e9c693 100644
--- a/otautil/dirutil.cpp
+++ b/otautil/dirutil.cpp
@@ -48,6 +48,11 @@
 
 int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
                       const selabel_handle* sehnd) {
+  return mkdir_recursively(input_path, mode, strip_filename, sehnd, NULL);
+}
+
+int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
+                      const selabel_handle* sehnd, const struct utimbuf* timestamp) {
   // Check for an empty string before we bother making any syscalls.
   if (input_path.empty()) {
     errno = ENOENT;
@@ -104,6 +109,9 @@
         if (err != 0) {
           return -1;
         }
+        if (timestamp != NULL && utime(dir_path.c_str(), timestamp)) {
+          return -1;
+        }
         break;
       }
       default:
@@ -114,3 +122,57 @@
   }
   return 0;
 }
+
+int dirUnlinkHierarchy(const char* path) {
+  struct stat st;
+  DIR* dir;
+  struct dirent* de;
+  int fail = 0;
+
+  /* is it a file or directory? */
+  if (lstat(path, &st) < 0) {
+    return -1;
+  }
+
+  /* a file, so unlink it */
+  if (!S_ISDIR(st.st_mode)) {
+    return unlink(path);
+  }
+
+  /* a directory, so open handle */
+  dir = opendir(path);
+  if (dir == NULL) {
+    return -1;
+  }
+
+  /* recurse over components */
+  errno = 0;
+  while ((de = readdir(dir)) != NULL) {
+    // TODO: don't blow the stack
+    char dn[PATH_MAX];
+    if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
+      continue;
+    }
+    snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
+    if (dirUnlinkHierarchy(dn) < 0) {
+      fail = 1;
+      break;
+    }
+    errno = 0;
+  }
+  /* in case readdir or unlink_recursive failed */
+  if (fail || errno < 0) {
+    int save = errno;
+    closedir(dir);
+    errno = save;
+    return -1;
+  }
+
+  /* close directory handle */
+  if (closedir(dir) < 0) {
+    return -1;
+  }
+
+  /* delete target directory */
+  return rmdir(path);
+}
diff --git a/otautil/include/otautil/dirutil.h b/otautil/include/otautil/dirutil.h
index 85d6c16..bb0e3d4 100644
--- a/otautil/include/otautil/dirutil.h
+++ b/otautil/include/otautil/dirutil.h
@@ -17,7 +17,9 @@
 #ifndef OTAUTIL_DIRUTIL_H_
 #define OTAUTIL_DIRUTIL_H_
 
+#include <limits.h>    // PATH_MAX
 #include <sys/stat.h>  // mode_t
+#include <utime.h>     // utime/utimbuf
 
 #include <string>
 
@@ -36,4 +38,11 @@
 int mkdir_recursively(const std::string& path, mode_t mode, bool strip_filename,
                       const struct selabel_handle* sehnd);
 
+// As above, but if timestamp is non-NULL, directories will be timestamped accordingly.
+int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
+                      const selabel_handle* sehnd, const struct utimbuf* timestamp);
+
+// rm -rf <path>
+int dirUnlinkHierarchy(const char* path);
+
 #endif  // OTAUTIL_DIRUTIL_H_
diff --git a/otautil/include/otautil/ziputil.h b/otautil/include/otautil/ziputil.h
new file mode 100644
index 0000000..36879ed
--- /dev/null
+++ b/otautil/include/otautil/ziputil.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _OTAUTIL_ZIPUTIL_H
+#define _OTAUTIL_ZIPUTIL_H
+
+#include <utime.h>
+
+#include <string>
+
+#include <selinux/label.h>
+#include <ziparchive/zip_archive.h>
+
+/*
+ * Inflate all files under zip_path to the directory specified by
+ * dest_path, which must exist and be a writable directory. The zip_path
+ * is allowed to be an empty string, in which case the whole package
+ * will be extracted.
+ *
+ * Directory entries are not extracted.
+ *
+ * The immediate children of zip_path will become the immediate
+ * children of dest_path; e.g., if the archive contains the entries
+ *
+ *     a/b/c/one
+ *     a/b/c/two
+ *     a/b/c/d/three
+ *
+ * and ExtractPackageRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting
+ * files will be
+ *
+ *     /tmp/one
+ *     /tmp/two
+ *     /tmp/d/three
+ *
+ * If timestamp is non-NULL, file timestamps will be set accordingly.
+ *
+ * Returns true on success, false on failure.
+ */
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
+                             const std::string& dest_path, const struct utimbuf* timestamp,
+                             struct selabel_handle* sehnd);
+
+#endif  // _OTAUTIL_ZIPUTIL_H
diff --git a/otautil/ziputil.cpp b/otautil/ziputil.cpp
new file mode 100644
index 0000000..598bb7c
--- /dev/null
+++ b/otautil/ziputil.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 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 "otautil/ziputil.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <utime.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+#include <ziparchive/zip_archive.h>
+
+#include "otautil/dirutil.h"
+
+static constexpr mode_t UNZIP_DIRMODE = 0755;
+static constexpr mode_t UNZIP_FILEMODE = 0644;
+
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
+                             const std::string& dest_path, const struct utimbuf* timestamp,
+                             struct selabel_handle* sehnd) {
+  if (!zip_path.empty() && zip_path[0] == '/') {
+    LOG(ERROR) << "ExtractPackageRecursive(): zip_path must be a relative path " << zip_path;
+    return false;
+  }
+  if (dest_path.empty() || dest_path[0] != '/') {
+    LOG(ERROR) << "ExtractPackageRecursive(): dest_path must be an absolute path " << dest_path;
+    return false;
+  }
+
+  void* cookie;
+  std::string target_dir(dest_path);
+  if (dest_path.back() != '/') {
+    target_dir += '/';
+  }
+  std::string prefix_path(zip_path);
+  if (!zip_path.empty() && zip_path.back() != '/') {
+    prefix_path += '/';
+  }
+
+  int ret = StartIteration(zip, &cookie, prefix_path, "");
+  if (ret != 0) {
+    LOG(ERROR) << "failed to start iterating zip entries.";
+    return false;
+  }
+
+  std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
+  ZipEntry entry;
+  std::string name;
+  int extractCount = 0;
+  while (Next(cookie, &entry, &name) == 0) {
+    CHECK_LE(prefix_path.size(), name.size());
+    std::string path = target_dir + name.substr(prefix_path.size());
+    // Skip dir.
+    if (path.back() == '/') {
+      continue;
+    }
+
+    if (mkdir_recursively(path.c_str(), UNZIP_DIRMODE, true, sehnd, timestamp) != 0) {
+      LOG(ERROR) << "failed to create dir for " << path;
+      return false;
+    }
+
+    char* secontext = NULL;
+    if (sehnd) {
+      selabel_lookup(sehnd, &secontext, path.c_str(), UNZIP_FILEMODE);
+      setfscreatecon(secontext);
+    }
+    android::base::unique_fd fd(open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, UNZIP_FILEMODE));
+    if (fd == -1) {
+      PLOG(ERROR) << "Can't create target file \"" << path << "\"";
+      return false;
+    }
+    if (secontext) {
+      freecon(secontext);
+      setfscreatecon(NULL);
+    }
+
+    int err = ExtractEntryToFile(zip, &entry, fd);
+    if (err != 0) {
+      LOG(ERROR) << "Error extracting \"" << path << "\" : " << ErrorCodeString(err);
+      return false;
+    }
+
+    if (fsync(fd) != 0) {
+      PLOG(ERROR) << "Error syncing file descriptor when extracting \"" << path << "\"";
+      return false;
+    }
+
+    if (timestamp != nullptr && utime(path.c_str(), timestamp)) {
+      PLOG(ERROR) << "Error touching \"" << path << "\"";
+      return false;
+    }
+
+    LOG(INFO) << "Extracted file \"" << path << "\"";
+    ++extractCount;
+  }
+
+  LOG(INFO) << "Extracted " << extractCount << " file(s)";
+  return true;
+}
diff --git a/recovery.cpp b/recovery.cpp
index e7a33a9..5824ff5 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -22,12 +22,14 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mount.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #include <functional>
 #include <iterator>
 #include <memory>
+#include <regex>
 #include <string>
 #include <vector>
 
@@ -39,6 +41,7 @@
 #include <android-base/strings.h>
 #include <cutils/properties.h> /* for property_list */
 #include <fs_mgr/roots.h>
+#include <volume_manager/VolumeManager.h>
 #include <ziparchive/zip_archive.h>
 
 #include "bootloader_message/bootloader_message.h"
@@ -57,6 +60,10 @@
 #include "recovery_utils/battery_utils.h"
 #include "recovery_utils/logging.h"
 #include "recovery_utils/roots.h"
+#include "volclient.h"
+
+using android::volmgr::VolumeManager;
+using android::volmgr::VolumeInfo;
 
 static constexpr const char* COMMAND_FILE = "/cache/recovery/command";
 static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
@@ -164,17 +171,84 @@
   return (chosen_item == 1);
 }
 
-static bool ask_to_wipe_data(Device* device) {
-  std::vector<std::string> headers{ "Wipe all user data?", "  THIS CAN NOT BE UNDONE!" };
-  std::vector<std::string> items{ " Cancel", " Factory data reset" };
+bool ask_to_ab_reboot(Device* device) {
+  device->GetUI()->SetProgressType(RecoveryUI::EMPTY);
+  return yes_no(device, "To install additional packages, you need to reboot recovery first",
+                "Do you want to reboot to recovery now?");
+}
 
-  size_t chosen_item = device->GetUI()->ShowPromptWipeDataConfirmationMenu(
-      headers, items,
+bool ask_to_continue_unverified(Device* device) {
+  device->GetUI()->SetProgressType(RecoveryUI::EMPTY);
+  return yes_no(device, "Signature verification failed", "Install anyway?");
+}
+
+bool ask_to_continue_downgrade(Device* device) {
+  if (get_build_type() == "user") {
+    return false;
+  } else {
+    device->GetUI()->SetProgressType(RecoveryUI::EMPTY);
+    return yes_no(device, "This package will downgrade your system", "Install anyway?");
+  }
+}
+
+static bool ask_to_wipe_data(Device* device) {
+  std::vector<std::string> headers{ "Format user data?", "This includes internal storage.", "THIS CANNOT BE UNDONE!" };
+  std::vector<std::string> items{ " Cancel", " Format data" };
+
+  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);
 }
 
+static InstallResult apply_update_menu(Device* device, Device::BuiltinAction* reboot_action){
+  RecoveryUI* ui = device->GetUI();
+  std::vector<std::string> headers{ "Apply update" };
+  std::vector<std::string> items;
+
+  const int item_sideload = 0;
+  std::vector<VolumeInfo> volumes;
+
+  InstallResult status = INSTALL_NONE;
+
+  for (;;) {
+    items.clear();
+    items.push_back("Apply from ADB");
+    VolumeManager::Instance()->getVolumeInfo(volumes);
+    for (auto vol = volumes.begin(); vol != volumes.end(); /* empty */) {
+      if (!vol->mMountable) {
+        vol = volumes.erase(vol);
+        continue;
+      }
+      items.push_back("Choose from " + vol->mLabel);
+      ++vol;
+    }
+
+    int chosen = ui->ShowMenu(
+      headers, items, 0, false,
+      std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2),
+      true /* refreshable */);
+    if (chosen == Device::kRefresh) {
+      continue;
+    }
+    if (chosen == Device::kGoBack) {
+      break;
+    }
+    if (chosen == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
+      return INSTALL_KEY_INTERRUPTED;
+    }
+
+    if (chosen == item_sideload) {
+      status = ApplyFromAdb(device, false /* rescue_mode */, reboot_action);
+    } else {
+      status = ApplyFromStorage(device, volumes[chosen - 1]);
+    }
+    break;
+  }
+  return status;
+}
+
 static InstallResult prompt_and_wipe_data(Device* device) {
   // Use a single string and let ScreenRecoveryUI handles the wrapping.
   std::vector<std::string> wipe_data_menu_headers{
@@ -197,7 +271,10 @@
     if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
       return INSTALL_KEY_INTERRUPTED;
     }
-    if (chosen_item != 1) {
+    if (chosen_item == Device::kGoBack) {
+      return INSTALL_NONE;     // Go back, show menu
+    }
+    if (chosen_item == 0) {
       return INSTALL_SUCCESS;  // Just reboot, no wipe; not a failure, user asked for it
     }
 
@@ -256,7 +333,10 @@
     if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
       break;
     }
-    if (entries[chosen_item] == "Back") break;
+    if (chosen_item == Device::kGoHome || chosen_item == Device::kGoBack ||
+        chosen_item == entries.size() - 1) {
+      break;
+    }
 
     device->GetUI()->ShowFile(entries[chosen_item]);
   }
@@ -379,19 +459,20 @@
     }
     ui->SetProgressType(RecoveryUI::EMPTY);
 
-    std::vector<std::string> headers;
-    if (update_in_progress) {
-      headers = { "WARNING: Previous installation has failed.",
-                  "  Your device may fail to boot if you reboot or power off now." };
-    }
-
+change_menu:
     size_t chosen_item = ui->ShowMenu(
-        headers, device->GetMenuItems(), 0, false,
+        device->GetMenuHeaders(), device->GetMenuItems(), 0, false,
         std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
     // Handle Interrupt key
     if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
       return Device::KEY_INTERRUPTED;
     }
+
+    if (chosen_item == Device::kGoBack || chosen_item == Device::kGoHome) {
+      device->GoHome();
+      goto change_menu;
+    }
+
     // Device-specific code may take some action here. It may return one of the core actions
     // handled in the switch statement below.
     Device::BuiltinAction chosen_action =
@@ -400,6 +481,11 @@
             : device->InvokeMenuItem(chosen_item);
 
     switch (chosen_action) {
+      case Device::MENU_BASE:
+      case Device::MENU_WIPE:
+      case Device::MENU_ADVANCED:
+        goto change_menu;
+
       case Device::REBOOT_FROM_FASTBOOT:    // Can not happen
       case Device::SHUTDOWN_FROM_FASTBOOT:  // Can not happen
       case Device::NO_ACTION:
@@ -443,15 +529,24 @@
       case Device::WIPE_CACHE: {
         save_current_log = true;
         std::function<bool()> confirm_func = [&device]() {
-          return yes_no(device, "Wipe cache?", "  THIS CAN NOT BE UNDONE!");
+          return yes_no(device, "Format cache?", "  THIS CAN NOT BE UNDONE!");
         };
         WipeCache(ui, ui->IsTextVisible() ? confirm_func : nullptr);
         if (!ui->IsTextVisible()) return Device::NO_ACTION;
         break;
       }
 
-      case Device::APPLY_ADB_SIDELOAD:
-      case Device::APPLY_SDCARD:
+      case Device::WIPE_SYSTEM: {
+        save_current_log = true;
+        std::function<bool()> confirm_func = [&device]() {
+          return yes_no(device, "Format system?", "  THIS CAN NOT BE UNDONE!");
+        };
+        WipeSystem(ui, ui->IsTextVisible() ? confirm_func : nullptr);
+        if (!ui->IsTextVisible()) return Device::NO_ACTION;
+        break;
+      }
+
+      case Device::APPLY_UPDATE:
       case Device::ENTER_RESCUE: {
         save_current_log = true;
 
@@ -464,18 +559,18 @@
           // Switch to graphics screen.
           ui->ShowText(false);
           status = ApplyFromAdb(device, true /* rescue_mode */, &reboot_action);
-        } else if (chosen_action == Device::APPLY_ADB_SIDELOAD) {
-          status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action);
-        } else {
-          adb = false;
-          status = ApplyFromSdcard(device);
+        } else if (chosen_action == Device::APPLY_UPDATE) {
+          status = apply_update_menu(device, &reboot_action);
         }
 
-        ui->Print("\nInstall from %s completed with status %d.\n", adb ? "ADB" : "SD card", status);
         if (status == INSTALL_REBOOT) {
           return reboot_action;
         }
+        if (status == INSTALL_NONE) {
+          update_in_progress = false;
+        }
 
+        ui->Print("\nInstall completed with status %d.\n", status);
         if (status == INSTALL_SUCCESS) {
           update_in_progress = false;
           if (!ui->IsTextVisible()) {
@@ -493,6 +588,14 @@
         choose_recovery_file(device);
         break;
 
+      case Device::ENABLE_ADB:
+        android::base::SetProperty("ro.adb.secure.recovery", "0");
+        android::base::SetProperty("ctl.restart", "adbd");
+        device->RemoveMenuItemForAction(Device::ENABLE_ADB);
+        device->GoHome();
+        ui->Print("Enabled ADB.\n");
+        break;
+
       case Device::RUN_GRAPHICS_TEST:
         run_graphics_test(ui);
         break;
@@ -503,16 +606,26 @@
         break;
       }
 
-      case Device::MOUNT_SYSTEM:
-        // For Virtual A/B, set up the snapshot devices (if exist).
-        if (!CreateSnapshotPartitions()) {
-          ui->Print("Virtual A/B: snapshot partitions creation failed.\n");
-          break;
-        }
-        if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) {
-          ui->Print("Mounted /system.\n");
+      case Device::MOUNT_SYSTEM: {
+        static bool mounted = false;
+        if (!mounted) {
+          // For Virtual A/B, set up the snapshot devices (if exist).
+          if (!logical_partitions_mapped() && !CreateSnapshotPartitions()) {
+            ui->Print("Virtual A/B: snapshot partitions creation failed.\n");
+            break;
+          }
+          if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) {
+            ui->Print("Mounted /mnt/system.\n");
+            mounted = true;
+          }
+        } else {
+          if (umount("/mnt/system") != -1) {
+            ui->Print("Unounted /mnt/system.\n");
+            mounted = false;
+          }
         }
         break;
+      }
 
       case Device::KEY_INTERRUPTED:
         return Device::KEY_INTERRUPTED;
@@ -694,6 +807,12 @@
 
   auto ui = device->GetUI();
 
+  VolumeClient* volclient = new VolumeClient(device);
+  VolumeManager* volmgr = VolumeManager::Instance();
+  if (!volmgr->start(volclient)) {
+    printf("Failed to start volume manager\n");
+  }
+
   // Set background string to "installing security update" for security update,
   // otherwise set it to "installing system update".
   ui->SetSystemUpdateText(security_update);
@@ -704,9 +823,16 @@
     ui->SetStage(st_cur, st_max);
   }
 
-  std::vector<std::string> title_lines =
-      android::base::Split(android::base::GetProperty("ro.build.fingerprint", ""), ":");
-  title_lines.insert(std::begin(title_lines), "Android Recovery");
+  std::vector<std::string> title_lines = {
+    "Version " + android::base::GetProperty("ro.leaf.build.version", "(unknown)") +
+        " (" + android::base::GetProperty("ro.leaf.build.date", "unknown") + ")",
+  };
+  title_lines.push_back("Product name - " + android::base::GetProperty("ro.product.device", ""));
+  if (android::base::GetBoolProperty("ro.build.ab_update", false)) {
+    std::string slot = android::base::GetProperty("ro.boot.slot_suffix", "");
+    if (android::base::StartsWith(slot, "_")) slot.erase(0, 1);
+    title_lines.push_back("Active slot: " + slot);
+  }
   ui->SetTitle(title_lines);
 
   ui->ResetKeyInterruptStatus();
@@ -807,7 +933,7 @@
     ui->ShowText(true);
     ui->SetBackground(RecoveryUI::ERROR);
     status = prompt_and_wipe_data(device);
-    if (status != INSTALL_KEY_INTERRUPTED) {
+    if (status == INSTALL_SUCCESS) {
       ui->ShowText(false);
     }
   } else if (should_wipe_cache) {
@@ -828,6 +954,7 @@
     if (!sideload_auto_reboot) {
       ui->ShowText(true);
     }
+    ui->SetSideloadAutoReboot(sideload_auto_reboot);
     status = ApplyFromAdb(device, false /* rescue_mode */, &next_action);
     ui->Print("\nInstall from ADB complete (status: %d).\n", status);
     if (sideload_auto_reboot) {
@@ -839,12 +966,10 @@
     status = ApplyFromAdb(device, true /* rescue_mode */, &next_action);
     ui->Print("\nInstall from ADB complete (status: %d).\n", status);
   } else if (!just_exit) {
-    // If this is an eng or userdebug build, automatically turn on the text display if no command
-    // is specified. Note that this should be called before setting the background to avoid
+    // Always show menu if no command is specified.
+    // Note that this should be called before setting the background to avoid
     // flickering the background image.
-    if (IsRoDebuggable()) {
-      ui->ShowText(true);
-    }
+    ui->ShowText(true);
     status = INSTALL_NONE;  // No command specified
     ui->SetBackground(RecoveryUI::NO_COMMAND);
   }
@@ -877,5 +1002,11 @@
   // Save logs and clean up before rebooting or shutting down.
   FinishRecovery(ui);
 
+  volmgr->unmountAll();
+  volmgr->stop();
+  delete volclient;
+
+  sync();
+
   return next_action;
 }
diff --git a/recovery.h b/recovery.h
index f050549..08bacbf 100644
--- a/recovery.h
+++ b/recovery.h
@@ -22,3 +22,5 @@
 #include "recovery_ui/device.h"
 
 Device::BuiltinAction start_recovery(Device* device, const std::vector<std::string>& args);
+
+std::string get_build_type();
diff --git a/recovery_main.cpp b/recovery_main.cpp
index 345d645..5e27419 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -73,6 +73,10 @@
   return "orange" == android::base::GetProperty("ro.boot.verifiedbootstate", "");
 }
 
+std::string get_build_type() {
+  return android::base::GetProperty("ro.build.type", "");
+}
+
 static void UiLogger(android::base::LogId log_buffer_id, android::base::LogSeverity severity,
                      const char* tag, const char* file, unsigned int line, const char* message) {
   android::base::KernelLogger(log_buffer_id, severity, tag, file, line, message);
@@ -376,7 +380,8 @@
         } else if (option == "reason") {
           reason = optarg;
         } else if (option == "fastboot" &&
-                   android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
+                   (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) ||
+                    android::base::GetBoolProperty("ro.fastbootd.available", false))) {
           fastboot = true;
         }
         break;
@@ -440,14 +445,33 @@
     device->RemoveMenuItemForAction(Device::WIPE_CACHE);
   }
 
-  if (!android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
+  if (android::base::GetBoolProperty("ro.build.ab_update", false)) {
+    // There's not much point in formatting the active slot's system partition
+    // because ROMs are flashed to the inactive slot. Removing the menu option
+    // prevents users from accidentally trashing a functioning ROM.
+    device->RemoveMenuItemForAction(Device::WIPE_SYSTEM);
+  }
+
+  if (!android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) &&
+      !android::base::GetBoolProperty("ro.fastbootd.available", false)) {
     device->RemoveMenuItemForAction(Device::ENTER_FASTBOOT);
   }
 
-  if (!IsRoDebuggable()) {
+  if (get_build_type() != "eng") {
+    device->RemoveMenuItemForAction(Device::RUN_GRAPHICS_TEST);
+    device->RemoveMenuItemForAction(Device::RUN_LOCALE_TEST);
     device->RemoveMenuItemForAction(Device::ENTER_RESCUE);
   }
 
+  if (get_build_type() != "userdebug") {
+    device->RemoveMenuItemForAction(Device::ENABLE_ADB);
+  }
+
+  if (get_build_type() == "user") {
+    device->RemoveMenuItemForAction(Device::WIPE_SYSTEM);
+    device->RemoveMenuItemForAction(Device::MOUNT_SYSTEM);
+  }
+
   ui->SetBackground(RecoveryUI::NONE);
   if (show_text) ui->ShowText(true);
 
@@ -466,6 +490,9 @@
   std::thread listener_thread(ListenRecoverySocket, ui, std::ref(action));
   listener_thread.detach();
 
+  // Enable root before starting ADB.
+  android::base::SetProperty("service.adb.root", "1");
+
   while (true) {
     // We start adbd in recovery for the device with userdebug build or a unlocked bootloader.
     std::string usb_config =
@@ -536,7 +563,7 @@
       }
 
       case Device::ENTER_FASTBOOT:
-        if (android::fs_mgr::LogicalPartitionsMapped()) {
+        if (logical_partitions_mapped()) {
           ui->Print("Partitions may be mounted - rebooting to enter fastboot.");
           Reboot("fastboot");
         } else {
@@ -548,6 +575,8 @@
       case Device::ENTER_RECOVERY:
         LOG(INFO) << "Entering recovery";
         fastboot = false;
+        ui->SetEnableFastbootdLogo(fastboot);
+        device->GoHome();
         break;
 
       case Device::REBOOT:
diff --git a/recovery_ui/Android.bp b/recovery_ui/Android.bp
index f64b0d1..497520f 100644
--- a/recovery_ui/Android.bp
+++ b/recovery_ui/Android.bp
@@ -43,14 +43,21 @@
     export_include_dirs: ["include"],
 
     static_libs: [
+        "libbatterymonitor",
+        "libhealthloop",
         "libminui",
         "libotautil",
     ],
 
     shared_libs: [
+        "android.hardware.health-V3-ndk",
         "libbase",
+        "libcutils",
+        "libhidlbase",
         "libpng",
+        "libutils",
         "libz",
+        "libvolume_manager",
     ],
 }
 
diff --git a/recovery_ui/device.cpp b/recovery_ui/device.cpp
index d46df92..e950ea6 100644
--- a/recovery_ui/device.cpp
+++ b/recovery_ui/device.cpp
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,50 +27,97 @@
 #include "otautil/boot_state.h"
 #include "recovery_ui/ui.h"
 
-static std::vector<std::pair<std::string, Device::BuiltinAction>> g_menu_actions{
+typedef std::pair<std::string, Device::BuiltinAction> menu_action_t;
+
+static std::vector<std::string> g_main_header{};
+static std::vector<menu_action_t> g_main_actions{
   { "Reboot system now", Device::REBOOT },
-  { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
+  { "Apply update", Device::APPLY_UPDATE },
+  { "Factory reset", Device::MENU_WIPE },
+  { "Advanced", Device::MENU_ADVANCED },
+};
+
+static std::vector<std::string> g_advanced_header{ "Advanced options" };
+static std::vector<menu_action_t> g_advanced_actions{
   { "Enter fastboot", Device::ENTER_FASTBOOT },
-  { "Apply update from ADB", Device::APPLY_ADB_SIDELOAD },
-  { "Apply update from SD card", Device::APPLY_SDCARD },
-  { "Wipe data/factory reset", Device::WIPE_DATA },
-  { "Wipe cache partition", Device::WIPE_CACHE },
-  { "Mount /system", Device::MOUNT_SYSTEM },
+  { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
+  { "Reboot to recovery", Device::REBOOT_RECOVERY },
+  { "Mount/unmount system", Device::MOUNT_SYSTEM },
   { "View recovery logs", Device::VIEW_RECOVERY_LOGS },
+  { "Enable ADB", Device::ENABLE_ADB },
   { "Run graphics test", Device::RUN_GRAPHICS_TEST },
   { "Run locale test", Device::RUN_LOCALE_TEST },
   { "Enter rescue", Device::ENTER_RESCUE },
   { "Power off", Device::SHUTDOWN },
 };
 
+static std::vector<std::string> g_wipe_header{ "Factory reset" };
+static std::vector<menu_action_t> g_wipe_actions{
+  { "Format data/factory reset", Device::WIPE_DATA },
+  { "Format cache partition", Device::WIPE_CACHE },
+  { "Format system partition", Device::WIPE_SYSTEM },
+};
+
+static std::vector<menu_action_t>* current_menu_ = &g_main_actions;
 static std::vector<std::string> g_menu_items;
 
 static void PopulateMenuItems() {
   g_menu_items.clear();
-  std::transform(g_menu_actions.cbegin(), g_menu_actions.cend(), std::back_inserter(g_menu_items),
+  std::transform(current_menu_->cbegin(), current_menu_->cend(), std::back_inserter(g_menu_items),
                  [](const auto& entry) { return entry.first; });
 }
 
 Device::Device(RecoveryUI* ui) : ui_(ui) {
+  ui->SetDevice(this);
   PopulateMenuItems();
 }
 
-void Device::RemoveMenuItemForAction(Device::BuiltinAction action) {
-  g_menu_actions.erase(
-      std::remove_if(g_menu_actions.begin(), g_menu_actions.end(),
-                     [action](const auto& entry) { return entry.second == action; }));
-  CHECK(!g_menu_actions.empty());
-
-  // Re-populate the menu items.
+void Device::GoHome() {
+  current_menu_ = &g_main_actions;
   PopulateMenuItems();
 }
 
+static void RemoveMenuItemForAction(std::vector<menu_action_t>& menu, Device::BuiltinAction action) {
+  menu.erase(
+      std::remove_if(menu.begin(), menu.end(),
+                     [action](const auto& entry) { return entry.second == action; }), menu.end());
+  CHECK(!menu.empty());
+}
+
+void Device::RemoveMenuItemForAction(Device::BuiltinAction action) {
+  ::RemoveMenuItemForAction(g_wipe_actions, action);
+  ::RemoveMenuItemForAction(g_advanced_actions, action);
+}
+
 const std::vector<std::string>& Device::GetMenuItems() {
   return g_menu_items;
 }
 
+const std::vector<std::string>& Device::GetMenuHeaders() {
+  if (current_menu_ == &g_wipe_actions)
+      return g_wipe_header;
+  if (current_menu_ == &g_advanced_actions)
+      return g_advanced_header;
+  return g_main_header;
+}
+
 Device::BuiltinAction Device::InvokeMenuItem(size_t menu_position) {
-  return g_menu_actions[menu_position].second;
+  Device::BuiltinAction action = (*current_menu_)[menu_position].second;
+
+  if (action > MENU_BASE) {
+    switch (action) {
+      case Device::BuiltinAction::MENU_WIPE:
+        current_menu_ = &g_wipe_actions;
+        break;
+      case Device::BuiltinAction::MENU_ADVANCED:
+        current_menu_ = &g_advanced_actions;
+        break;
+      default:
+        break;
+    }
+    PopulateMenuItems();
+  }
+  return action;
 }
 
 int Device::HandleMenuKey(int key, bool visible) {
@@ -78,18 +126,48 @@
   }
 
   switch (key) {
+    case KEY_RIGHTSHIFT:
     case KEY_DOWN:
     case KEY_VOLUMEDOWN:
+    case KEY_MENU:
+    case BTN_NORTH:
+    case BTN_DPAD_DOWN:
       return kHighlightDown;
 
     case KEY_UP:
     case KEY_VOLUMEUP:
+    case KEY_SEARCH:
+    case BTN_WEST:
+    case BTN_DPAD_UP:
       return kHighlightUp;
 
+    case KEY_SCROLLUP:
+      return kScrollUp;
+    case KEY_SCROLLDOWN:
+      return kScrollDown;
+
     case KEY_ENTER:
     case KEY_POWER:
+    case BTN_MOUSE:
+    case KEY_SEND:
+    case BTN_SOUTH:
+    case BTN_START:
       return kInvokeItem;
 
+    case KEY_HOME:
+    case KEY_HOMEPAGE:
+      return kGoHome;
+
+    case KEY_BACKSPACE:
+    case KEY_BACK:
+      return kGoBack;
+
+    case KEY_AGAIN:
+      return kDoSideload;
+
+    case KEY_REFRESH:
+      return kRefresh;
+
     default:
       // If you have all of the above buttons, any other buttons
       // are ignored. Otherwise, any button cycles the highlight.
diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h
index 76166f0..c2fc38d 100644
--- a/recovery_ui/include/recovery_ui/device.h
+++ b/recovery_ui/include/recovery_ui/device.h
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,8 +25,7 @@
 #include <string>
 #include <vector>
 
-// Forward declaration to avoid including "ui.h".
-class RecoveryUI;
+#include "ui.h"
 
 class BootState;
 
@@ -35,6 +35,12 @@
   static constexpr const int kHighlightUp = -2;
   static constexpr const int kHighlightDown = -3;
   static constexpr const int kInvokeItem = -4;
+  static constexpr const int kGoBack = -5;
+  static constexpr const int kGoHome = -6;
+  static constexpr const int kDoSideload = -7;
+  static constexpr const int kScrollUp = -8;
+  static constexpr const int kScrollDown = -9;
+  static constexpr const int kRefresh = -10;
 
   // ENTER vs REBOOT: The latter will trigger a reboot that goes through bootloader, which allows
   // using a new bootloader / recovery image if applicable. For example, REBOOT_RESCUE goes from
@@ -43,9 +49,9 @@
   enum BuiltinAction {
     NO_ACTION = 0,
     REBOOT = 1,
-    APPLY_SDCARD = 2,
+    APPLY_UPDATE = 2,
     // APPLY_CACHE was 3.
-    APPLY_ADB_SIDELOAD = 4,
+    // APPLY_ADB_SIDELOAD was 4.
     WIPE_DATA = 5,
     WIPE_CACHE = 6,
     REBOOT_BOOTLOADER = 7,
@@ -63,6 +69,11 @@
     REBOOT_RESCUE = 19,
     REBOOT_FROM_FASTBOOT = 20,
     SHUTDOWN_FROM_FASTBOOT = 21,
+    WIPE_SYSTEM = 100,
+    ENABLE_ADB = 101,
+    MENU_BASE = 200,
+    MENU_WIPE = 202,
+    MENU_ADVANCED = 203,
   };
 
   explicit Device(RecoveryUI* ui);
@@ -112,10 +123,16 @@
   //   - invoke a specific action (a menu position: non-negative value)
   virtual int HandleMenuKey(int key, bool visible);
 
-  // Returns the list of menu items (a vector of strings). The menu_position passed to
-  // InvokeMenuItem() will correspond to the indexes into this array.
+  // Returns the list of the currently visible menu items (a vector of strings).
+  // The menu_position passed to InvokeMenuItem() will correspond to the indexes into this array.
   virtual const std::vector<std::string>& GetMenuItems();
 
+  // Returns headers for the currently visible menu. Can be empty vector.
+  virtual const std::vector<std::string>& GetMenuHeaders();
+
+  // Return to the main menu
+  virtual void GoHome();
+
   // Performs a recovery action selected from the menu. 'menu_position' will be the index of the
   // selected menu item, or a non-negative value returned from HandleMenuKey(). The menu will be
   // hidden when this is called; implementations can call GetUI()->Print() to print information to
@@ -147,6 +164,10 @@
   std::optional<std::string> GetReason() const;
   std::optional<std::string> GetStage() const;
 
+  virtual void handleVolumeChanged() {
+    ui_->onVolumeChanged();
+  }
+
  private:
   // The RecoveryUI object that should be used to display the user interface for this device.
   std::unique_ptr<RecoveryUI> ui_;
diff --git a/recovery_ui/include/recovery_ui/screen_ui.h b/recovery_ui/include/recovery_ui/screen_ui.h
index 2e0fcc2..6551cbc 100644
--- a/recovery_ui/include/recovery_ui/screen_ui.h
+++ b/recovery_ui/include/recovery_ui/screen_ui.h
@@ -32,11 +32,13 @@
 class GRSurface;
 
 enum class UIElement {
+  BATTERY_LOW,
   HEADER,
   MENU,
   MENU_SEL_BG,
   MENU_SEL_BG_ACTIVE,
   MENU_SEL_FG,
+  SCROLLBAR,
   LOG,
   TEXT_FILL,
   INFO
@@ -56,6 +58,9 @@
   // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis.
   virtual int DrawHorizontalRule(int y) const = 0;
 
+  // Draws a vertical line from y to y + height, on the right of the screen.
+  virtual void DrawScrollBar(int y, int height) const = 0;
+
   // Draws a line of text. Returns the offset it should be moving along Y-axis.
   virtual int DrawTextLine(int x, int y, const std::string& line, bool bold) const = 0;
 
@@ -76,6 +81,11 @@
   // keeps symmetrical margins of 'x' at each end of a line. Returns the offset it should be moving
   // along Y-axis.
   virtual int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const = 0;
+
+  virtual int MenuCharHeight() const = 0;
+  virtual int MenuCharWidth() const = 0;
+  virtual int MenuItemPadding() const = 0;
+  virtual int MenuItemHeight() const = 0;
 };
 
 // Interface for classes that maintain the menu selection and display.
@@ -83,19 +93,27 @@
  public:
   virtual ~Menu() = default;
   // Returns the current menu selection.
-  size_t selection() const;
+  int selection() const;
   // Sets the current selection to |sel|. Handle the overflow cases depending on if the menu is
   // scrollable.
   virtual int Select(int sel) = 0;
+  // Select by index within the currently visible items.
+  // Matches Select() if not scrollable
+  virtual int SelectVisible(int relative_sel) = 0;
+  // Scroll the menu by updown, if scrollable
+  virtual int Scroll(int updown) = 0;
   // Displays the menu headers on the screen at offset x, y
   virtual int DrawHeader(int x, int y) const = 0;
   // Iterates over the menu items and displays each of them at offset x, y.
   virtual int DrawItems(int x, int y, int screen_width, bool long_press) const = 0;
+  virtual size_t ItemsCount() const = 0;
+  virtual bool IsMain() const = 0;
+  virtual void SetMenuHeight(int height) = 0;
 
  protected:
   Menu(size_t initial_selection, const DrawInterface& draw_func);
   // Current menu selection.
-  size_t selection_;
+  int selection_;
   // Reference to the class that implements all the draw functions.
   const DrawInterface& draw_funcs_;
 };
@@ -105,20 +123,25 @@
  public:
   // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial
   // selection to |initial_selection|.
-  TextMenu(bool scrollable, size_t max_items, size_t max_length,
+  TextMenu(bool wrappable, size_t max_length,
            const std::vector<std::string>& headers, const std::vector<std::string>& items,
            size_t initial_selection, int char_height, const DrawInterface& draw_funcs);
 
   int Select(int sel) override;
+  int SelectVisible(int relative_sel) override;
+  int Scroll(int updown) override;
   int DrawHeader(int x, int y) const override;
   int DrawItems(int x, int y, int screen_width, bool long_press) const override;
+  size_t ItemsCount() const override;
 
-  bool scrollable() const {
-    return scrollable_;
+  bool IsMain() const override {
+    // Main menus have no headers
+    return text_headers_.size() == 0;
   }
 
-  // Returns count of menu items.
-  size_t ItemsCount() const;
+  bool wrappable() const {
+    return wrappable_;
+  }
 
   // Returns the index of the first menu item.
   size_t MenuStart() const;
@@ -127,7 +150,7 @@
   size_t MenuEnd() const;
 
   // Menu example:
-  // info:                           Android Recovery
+  // info:                           Leaf Recovery
   //                                 ....
   // help messages:                  Swipe up/down to move
   //                                 Swipe left/right to select
@@ -144,11 +167,23 @@
   // |cur_selection_str| if the items exceed the screen limit.
   bool ItemsOverflow(std::string* cur_selection_str) const;
 
+  // The number of displayable items is only known after we started drawing the menu (to consider logo, header, etc.)
+  // Make it settable after the menu is created
+  void SetMenuHeight(int height) {
+    if (!calibrated_height_) {
+      max_display_items_ = height / draw_funcs_.MenuItemHeight();
+      menu_start_ = std::max(0, (int)selection_ - (int)max_display_items_ + 1);
+      calibrated_height_ = true;
+    }
+  }
+
  private:
   // The menu is scrollable to display more items. Used on wear devices who have smaller screens.
-  const bool scrollable_;
+  const bool wrappable_;
+  // Did we compute our max height already?
+  bool calibrated_height_;
   // The max number of menu items to fit vertically on a screen.
-  const size_t max_display_items_;
+  size_t max_display_items_;
   // The length of each item to fit horizontally on a screen.
   const size_t max_item_length_;
   // The menu headers.
@@ -171,8 +206,19 @@
               size_t initial_selection, const DrawInterface& draw_funcs);
 
   int Select(int sel) override;
+  int SelectVisible(int sel) override {
+    return Select(sel);
+  }
+  int Scroll(int updown __unused) override {
+    return selection_;
+  };
   int DrawHeader(int x, int y) const override;
   int DrawItems(int x, int y, int screen_width, bool long_press) const override;
+  size_t ItemsCount() const override;
+  bool IsMain() const override {
+    return true;
+  }
+  void SetMenuHeight(int height __unused) override {}
 
   // Checks if all the header and items are valid GRSurface's; and that they can fit in the area
   // defined by |max_width| and |max_height|.
@@ -189,12 +235,56 @@
   std::vector<std::unique_ptr<GRSurface>> graphic_items_;
 };
 
+class MenuDrawFunctions : public DrawInterface {
+ public:
+  MenuDrawFunctions(const DrawInterface& wrappee);
+  void SetColor(UIElement e) const override {
+    wrappee_.SetColor(e);
+  }
+  void DrawHighlightBar(int x, int y, int width, int height) const override {
+    wrappee_.DrawHighlightBar(x, y, width, height);
+  };
+  void DrawScrollBar(int y, int height) const override {
+    wrappee_.DrawScrollBar(y, height);
+  }
+  int DrawHorizontalRule(int y) const override {
+    return wrappee_.DrawHorizontalRule(y);
+  }
+  void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx,
+                   int dy) const override {
+    wrappee_.DrawSurface(surface, sx, sy, w, h, dx, dy);
+  }
+  void DrawFill(int x, int y, int w, int h) const override {
+    wrappee_.DrawFill(x, y, w, h);
+  }
+  void DrawTextIcon(int x, int y, const GRSurface* surface) const override {
+    wrappee_.DrawTextIcon(x, y, surface);
+  }
+  int MenuCharHeight() const override {
+    return wrappee_.MenuCharHeight();
+  };
+  int MenuCharWidth() const override {
+    return wrappee_.MenuCharWidth();
+  };
+  int MenuItemPadding() const override {
+    return wrappee_.MenuItemPadding();
+  };
+  int MenuItemHeight() const override {
+    return wrappee_.MenuItemHeight();
+  };
+  int DrawTextLine(int x, int y, const std::string& line, bool bold) const override;
+  int DrawTextLines(int x, int y, const std::vector<std::string>& lines) const override;
+  int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const override;
+
+ private:
+  const DrawInterface& wrappee_;
+};
+
 // Implementation of RecoveryUI appropriate for devices with a screen
 // (shows an icon + a progress bar, text logging, menu, etc.)
 class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
  public:
   ScreenRecoveryUI();
-  explicit ScreenRecoveryUI(bool scrollable_menu);
   ~ScreenRecoveryUI() override;
 
   bool Init(const std::string& locale) override;
@@ -224,7 +314,7 @@
   // menu display
   size_t 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) override;
+                  const std::function<int(int, bool)>& key_handler, bool refreshable) override;
   void SetTitle(const std::vector<std::string>& lines) override;
 
   void KeyLongPress(int) override;
@@ -248,8 +338,12 @@
   // For Lid switch handle
   int SetSwCallback(int code, int value) override;
 
+  int MenuItemHeight() const override {
+    return MenuCharHeight() + 2 * MenuItemPadding();
+  }
+
  protected:
-  static constexpr int kMenuIndent = 4;
+  static constexpr int kMenuIndent = 24;
 
   // The margin that we don't want to use for showing texts (e.g. round screen, or screen with
   // rounded corners).
@@ -262,6 +356,9 @@
   // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
   const float density_;
 
+  // Whether we should blank and unblank screen on init to workaround device specific issues
+  bool blank_unblank_on_init_;
+
   virtual bool InitTextParams();
 
   virtual bool LoadWipeDataMenuText();
@@ -283,16 +380,20 @@
 
   // Takes the ownership of |menu| and displays it.
   virtual size_t ShowMenu(std::unique_ptr<Menu>&& menu, bool menu_only,
-                          const std::function<int(int, bool)>& key_handler);
+                          const std::function<int(int, bool)>& key_handler,
+                          bool refreshable = false);
 
   // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item
   // selected.
   virtual int SelectMenu(int sel);
+  virtual int SelectMenu(const Point& point);
+  virtual int ScrollMenu(int updown);
 
   // 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_battery_capacity_locked();
   virtual void draw_foreground_locked();
   virtual void draw_screen_locked();
   virtual void draw_menu_and_text_buffer_locked(const std::vector<std::string>& help_message);
@@ -302,6 +403,7 @@
   const GRSurface* GetCurrentFrame() const;
   const GRSurface* GetCurrentText() const;
 
+  void BattMonitorThreadLoop();
   void ProgressThreadLoop();
 
   virtual void ShowFile(FILE*);
@@ -326,6 +428,7 @@
   // Implementation of the draw functions in DrawInterface.
   void SetColor(UIElement e) const override;
   void DrawHighlightBar(int x, int y, int width, int height) const override;
+  void DrawScrollBar(int y, int height) const override;
   int DrawHorizontalRule(int y) const override;
   void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx,
                    int dy) const override;
@@ -334,6 +437,17 @@
   int DrawTextLine(int x, int y, const std::string& line, bool bold) const override;
   int DrawTextLines(int x, int y, const std::vector<std::string>& lines) const override;
   int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const override;
+  int MenuCharHeight() const override {
+    return menu_char_height_;
+  }
+  int MenuCharWidth() const override {
+    return menu_char_width_;
+  }
+  int MenuItemPadding() const override {
+    return menu_char_height_;
+  }
+
+  std::unique_ptr<MenuDrawFunctions> menu_draw_funcs_;
 
   // The layout to use.
   int layout_;
@@ -351,6 +465,9 @@
   std::unique_ptr<GRSurface> wipe_data_confirmation_text_;
   std::unique_ptr<GRSurface> wipe_data_menu_header_text_;
 
+  std::unique_ptr<GRSurface> lineage_logo_;
+  std::unique_ptr<GRSurface> back_icon_;
+  std::unique_ptr<GRSurface> back_icon_sel_;
   std::unique_ptr<GRSurface> fastbootd_logo_;
 
   // current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by
@@ -387,8 +504,8 @@
 
   std::vector<std::string> title_lines_;
 
-  bool scrollable_menu_;
   std::unique_ptr<Menu> menu_;
+  int menu_start_y_;
 
   // An alternate text screen, swapped with 'text_' when we're viewing a log file.
   char** file_viewer_text_;
@@ -400,6 +517,8 @@
 
   int char_width_;
   int char_height_;
+  int menu_char_height_;
+  int menu_char_width_;
 
   // The locale that's used to show the rendered texts.
   std::string locale_;
@@ -407,6 +526,11 @@
 
   std::mutex updateMutex;
 
+  std::thread batt_monitor_thread_;
+  std::atomic<bool> batt_monitor_thread_stopped_{ false };
+  int32_t batt_capacity_;
+  bool charging_;
+
   // Switch the display to active one after graphics is ready
   bool is_graphics_available;
 
diff --git a/recovery_ui/include/recovery_ui/stub_ui.h b/recovery_ui/include/recovery_ui/stub_ui.h
index 49689ba..838f2fa 100644
--- a/recovery_ui/include/recovery_ui/stub_ui.h
+++ b/recovery_ui/include/recovery_ui/stub_ui.h
@@ -64,7 +64,8 @@
   size_t 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 */) override;
+                  const std::function<int(int, bool)>& /* key_handler */,
+                  bool /*refreshable*/) 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 c3bb03f..1ed0a4e 100644
--- a/recovery_ui/include/recovery_ui/ui.h
+++ b/recovery_ui/include/recovery_ui/ui.h
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,8 +28,55 @@
 #include <thread>
 #include <vector>
 
+class Device;
+
 static constexpr const char* DEFAULT_LOCALE = "en-US";
 
+/*
+ * Simple representation of a (x,y) coordinate with convenience operators
+ */
+class Point {
+ public:
+  Point() : x_(0), y_(0) {}
+  Point(int x, int y) : x_(x), y_(y) {}
+  int x() const {
+    return x_;
+  }
+  int y() const {
+    return y_;
+  }
+  void x(int x) {
+    x_ = x;
+  }
+  void y(int y) {
+    y_ = y;
+  }
+
+  bool operator==(const Point& rhs) const {
+    return (x() == rhs.x() && y() == rhs.y());
+  }
+  bool operator!=(const Point& rhs) const {
+    return !(*this == rhs);
+  }
+
+  Point operator+(const Point& rhs) const {
+    Point tmp;
+    tmp.x_ = x_ + rhs.x_;
+    tmp.y_ = y_ + rhs.y_;
+    return tmp;
+  }
+  Point operator-(const Point& rhs) const {
+    Point tmp;
+    tmp.x_ = x_ - rhs.x_;
+    tmp.y_ = y_ - rhs.y_;
+    return tmp;
+  }
+
+ private:
+  int x_;
+  int y_;
+};
+
 // Abstract class for controlling the user interface during recovery.
 class RecoveryUI {
  public:
@@ -58,10 +106,54 @@
     INTERRUPTED = -2,
   };
 
+  enum EventType {
+    EXTRA,
+    KEY,
+    TOUCH,
+  };
+
+  class InputEvent {
+   public:
+    InputEvent() : type_(EventType::EXTRA), evt_({ 0 }) {
+      evt_.key = static_cast<int>(KeyError::TIMED_OUT);
+    }
+    explicit InputEvent(EventType type, KeyError key)
+        : type_(type), evt_({ static_cast<int>(key) }) {}
+    explicit InputEvent(int key) : type_(EventType::KEY), evt_({ key }) {}
+    explicit InputEvent(const Point& pos) : type_(EventType::TOUCH), evt_({ 0 }) {
+      evt_.pos = pos;
+    }
+
+    EventType type() const {
+      return type_;
+    }
+    int key() const {
+      return evt_.key;
+    }
+    const Point& pos() const {
+      return evt_.pos;
+    }
+
+   private:
+    EventType type_;
+    union {
+      int key;
+      Point pos;
+    } evt_;
+  };
+
   RecoveryUI();
 
   virtual ~RecoveryUI();
 
+  void SetDevice(Device* device) {
+    device_ = device;
+  }
+
+  Device* GetDevice() {
+    return device_;
+  }
+
   // Initializes the object; called before anything else. UI texts will be initialized according
   // to the given locale. Returns true on success.
   virtual bool Init(const std::string& locale);
@@ -108,8 +200,9 @@
 
   // Waits for a key and return it. May return TIMED_OUT after timeout and
   // KeyError::INTERRUPTED on a key interrupt.
-  virtual int WaitKey();
+  virtual InputEvent WaitInputEvent();
 
+  virtual void CancelWaitKey();
   // Wakes up the UI if it is waiting on key input, causing WaitKey to return KeyError::INTERRUPTED.
   virtual void InterruptKey();
 
@@ -162,7 +255,8 @@
   // static_cast<size_t>(ERR_KEY_INTERTUPT) if interrupted, such as by InterruptKey().
   virtual size_t 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) = 0;
+                          bool menu_only, const std::function<int(int, bool)>& key_handler,
+                          bool refreshable = false) = 0;
 
   // Displays the localized wipe data menu with pre-generated graphs. If there's an issue
   // with the graphs, falls back to use the backup string headers and items instead. The initial
@@ -181,6 +275,10 @@
     return false;
   }
 
+  virtual int MenuItemHeight() const {
+    return 1;
+  }
+
   // Set whether or not the fastbootd logo is displayed.
   void SetEnableFastbootdLogo(bool enable) {
     fastbootd_logo_enabled_ = enable;
@@ -198,8 +296,22 @@
 
   virtual bool IsUsbConnected();
 
+  // Notify of volume state change
+  void onVolumeChanged() {
+    EnqueueKey(KEY_REFRESH);
+  }
+
+  bool IsSideloadAutoReboot() const {
+    return sideload_auto_reboot_;
+  }
+
+  void SetSideloadAutoReboot(bool sar) {
+    sideload_auto_reboot_ = sar;
+  }
+
  protected:
   void EnqueueKey(int key_code);
+  void EnqueueTouch(const Point& pos);
 
   // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% of
   // the max_brightness). Because the absolute values may vary across devices. These two values can
@@ -209,11 +321,13 @@
   std::string brightness_file_;
   std::string max_brightness_file_;
 
-  // Whether we should listen for touch inputs (default: false).
+  // Whether we should listen for touch inputs (default: true).
   bool touch_screen_allowed_;
 
   bool fastbootd_logo_enabled_;
 
+  bool sideload_auto_reboot_;
+
  private:
   enum class ScreensaverState {
     DISABLED,
@@ -222,12 +336,18 @@
     OFF,
   };
 
+  Device* device_;
+
   // The sensitivity when detecting a swipe.
   const int touch_low_threshold_;
   const int touch_high_threshold_;
 
+  void OnTouchDeviceDetected(int fd);
   void OnKeyDetected(int key_code);
-  void OnTouchDetected(int dx, int dy);
+  void CalibrateTouch(int fd);
+  void OnTouchPress();
+  void OnTouchTrack();
+  void OnTouchRelease();
   int OnInputEvent(int fd, uint32_t epevents);
   void ProcessKey(int key_code, int updown);
   void TimeKey(int key_code, int count);
@@ -238,10 +358,11 @@
   virtual int SetSwCallback(int code, int value) = 0;
 
   // Key event input queue
-  std::mutex key_queue_mutex;
-  std::condition_variable key_queue_cond;
+  std::mutex event_queue_mutex;
+  std::condition_variable event_queue_cond;
   bool key_interrupted_;
-  int key_queue[256], key_queue_len;
+  InputEvent event_queue[256];
+  int event_queue_len;
 
   // key press events
   std::mutex key_press_mutex;
@@ -259,14 +380,28 @@
   bool has_down_key;
   bool has_touch_screen;
 
+  struct vkey_t {
+    bool inside(const Point& p) const {
+      return (p.x() >= min_.x() && p.x() < max_.x() && p.y() >= min_.y() && p.y() < max_.y());
+    }
+
+    int keycode;
+    Point min_;
+    Point max_;
+  };
+
   // Touch event related variables. See the comments in RecoveryUI::OnInputEvent().
   int touch_slot_;
-  int touch_X_;
-  int touch_Y_;
-  int touch_start_X_;
-  int touch_start_Y_;
   bool touch_finger_down_;
-  bool touch_swiping_;
+  bool touch_saw_x_;
+  bool touch_saw_y_;
+  bool touch_reported_;
+  Point touch_pos_;
+  Point touch_start_;
+  Point touch_track_;
+  Point touch_max_;
+  Point touch_min_;
+  std::vector<vkey_t> virtual_keys_;
   bool is_bootreason_recovery_ui_;
 
   std::thread input_thread_;
diff --git a/recovery_ui/screen_ui.cpp b/recovery_ui/screen_ui.cpp
index ee3cbb1..dc0d197 100644
--- a/recovery_ui/screen_ui.cpp
+++ b/recovery_ui/screen_ui.cpp
@@ -37,12 +37,17 @@
 #include <unordered_map>
 #include <vector>
 
+#include <aidl/android/hardware/health/BatteryStatus.h>
+
 #include <android-base/chrono_utils.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
+#include <health/utils.h>
+#include <healthd/BatteryMonitor.h>
+
 #include "minui/minui.h"
 #include "otautil/paths.h"
 #include "recovery_ui/device.h"
@@ -63,24 +68,21 @@
 Menu::Menu(size_t initial_selection, const DrawInterface& draw_func)
     : selection_(initial_selection), draw_funcs_(draw_func) {}
 
-size_t Menu::selection() const {
+int Menu::selection() const {
   return selection_;
 }
 
-TextMenu::TextMenu(bool scrollable, size_t max_items, size_t max_length,
+TextMenu::TextMenu(bool wrappable, size_t max_length,
                    const std::vector<std::string>& headers, const std::vector<std::string>& items,
                    size_t initial_selection, int char_height, const DrawInterface& draw_funcs)
     : Menu(initial_selection, draw_funcs),
-      scrollable_(scrollable),
-      max_display_items_(max_items),
+      wrappable_(wrappable),
+      calibrated_height_(false),
       max_item_length_(max_length),
       text_headers_(headers),
-      menu_start_(0),
       char_height_(char_height) {
-  CHECK_LE(max_items, static_cast<size_t>(std::numeric_limits<int>::max()));
 
-  // It's fine to have more entries than text_rows_ if scrollable menu is supported.
-  size_t items_count = scrollable_ ? items.size() : std::min(items.size(), max_display_items_);
+  size_t items_count = items.size();
   for (size_t i = 0; i < items_count; ++i) {
     text_items_.emplace_back(items[i].substr(0, max_item_length_));
   }
@@ -111,12 +113,12 @@
 }
 
 bool TextMenu::ItemsOverflow(std::string* cur_selection_str) const {
-  if (!scrollable_ || ItemsCount() <= max_display_items_) {
+  if (ItemsCount() <= max_display_items_) {
     return false;
   }
 
   *cur_selection_str =
-      android::base::StringPrintf("Current item: %zu/%zu", selection_ + 1, ItemsCount());
+      android::base::StringPrintf("Current item: %d/%zu", selection_ + 1, ItemsCount());
   return true;
 }
 
@@ -125,32 +127,50 @@
   CHECK_LE(ItemsCount(), static_cast<size_t>(std::numeric_limits<int>::max()));
   int count = ItemsCount();
 
-  // Wraps the selection at boundary if the menu is not scrollable.
-  if (!scrollable_) {
-    if (sel < 0) {
-      selection_ = count - 1;
-    } else if (sel >= count) {
-      selection_ = 0;
-    } else {
-      selection_ = sel;
-    }
+  int min = IsMain() ? 0 : -1; // -1 is back arrow
 
-    return selection_;
-  }
-
-  if (sel < 0) {
-    selection_ = 0;
+  if (sel < min) {
+    selection_ = wrappable() ? count - 1 : min;
   } else if (sel >= count) {
-    selection_ = count - 1;
+    selection_ = wrappable() ? min : count - 1;
   } else {
-    if (static_cast<size_t>(sel) < menu_start_) {
-      menu_start_--;
-    } else if (static_cast<size_t>(sel) >= MenuEnd()) {
-      menu_start_++;
-    }
     selection_ = sel;
   }
 
+  if (selection_ >= 0) {
+    if (selection_ < menu_start_) {
+      menu_start_ = selection_;
+    } else if (static_cast<size_t>(selection_) >= MenuEnd()) {
+      menu_start_ = selection_ - max_display_items_ + 1;
+    }
+  }
+
+  return selection_;
+}
+
+int TextMenu::SelectVisible(int relative_sel) {
+  int sel = relative_sel;
+  if (menu_start_ > 0) {
+    sel += menu_start_;
+  }
+
+  return Select(sel);
+}
+
+int TextMenu::Scroll(int updown) {
+  if ((updown > 0 && menu_start_ + max_display_items_ < ItemsCount()) ||
+      (updown < 0 && menu_start_ > 0)) {
+    menu_start_ += updown;
+
+    /* We can receive a kInvokeItem event from a different source than touch,
+       like from Power button. For this reason, selection should not get out of
+       the screen. Constrain it to the first or last visible item of the list */
+    if (selection_ < menu_start_) {
+      selection_ = menu_start_;
+    } else if (selection_ >= menu_start_ + max_display_items_) {
+      selection_ = menu_start_ + max_display_items_ - 1;
+    }
+  }
   return selection_;
 }
 
@@ -158,48 +178,46 @@
   int offset = 0;
 
   draw_funcs_.SetColor(UIElement::HEADER);
-  if (!scrollable()) {
-    offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers());
-  } else {
-    offset += draw_funcs_.DrawTextLines(x, y + offset, text_headers());
-    // Show the current menu item number in relation to total number if items don't fit on the
-    // screen.
-    std::string cur_selection_str;
-    if (ItemsOverflow(&cur_selection_str)) {
-      offset += draw_funcs_.DrawTextLine(x, y + offset, cur_selection_str, true);
-    }
-  }
+  offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers());
 
   return offset;
 }
 
 int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const {
   int offset = 0;
+  int padding = draw_funcs_.MenuItemPadding();
 
   draw_funcs_.SetColor(UIElement::MENU);
-  // Do not draw the horizontal rule for wear devices.
-  if (!scrollable()) {
-    offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
-  }
+  offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
+
+  int item_container_offset = offset; // store it for drawing scrollbar on most top
+
   for (size_t i = MenuStart(); i < MenuEnd(); ++i) {
-    bool bold = false;
     if (i == selection()) {
       // Draw the highlight bar.
       draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG);
 
-      int bar_height = char_height_ + 4;
-      draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height);
+      int bar_height = padding + char_height_ + padding;
+      draw_funcs_.DrawHighlightBar(0, y + offset, screen_width, bar_height);
 
-      // Bold white text for the selected item.
+      // Colored text for the selected item.
       draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
-      bold = true;
     }
-    offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), bold);
+    offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), false /* bold */);
 
     draw_funcs_.SetColor(UIElement::MENU);
   }
   offset += draw_funcs_.DrawHorizontalRule(y + offset);
 
+  std::string unused;
+  if (ItemsOverflow(&unused)) {
+    int container_height = max_display_items_ * (2 * padding + char_height_);
+    int bar_height = container_height / (text_items_.size() - max_display_items_ + 1);
+    int start_y = y + item_container_offset + bar_height * menu_start_;
+    draw_funcs_.SetColor(UIElement::SCROLLBAR);
+    draw_funcs_.DrawScrollBar(start_y, bar_height);
+  }
+
   return offset;
 }
 
@@ -263,6 +281,10 @@
   return offset;
 }
 
+size_t GraphicMenu::ItemsCount() const {
+  return graphic_items_.size();
+}
+
 bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers,
                            const std::vector<const GRSurface*>& graphic_items) {
   int offset = 0;
@@ -305,13 +327,61 @@
   return true;
 }
 
-ScreenRecoveryUI::ScreenRecoveryUI() : ScreenRecoveryUI(false) {}
+MenuDrawFunctions::MenuDrawFunctions(const DrawInterface& wrappee)
+    : wrappee_(wrappee) {
+}
+
+int MenuDrawFunctions::DrawTextLine(int x, int y, const std::string& line, bool bold) const {
+  gr_text(gr_menu_font(), x, y + MenuItemPadding(), line.c_str(), bold);
+  return 2 * MenuItemPadding() + MenuCharHeight();
+}
+
+int MenuDrawFunctions::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const {
+  int offset = 0;
+  for (const auto& line : lines) {
+    offset += DrawTextLine(x, y + offset, line, false);
+  }
+  return offset;
+}
+
+int MenuDrawFunctions::DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const {
+  const int padding = MenuItemPadding() / 2;
+
+  // Keep symmetrical margins based on the given offset (i.e. x).
+  size_t text_cols = (gr_fb_width() - x * 2) / MenuCharWidth();
+  int offset = 0;
+  for (const auto& line : lines) {
+    size_t next_start = 0;
+    while (next_start < line.size()) {
+      std::string sub = line.substr(next_start, text_cols + 1);
+      if (sub.size() <= text_cols) {
+        next_start += sub.size();
+      } else {
+        // Line too long and must be wrapped to text_cols columns.
+        size_t last_space = sub.find_last_of(" \t\n");
+        if (last_space == std::string::npos) {
+          // No space found, just draw as much as we can.
+          sub.resize(text_cols);
+          next_start += text_cols;
+        } else {
+          sub.resize(last_space);
+          next_start += last_space + 1;
+        }
+      }
+      offset += DrawTextLine(x, y + offset, sub, false) - (2 * MenuItemPadding() - padding);
+    }
+  }
+  if (!lines.empty()) {
+    offset += 2 * MenuItemPadding() - padding;
+  }
+  return offset;
+}
 
 constexpr int kDefaultMarginHeight = 0;
 constexpr int kDefaultMarginWidth = 0;
 constexpr int kDefaultAnimationFps = 30;
 
-ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu)
+ScreenRecoveryUI::ScreenRecoveryUI()
     : margin_width_(
           android::base::GetIntProperty("ro.recovery.ui.margin_width", kDefaultMarginWidth)),
       margin_height_(
@@ -319,6 +389,8 @@
       animation_fps_(
           android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)),
       density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
+      blank_unblank_on_init_(
+          android::base::GetBoolProperty("ro.recovery.ui.blank_unblank_on_init", false)),
       current_icon_(NONE),
       current_frame_(0),
       intro_done_(false),
@@ -334,15 +406,21 @@
       text_row_(0),
       show_text(false),
       show_text_ever(false),
-      scrollable_menu_(scrollable_menu),
       file_viewer_text_(nullptr),
       stage(-1),
       max_stage(-1),
       locale_(""),
       rtl_locale_(false),
+      batt_capacity_(0),
+      charging_(false),
       is_graphics_available(false) {}
 
 ScreenRecoveryUI::~ScreenRecoveryUI() {
+  batt_monitor_thread_stopped_ = true;
+  if (batt_monitor_thread_.joinable()) {
+    batt_monitor_thread_.join();
+  }
+
   progress_thread_stopped_ = true;
   if (progress_thread_.joinable()) {
     progress_thread_.join();
@@ -498,23 +576,48 @@
   }
 }
 
+/* recovery dark:  #33AC33
+   recovery light: #90EE90
+   fastbootd dark: #E65100
+   fastboot light: #FDD835 */
 void ScreenRecoveryUI::SetColor(UIElement e) const {
   switch (e) {
+    case UIElement::BATTERY_LOW:
+      if (fastbootd_logo_enabled_)
+        gr_color(0xfd, 0x35, 0x35, 255);
+      else
+        gr_color(0xc7, 0x15, 0x85, 255);
+      break;
     case UIElement::INFO:
-      gr_color(249, 194, 0, 255);
+      if (fastbootd_logo_enabled_)
+        gr_color(0xfd, 0xd8, 0x35, 255);
+      else
+        gr_color(0x90, 0xee, 0x90, 255);
       break;
     case UIElement::HEADER:
-      gr_color(247, 0, 6, 255);
+      if (fastbootd_logo_enabled_)
+        gr_color(0xfd, 0xd8,0x35, 255);
+      else
+        gr_color(0x90, 0xee, 0x90, 255);
       break;
     case UIElement::MENU:
+      gr_color(0xd8, 0xd8, 0xd8, 255);
+      break;
     case UIElement::MENU_SEL_BG:
-      gr_color(0, 106, 157, 255);
+    case UIElement::SCROLLBAR:
+      if (fastbootd_logo_enabled_)
+        gr_color(0xe6, 0x51, 0x00, 255);
+      else
+        gr_color(0x33, 0xac, 0x33, 255);
       break;
     case UIElement::MENU_SEL_BG_ACTIVE:
       gr_color(0, 156, 100, 255);
       break;
     case UIElement::MENU_SEL_FG:
-      gr_color(255, 255, 255, 255);
+      if (fastbootd_logo_enabled_)
+        gr_color(0, 0, 0, 255);
+      else
+        gr_color(0xd8, 0xd8, 0xd8, 255);
       break;
     case UIElement::LOG:
       gr_color(196, 196, 196, 255);
@@ -590,16 +693,20 @@
 
   FlushKeys();
   while (true) {
-    int key = WaitKey();
-    if (key == static_cast<int>(KeyError::INTERRUPTED)) break;
-    if (key == KEY_POWER || key == KEY_ENTER) {
-      break;
-    } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
-      selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
-      SelectAndShowBackgroundText(locales_entries, selected);
-    } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) {
-      selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
-      SelectAndShowBackgroundText(locales_entries, selected);
+    InputEvent evt = WaitInputEvent();
+    if (evt.type() == EventType::EXTRA) {
+      if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) break;
+    }
+    if (evt.type() == EventType::KEY) {
+      if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER) {
+        break;
+      } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP) {
+        selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
+        SelectAndShowBackgroundText(locales_entries, selected);
+      } else if (evt.key() == KEY_DOWN || evt.key() == KEY_VOLUMEDOWN) {
+        selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
+        SelectAndShowBackgroundText(locales_entries, selected);
+      }
     }
   }
 
@@ -625,9 +732,17 @@
 }
 
 void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const {
+  if (y + height > ScreenHeight())
+    height = ScreenHeight() - y;
   gr_fill(x, y, x + width, y + height);
 }
 
+void ScreenRecoveryUI::DrawScrollBar(int y, int height) const {
+  int x = ScreenWidth() - margin_width_;
+  int width = 8;
+  gr_fill(x - width, y, x, y + height);
+}
+
 void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const {
   gr_fill(x, y, w, h);
 }
@@ -685,14 +800,16 @@
 std::vector<std::string> ScreenRecoveryUI::GetMenuHelpMessage() const {
   // clang-format off
   static std::vector<std::string> REGULAR_HELP{
-    "Use volume up/down and power.",
+    "Use the volume up/down keys to navigate.",
+    "Use the power key to select.",
   };
   static std::vector<std::string> LONG_PRESS_HELP{
     "Any button cycles highlight.",
     "Long-press activates.",
   };
+  static const std::vector<std::string> NO_HELP = {};
   // clang-format on
-  return HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP;
+  return HasTouchScreen() ? NO_HELP : HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP;
 }
 
 // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
@@ -708,6 +825,7 @@
   gr_clear();
 
   draw_menu_and_text_buffer_locked(GetMenuHelpMessage());
+  draw_battery_capacity_locked();
 }
 
 // Draws the menu and text buffer on the screen. Should only be called with updateMutex locked.
@@ -715,28 +833,37 @@
     const std::vector<std::string>& help_message) {
   int y = margin_height_;
 
-  if (fastbootd_logo_ && fastbootd_logo_enabled_) {
-    // Try to get this centered on screen.
-    auto width = gr_get_width(fastbootd_logo_.get());
-    auto height = gr_get_height(fastbootd_logo_.get());
-    auto centered_x = ScreenWidth() / 2 - width / 2;
-    DrawSurface(fastbootd_logo_.get(), 0, 0, width, height, centered_x, y);
-    y += height;
-  }
-
   if (menu_) {
-    int x = margin_width_ + kMenuIndent;
+    auto& logo = fastbootd_logo_enabled_ ? fastbootd_logo_ : lineage_logo_;
+    auto logo_width = gr_get_width(logo.get());
+    auto logo_height = gr_get_height(logo.get());
+    auto centered_x = ScreenWidth() / 2 - logo_width / 2;
+    DrawSurface(logo.get(), 0, 0, logo_width, logo_height, centered_x, y);
+    y += logo_height;
 
-    SetColor(UIElement::INFO);
-
-    for (size_t i = 0; i < title_lines_.size(); i++) {
-      y += DrawTextLine(x, y, title_lines_[i], i == 0);
+    if (!menu_->IsMain()) {
+      auto icon_w = gr_get_width(back_icon_.get());
+      auto icon_h = gr_get_height(back_icon_.get());
+      auto icon_x = centered_x / 2 - icon_w / 2;
+      auto icon_y = y - logo_height / 2 - icon_h / 2;
+      gr_blit(back_icon_sel_ && menu_->selection() == -1 ? back_icon_sel_.get() : back_icon_.get(),
+              0, 0, icon_w, icon_h, icon_x, icon_y);
     }
 
-    y += DrawTextLines(x, y, help_message);
-
+    int x = margin_width_ + kMenuIndent;
+    if (!title_lines_.empty()) {
+      SetColor(UIElement::INFO);
+      y += DrawTextLines(x, y, title_lines_);
+    }
     y += menu_->DrawHeader(x, y);
+    menu_start_y_ = y + 12; // Skip horizontal rule and some margin
+    menu_->SetMenuHeight(std::max(0, ScreenHeight() - menu_start_y_));
     y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress());
+    if (!help_message.empty()) {
+      y += MenuItemPadding();
+      SetColor(UIElement::INFO);
+      y += DrawTextLines(x, y, help_message);
+    }
   }
 
   // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
@@ -752,6 +879,60 @@
   }
 }
 
+// Draws the battery capacity on the screen. Should only be called with updateMutex locked.
+void ScreenRecoveryUI::draw_battery_capacity_locked() {
+  int x;
+  int y = gr_get_height(lineage_logo_.get());
+  int icon_x, icon_y, icon_h, icon_w;
+
+  // Battery status
+  std::string batt_capacity = std::to_string(batt_capacity_) + '%';
+
+  if (charging_)
+    batt_capacity.push_back('+');
+  else if (batt_capacity.back() == '+')
+    batt_capacity.pop_back();
+
+  if (menu_) {
+    // Battery icon
+    x = (ScreenWidth() - margin_width_ * 2 - kMenuIndent) - char_width_;
+
+    SetColor(UIElement::INFO);
+
+    // Top
+    icon_x = x + char_width_ / 3;
+    icon_y = y;
+    icon_w = char_width_ / 3;
+    icon_h = char_height_ / 12;
+    gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h);
+
+    // Main rect
+    icon_x = x;
+    icon_y = y + icon_h;
+    icon_w = char_width_;
+    icon_h = char_height_ - (char_height_ / 12);
+    gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h);
+
+    // Capacity
+    if (batt_capacity_ <= 15) SetColor(UIElement::BATTERY_LOW);
+    icon_x = x + char_width_ / 6;
+    icon_y = y + char_height_ / 12;
+    icon_w = char_width_ - (2 * char_width_ / 6);
+    icon_h = char_height_ - (3 * char_height_ / 12);
+    int cap_h = icon_h * batt_capacity_ / 100;
+    gr_fill(icon_x, icon_y + icon_h - cap_h, icon_x + icon_w, icon_y + icon_h);
+    gr_color(0, 0, 0, 255);
+    gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h - cap_h);
+
+    x -= char_width_;  // Separator
+
+    // Battery text
+    SetColor(UIElement::INFO);
+    x -= batt_capacity.size() * char_width_;
+    DrawTextLine(x, icon_y, batt_capacity.c_str(), false);
+  }
+}
+
 // Redraw everything on the screen and flip the screen (make it visible).
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::update_screen_locked() {
@@ -771,6 +952,51 @@
   gr_flip();
 }
 
+void ScreenRecoveryUI::BattMonitorThreadLoop() {
+  using aidl::android::hardware::health::BatteryStatus;
+  using android::hardware::health::InitHealthdConfig;
+
+  auto config = std::make_unique<healthd_config>();
+  InitHealthdConfig(config.get());
+
+  auto batt_monitor = std::make_unique<android::BatteryMonitor>();
+  batt_monitor->init(config.get());
+
+  while (!batt_monitor_thread_stopped_) {
+    bool redraw = false;
+    {
+      std::lock_guard<std::mutex> lg(updateMutex);
+
+      auto charge_status = static_cast<BatteryStatus>(batt_monitor->getChargeStatus());
+      // Treat unknown status as on charger.
+      bool charging = (charge_status != BatteryStatus::DISCHARGING &&
+                       charge_status != BatteryStatus::NOT_CHARGING &&
+                       charge_status != BatteryStatus::FULL);
+      if (charging_ != charging) {
+        charging_ = charging;
+        redraw = true;
+      }
+
+      android::BatteryProperty prop;
+      android::status_t status = batt_monitor->getProperty(android::BATTERY_PROP_CAPACITY, &prop);
+      // 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 (status != android::OK) {
+        prop.valueInt64 = 100;
+      }
+
+      int32_t batt_capacity = static_cast<int32_t>(prop.valueInt64);
+      if (batt_capacity_ != batt_capacity) {
+        batt_capacity_ = batt_capacity;
+        redraw = true;
+      }
+
+      if (redraw) update_screen_locked();
+    }
+    std::this_thread::sleep_for(5s);
+  }
+}
+
 void ScreenRecoveryUI::ProgressThreadLoop() {
   double interval = 1.0 / animation_fps_;
   while (!progress_thread_stopped_) {
@@ -834,17 +1060,12 @@
   if (result == 0) {
     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;
 }
 
@@ -873,6 +1094,7 @@
     return false;
   }
   gr_font_size(gr_sys_font(), &char_width_, &char_height_);
+  gr_font_size(gr_menu_font(), &menu_char_width_, &menu_char_height_);
   text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_;
   text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_;
   return true;
@@ -917,6 +1139,12 @@
   if (!InitTextParams()) {
     return false;
   }
+  menu_draw_funcs_ = std::make_unique<MenuDrawFunctions>(*this);
+
+  if (blank_unblank_on_init_) {
+    gr_fb_blank(true);
+    gr_fb_blank(false);
+  }
 
   // Are we portrait or landscape?
   layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
@@ -942,8 +1170,14 @@
   no_command_text_ = LoadLocalizedBitmap("no_command_text");
   error_text_ = LoadLocalizedBitmap("error_text");
 
-  if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
+  back_icon_ = LoadBitmap("ic_back");
+  back_icon_sel_ = LoadBitmap("ic_back_sel");
+  if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) ||
+      android::base::GetBoolProperty("ro.fastbootd.available", false)) {
+    lineage_logo_ = LoadBitmap("logo_image_switch");
     fastbootd_logo_ = LoadBitmap("fastbootd");
+  } else {
+    lineage_logo_ = LoadBitmap("logo_image");
   }
 
   // Background text for "installing_update" could be "installing update" or
@@ -954,6 +1188,9 @@
 
   LoadAnimation();
 
+  // Keep the battery capacity updated.
+  batt_monitor_thread_ = std::thread(&ScreenRecoveryUI::BattMonitorThreadLoop, this);
+
   // Keep the progress bar updated, even when the process is otherwise busy.
   progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this);
 
@@ -1129,11 +1366,20 @@
       Redraw();
       while (show_prompt) {
         show_prompt = false;
-        int key = WaitKey();
-        if (key == static_cast<int>(KeyError::INTERRUPTED)) return;
-        if (key == KEY_POWER || key == KEY_ENTER) {
+        InputEvent evt = WaitInputEvent();
+        if (evt.type() == EventType::EXTRA) {
+          if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) {
+            return;
+          }
+        }
+        if (evt.type() != EventType::KEY) {
+          show_prompt = true;
+          continue;
+        }
+        if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER || evt.key() == KEY_BACKSPACE ||
+            evt.key() == KEY_BACK || evt.key() == KEY_HOME || evt.key() == KEY_HOMEPAGE) {
           return;
-        } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
+        } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP || evt.key() == KEY_SCROLLUP) {
           if (offsets.size() <= 1) {
             show_prompt = true;
           } else {
@@ -1206,14 +1452,12 @@
 std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(const std::vector<std::string>& text_headers,
                                                    const std::vector<std::string>& text_items,
                                                    size_t initial_selection) const {
-  if (text_rows_ > 0 && text_cols_ > 1) {
-    return std::make_unique<TextMenu>(scrollable_menu_, text_rows_, text_cols_ - 1, text_headers,
-                                      text_items, initial_selection, char_height_, *this);
-  }
-
-  fprintf(stderr, "Failed to create text menu, text_rows %zu, text_cols %zu.\n", text_rows_,
-          text_cols_);
-  return nullptr;
+  int menu_char_width = MenuCharWidth();
+  int menu_char_height = MenuCharHeight();
+  int menu_cols = (ScreenWidth() - margin_width_*2 - kMenuIndent) / menu_char_width;
+  bool wrap_selection = !HasThreeButtons() && !HasTouchScreen();
+  return std::make_unique<TextMenu>(wrap_selection, menu_cols, text_headers, text_items,
+                                    initial_selection, menu_char_height, *menu_draw_funcs_);
 }
 
 int ScreenRecoveryUI::SelectMenu(int sel) {
@@ -1229,8 +1473,81 @@
   return sel;
 }
 
+int ScreenRecoveryUI::SelectMenu(const Point& p) {
+  Point point;
+
+  const auto scaleX = static_cast<double>(p.x()) / ScreenWidth();
+  const auto scaleY = static_cast<double>(p.y()) / ScreenHeight();
+
+  // Correct position for touch rotation
+  switch (gr_touch_rotation()) {
+    case GRRotation::NONE:
+      point.x(ScreenWidth() * scaleX);
+      point.y(ScreenHeight() * scaleY);
+      break;
+    case GRRotation::RIGHT:
+      point.x(ScreenWidth() * scaleY);
+      point.y(ScreenHeight() - (ScreenHeight() * scaleX));
+      break;
+    case GRRotation::DOWN:
+      point.x(ScreenWidth() - (ScreenWidth() * scaleX));
+      point.y(ScreenHeight() - (ScreenHeight() * scaleY));
+      break;
+    case GRRotation::LEFT:
+      point.x(ScreenWidth() - (ScreenWidth() * scaleY));
+      point.y(ScreenHeight() * scaleX);
+      break;
+  }
+
+  // Correct position for overscan
+  point.x(point.x() - gr_overscan_offset_x());
+  point.y(point.y() - gr_overscan_offset_y());
+
+  int new_sel = Device::kNoAction;
+  std::lock_guard<std::mutex> lg(updateMutex);
+  if (menu_) {
+    if (!menu_->IsMain()) {
+      // Back arrow hitbox
+      const static int logo_width = gr_get_width(lineage_logo_.get());
+      const static int logo_height = gr_get_height(lineage_logo_.get());
+      const static int icon_w = gr_get_width(back_icon_.get());
+      const static int icon_h = gr_get_height(back_icon_.get());
+      const static int centered_x = ScreenWidth() / 2 - logo_width / 2;
+      const static int icon_x = centered_x / 2 - icon_w / 2;
+      const static int icon_y = margin_height_ + logo_height / 2 - icon_h / 2;
+
+      if (point.x() >= icon_x && point.x() <= icon_x + icon_w &&
+          point.y() >= icon_y && point.y() <= icon_y + icon_h) {
+        return Device::kGoBack;
+      }
+    }
+
+    if (point.y() >= menu_start_y_ &&
+        point.y() < menu_start_y_ + menu_->ItemsCount() * MenuItemHeight()) {
+      int old_sel = menu_->selection();
+      int relative_sel = (point.y() - menu_start_y_) / MenuItemHeight();
+      new_sel = menu_->SelectVisible(relative_sel);
+      if (new_sel != -1 && new_sel != old_sel) {
+        update_screen_locked();
+      }
+    }
+  }
+  return new_sel;
+}
+
+int ScreenRecoveryUI::ScrollMenu(int updown) {
+  std::lock_guard<std::mutex> lg(updateMutex);
+  int sel = Device::kNoAction;
+  if (menu_) {
+    sel = menu_->Scroll(updown);
+    update_screen_locked();
+  }
+  return sel;
+}
+
 size_t ScreenRecoveryUI::ShowMenu(std::unique_ptr<Menu>&& menu, bool menu_only,
-                                  const std::function<int(int, bool)>& key_handler) {
+                                  const std::function<int(int, bool)>& key_handler,
+                                  bool refreshable) {
   // Throw away keys pressed previously, so user doesn't accidentally trigger menu items.
   FlushKeys();
 
@@ -1247,23 +1564,38 @@
   int selected = menu_->selection();
   int chosen_item = -1;
   while (chosen_item < 0) {
-    int key = WaitKey();
-    if (key == static_cast<int>(KeyError::INTERRUPTED)) {  // WaitKey() was interrupted.
-      return static_cast<size_t>(KeyError::INTERRUPTED);
-    }
-    if (key == static_cast<int>(KeyError::TIMED_OUT)) {  // WaitKey() timed out.
-      if (WasTextEverVisible()) {
-        continue;
-      } else {
-        LOG(INFO) << "Timed out waiting for key input; rebooting.";
-        menu_.reset();
-        Redraw();
-        return static_cast<size_t>(KeyError::TIMED_OUT);
+    InputEvent evt = WaitInputEvent();
+    if (evt.type() == EventType::EXTRA) {
+      if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) {
+        // WaitKey() was interrupted.
+        return static_cast<size_t>(KeyError::INTERRUPTED);
+      }
+      if (evt.key() == static_cast<int>(KeyError::TIMED_OUT)) {  // WaitKey() timed out.
+        if (WasTextEverVisible()) {
+          continue;
+        } else {
+          LOG(INFO) << "Timed out waiting for key input; rebooting.";
+          menu_.reset();
+          Redraw();
+          return static_cast<size_t>(KeyError::TIMED_OUT);
+        }
       }
     }
 
-    bool visible = IsTextVisible();
-    int action = key_handler(key, visible);
+    int action = Device::kNoAction;
+    if (evt.type() == EventType::TOUCH) {
+      int touch_sel = SelectMenu(evt.pos());
+      if (touch_sel < 0) {
+        action = touch_sel;
+      } else {
+        action = Device::kInvokeItem;
+        selected = touch_sel;
+      }
+    } else {
+      bool visible = IsTextVisible();
+      action = key_handler(evt.key(), visible);
+    }
+
     if (action < 0) {
       switch (action) {
         case Device::kHighlightUp:
@@ -1272,19 +1604,47 @@
         case Device::kHighlightDown:
           selected = SelectMenu(++selected);
           break;
+        case Device::kScrollUp:
+          selected = ScrollMenu(-1);
+          break;
+        case Device::kScrollDown:
+          selected = ScrollMenu(1);
+          break;
         case Device::kInvokeItem:
-          chosen_item = selected;
+          if (selected < 0) {
+            chosen_item = Device::kGoBack;
+          } else {
+            chosen_item = selected;
+          }
           break;
         case Device::kNoAction:
           break;
+        case Device::kGoBack:
+          chosen_item = Device::kGoBack;
+          break;
+        case Device::kGoHome:
+          chosen_item = Device::kGoHome;
+          break;
+        case Device::kDoSideload:
+          chosen_item = Device::kDoSideload;
+          break;
+        case Device::kRefresh:
+          if (refreshable) {
+            chosen_item = Device::kRefresh;
+          }
+          break;
       }
     } else if (!menu_only) {
       chosen_item = action;
     }
+
+    if (chosen_item == Device::kGoBack || chosen_item == Device::kGoHome ||
+        chosen_item == Device::kDoSideload || chosen_item == Device::kRefresh) {
+      break;
+    }
   }
 
   menu_.reset();
-  Redraw();
 
   return chosen_item;
 }
@@ -1292,21 +1652,20 @@
 size_t ScreenRecoveryUI::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) {
+                                  const std::function<int(int, bool)>& key_handler,
+                                  bool refreshable) {
   auto menu = CreateMenu(headers, items, initial_selection);
   if (menu == nullptr) {
     return initial_selection;
   }
 
-  return ShowMenu(std::move(menu), menu_only, key_handler);
+  return ShowMenu(std::move(menu), menu_only, key_handler, refreshable);
 }
 
 size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers,
                                                 const std::vector<std::string>& backup_items,
                                                 const std::function<int(int, bool)>& key_handler) {
-  auto wipe_data_menu = CreateMenu(wipe_data_menu_header_text_.get(),
-                                   { try_again_text_.get(), factory_data_reset_text_.get() },
-                                   backup_headers, backup_items, 0);
+  auto wipe_data_menu = CreateMenu(backup_headers, backup_items, 0);
   if (wipe_data_menu == nullptr) {
     return 0;
   }
diff --git a/recovery_ui/stub_ui.cpp b/recovery_ui/stub_ui.cpp
index a56b3f7..cabbd5f 100644
--- a/recovery_ui/stub_ui.cpp
+++ b/recovery_ui/stub_ui.cpp
@@ -23,13 +23,15 @@
 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*/) {
+                                const std::function<int(int, bool)>& /*key_handler*/,
+                                bool /* refreshable */) {
   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);
+    InputEvent evt = WaitInputEvent();
+    if (evt.type() == EventType::EXTRA) {
+      if (evt.key() == static_cast<int>(KeyError::INTERRUPTED) ||
+        evt.key() == static_cast<int>(KeyError::TIMED_OUT))
+          return static_cast<size_t>(evt.key());
     }
   }
   LOG(FATAL) << "Unreachable key selected in ShowMenu of stub UI";
diff --git a/recovery_ui/ui.cpp b/recovery_ui/ui.cpp
index 9b0fd94..7e8a56f 100644
--- a/recovery_ui/ui.cpp
+++ b/recovery_ui/ui.cpp
@@ -36,6 +36,7 @@
 #include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
+#include <volume_manager/VolumeManager.h>
 
 #include "minui/minui.h"
 #include "otautil/sysutil.h"
@@ -67,14 +68,15 @@
           android::base::GetProperty("ro.recovery.ui.brightness_file", DEFAULT_BRIGHTNESS_FILE)),
       max_brightness_file_(android::base::GetProperty("ro.recovery.ui.max_brightness_file",
                                                       DEFAULT_MAX_BRIGHTNESS_FILE)),
-      touch_screen_allowed_(false),
+      touch_screen_allowed_(true),
       fastbootd_logo_enabled_(false),
+      sideload_auto_reboot_(false),
       touch_low_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_low_threshold",
                                                          kDefaultTouchLowThreshold)),
       touch_high_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_high_threshold",
                                                           kDefaultTouchHighThreshold)),
       key_interrupted_(false),
-      key_queue_len(0),
+      event_queue_len(0),
       key_last_down(-1),
       key_long_press(false),
       key_down_count(0),
@@ -85,6 +87,10 @@
       has_down_key(false),
       has_touch_screen(false),
       touch_slot_(0),
+      touch_finger_down_(false),
+      touch_saw_x_(false),
+      touch_saw_y_(false),
+      touch_reported_(false),
       is_bootreason_recovery_ui_(false),
       screensaver_state_(ScreensaverState::DISABLED) {
   memset(key_pressed, 0, sizeof(key_pressed));
@@ -98,6 +104,48 @@
   }
 }
 
+void RecoveryUI::OnTouchDeviceDetected(int fd) {
+  char name[256];
+  char path[PATH_MAX];
+  char buf[4096];
+
+  memset(name, 0, sizeof(name));
+  if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
+    return;
+  }
+  sprintf(path, "/sys/board_properties/virtualkeys.%s", name);
+  int vkfd = open(path, O_RDONLY);
+  if (vkfd < 0) {
+    LOG(INFO) << "vkeys: could not open " << path;
+    return;
+  }
+  ssize_t len = read(vkfd, buf, sizeof(buf));
+  close(vkfd);
+  if (len <= 0) {
+    LOG(ERROR) << "vkeys: could not read " << path;
+    return;
+  }
+  buf[len] = '\0';
+
+  char* p = buf;
+  char* endp;
+  for (size_t n = 0; p < buf + len && *p == '0'; ++n) {
+    int val[6];
+    int f;
+    for (f = 0; *p && f < 6; ++f) {
+      val[f] = strtol(p, &endp, 0);
+      if (p == endp) break;
+      p = endp + 1;
+    }
+    if (f != 6 || val[0] != 0x01) break;
+    vkey_t vk;
+    vk.keycode = val[1];
+    vk.min_ = Point(val[2] - val[4] / 2, val[3] - val[5] / 2);
+    vk.max_ = Point(val[2] + val[4] / 2, val[3] + val[5] / 2);
+    virtual_keys_.push_back(vk);
+  }
+}
+
 void RecoveryUI::OnKeyDetected(int key_code) {
   if (key_code == KEY_POWER) {
     has_power_key = true;
@@ -165,7 +213,9 @@
   ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
 
   if (touch_screen_allowed_) {
-    ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+    ev_iterate_touch_inputs(
+        std::bind(&RecoveryUI::OnTouchDeviceDetected, this, std::placeholders::_1),
+        std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
 
     // Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of
     // "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way
@@ -197,66 +247,68 @@
   return true;
 }
 
-enum SwipeDirection { UP, DOWN, RIGHT, LEFT };
+void RecoveryUI::CalibrateTouch(int fd) {
+  struct input_absinfo info;
+  static bool calibrated = false;
 
-static SwipeDirection FlipSwipeDirection(SwipeDirection direction) {
-  switch (direction) {
-    case UP:
-      return SwipeDirection::DOWN;
-    case DOWN:
-      return SwipeDirection::UP;
-    case RIGHT:
-      return SwipeDirection::LEFT;
-    case LEFT:
-      return SwipeDirection::RIGHT;
+  if (calibrated) return;
+
+  memset(&info, 0, sizeof(info));
+  if (ioctl(fd, EVIOCGABS(ABS_MT_POSITION_X), &info) == 0) {
+    touch_min_.x(info.minimum);
+    touch_max_.x(info.maximum);
+  }
+  memset(&info, 0, sizeof(info));
+  if (ioctl(fd, EVIOCGABS(ABS_MT_POSITION_Y), &info) == 0) {
+    touch_min_.y(info.minimum);
+    touch_max_.y(info.maximum);
+  }
+
+  calibrated = true;
+}
+
+void RecoveryUI::OnTouchPress() {
+  touch_start_ = touch_track_ = touch_pos_;
+}
+
+void RecoveryUI::OnTouchTrack() {
+  if (touch_pos_.y() <= gr_fb_height_real()) {
+    while (abs(touch_pos_.y() - touch_track_.y()) >= MenuItemHeight()) {
+      int dy = touch_pos_.y() - touch_track_.y();
+      int key = (dy < 0) ? KEY_SCROLLDOWN : KEY_SCROLLUP;
+      ProcessKey(key, 1);  // press key
+      ProcessKey(key, 0);  // and release it
+      int sgn = (dy > 0) - (dy < 0);
+      touch_track_.y(touch_track_.y() + sgn * MenuItemHeight());
+    }
   }
 }
 
-void RecoveryUI::OnTouchDetected(int dx, int dy) {
-  SwipeDirection direction;
-
-  // We only consider a valid swipe if:
-  // - the delta along one axis is below touch_low_threshold_;
-  // - and the delta along the other axis is beyond touch_high_threshold_.
-  if (abs(dy) < touch_low_threshold_ && abs(dx) > touch_high_threshold_) {
-    direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT;
-  } else if (abs(dx) < touch_low_threshold_ && abs(dy) > touch_high_threshold_) {
-    direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN;
-  } else {
-    LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << touch_low_threshold_
-               << ", high: " << touch_high_threshold_ << ")";
-    return;
-  }
-
+void RecoveryUI::OnTouchRelease() {
   // Allow turning on text mode with any swipe, if bootloader has set a bootreason of recovery_ui.
   if (is_bootreason_recovery_ui_ && !IsTextVisible()) {
     ShowText(true);
     return;
   }
 
-  // Flip swipe direction if screen is rotated upside down
-  if (gr_get_rotation() == GRRotation::DOWN) {
-    direction = FlipSwipeDirection(direction);
+  // Check vkeys.  Only report if touch both starts and ends in the vkey.
+  if (touch_start_.y() > gr_fb_height_real() && touch_pos_.y() > gr_fb_height_real()) {
+    for (const auto& vk : virtual_keys_) {
+      if (vk.inside(touch_start_) && vk.inside(touch_pos_)) {
+        ProcessKey(vk.keycode, 1);  // press key
+        ProcessKey(vk.keycode, 0);  // and release it
+      }
+    }
+    return;
   }
 
-  LOG(DEBUG) << "Swipe direction=" << direction;
-  switch (direction) {
-    case SwipeDirection::UP:
-      ProcessKey(KEY_UP, 1);  // press up key
-      ProcessKey(KEY_UP, 0);  // and release it
-      break;
+  // If we tracked a vertical swipe, ignore the release
+  if (touch_track_ != touch_start_) {
+    return;
+  }
 
-    case SwipeDirection::DOWN:
-      ProcessKey(KEY_DOWN, 1);  // press down key
-      ProcessKey(KEY_DOWN, 0);  // and release it
-      break;
-
-    case SwipeDirection::LEFT:
-    case SwipeDirection::RIGHT:
-      ProcessKey(KEY_POWER, 1);  // press power key
-      ProcessKey(KEY_POWER, 0);  // and release it
-      break;
-  };
+  // Simple touch
+  EnqueueTouch(touch_pos_);
 }
 
 int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
@@ -267,10 +319,6 @@
 
   // Touch inputs handling.
   //
-  // We handle the touch inputs by tracking the position changes between initial contacting and
-  // upon lifting. touch_start_X/Y record the initial positions, with touch_finger_down set. Upon
-  // detecting the lift, we unset touch_finger_down and detect a swipe based on position changes.
-  //
   // Per the doc Multi-touch Protocol at below, there are two protocols.
   // https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt
   //
@@ -287,15 +335,17 @@
 
   if (ev.type == EV_SYN) {
     if (touch_screen_allowed_ && ev.code == SYN_REPORT) {
-      // There might be multiple SYN_REPORT events. We should only detect a swipe after lifting the
-      // contact.
-      if (touch_finger_down_ && !touch_swiping_) {
-        touch_start_X_ = touch_X_;
-        touch_start_Y_ = touch_Y_;
-        touch_swiping_ = true;
-      } else if (!touch_finger_down_ && touch_swiping_) {
-        touch_swiping_ = false;
-        OnTouchDetected(touch_X_ - touch_start_X_, touch_Y_ - touch_start_Y_);
+      // There might be multiple SYN_REPORT events. Only report press/release once.
+      if (!touch_reported_ && touch_finger_down_) {
+        if (touch_saw_x_ && touch_saw_y_) {
+          OnTouchPress();
+          touch_reported_ = true;
+          touch_saw_x_ = touch_saw_y_ = false;
+        }
+      } else if (touch_reported_ && !touch_finger_down_) {
+        OnTouchRelease();
+        touch_reported_ = false;
+        touch_saw_x_ = touch_saw_y_ = false;
       }
     }
     return 0;
@@ -323,6 +373,7 @@
   }
 
   if (touch_screen_allowed_ && ev.type == EV_ABS) {
+    CalibrateTouch(fd);
     if (ev.code == ABS_MT_SLOT) {
       touch_slot_ = ev.value;
     }
@@ -331,13 +382,23 @@
 
     switch (ev.code) {
       case ABS_MT_POSITION_X:
-        touch_X_ = ev.value;
         touch_finger_down_ = true;
+        touch_saw_x_ = true;
+        touch_pos_.x(ev.value * gr_fb_width_real() / (touch_max_.x() - touch_min_.x()));
+        if (touch_reported_ && touch_saw_y_) {
+          OnTouchTrack();
+          touch_saw_x_ = touch_saw_y_ = false;
+        }
         break;
 
       case ABS_MT_POSITION_Y:
-        touch_Y_ = ev.value;
         touch_finger_down_ = true;
+        touch_saw_y_ = true;
+        touch_pos_.y(ev.value * gr_fb_height_real() / (touch_max_.y() - touch_min_.y()));
+        if (touch_reported_ && touch_saw_x_) {
+          OnTouchTrack();
+          touch_saw_x_ = touch_saw_y_ = false;
+        }
         break;
 
       case ABS_MT_TRACKING_ID:
@@ -418,6 +479,7 @@
 
       case RecoveryUI::REBOOT:
         if (reboot_enabled) {
+          android::volmgr::VolumeManager::Instance()->unmountAll();
           Reboot("userrequested,recovery,ui");
         }
         break;
@@ -442,11 +504,22 @@
 }
 
 void RecoveryUI::EnqueueKey(int key_code) {
-  std::lock_guard<std::mutex> lg(key_queue_mutex);
-  const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
-  if (key_queue_len < queue_max) {
-    key_queue[key_queue_len++] = key_code;
-    key_queue_cond.notify_one();
+  std::lock_guard<std::mutex> lg(event_queue_mutex);
+  const int queue_max = sizeof(event_queue) / sizeof(event_queue[0]);
+  if (event_queue_len < queue_max) {
+    InputEvent event(key_code);
+    event_queue[event_queue_len++] = event;
+    event_queue_cond.notify_one();
+  }
+}
+
+void RecoveryUI::EnqueueTouch(const Point& pos) {
+  std::lock_guard<std::mutex> lg(event_queue_mutex);
+  const int queue_max = sizeof(event_queue) / sizeof(event_queue[0]);
+  if (event_queue_len < queue_max) {
+    InputEvent event(pos);
+    event_queue[event_queue_len++] = event;
+    event_queue_cond.notify_one();
   }
 }
 
@@ -485,23 +558,23 @@
   }
 }
 
-int RecoveryUI::WaitKey() {
-  std::unique_lock<std::mutex> lk(key_queue_mutex);
+RecoveryUI::InputEvent RecoveryUI::WaitInputEvent() {
+  std::unique_lock<std::mutex> lk(event_queue_mutex);
 
   // Check for a saved key queue interruption.
   if (key_interrupted_) {
     SetScreensaverState(ScreensaverState::NORMAL);
-    return static_cast<int>(KeyError::INTERRUPTED);
+    return InputEvent(EventType::EXTRA, KeyError::INTERRUPTED);
   }
 
   // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is plugged in.
   do {
-    bool rc = key_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC), [this] {
-      return this->key_queue_len != 0 || key_interrupted_;
+    bool rc = event_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC), [this] {
+      return this->event_queue_len != 0 || key_interrupted_;
     });
     if (key_interrupted_) {
       SetScreensaverState(ScreensaverState::NORMAL);
-      return static_cast<int>(KeyError::INTERRUPTED);
+      return InputEvent(EventType::EXTRA, KeyError::INTERRUPTED);
     }
     if (screensaver_state_ != ScreensaverState::DISABLED) {
       if (!rc) {
@@ -514,8 +587,8 @@
       } else if (screensaver_state_ != ScreensaverState::NORMAL) {
         // Drop the first key if it's changing from OFF to NORMAL.
         if (screensaver_state_ == ScreensaverState::OFF) {
-          if (key_queue_len > 0) {
-            memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+          if (event_queue_len > 0) {
+            memcpy(&event_queue[0], &event_queue[1], sizeof(int) * --event_queue_len);
           }
         }
 
@@ -523,22 +596,26 @@
         SetScreensaverState(ScreensaverState::NORMAL);
       }
     }
-  } while (IsUsbConnected() && key_queue_len == 0);
+  } while (IsUsbConnected() && event_queue_len == 0);
 
-  int key = static_cast<int>(KeyError::TIMED_OUT);
-  if (key_queue_len > 0) {
-    key = key_queue[0];
-    memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+  InputEvent event;
+  if (event_queue_len > 0) {
+    event = event_queue[0];
+    memcpy(&event_queue[0], &event_queue[1], sizeof(InputEvent) * --event_queue_len);
   }
-  return key;
+  return event;
+}
+
+void RecoveryUI::CancelWaitKey() {
+  EnqueueKey(KEY_AGAIN);
 }
 
 void RecoveryUI::InterruptKey() {
   {
-    std::lock_guard<std::mutex> lg(key_queue_mutex);
+    std::lock_guard<std::mutex> lg(event_queue_mutex);
     key_interrupted_ = true;
   }
-  key_queue_cond.notify_one();
+  event_queue_cond.notify_one();
 }
 
 bool RecoveryUI::IsUsbConnected() {
@@ -582,8 +659,8 @@
 }
 
 void RecoveryUI::FlushKeys() {
-  std::lock_guard<std::mutex> lg(key_queue_mutex);
-  key_queue_len = 0;
+  std::lock_guard<std::mutex> lg(event_queue_mutex);
+  event_queue_len = 0;
 }
 
 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
diff --git a/recovery_ui/wear_ui.cpp b/recovery_ui/wear_ui.cpp
index 552f0cf..a1c2cb1 100644
--- a/recovery_ui/wear_ui.cpp
+++ b/recovery_ui/wear_ui.cpp
@@ -34,7 +34,7 @@
 constexpr bool kDefaultIsScreenCircle = true;
 
 WearRecoveryUI::WearRecoveryUI()
-    : ScreenRecoveryUI(true),
+    : ScreenRecoveryUI(),
       progress_bar_baseline_(android::base::GetIntProperty("ro.recovery.ui.progress_bar_baseline",
                                                            kDefaultProgressBarBaseline)),
       menu_unusable_rows_(android::base::GetIntProperty("ro.recovery.ui.menu_unusable_rows",
@@ -261,9 +261,8 @@
                                                  const std::vector<std::string>& text_items,
                                                  size_t initial_selection) const {
   if (text_rows_ > 0 && text_cols_ > 0) {
-    return std::make_unique<TextMenu>(scrollable_menu_, text_rows_ - menu_unusable_rows_ - 1,
-                                      text_cols_ - 1, text_headers, text_items, initial_selection,
-                                      char_height_, *this);
+    return std::make_unique<TextMenu>(false, text_cols_ - 1, text_headers, text_items,
+                                      initial_selection, char_height_, *this);
   }
 
   return nullptr;
diff --git a/recovery_utils/include/recovery_utils/roots.h b/recovery_utils/include/recovery_utils/roots.h
index 6afefb8..aa7b0ee 100644
--- a/recovery_utils/include/recovery_utils/roots.h
+++ b/recovery_utils/include/recovery_utils/roots.h
@@ -39,6 +39,10 @@
 // success (volume is unmounted);
 int ensure_path_unmounted(const std::string& path);
 
+// Make sure that the volume at 'blk_device' is unmounted.
+// Returns 0 on success.
+int ensure_volume_unmounted(const std::string& blk_device);
+
 // Reformat the given volume (must be the mount point only, eg
 // "/cache"), no paths permitted.  Attempts to unmount the volume if
 // it is mounted.
@@ -57,3 +61,7 @@
 
 // Returns true if there is /cache in the volumes.
 bool HasCache();
+
+void map_logical_partitions();
+
+bool logical_partitions_mapped();
diff --git a/recovery_utils/roots.cpp b/recovery_utils/roots.cpp
index e7a7d65..9ebb3e0 100644
--- a/recovery_utils/roots.cpp
+++ b/recovery_utils/roots.cpp
@@ -24,6 +24,7 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
+#include <sys/mount.h>
 
 #include <iostream>
 #include <string>
@@ -37,12 +38,15 @@
 #include <ext4_utils/wipe.h>
 #include <fs_mgr.h>
 #include <fs_mgr/roots.h>
+#include <fs_mgr_dm_linear.h>
 
 #include "otautil/sysutil.h"
 
 using android::fs_mgr::Fstab;
 using android::fs_mgr::FstabEntry;
 using android::fs_mgr::ReadDefaultFstab;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
 
 static Fstab fstab;
 
@@ -89,6 +93,27 @@
   return android::fs_mgr::EnsurePathUnmounted(&fstab, path) ? 0 : -1;
 }
 
+int ensure_volume_unmounted(const std::string& blk_device) {
+  android::fs_mgr::Fstab mounted_fstab;
+  if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounted_fstab)) {
+    LOG(ERROR) << "Failed to read /proc/mounts";
+    return -1;
+  }
+
+  /* find any entries with the volume */
+  for (auto& entry : mounted_fstab) {
+    if (entry.blk_device == blk_device) {
+      int result = umount(entry.mount_point.c_str());
+      if (result == -1) {
+        LOG(ERROR) << "Failed to unmount " << blk_device << " from " << entry.mount_point << ": "
+                   << errno;
+        return -1;
+      }
+    }
+  }
+  return 0;
+}
+
 static int exec_cmd(const std::vector<std::string>& args) {
   CHECK(!args.empty());
   auto argv = StringVectorToNullTerminatedArray(args);
@@ -145,7 +170,7 @@
     LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume";
     return -1;
   }
-  if (ensure_path_unmounted(volume) != 0) {
+  if (ensure_volume_unmounted(v->blk_device) != 0) {
     LOG(ERROR) << "format_volume: Failed to unmount \"" << v->mount_point << "\"";
     return -1;
   }
@@ -329,3 +354,36 @@
   static bool has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
   return has_cache;
 }
+
+static bool logical_partitions_auto_mapped = false;
+
+void map_logical_partitions() {
+  if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) &&
+      !logical_partitions_mapped()) {
+    std::string super_name = fs_mgr_get_super_partition_name();
+    if (!android::fs_mgr::CreateLogicalPartitions("/dev/block/by-name/" + super_name)) {
+      LOG(ERROR) << "Failed to map logical partitions";
+    } else {
+      logical_partitions_auto_mapped = true;
+    }
+  }
+}
+
+bool dm_find_system() {
+  auto rec = GetEntryForPath(&fstab, android::fs_mgr::GetSystemRoot());
+  if (!rec->fs_mgr_flags.logical) {
+    return false;
+  }
+  // If the fstab entry for system it's a path instead of a name, then it was already mapped
+  if (rec->blk_device[0] != '/') {
+    if (DeviceMapper::Instance().GetState(rec->blk_device) == DmDeviceState::INVALID) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool logical_partitions_mapped() {
+  return android::fs_mgr::LogicalPartitionsMapped() || logical_partitions_auto_mapped ||
+      dm_find_system();
+}
diff --git a/res-hdpi/images/fastbootd.png b/res-hdpi/images/fastbootd.png
index 5127b3b..ffb6c9a 100644
--- a/res-hdpi/images/fastbootd.png
+++ b/res-hdpi/images/fastbootd.png
Binary files differ
diff --git a/res-hdpi/images/font_menu.png b/res-hdpi/images/font_menu.png
new file mode 100644
index 0000000..d1aad6d
--- /dev/null
+++ b/res-hdpi/images/font_menu.png
Binary files differ
diff --git a/res-hdpi/images/ic_back.png b/res-hdpi/images/ic_back.png
new file mode 100644
index 0000000..ae11ddf
--- /dev/null
+++ b/res-hdpi/images/ic_back.png
Binary files differ
diff --git a/res-hdpi/images/ic_back_sel.png b/res-hdpi/images/ic_back_sel.png
new file mode 100644
index 0000000..fb3f72b
--- /dev/null
+++ b/res-hdpi/images/ic_back_sel.png
Binary files differ
diff --git a/res-hdpi/images/logo_image.png b/res-hdpi/images/logo_image.png
new file mode 100644
index 0000000..eb10b89
--- /dev/null
+++ b/res-hdpi/images/logo_image.png
Binary files differ
diff --git a/res-hdpi/images/logo_image_switch.png b/res-hdpi/images/logo_image_switch.png
new file mode 100644
index 0000000..26d61ed
--- /dev/null
+++ b/res-hdpi/images/logo_image_switch.png
Binary files differ
diff --git a/res-mdpi/images/fastbootd.png b/res-mdpi/images/fastbootd.png
index c1b7bc2..ae1eacd 100644
--- a/res-mdpi/images/fastbootd.png
+++ b/res-mdpi/images/fastbootd.png
Binary files differ
diff --git a/res-mdpi/images/font_menu.png b/res-mdpi/images/font_menu.png
new file mode 100644
index 0000000..59b990a
--- /dev/null
+++ b/res-mdpi/images/font_menu.png
Binary files differ
diff --git a/res-mdpi/images/ic_back.png b/res-mdpi/images/ic_back.png
new file mode 100644
index 0000000..1e7b9c0
--- /dev/null
+++ b/res-mdpi/images/ic_back.png
Binary files differ
diff --git a/res-mdpi/images/ic_back_sel.png b/res-mdpi/images/ic_back_sel.png
new file mode 100644
index 0000000..9ba131f
--- /dev/null
+++ b/res-mdpi/images/ic_back_sel.png
Binary files differ
diff --git a/res-mdpi/images/logo_image.png b/res-mdpi/images/logo_image.png
new file mode 100644
index 0000000..b06ea95
--- /dev/null
+++ b/res-mdpi/images/logo_image.png
Binary files differ
diff --git a/res-mdpi/images/logo_image_switch.png b/res-mdpi/images/logo_image_switch.png
new file mode 100644
index 0000000..8a97307
--- /dev/null
+++ b/res-mdpi/images/logo_image_switch.png
Binary files differ
diff --git a/res-xhdpi/images/fastbootd.png b/res-xhdpi/images/fastbootd.png
index e3da85a..a0f52fa 100644
--- a/res-xhdpi/images/fastbootd.png
+++ b/res-xhdpi/images/fastbootd.png
Binary files differ
diff --git a/res-xhdpi/images/font_menu.png b/res-xhdpi/images/font_menu.png
new file mode 100644
index 0000000..6aead73
--- /dev/null
+++ b/res-xhdpi/images/font_menu.png
Binary files differ
diff --git a/res-xhdpi/images/ic_back.png b/res-xhdpi/images/ic_back.png
new file mode 100644
index 0000000..0009d50
--- /dev/null
+++ b/res-xhdpi/images/ic_back.png
Binary files differ
diff --git a/res-xhdpi/images/ic_back_sel.png b/res-xhdpi/images/ic_back_sel.png
new file mode 100644
index 0000000..37dc34d
--- /dev/null
+++ b/res-xhdpi/images/ic_back_sel.png
Binary files differ
diff --git a/res-xhdpi/images/logo_image.png b/res-xhdpi/images/logo_image.png
new file mode 100644
index 0000000..3fe4c5f
--- /dev/null
+++ b/res-xhdpi/images/logo_image.png
Binary files differ
diff --git a/res-xhdpi/images/logo_image_switch.png b/res-xhdpi/images/logo_image_switch.png
new file mode 100644
index 0000000..723f9c9
--- /dev/null
+++ b/res-xhdpi/images/logo_image_switch.png
Binary files differ
diff --git a/res-xxhdpi/images/fastbootd.png b/res-xxhdpi/images/fastbootd.png
index 482dbb2..a08a431 100644
--- a/res-xxhdpi/images/fastbootd.png
+++ b/res-xxhdpi/images/fastbootd.png
Binary files differ
diff --git a/res-xxhdpi/images/font_menu.png b/res-xxhdpi/images/font_menu.png
new file mode 100644
index 0000000..f3b54b3
--- /dev/null
+++ b/res-xxhdpi/images/font_menu.png
Binary files differ
diff --git a/res-xxhdpi/images/ic_back.png b/res-xxhdpi/images/ic_back.png
new file mode 100644
index 0000000..eb1d0c2
--- /dev/null
+++ b/res-xxhdpi/images/ic_back.png
Binary files differ
diff --git a/res-xxhdpi/images/ic_back_sel.png b/res-xxhdpi/images/ic_back_sel.png
new file mode 100644
index 0000000..90fea65
--- /dev/null
+++ b/res-xxhdpi/images/ic_back_sel.png
Binary files differ
diff --git a/res-xxhdpi/images/logo_image.png b/res-xxhdpi/images/logo_image.png
new file mode 100644
index 0000000..de3adfd
--- /dev/null
+++ b/res-xxhdpi/images/logo_image.png
Binary files differ
diff --git a/res-xxhdpi/images/logo_image_switch.png b/res-xxhdpi/images/logo_image_switch.png
new file mode 100644
index 0000000..f88edc6
--- /dev/null
+++ b/res-xxhdpi/images/logo_image_switch.png
Binary files differ
diff --git a/res-xxxhdpi/images/fastbootd.png b/res-xxxhdpi/images/fastbootd.png
index a878b1f..d698f11 100644
--- a/res-xxxhdpi/images/fastbootd.png
+++ b/res-xxxhdpi/images/fastbootd.png
Binary files differ
diff --git a/res-xxxhdpi/images/font_menu.png b/res-xxxhdpi/images/font_menu.png
new file mode 100644
index 0000000..d7c326a
--- /dev/null
+++ b/res-xxxhdpi/images/font_menu.png
Binary files differ
diff --git a/res-xxxhdpi/images/ic_back.png b/res-xxxhdpi/images/ic_back.png
new file mode 100644
index 0000000..481305e
--- /dev/null
+++ b/res-xxxhdpi/images/ic_back.png
Binary files differ
diff --git a/res-xxxhdpi/images/ic_back_sel.png b/res-xxxhdpi/images/ic_back_sel.png
new file mode 100644
index 0000000..7c6ccd3
--- /dev/null
+++ b/res-xxxhdpi/images/ic_back_sel.png
Binary files differ
diff --git a/res-xxxhdpi/images/logo_image.png b/res-xxxhdpi/images/logo_image.png
new file mode 100644
index 0000000..6bf9ecc
--- /dev/null
+++ b/res-xxxhdpi/images/logo_image.png
Binary files differ
diff --git a/res-xxxhdpi/images/logo_image_switch.png b/res-xxxhdpi/images/logo_image_switch.png
new file mode 100644
index 0000000..10f2866
--- /dev/null
+++ b/res-xxxhdpi/images/logo_image_switch.png
Binary files differ
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
index 4dd111a..dc7e957 100644
--- a/tests/unit/dirutil_test.cpp
+++ b/tests/unit/dirutil_test.cpp
@@ -107,3 +107,35 @@
   ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str()));
   ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
 }
+
+TEST(DirUtilTest, unlink_invalid) {
+  // File doesn't exist.
+  ASSERT_EQ(-1, dirUnlinkHierarchy("doesntexist"));
+
+  // Nonexistent directory.
+  TemporaryDir td;
+  std::string path(td.path);
+  ASSERT_EQ(-1, dirUnlinkHierarchy((path + "/a").c_str()));
+  ASSERT_EQ(ENOENT, errno);
+}
+
+TEST(DirUtilTest, unlink_smoke) {
+  // Unlink a file.
+  TemporaryFile tf;
+  ASSERT_EQ(0, dirUnlinkHierarchy(tf.path));
+  ASSERT_EQ(-1, access(tf.path, F_OK));
+
+  TemporaryDir td;
+  std::string path(td.path);
+  constexpr mode_t mode = 0700;
+  ASSERT_EQ(0, mkdir((path + "/a").c_str(), mode));
+  ASSERT_EQ(0, mkdir((path + "/a/b").c_str(), mode));
+  ASSERT_EQ(0, mkdir((path + "/a/b/c").c_str(), mode));
+  ASSERT_EQ(0, mkdir((path + "/a/d").c_str(), mode));
+
+  // Remove "../a" recursively.
+  ASSERT_EQ(0, dirUnlinkHierarchy((path + "/a").c_str()));
+
+  // Verify it's gone.
+  ASSERT_EQ(-1, access((path + "/a").c_str(), F_OK));
+}
diff --git a/tests/unit/updater_test.cpp b/tests/unit/updater_test.cpp
index 8993dd8..0720e82 100644
--- a/tests/unit/updater_test.cpp
+++ b/tests/unit/updater_test.cpp
@@ -339,6 +339,212 @@
     expect("", script6, kNoCause);
 }
 
+TEST_F(UpdaterTest, delete) {
+  // Delete none.
+  expect("0", "delete()", kNoCause);
+  expect("0", "delete(\"/doesntexist\")", kNoCause);
+  expect("0", "delete(\"/doesntexist1\", \"/doesntexist2\")", kNoCause);
+  expect("0", "delete(\"/doesntexist1\", \"/doesntexist2\", \"/doesntexist3\")", kNoCause);
+
+  // Delete one file.
+  TemporaryFile temp_file1;
+  ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file1.path));
+  std::string script1("delete(\"" + std::string(temp_file1.path) + "\")");
+  expect("1", script1.c_str(), kNoCause);
+
+  // Delete two files.
+  TemporaryFile temp_file2;
+  ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file2.path));
+  TemporaryFile temp_file3;
+  ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file3.path));
+  std::string script2("delete(\"" + std::string(temp_file2.path) + "\", \"" +
+                      std::string(temp_file3.path) + "\")");
+  expect("2", script2.c_str(), kNoCause);
+
+  // Delete already deleted files.
+  expect("0", script2.c_str(), kNoCause);
+
+  // Delete one out of three.
+  TemporaryFile temp_file4;
+  ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file4.path));
+  std::string script3("delete(\"/doesntexist1\", \"" + std::string(temp_file4.path) +
+                      "\", \"/doesntexist2\")");
+  expect("1", script3.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, rename) {
+  // rename() expects two arguments.
+  expect(nullptr, "rename()", kArgsParsingFailure);
+  expect(nullptr, "rename(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "rename(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  // src_name or dst_name cannot be empty.
+  expect(nullptr, "rename(\"\", \"arg2\")", kArgsParsingFailure);
+  expect(nullptr, "rename(\"arg1\", \"\")", kArgsParsingFailure);
+
+  // File doesn't exist (both of src and dst).
+  expect(nullptr, "rename(\"/doesntexist\", \"/doesntexisteither\")", kFileRenameFailure);
+
+  // Can't create parent directory.
+  TemporaryFile temp_file1;
+  ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file1.path));
+  std::string script1("rename(\"" + std::string(temp_file1.path) + "\", \"/proc/0/file1\")");
+  expect(nullptr, script1.c_str(), kFileRenameFailure);
+
+  // Rename.
+  TemporaryFile temp_file2;
+  std::string script2("rename(\"" + std::string(temp_file1.path) + "\", \"" +
+                      std::string(temp_file2.path) + "\")");
+  expect(temp_file2.path, script2.c_str(), kNoCause);
+
+  // Already renamed.
+  expect(temp_file2.path, script2.c_str(), kNoCause);
+
+  // Parents create successfully.
+  TemporaryFile temp_file3;
+  TemporaryDir td;
+  std::string temp_dir(td.path);
+  std::string dst_file = temp_dir + "/aaa/bbb/a.txt";
+  std::string script3("rename(\"" + std::string(temp_file3.path) + "\", \"" + dst_file + "\")");
+  expect(dst_file.c_str(), script3.c_str(), kNoCause);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink(dst_file.c_str()));
+  ASSERT_EQ(0, rmdir((temp_dir + "/aaa/bbb").c_str()));
+  ASSERT_EQ(0, rmdir((temp_dir + "/aaa").c_str()));
+}
+
+TEST_F(UpdaterTest, symlink) {
+  // symlink expects 1+ argument.
+  expect(nullptr, "symlink()", kArgsParsingFailure);
+
+  // symlink should fail if src is an empty string.
+  TemporaryFile temp_file1;
+  std::string script1("symlink(\"" + std::string(temp_file1.path) + "\", \"\")");
+  expect(nullptr, script1.c_str(), kSymlinkFailure);
+
+  std::string script2("symlink(\"" + std::string(temp_file1.path) + "\", \"src1\", \"\")");
+  expect(nullptr, script2.c_str(), kSymlinkFailure);
+
+  // symlink failed to remove old src.
+  std::string script3("symlink(\"" + std::string(temp_file1.path) + "\", \"/proc\")");
+  expect(nullptr, script3.c_str(), kSymlinkFailure);
+
+  // symlink can create symlinks.
+  TemporaryFile temp_file;
+  std::string content = "magicvalue";
+  ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+
+  TemporaryDir td;
+  std::string src1 = std::string(td.path) + "/symlink1";
+  std::string src2 = std::string(td.path) + "/symlink2";
+  std::string script4("symlink(\"" + std::string(temp_file.path) + "\", \"" + src1 + "\", \"" +
+                      src2 + "\")");
+  expect("t", script4.c_str(), kNoCause);
+
+  // Verify the created symlinks.
+  struct stat sb;
+  ASSERT_TRUE(lstat(src1.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode));
+  ASSERT_TRUE(lstat(src2.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode));
+
+  // Clean up the leftovers.
+  ASSERT_EQ(0, unlink(src1.c_str()));
+  ASSERT_EQ(0, unlink(src2.c_str()));
+}
+
+TEST_F(UpdaterTest, package_extract_dir) {
+  // package_extract_dir expects 2 arguments.
+  expect(nullptr, "package_extract_dir()", kArgsParsingFailure);
+  expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Need to set up the ziphandle.
+  SetUpdaterOtaPackageHandle(handle);
+
+  // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>").
+  TemporaryDir td;
+  std::string temp_dir(td.path);
+  std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")");
+  expect("t", script.c_str(), kNoCause, &updater_);
+
+  // Verify.
+  std::string data;
+  std::string file_c = temp_dir + "/c.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+
+  std::string file_d = temp_dir + "/d.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  // Modify the contents in order to retry. It's expected to be overwritten.
+  ASSERT_TRUE(android::base::WriteStringToFile("random", file_c));
+  ASSERT_TRUE(android::base::WriteStringToFile("random", file_d));
+
+  // Extract again and verify.
+  expect("t", script.c_str(), kNoCause, &updater_);
+
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  // Extracting "b/" (with slash) should give the same result.
+  script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_);
+
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  // Extracting "" is allowed. The entries will carry the path name.
+  script = "package_extract_dir(\"\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_);
+
+  std::string file_a = temp_dir + "/a.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_a, &data));
+  ASSERT_EQ(kATxtContents, data);
+  std::string file_b = temp_dir + "/b.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b, &data));
+  ASSERT_EQ(kBTxtContents, data);
+  std::string file_b_c = temp_dir + "/b/c.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  std::string file_b_d = temp_dir + "/b/d.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  ASSERT_EQ(0, unlink(file_a.c_str()));
+  ASSERT_EQ(0, unlink(file_b.c_str()));
+  ASSERT_EQ(0, unlink(file_b_c.c_str()));
+  ASSERT_EQ(0, unlink(file_b_d.c_str()));
+  ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str()));
+
+  // Extracting non-existent entry should still give "t".
+  script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_);
+
+  // Only relative zip_path is allowed.
+  script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")";
+  expect("", script.c_str(), kNoCause, &updater_);
+
+  // Only absolute dest_path is allowed.
+  script = "package_extract_dir(\"b\", \"path\")";
+  expect("", script.c_str(), kNoCause, &updater_);
+}
+
 // TODO: Test extracting to block device.
 TEST_F(UpdaterTest, package_extract_file) {
   // package_extract_file expects 1 or 2 arguments.
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index e065bb8..903c9db 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -22,11 +22,48 @@
 
 #include <android-base/file.h>
 #include <gtest/gtest.h>
+#include <otautil/ziputil.h>
 #include <ziparchive/zip_archive.h>
 
 #include "common/test_constants.h"
 #include "otautil/sysutil.h"
 
+TEST(ZipTest, ExtractPackageRecursive) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract the whole package into a temp directory.
+  TemporaryDir td;
+  ASSERT_NE(nullptr, td.path);
+  ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+  // Make sure all the files are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+  ASSERT_EQ(kATxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  CloseArchive(handle);
+
+  // Clean up.
+  ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+  ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+}
+
 TEST(ZipTest, OpenFromMemory) {
   std::string zip_path = from_testdata_base("ziptest_fake-update.zip");
   MemMapping map;
diff --git a/tests/unit/ziputil_test.cpp b/tests/unit/ziputil_test.cpp
new file mode 100644
index 0000000..14e5416
--- /dev/null
+++ b/tests/unit/ziputil_test.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/ZipUtil.h>
+#include <ziparchive/zip_archive.h>
+
+#include "common/test_constants.h"
+
+TEST(ZipUtilTest, invalid_args) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // zip_path must be a relative path.
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "/a/b", "/tmp", nullptr, nullptr));
+
+  // dest_path must be an absolute path.
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "tmp", nullptr, nullptr));
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "", nullptr, nullptr));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_all) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract the whole package into a temp directory.
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+  // Make sure all the files are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+  ASSERT_EQ(kATxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+  ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_with_slash) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract all the entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b/", td.path, nullptr, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+  // And the rest are not extracted.
+  ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+  ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+  ASSERT_EQ(kCTxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_without_slash) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract all the file entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b", td.path, nullptr, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+  // And the rest are not extracted.
+  ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+  ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+  ASSERT_EQ(kCTxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, set_timestamp) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Set the timestamp to 8/1/2008.
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
+
+  // Extract all the entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b", td.path, &timestamp, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  std::string file_c = path + "/c.txt";
+  std::string file_d = path + "/d.txt";
+  ASSERT_EQ(0, access(file_c.c_str(), F_OK));
+  ASSERT_EQ(0, access(file_d.c_str(), F_OK));
+
+  // Verify the timestamp.
+  timespec time;
+  time.tv_sec = 1217592000;
+  time.tv_nsec = 0;
+
+  struct stat sb;
+  ASSERT_EQ(0, stat(file_c.c_str(), &sb)) << strerror(errno);
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+  ASSERT_EQ(0, stat(file_d.c_str(), &sb)) << strerror(errno);
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  CloseArchive(handle);
+}
diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h
index b943dfc..fc537d2 100644
--- a/updater/include/updater/updater_runtime.h
+++ b/updater/include/updater/updater_runtime.h
@@ -24,8 +24,6 @@
 
 #include "edify/updater_runtime_interface.h"
 
-struct selabel_handle;
-
 class UpdaterRuntime : public UpdaterRuntimeInterface {
  public:
   explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {}
@@ -58,6 +56,10 @@
   bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
   std::string AddSlotSuffix(const std::string_view arg) const override;
 
+  struct selabel_handle* sehandle() const override {
+    return sehandle_;
+  }
+
  private:
   struct selabel_handle* sehandle_{ nullptr };
 };
diff --git a/updater/install.cpp b/updater/install.cpp
index 2959650..70a1235 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -35,6 +35,8 @@
 #include <unistd.h>
 #include <utime.h>
 
+#include <linux/xattr.h>
+
 #include <limits>
 #include <memory>
 #include <string>
@@ -63,6 +65,7 @@
 #include "otautil/error_code.h"
 #include "otautil/print_sha1.h"
 #include "otautil/sysutil.h"
+#include "otautil/ziputil.h"
 
 #ifndef __ANDROID__
 #include <cutils/memory.h>  // for strlcpy
@@ -80,6 +83,34 @@
   return true;
 }
 
+static bool is_dir(const std::string& dirpath) {
+  struct stat st;
+  return stat(dirpath.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
+}
+
+// Create all parent directories of name, if necessary.
+static bool make_parents(const std::string& name) {
+  size_t prev_end = 0;
+  while (prev_end < name.size()) {
+    size_t next_end = name.find('/', prev_end + 1);
+    if (next_end == std::string::npos) {
+      break;
+    }
+    std::string dir_path = name.substr(0, next_end);
+    if (!is_dir(dir_path)) {
+      int result = mkdir(dir_path.c_str(), 0700);
+      if (result != 0) {
+        PLOG(ERROR) << "failed to mkdir " << dir_path << " when make parents for " << name;
+        return false;
+      }
+
+      LOG(INFO) << "created [" << dir_path << "]";
+    }
+    prev_end = next_end;
+  }
+  return true;
+}
+
 // This is the updater side handler for ui_print() in edify script. Contents will be sent over to
 // the recovery side for on-screen display.
 Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -93,6 +124,39 @@
   return StringValue(buffer);
 }
 
+// package_extract_dir(package_dir, dest_dir)
+//   Extracts all files from the package underneath package_dir and writes them to the
+//   corresponding tree beneath dest_dir. Any existing files are overwritten.
+//   Example: package_extract_dir("system", "/system")
+//
+//   Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path.
+Value* PackageExtractDirFn(const char* name, State* state,
+                           const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& zip_path = args[0];
+  const std::string& dest_path = args[1];
+
+  auto updater = state->updater;
+
+  ZipArchiveHandle za = updater->GetPackageHandle();
+
+  // To create a consistent system image, never use the clock for timestamps.
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
+
+  bool success = ExtractPackageRecursive(za, zip_path, dest_path, &timestamp,
+                                         updater->GetRuntime()->sehandle());
+
+  return StringValue(success ? "t" : "");
+}
+
 // package_extract_file(package_file[, dest_file])
 //   Extracts a single package_file from the update package and writes it to dest_file,
 //   overwriting existing files if necessary. Without the dest_file argument, returns the
@@ -480,6 +544,66 @@
   return nullptr;
 }
 
+// rename(src_name, dst_name)
+//   Renames src_name to dst_name. It automatically creates the necessary directories for dst_name.
+//   Example: rename("system/app/Hangouts/Hangouts.apk", "system/priv-app/Hangouts/Hangouts.apk")
+Value* RenameFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& src_name = args[0];
+  const std::string& dst_name = args[1];
+
+  if (src_name.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "src_name argument to %s() can't be empty", name);
+  }
+  if (dst_name.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "dst_name argument to %s() can't be empty", name);
+  }
+  if (!make_parents(dst_name)) {
+    return ErrorAbort(state, kFileRenameFailure, "Creating parent of %s failed, error %s",
+                      dst_name.c_str(), strerror(errno));
+  } else if (access(dst_name.c_str(), F_OK) == 0 && access(src_name.c_str(), F_OK) != 0) {
+    // File was already moved
+    return StringValue(dst_name);
+  } else if (rename(src_name.c_str(), dst_name.c_str()) != 0) {
+    return ErrorAbort(state, kFileRenameFailure, "Rename of %s to %s failed, error %s",
+                      src_name.c_str(), dst_name.c_str(), strerror(errno));
+  }
+
+  return StringValue(dst_name);
+}
+
+// delete([filename, ...])
+//   Deletes all the filenames listed. Returns the number of files successfully deleted.
+//
+// delete_recursive([dirname, ...])
+//   Recursively deletes dirnames and all their contents. Returns the number of directories
+//   successfully deleted.
+Value* DeleteFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  std::vector<std::string> paths;
+  if (!ReadArgs(state, argv, &paths)) {
+    return nullptr;
+  }
+
+  bool recursive = (strcmp(name, "delete_recursive") == 0);
+
+  int success = 0;
+  for (const auto& path : paths) {
+    if ((recursive ? dirUnlinkHierarchy(path.c_str()) : unlink(path.c_str())) == 0) {
+      ++success;
+    }
+  }
+
+  return StringValue(std::to_string(success));
+}
+
 Value* ShowProgressFn(const char* name, State* state,
                       const std::vector<std::unique_ptr<Expr>>& argv) {
   if (argv.size() != 2) {
@@ -533,6 +657,308 @@
   return StringValue(frac_str);
 }
 
+// symlink(target, [src1, src2, ...])
+//   Creates all sources as symlinks to target. It unlinks any previously existing src1, src2, etc
+//   before creating symlinks.
+Value* SymlinkFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() == 0) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1+ args, got %zu", name,
+                      argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
+  }
+
+  const auto& target = args[0];
+  if (target.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() target argument can't be empty", name);
+  }
+
+  size_t bad = 0;
+  for (size_t i = 1; i < args.size(); ++i) {
+    const auto& src = args[i];
+    if (unlink(src.c_str()) == -1 && errno != ENOENT) {
+      PLOG(ERROR) << name << ": failed to remove " << src;
+      ++bad;
+    } else if (!make_parents(src)) {
+      LOG(ERROR) << name << ": failed to symlink " << src << " to " << target
+                 << ": making parents failed";
+      ++bad;
+    } else if (symlink(target.c_str(), src.c_str()) == -1) {
+      PLOG(ERROR) << name << ": failed to symlink " << src << " to " << target;
+      ++bad;
+    }
+  }
+  if (bad != 0) {
+    return ErrorAbort(state, kSymlinkFailure, "%s: Failed to create %zu symlink(s)", name, bad);
+  }
+  return StringValue("t");
+}
+
+struct perm_parsed_args {
+  bool has_uid;
+  uid_t uid;
+  bool has_gid;
+  gid_t gid;
+  bool has_mode;
+  mode_t mode;
+  bool has_fmode;
+  mode_t fmode;
+  bool has_dmode;
+  mode_t dmode;
+  bool has_selabel;
+  const char* selabel;
+  bool has_capabilities;
+  uint64_t capabilities;
+};
+
+static struct perm_parsed_args ParsePermArgs(State* state, const std::vector<std::string>& args) {
+  struct perm_parsed_args parsed;
+  auto updater = state->updater;
+  int bad = 0;
+  static int max_warnings = 20;
+
+  memset(&parsed, 0, sizeof(parsed));
+
+  for (size_t i = 1; i < args.size(); i += 2) {
+    if (args[i] == "uid") {
+      int64_t uid;
+      if (sscanf(args[i + 1].c_str(), "%" SCNd64, &uid) == 1) {
+        parsed.uid = uid;
+        parsed.has_uid = true;
+      } else {
+        updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid UID \"%s\"\n",
+                                                     args[i + 1].c_str()));
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "gid") {
+      int64_t gid;
+      if (sscanf(args[i + 1].c_str(), "%" SCNd64, &gid) == 1) {
+        parsed.gid = gid;
+        parsed.has_gid = true;
+      } else {
+        updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid GID \"%s\"\n",
+                                                     args[i + 1].c_str()));
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "mode") {
+      int32_t mode;
+      if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) {
+        parsed.mode = mode;
+        parsed.has_mode = true;
+      } else {
+        updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid mode \"%s\"\n",
+                                                     args[i + 1].c_str()));
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "dmode") {
+      int32_t mode;
+      if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) {
+        parsed.dmode = mode;
+        parsed.has_dmode = true;
+      } else {
+        updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid dmode \"%s\"\n",
+                                                     args[i + 1].c_str()));
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "fmode") {
+      int32_t mode;
+      if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) {
+        parsed.fmode = mode;
+        parsed.has_fmode = true;
+      } else {
+        updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid fmode \"%s\"\n",
+                                                     args[i + 1].c_str()));
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "capabilities") {
+      int64_t capabilities;
+      if (sscanf(args[i + 1].c_str(), "%" SCNi64, &capabilities) == 1) {
+        parsed.capabilities = capabilities;
+        parsed.has_capabilities = true;
+      } else {
+        updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid capabilities \"%s\"\n",
+                                                     args[i + 1].c_str()));
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "selabel") {
+      if (!args[i + 1].empty()) {
+        parsed.selabel = args[i + 1].c_str();
+        parsed.has_selabel = true;
+      } else {
+        updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid selabel \"%s\"\n",
+                                                     args[i + 1].c_str()));
+        bad++;
+      }
+      continue;
+    }
+    if (max_warnings != 0) {
+      printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i].c_str());
+      max_warnings--;
+      if (max_warnings == 0) {
+        LOG(INFO) << "ParsedPermArgs: suppressing further warnings";
+      }
+    }
+  }
+  return parsed;
+}
+
+static int ApplyParsedPerms(State* state, const char* filename, const struct stat* statptr,
+                            struct perm_parsed_args parsed) {
+  auto updater = state->updater;
+  int bad = 0;
+
+  if (parsed.has_selabel) {
+    if (lsetfilecon(filename, parsed.selabel) != 0) {
+      updater->UiPrint(android::base::StringPrintf(
+                            "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n",
+                            filename, parsed.selabel, strerror(errno)));
+      bad++;
+    }
+  }
+
+  /* ignore symlinks */
+  if (S_ISLNK(statptr->st_mode)) {
+    return bad;
+  }
+
+  if (parsed.has_uid) {
+    if (chown(filename, parsed.uid, -1) < 0) {
+      updater->UiPrint(android::base::StringPrintf(
+                            "ApplyParsedPerms: chown of %s to %d failed: %s\n",
+                            filename, parsed.uid, strerror(errno)));
+      bad++;
+    }
+  }
+
+  if (parsed.has_gid) {
+    if (chown(filename, -1, parsed.gid) < 0) {
+      updater->UiPrint(android::base::StringPrintf(
+                            "ApplyParsedPerms: chgrp of %s to %d failed: %s\n",
+                            filename, parsed.gid, strerror(errno)));
+      bad++;
+    }
+  }
+
+  if (parsed.has_mode) {
+    if (chmod(filename, parsed.mode) < 0) {
+      updater->UiPrint(android::base::StringPrintf(
+                            "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+                            filename, parsed.mode, strerror(errno)));
+      bad++;
+    }
+  }
+
+  if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) {
+    if (chmod(filename, parsed.dmode) < 0) {
+      updater->UiPrint(android::base::StringPrintf(
+                            "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+                            filename, parsed.dmode, strerror(errno)));
+      bad++;
+    }
+  }
+
+  if (parsed.has_fmode && S_ISREG(statptr->st_mode)) {
+    if (chmod(filename, parsed.fmode) < 0) {
+      updater->UiPrint(android::base::StringPrintf(
+                            "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+                            filename, parsed.fmode, strerror(errno)));
+      bad++;
+    }
+  }
+
+  if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) {
+    if (parsed.capabilities == 0) {
+      if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) {
+        // Report failure unless it's ENODATA (attribute not set)
+        updater->UiPrint(android::base::StringPrintf(
+                                "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n",
+                                filename, parsed.capabilities, strerror(errno)));
+        bad++;
+      }
+    } else {
+      struct vfs_cap_data cap_data;
+      memset(&cap_data, 0, sizeof(cap_data));
+      cap_data.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE;
+      cap_data.data[0].permitted = (uint32_t)(parsed.capabilities & 0xffffffff);
+      cap_data.data[0].inheritable = 0;
+      cap_data.data[1].permitted = (uint32_t)(parsed.capabilities >> 32);
+      cap_data.data[1].inheritable = 0;
+      if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) {
+        updater->UiPrint(android::base::StringPrintf(
+                                "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n",
+                                filename, parsed.capabilities, strerror(errno)));
+        bad++;
+      }
+    }
+  }
+
+  return bad;
+}
+
+// nftw doesn't allow us to pass along context, so we need to use
+// global variables.  *sigh*
+static struct perm_parsed_args recursive_parsed_args;
+static State* recursive_state;
+
+static int do_SetMetadataRecursive(const char* filename, const struct stat* statptr,
+                                   int /*fileflags*/, struct FTW* /*pfwt*/) {
+  return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args);
+}
+
+static Value* SetMetadataFn(const char* name, State* state,
+                            const std::vector<std::unique_ptr<Expr>>& argv) {
+  if ((argv.size() % 2) != 1) {
+    return ErrorAbort(state, kArgsParsingFailure,
+                      "%s() expects an odd number of arguments, got %zu", name, argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+
+  struct stat sb;
+  if (lstat(args[0].c_str(), &sb) == -1) {
+    return ErrorAbort(state, kSetMetadataFailure, "%s: Error on lstat of \"%s\": %s", name,
+                      args[0].c_str(), strerror(errno));
+  }
+
+  struct perm_parsed_args parsed = ParsePermArgs(state, args);
+  int bad = 0;
+  bool recursive = (strcmp(name, "set_metadata_recursive") == 0);
+
+  if (recursive) {
+    recursive_parsed_args = parsed;
+    recursive_state = state;
+    bad += nftw(args[0].c_str(), do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS);
+    memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args));
+    recursive_state = NULL;
+  } else {
+    bad += ApplyParsedPerms(state, args[0].c_str(), &sb, parsed);
+  }
+
+  if (bad > 0) {
+    return ErrorAbort(state, kSetMetadataFailure, "%s: some changes failed", name);
+  }
+
+  return StringValue("");
+}
+
 Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
   if (argv.size() != 1) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
@@ -879,7 +1305,25 @@
   RegisterFunction("format", FormatFn);
   RegisterFunction("show_progress", ShowProgressFn);
   RegisterFunction("set_progress", SetProgressFn);
+  RegisterFunction("delete", DeleteFn);
+  RegisterFunction("delete_recursive", DeleteFn);
+  RegisterFunction("package_extract_dir", PackageExtractDirFn);
   RegisterFunction("package_extract_file", PackageExtractFileFn);
+  RegisterFunction("symlink", SymlinkFn);
+
+  // Usage:
+  //   set_metadata("filename", "key1", "value1", "key2", "value2", ...)
+  // Example:
+  //   set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel",
+  //                "u:object_r:system_file:s0", "capabilities", 0x0);
+  RegisterFunction("set_metadata", SetMetadataFn);
+
+  // Usage:
+  //   set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...)
+  // Example:
+  //   set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755,
+  //                          "selabel", "u:object_r:system_file:s0", "capabilities", 0x0);
+  RegisterFunction("set_metadata_recursive", SetMetadataFn);
 
   RegisterFunction("getprop", GetPropFn);
   RegisterFunction("file_getprop", FileGetPropFn);
@@ -891,6 +1335,7 @@
   RegisterFunction("wipe_block_device", WipeBlockDeviceFn);
 
   RegisterFunction("read_file", ReadFileFn);
+  RegisterFunction("rename", RenameFn);
   RegisterFunction("write_value", WriteValueFn);
 
   RegisterFunction("wipe_cache", WipeCacheFn);
diff --git a/volclient.cpp b/volclient.cpp
new file mode 100644
index 0000000..b775370
--- /dev/null
+++ b/volclient.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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 "volclient.h"
+#include <volume_manager/ResponseCode.h>
+
+void VolumeClient::handleEvent(int code, const std::vector<std::string>& argv) {
+  // This client is only interested in volume addition/deletion
+  if (code != ResponseCode::VolumeDestroyed &&
+      code != ResponseCode::DiskScanned)
+    return;
+
+  printf("VolumeClient::handleEvent: code=%d, argv=<", code);
+  for (auto& arg : argv) {
+    printf("%s,", arg.c_str());
+  }
+  printf(">\n");
+
+  mDevice->handleVolumeChanged();
+}
diff --git a/volclient.h b/volclient.h
new file mode 100644
index 0000000..e534068
--- /dev/null
+++ b/volclient.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _RECOVERY_VOLCLIENT_H
+#define _RECOVERY_VOLCLIENT_H
+
+#include <volume_manager/VolumeManager.h>
+#include "recovery_ui/device.h"
+
+class VolumeClient : public VolumeWatcher {
+ public:
+  VolumeClient(Device* device) : mDevice(device) {}
+  virtual ~VolumeClient(void) {}
+  virtual void handleEvent(int code, const std::vector<std::string>& argv);
+
+ private:
+  Device* mDevice;
+};
+
+#endif
diff --git a/volume_manager/.clang-format b/volume_manager/.clang-format
new file mode 100644
index 0000000..ae4a451
--- /dev/null
+++ b/volume_manager/.clang-format
@@ -0,0 +1,11 @@
+BasedOnStyle: Google
+AccessModifierOffset: -2
+AllowShortFunctionsOnASingleLine: Inline
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+UseTab: Never
+PenaltyExcessCharacter: 32
diff --git a/volume_manager/Android.bp b/volume_manager/Android.bp
new file mode 100644
index 0000000..e386721
--- /dev/null
+++ b/volume_manager/Android.bp
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2019 The LineageOS Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_library_shared {
+    name: "libvolume_manager",
+    recovery_available: true,
+    srcs: [
+        "Disk.cpp",
+        "DiskPartition.cpp",
+        "EmulatedVolume.cpp",
+        "NetlinkHandler.cpp",
+        "NetlinkManager.cpp",
+        "Process.cpp",
+        "PublicVolume.cpp",
+        "Utils.cpp",
+        "VolumeBase.cpp",
+        "VolumeManager.cpp",
+        "fs/Exfat.cpp",
+        "fs/Ext4.cpp",
+        "fs/F2fs.cpp",
+        "fs/Ntfs.cpp",
+        "fs/Vfat.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    include_dirs: [
+        "external/gptfdisk",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libselinux",
+        "libsgdisk",
+        "libsysutils",
+    ],
+    static_libs: [
+        "libfs_mgr",
+        "libfstab",
+        "libext2_blkid",
+        "libext2_uuid",
+    ],
+    export_include_dirs: ["include"],
+}
diff --git a/volume_manager/Disk.cpp b/volume_manager/Disk.cpp
new file mode 100644
index 0000000..79a0ddc
--- /dev/null
+++ b/volume_manager/Disk.cpp
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 "Disk.h"
+#include "PublicVolume.h"
+#include <volume_manager/ResponseCode.h>
+#include <volume_manager/VolumeManager.h>
+#include "Utils.h"
+#include "VolumeBase.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+
+#include <sgdisk.h>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <vector>
+
+using android::base::ReadFileToString;
+using android::base::WriteStringToFile;
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+
+static const char* kSysfsLoopMaxMinors = "/sys/module/loop/parameters/max_part";
+static const char* kSysfsMmcMaxMinorsDeprecated = "/sys/module/mmcblk/parameters/perdev_minors";
+static const char* kSysfsMmcMaxMinors = "/sys/module/mmc_block/parameters/perdev_minors";
+
+static const unsigned int kMajorBlockLoop = 7;
+static const unsigned int kMajorBlockScsiA = 8;
+static const unsigned int kMajorBlockScsiB = 65;
+static const unsigned int kMajorBlockScsiC = 66;
+static const unsigned int kMajorBlockScsiD = 67;
+static const unsigned int kMajorBlockScsiE = 68;
+static const unsigned int kMajorBlockScsiF = 69;
+static const unsigned int kMajorBlockScsiG = 70;
+static const unsigned int kMajorBlockScsiH = 71;
+static const unsigned int kMajorBlockScsiI = 128;
+static const unsigned int kMajorBlockScsiJ = 129;
+static const unsigned int kMajorBlockScsiK = 130;
+static const unsigned int kMajorBlockScsiL = 131;
+static const unsigned int kMajorBlockScsiM = 132;
+static const unsigned int kMajorBlockScsiN = 133;
+static const unsigned int kMajorBlockScsiO = 134;
+static const unsigned int kMajorBlockScsiP = 135;
+static const unsigned int kMajorBlockMmc = 179;
+static const unsigned int kMajorBlockExperimentalMin = 240;
+static const unsigned int kMajorBlockExperimentalMax = 254;
+
+static const char* kGptBasicData = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7";
+static const char* kGptLinuxFilesystem = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
+
+enum class Table {
+    kUnknown,
+    kMbr,
+    kGpt,
+};
+
+static bool isVirtioBlkDevice(unsigned int major) {
+    /*
+     * The new emulator's "ranchu" virtual board no longer includes a goldfish
+     * MMC-based SD card device; instead, it emulates SD cards with virtio-blk,
+     * which has been supported by upstream kernel and QEMU for quite a while.
+     * Unfortunately, the virtio-blk block device driver does not use a fixed
+     * major number, but relies on the kernel to assign one from a specific
+     * range of block majors, which are allocated for "LOCAL/EXPERIMENAL USE"
+     * per Documentation/devices.txt. This is true even for the latest Linux
+     * kernel (4.4; see init() in drivers/block/virtio_blk.c).
+     *
+     * This makes it difficult for vold to detect a virtio-blk based SD card.
+     * The current solution checks two conditions (both must be met):
+     *
+     *  a) If the running environment is the emulator;
+     *  b) If the major number is an experimental block device major number (for
+     *     x86/x86_64 3.10 ranchu kernels, virtio-blk always gets major number
+     *     253, but it is safer to match the range than just one value).
+     *
+     * Other conditions could be used, too, e.g. the hardware name should be
+     * "ranchu", the device's sysfs path should end with "/block/vd[d-z]", etc.
+     * But just having a) and b) is enough for now.
+     */
+    return IsRunningInEmulator() && major >= kMajorBlockExperimentalMin &&
+           major <= kMajorBlockExperimentalMax;
+}
+
+Disk::Disk(const std::string& eventPath, dev_t device, const std::string& nickname, int flags)
+    : mDevice(device),
+      mSize(-1),
+      mNickname(nickname),
+      mFlags(flags),
+      mCreated(false),
+      mSkipChange(false) {
+    mId = StringPrintf("disk:%u_%u", major(device), minor(device));
+    mEventPath = eventPath;
+    mSysPath = StringPrintf("/sys/%s", eventPath.c_str());
+    mDevPath = StringPrintf("/dev/block/volmgr/%s", mId.c_str());
+    CreateDeviceNode(mDevPath, mDevice);
+}
+
+Disk::~Disk() {
+    CHECK(!mCreated);
+    DestroyDeviceNode(mDevPath);
+}
+
+void Disk::getVolumeInfo(std::vector<VolumeInfo>& info) {
+    for (auto vol : mVolumes) {
+        info.push_back(VolumeInfo(vol.get()));
+    }
+}
+
+std::shared_ptr<VolumeBase> Disk::findVolume(const std::string& id) {
+    for (auto vol : mVolumes) {
+        if (vol->getId() == id) {
+            return vol;
+        }
+    }
+    return nullptr;
+}
+
+void Disk::listVolumes(VolumeBase::Type type, std::list<std::string>& list) {
+    for (const auto& vol : mVolumes) {
+        if (vol->getType() == type) {
+            list.push_back(vol->getId());
+        }
+        // TODO: consider looking at stacked volumes
+    }
+}
+
+status_t Disk::create() {
+    CHECK(!mCreated);
+    mCreated = true;
+    VolumeManager::Instance()->notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags));
+    readMetadata();
+    readPartitions();
+    return OK;
+}
+
+status_t Disk::destroy() {
+    CHECK(mCreated);
+    destroyAllVolumes();
+    mCreated = false;
+    VolumeManager::Instance()->notifyEvent(ResponseCode::DiskDestroyed);
+    return OK;
+}
+
+void Disk::createPublicVolume(dev_t device, const std::string& fstype /* = "" */,
+                              const std::string& mntopts /* = "" */) {
+    auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(device, mNickname, fstype, mntopts));
+
+    mVolumes.push_back(vol);
+    vol->setDiskId(getId());
+    vol->create();
+}
+
+void Disk::destroyAllVolumes() {
+    for (const auto& vol : mVolumes) {
+        vol->destroy();
+    }
+    mVolumes.clear();
+}
+
+status_t Disk::readMetadata() {
+    if (mSkipChange) {
+        return OK;
+    }
+
+    mSize = -1;
+    mLabel.clear();
+
+    int fd = open(mDevPath.c_str(), O_RDONLY | O_CLOEXEC);
+    if (fd != -1) {
+        if (ioctl(fd, BLKGETSIZE64, &mSize)) {
+            mSize = -1;
+        }
+        close(fd);
+    }
+
+    unsigned int majorId = major(mDevice);
+    switch (majorId) {
+        case kMajorBlockLoop: {
+            mLabel = "Virtual";
+            break;
+        }
+        case kMajorBlockScsiA:
+        case kMajorBlockScsiB:
+        case kMajorBlockScsiC:
+        case kMajorBlockScsiD:
+        case kMajorBlockScsiE:
+        case kMajorBlockScsiF:
+        case kMajorBlockScsiG:
+        case kMajorBlockScsiH:
+        case kMajorBlockScsiI:
+        case kMajorBlockScsiJ:
+        case kMajorBlockScsiK:
+        case kMajorBlockScsiL:
+        case kMajorBlockScsiM:
+        case kMajorBlockScsiN:
+        case kMajorBlockScsiO:
+        case kMajorBlockScsiP: {
+            std::string path(mSysPath + "/device/vendor");
+            std::string tmp;
+            if (!ReadFileToString(path, &tmp)) {
+                PLOG(WARNING) << "Failed to read vendor from " << path;
+                return -errno;
+            }
+            mLabel = tmp;
+            break;
+        }
+        case kMajorBlockMmc: {
+            std::string path(mSysPath + "/device/manfid");
+            std::string tmp;
+            if (!ReadFileToString(path, &tmp)) {
+                PLOG(WARNING) << "Failed to read manufacturer from " << path;
+                return -errno;
+            }
+            uint64_t manfid = strtoll(tmp.c_str(), nullptr, 16);
+            // Our goal here is to give the user a meaningful label, ideally
+            // matching whatever is silk-screened on the card.  To reduce
+            // user confusion, this list doesn't contain white-label manfid.
+            switch (manfid) {
+                case 0x000003:
+                    mLabel = "SanDisk";
+                    break;
+                case 0x00001b:
+                    mLabel = "Samsung";
+                    break;
+                case 0x000028:
+                    mLabel = "Lexar";
+                    break;
+                case 0x000074:
+                    mLabel = "Transcend";
+                    break;
+            }
+            break;
+        }
+        default: {
+            if (isVirtioBlkDevice(majorId)) {
+                LOG(DEBUG) << "Recognized experimental block major ID " << majorId
+                           << " as virtio-blk (emulator's virtual SD card device)";
+                mLabel = "Virtual";
+                break;
+            }
+            LOG(WARNING) << "Unsupported block major type " << majorId;
+            return -ENOTSUP;
+        }
+    }
+
+    VolumeManager::Instance()->notifyEvent(ResponseCode::DiskSizeChanged,
+                                           StringPrintf("%" PRIu64, mSize));
+    VolumeManager::Instance()->notifyEvent(ResponseCode::DiskLabelChanged, mLabel);
+    VolumeManager::Instance()->notifyEvent(ResponseCode::DiskSysPathChanged, mSysPath);
+    return OK;
+}
+
+status_t Disk::readPartitions() {
+    int8_t maxMinors = getMaxMinors();
+    if (maxMinors < 0) {
+        return -ENOTSUP;
+    }
+
+    if (mSkipChange) {
+        mSkipChange = false;
+        LOG(INFO) << "Skip first change";
+        return OK;
+    }
+
+    destroyAllVolumes();
+
+    // Parse partition table
+    sgdisk_partition_table ptbl;
+    std::vector<sgdisk_partition> partitions;
+    int res = sgdisk_read(mDevPath.c_str(), ptbl, partitions);
+    if (res != 0) {
+        LOG(WARNING) << "sgdisk failed to scan " << mDevPath;
+        VolumeManager::Instance()->notifyEvent(ResponseCode::DiskScanned);
+        return res;
+    }
+
+    Table table = Table::kUnknown;
+    bool foundParts = false;
+
+    switch (ptbl.type) {
+        case MBR:
+            table = Table::kMbr;
+            break;
+        case GPT:
+            table = Table::kGpt;
+            break;
+        default:
+            table = Table::kUnknown;
+    }
+
+    foundParts = partitions.size() > 0;
+    for (const auto& part : partitions) {
+        if (part.num <= 0 || part.num > maxMinors) {
+            LOG(WARNING) << mId << " is ignoring partition " << part.num
+                         << " beyond max supported devices";
+            continue;
+        }
+        dev_t partDevice = makedev(major(mDevice), minor(mDevice) + part.num);
+        if (table == Table::kMbr) {
+            switch (strtol(part.type.c_str(), nullptr, 16)) {
+                case 0x06:  // FAT16
+                case 0x07:  // NTFS/exFAT
+                case 0x0b:  // W95 FAT32 (LBA)
+                case 0x0c:  // W95 FAT32 (LBA)
+                case 0x0e:  // W95 FAT16 (LBA)
+                case 0x83:  // Linux EXT4/F2FS/...
+                    createPublicVolume(partDevice);
+                    break;
+            }
+        } else if (table == Table::kGpt) {
+            if (!strcasecmp(part.guid.c_str(), kGptBasicData) ||
+                !strcasecmp(part.guid.c_str(), kGptLinuxFilesystem)) {
+                createPublicVolume(partDevice);
+            }
+        }
+    }
+
+    // Ugly last ditch effort, treat entire disk as partition
+    if (table == Table::kUnknown || !foundParts) {
+        LOG(WARNING) << mId << " has unknown partition table; trying entire device";
+
+        std::string fsType;
+        std::string unused;
+        if (ReadMetadataUntrusted(mDevPath, fsType, unused, unused) == OK) {
+            createPublicVolume(mDevice);
+        } else {
+            LOG(WARNING) << mId << " failed to identify, giving up";
+        }
+    }
+
+    VolumeManager::Instance()->notifyEvent(ResponseCode::DiskScanned);
+    return OK;
+}
+
+status_t Disk::unmountAll() {
+    for (const auto& vol : mVolumes) {
+        vol->unmount();
+    }
+    return OK;
+}
+
+int Disk::getMaxMinors() {
+    // Figure out maximum partition devices supported
+    unsigned int majorId = major(mDevice);
+    switch (majorId) {
+        case kMajorBlockLoop: {
+            std::string tmp;
+            if (!ReadFileToString(kSysfsLoopMaxMinors, &tmp)) {
+                LOG(ERROR) << "Failed to read max minors";
+                return -errno;
+            }
+            return atoi(tmp.c_str());
+        }
+        case kMajorBlockScsiA:
+        case kMajorBlockScsiB:
+        case kMajorBlockScsiC:
+        case kMajorBlockScsiD:
+        case kMajorBlockScsiE:
+        case kMajorBlockScsiF:
+        case kMajorBlockScsiG:
+        case kMajorBlockScsiH:
+        case kMajorBlockScsiI:
+        case kMajorBlockScsiJ:
+        case kMajorBlockScsiK:
+        case kMajorBlockScsiL:
+        case kMajorBlockScsiM:
+        case kMajorBlockScsiN:
+        case kMajorBlockScsiO:
+        case kMajorBlockScsiP: {
+            // Per Documentation/devices.txt this is static
+            return 15;
+        }
+        case kMajorBlockMmc: {
+            // Per Documentation/devices.txt this is dynamic
+            std::string tmp;
+            if (!ReadFileToString(kSysfsMmcMaxMinors, &tmp) &&
+                !ReadFileToString(kSysfsMmcMaxMinorsDeprecated, &tmp)) {
+                LOG(ERROR) << "Failed to read max minors";
+                return -errno;
+            }
+            return atoi(tmp.c_str());
+        }
+        default: {
+            if (isVirtioBlkDevice(majorId)) {
+                // drivers/block/virtio_blk.c has "#define PART_BITS 4", so max is
+                // 2^4 - 1 = 15
+                return 15;
+            }
+        }
+    }
+
+    LOG(ERROR) << "Unsupported block major type " << majorId;
+    return -ENOTSUP;
+}
+
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/Disk.h b/volume_manager/Disk.h
new file mode 100644
index 0000000..af86f6d
--- /dev/null
+++ b/volume_manager/Disk.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VOLMGR_DISK_H
+#define ANDROID_VOLMGR_DISK_H
+
+#include "Utils.h"
+#include "VolumeBase.h"
+
+#include <utils/Errors.h>
+
+#include <vector>
+
+#include <volume_manager/VolumeManager.h>
+
+namespace android {
+namespace volmgr {
+
+class VolumeBase;
+
+/*
+ * Representation of detected physical media.
+ *
+ * Knows how to create volumes based on the partition tables found, and also
+ * how to repartition itself.
+ */
+class Disk {
+  public:
+    Disk(const std::string& eventPath, dev_t device, const std::string& nickname, int flags);
+    virtual ~Disk();
+
+    enum Flags {
+        /* Flag that disk is adoptable */
+        kAdoptable = 1 << 0,
+        /* Flag that disk is considered primary when the user hasn't
+         * explicitly picked a primary storage location */
+        kDefaultPrimary = 1 << 1,
+        /* Flag that disk is SD card */
+        kSd = 1 << 2,
+        /* Flag that disk is USB disk */
+        kUsb = 1 << 3,
+        /* Flag that disk is EMMC internal */
+        kEmmc = 1 << 4,
+        /* Flag that disk is non-removable */
+        kNonRemovable = 1 << 5,
+    };
+
+    const std::string& getId() { return mId; }
+    const std::string& getEventPath() { return mEventPath; }
+    const std::string& getSysPath() { return mSysPath; }
+    const std::string& getDevPath() { return mDevPath; }
+    dev_t getDevice() { return mDevice; }
+    uint64_t getSize() { return mSize; }
+    const std::string& getLabel() { return mLabel; }
+    int getFlags() { return mFlags; }
+
+    void getVolumeInfo(std::vector<VolumeInfo>& info);
+
+    std::shared_ptr<VolumeBase> findVolume(const std::string& id);
+
+    void listVolumes(VolumeBase::Type type, std::list<std::string>& list);
+
+    virtual status_t create();
+    virtual status_t destroy();
+
+    virtual status_t readMetadata();
+    virtual status_t readPartitions();
+
+    status_t unmountAll();
+
+  protected:
+    /* ID that uniquely references this disk */
+    std::string mId;
+    /* Original event path */
+    std::string mEventPath;
+    /* Device path under sysfs */
+    std::string mSysPath;
+    /* Device path under dev */
+    std::string mDevPath;
+    /* Kernel device representing disk */
+    dev_t mDevice;
+    /* Size of disk, in bytes */
+    uint64_t mSize;
+    /* User-visible label, such as manufacturer */
+    std::string mLabel;
+    /* Current partitions on disk */
+    std::vector<std::shared_ptr<VolumeBase>> mVolumes;
+    /* Nickname for this disk */
+    std::string mNickname;
+    /* Flags applicable to this disk */
+    int mFlags;
+    /* Flag indicating object is created */
+    bool mCreated;
+    /* Flag that we need to skip first disk change events after partitioning*/
+    bool mSkipChange;
+
+    void createPublicVolume(dev_t device, const std::string& fstype = "",
+                            const std::string& mntopts = "");
+
+    void destroyAllVolumes();
+
+    int getMaxMinors();
+
+    DISALLOW_COPY_AND_ASSIGN(Disk);
+};
+
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/DiskPartition.cpp b/volume_manager/DiskPartition.cpp
new file mode 100644
index 0000000..26ac6f5
--- /dev/null
+++ b/volume_manager/DiskPartition.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 Cyanogen, Inc.
+ * Copyright (C) 2019 The LineageOS 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 "DiskPartition.h"
+#include "PublicVolume.h"
+#include <volume_manager/ResponseCode.h>
+#include <volume_manager/VolumeManager.h>
+#include "Utils.h"
+#include "VolumeBase.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <vector>
+
+using android::base::ReadFileToString;
+using android::base::WriteStringToFile;
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+
+DiskPartition::DiskPartition(const std::string& eventPath, dev_t device, const std::string& nickname,
+                             int flags, int partnum, const std::string& fstype /* = "" */,
+                             const std::string& mntopts /* = "" */)
+    : Disk(eventPath, device, nickname, flags),
+      mPartNum(partnum),
+      mFsType(fstype),
+      mMntOpts(mntopts) {
+    // Empty
+}
+
+DiskPartition::~DiskPartition() {
+    // Empty
+}
+
+status_t DiskPartition::create() {
+    CHECK(!mCreated);
+    mCreated = true;
+    VolumeManager::Instance()->notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags));
+    dev_t partDevice = makedev(major(mDevice), minor(mDevice) + mPartNum);
+    createPublicVolume(partDevice, mFsType, mMntOpts);
+    return OK;
+}
+
+status_t DiskPartition::destroy() {
+    CHECK(mCreated);
+    destroyAllVolumes();
+    mCreated = false;
+    VolumeManager::Instance()->notifyEvent(ResponseCode::DiskDestroyed);
+    return OK;
+}
+
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/DiskPartition.h b/volume_manager/DiskPartition.h
new file mode 100644
index 0000000..0f39583
--- /dev/null
+++ b/volume_manager/DiskPartition.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 Cyanogen, Inc.
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VOLMGR_DISKPARTITION_H
+#define ANDROID_VOLMGR_DISKPARTITION_H
+
+#include "Disk.h"
+
+namespace android {
+namespace volmgr {
+
+/*
+ * Representation of a single partition on physical media.  Useful for
+ * single media partitions such as "internal" sdcard partitions.
+ */
+
+class DiskPartition : public Disk {
+  public:
+    DiskPartition(const std::string& eventPath, dev_t device, const std::string& nickname, int flags,
+                  int partnum, const std::string& fstype = "", const std::string& mntopts = "");
+    virtual ~DiskPartition();
+
+    virtual status_t create();
+    virtual status_t destroy();
+
+  private:
+    /* Partition number */
+    int mPartNum;
+    /* Filesystem type */
+    std::string mFsType;
+    /* Mount options */
+    std::string mMntOpts;
+};
+
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/EmulatedVolume.cpp b/volume_manager/EmulatedVolume.cpp
new file mode 100644
index 0000000..a1a0756
--- /dev/null
+++ b/volume_manager/EmulatedVolume.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 "EmulatedVolume.h"
+#include <volume_manager/ResponseCode.h>
+#include <volume_manager/VolumeManager.h>
+#include "Utils.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/fs.h>
+#include <private/android_filesystem_config.h>
+
+#include <iostream>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+
+static const std::string kStagingPath = "/mnt/staging/emulated";
+static const std::string kFbeKeyVersion = kStagingPath + "/unencrypted/key/version";
+
+EmulatedVolume::EmulatedVolume(FstabEntry* rec, const std::string& subdir)
+    : VolumeBase(Type::kEmulated),
+      mSubdir(subdir),
+      mDevPath(rec->blk_device),
+      mFsType(rec->fs_type),
+      mFlags(rec->flags),
+      mFsOptions(rec->fs_options) {
+    setId("emulated");
+    setPartLabel("internal storage");
+    setPath("/storage/emulated");
+}
+
+EmulatedVolume::~EmulatedVolume() {}
+
+status_t EmulatedVolume::doMount() {
+    if (fs_prepare_dir(kStagingPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
+        PLOG(ERROR) << getId() << " failed to create mount points";
+        return -errno;
+    }
+    if (fs_prepare_dir(getPath().c_str(), 0700, AID_ROOT, AID_ROOT)) {
+        PLOG(ERROR) << getId() << " failed to create mount points";
+        return -errno;
+    }
+
+    std::string bindPath = kStagingPath + "/" + mSubdir;
+
+    if (::mount(mDevPath.c_str(), kStagingPath.c_str(), mFsType.c_str(), mFlags,
+                mFsOptions.c_str()) != 0) {
+        // It's ok to fail mounting if we're encrytped, so avoid printing to recovery's UiLogger
+        std::cout << getId() << " failed to mount " << mDevPath << " on " << kStagingPath
+                  << ": " << std::strerror(errno);
+        return -EIO;
+    }
+    if (BindMount(bindPath, getPath()) != OK) {
+        ForceUnmount(kStagingPath);
+        return -EIO;
+    }
+
+    return OK;
+}
+
+status_t EmulatedVolume::doUnmount(bool detach /* = false */) {
+    ForceUnmount(getPath(), detach);
+    ForceUnmount(kStagingPath, detach);
+
+    rmdir(getPath().c_str());
+    rmdir(kStagingPath.c_str());
+
+    return OK;
+}
+
+bool EmulatedVolume::detectMountable() {
+    bool mountable = false;
+    if (doMount() == OK) {
+        // Check if FBE encrypted
+        mountable = access(kFbeKeyVersion.c_str(), F_OK) != 0;
+        doUnmount();
+    }
+    return mountable;
+}
+
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/EmulatedVolume.h b/volume_manager/EmulatedVolume.h
new file mode 100644
index 0000000..66421cc
--- /dev/null
+++ b/volume_manager/EmulatedVolume.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VOLMGR_EMULATED_VOLUME_H
+#define ANDROID_VOLMGR_EMULATED_VOLUME_H
+
+#include "VolumeBase.h"
+
+#include <cutils/multiuser.h>
+#include <fstab/fstab.h>
+
+using android::fs_mgr::FstabEntry;
+
+namespace android {
+namespace volmgr {
+
+/*
+ * Shared storage emulated on top of private storage.
+ *
+ * Knows how to spawn a FUSE daemon to synthesize permissions.  ObbVolume
+ * can be stacked above it.
+ *
+ * This volume is always multi-user aware, but is only binds itself to
+ * users when its primary storage.  This volume should never be presented
+ * as secondary storage, since we're strongly encouraging developers to
+ * store data local to their app.
+ */
+class EmulatedVolume : public VolumeBase {
+  public:
+    explicit EmulatedVolume(FstabEntry* rec, const std::string& subdir);
+    virtual ~EmulatedVolume();
+
+  protected:
+    status_t doMount() override;
+    status_t doUnmount(bool detach = false) override;
+
+  private:
+    std::string mSubdir;
+    std::string mDevPath;
+    std::string mFsType;
+    unsigned long mFlags;
+    std::string mFsOptions;
+
+    bool detectMountable() override;
+
+    DISALLOW_COPY_AND_ASSIGN(EmulatedVolume);
+};
+
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/NetlinkHandler.cpp b/volume_manager/NetlinkHandler.cpp
new file mode 100644
index 0000000..1cd469d
--- /dev/null
+++ b/volume_manager/NetlinkHandler.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define LOG_TAG "Vold"
+
+#include <cutils/log.h>
+
+#include <sysutils/NetlinkEvent.h>
+#include <volume_manager/VolumeManager.h>
+#include "NetlinkHandler.h"
+
+NetlinkHandler::NetlinkHandler(int listenerSocket) : NetlinkListener(listenerSocket) {}
+
+NetlinkHandler::~NetlinkHandler() {}
+
+bool NetlinkHandler::start() {
+    return this->startListener() == 0;
+}
+
+void NetlinkHandler::stop() {
+    this->stopListener();
+}
+
+void NetlinkHandler::onEvent(NetlinkEvent* evt) {
+    android::volmgr::VolumeManager* vm = android::volmgr::VolumeManager::Instance();
+    const char* subsys = evt->getSubsystem();
+
+    if (!subsys) {
+        SLOGW("No subsystem found in netlink event");
+        return;
+    }
+
+    if (!strcmp(subsys, "block")) {
+        vm->handleBlockEvent(evt);
+    }
+}
diff --git a/volume_manager/NetlinkHandler.h b/volume_manager/NetlinkHandler.h
new file mode 100644
index 0000000..09f474f
--- /dev/null
+++ b/volume_manager/NetlinkHandler.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _NETLINKHANDLER_H
+#define _NETLINKHANDLER_H
+
+#include <sysutils/NetlinkListener.h>
+
+class NetlinkHandler : public NetlinkListener {
+  public:
+    explicit NetlinkHandler(int listenerSocket);
+    virtual ~NetlinkHandler();
+
+    bool start(void);
+    void stop(void);
+
+  protected:
+    virtual void onEvent(NetlinkEvent* evt);
+};
+#endif
diff --git a/volume_manager/NetlinkManager.cpp b/volume_manager/NetlinkManager.cpp
new file mode 100644
index 0000000..28640e9
--- /dev/null
+++ b/volume_manager/NetlinkManager.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <linux/netlink.h>
+
+#define LOG_TAG "Vold"
+
+#include <cutils/log.h>
+
+#include "NetlinkHandler.h"
+#include "NetlinkManager.h"
+
+NetlinkManager* NetlinkManager::sInstance = NULL;
+
+NetlinkManager* NetlinkManager::Instance() {
+    if (!sInstance) sInstance = new NetlinkManager();
+    return sInstance;
+}
+
+NetlinkManager::NetlinkManager() {
+    // Empty
+}
+
+NetlinkManager::~NetlinkManager() {}
+
+bool NetlinkManager::start() {
+    struct sockaddr_nl nladdr;
+    int sz = 64 * 1024;
+    int on = 1;
+
+    memset(&nladdr, 0, sizeof(nladdr));
+    nladdr.nl_family = AF_NETLINK;
+    nladdr.nl_pid = getpid();
+    nladdr.nl_groups = 0xffffffff;
+
+    if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) < 0) {
+        SLOGE("Unable to create uevent socket: %s", strerror(errno));
+        return false;
+    }
+
+    // When running in a net/user namespace, SO_RCVBUFFORCE is not available.
+    // Try using SO_RCVBUF first.
+    if ((setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)) < 0) &&
+        (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0)) {
+        SLOGE("Unable to set uevent socket SO_RCVBUF/SO_RCVBUFFORCE option: %s", strerror(errno));
+        goto out;
+    }
+
+    if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
+        SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));
+        goto out;
+    }
+
+    if (bind(mSock, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) {
+        SLOGE("Unable to bind uevent socket: %s", strerror(errno));
+        goto out;
+    }
+
+    mHandler = new NetlinkHandler(mSock);
+    if (!mHandler->start()) {
+        SLOGE("Unable to start NetlinkHandler: %s", strerror(errno));
+        goto out;
+    }
+
+    return true;
+
+out:
+    close(mSock);
+    return false;
+}
+
+void NetlinkManager::stop() {
+    mHandler->stop();
+    delete mHandler;
+    mHandler = NULL;
+
+    close(mSock);
+    mSock = -1;
+}
diff --git a/volume_manager/NetlinkManager.h b/volume_manager/NetlinkManager.h
new file mode 100644
index 0000000..c09ff06
--- /dev/null
+++ b/volume_manager/NetlinkManager.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _NETLINKMANAGER_H
+#define _NETLINKMANAGER_H
+
+#include <sysutils/NetlinkListener.h>
+#include <sysutils/SocketListener.h>
+
+class NetlinkHandler;
+
+class NetlinkManager {
+  private:
+    static NetlinkManager* sInstance;
+
+  private:
+    NetlinkHandler* mHandler;
+    int mSock;
+
+  public:
+    virtual ~NetlinkManager();
+
+    bool start();
+    void stop();
+
+    static NetlinkManager* Instance();
+
+  private:
+    NetlinkManager();
+};
+#endif
diff --git a/volume_manager/Process.cpp b/volume_manager/Process.cpp
new file mode 100644
index 0000000..387d8d3
--- /dev/null
+++ b/volume_manager/Process.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define LOG_TAG "ProcessKiller"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/log.h>
+
+#include "Process.h"
+
+using android::base::ReadFileToString;
+using android::base::StringPrintf;
+
+int Process::readSymLink(const char* path, char* link, size_t max) {
+    struct stat s;
+    int length;
+
+    if (lstat(path, &s) < 0) return 0;
+    if ((s.st_mode & S_IFMT) != S_IFLNK) return 0;
+
+    // we have a symlink
+    length = readlink(path, link, max - 1);
+    if (length <= 0) return 0;
+    link[length] = 0;
+    return 1;
+}
+
+int Process::pathMatchesMountPoint(const char* path, const char* mountPoint) {
+    int length = strlen(mountPoint);
+    if (length > 1 && strncmp(path, mountPoint, length) == 0) {
+        // we need to do extra checking if mountPoint does not end in a '/'
+        if (mountPoint[length - 1] == '/') return 1;
+        // if mountPoint does not have a trailing slash, we need to make sure
+        // there is one in the path to avoid partial matches.
+        return (path[length] == 0 || path[length] == '/');
+    }
+
+    return 0;
+}
+
+void Process::getProcessName(int pid, std::string& out_name) {
+    if (!ReadFileToString(StringPrintf("/proc/%d/cmdline", pid), &out_name)) {
+        out_name = "???";
+    }
+}
+
+int Process::checkFileDescriptorSymLinks(int pid, const char* mountPoint) {
+    return checkFileDescriptorSymLinks(pid, mountPoint, NULL, 0);
+}
+
+int Process::checkFileDescriptorSymLinks(int pid, const char* mountPoint, char* openFilename,
+                                         size_t max) {
+    // compute path to process's directory of open files
+    char path[PATH_MAX];
+    snprintf(path, sizeof(path), "/proc/%d/fd", pid);
+    DIR* dir = opendir(path);
+    if (!dir) return 0;
+
+    // remember length of the path
+    int parent_length = strlen(path);
+    // append a trailing '/'
+    path[parent_length++] = '/';
+
+    struct dirent* de;
+    while ((de = readdir(dir))) {
+        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..") ||
+            strlen(de->d_name) + parent_length + 1 >= PATH_MAX)
+            continue;
+
+        // append the file name, after truncating to parent directory
+        path[parent_length] = 0;
+        strlcat(path, de->d_name, PATH_MAX);
+
+        char link[PATH_MAX];
+
+        if (readSymLink(path, link, sizeof(link)) && pathMatchesMountPoint(link, mountPoint)) {
+            if (openFilename) {
+                memset(openFilename, 0, max);
+                strlcpy(openFilename, link, max);
+            }
+            closedir(dir);
+            return 1;
+        }
+    }
+
+    closedir(dir);
+    return 0;
+}
+
+int Process::checkFileMaps(int pid, const char* mountPoint) {
+    return checkFileMaps(pid, mountPoint, NULL, 0);
+}
+
+int Process::checkFileMaps(int pid, const char* mountPoint, char* openFilename, size_t max) {
+    FILE* file;
+    char buffer[PATH_MAX + 100];
+
+    snprintf(buffer, sizeof(buffer), "/proc/%d/maps", pid);
+    file = fopen(buffer, "re");
+    if (!file) return 0;
+
+    while (fgets(buffer, sizeof(buffer), file)) {
+        // skip to the path
+        const char* path = strchr(buffer, '/');
+        if (path && pathMatchesMountPoint(path, mountPoint)) {
+            if (openFilename) {
+                memset(openFilename, 0, max);
+                strlcpy(openFilename, path, max);
+            }
+            fclose(file);
+            return 1;
+        }
+    }
+
+    fclose(file);
+    return 0;
+}
+
+int Process::checkSymLink(int pid, const char* mountPoint, const char* name) {
+    char path[PATH_MAX];
+    char link[PATH_MAX];
+
+    snprintf(path, sizeof(path), "/proc/%d/%s", pid, name);
+    if (readSymLink(path, link, sizeof(link)) && pathMatchesMountPoint(link, mountPoint)) return 1;
+    return 0;
+}
+
+int Process::getPid(const char* s) {
+    int result = 0;
+    while (*s) {
+        if (!isdigit(*s)) return -1;
+        result = 10 * result + (*s++ - '0');
+    }
+    return result;
+}
+
+/*
+ * Hunt down processes that have files open at the given mount point.
+ */
+int Process::killProcessesWithOpenFiles(const char* path, int signal) {
+    int count = 0;
+    DIR* dir;
+    struct dirent* de;
+
+    if (!(dir = opendir("/proc"))) {
+        SLOGE("opendir failed (%s)", strerror(errno));
+        return count;
+    }
+
+    while ((de = readdir(dir))) {
+        int pid = getPid(de->d_name);
+        if (pid == -1) continue;
+
+        std::string name;
+        getProcessName(pid, name);
+
+        char openfile[PATH_MAX];
+
+        if (checkFileDescriptorSymLinks(pid, path, openfile, sizeof(openfile))) {
+            SLOGE("Process %s (%d) has open file %s", name.c_str(), pid, openfile);
+        } else if (checkFileMaps(pid, path, openfile, sizeof(openfile))) {
+            SLOGE("Process %s (%d) has open filemap for %s", name.c_str(), pid, openfile);
+        } else if (checkSymLink(pid, path, "cwd")) {
+            SLOGE("Process %s (%d) has cwd within %s", name.c_str(), pid, path);
+        } else if (checkSymLink(pid, path, "root")) {
+            SLOGE("Process %s (%d) has chroot within %s", name.c_str(), pid, path);
+        } else if (checkSymLink(pid, path, "exe")) {
+            SLOGE("Process %s (%d) has executable path within %s", name.c_str(), pid, path);
+        } else {
+            continue;
+        }
+
+        if (signal != 0) {
+            SLOGW("Sending %s to process %d", strsignal(signal), pid);
+            kill(pid, signal);
+            count++;
+        }
+    }
+    closedir(dir);
+    return count;
+}
diff --git a/volume_manager/Process.h b/volume_manager/Process.h
new file mode 100644
index 0000000..4abdc1b
--- /dev/null
+++ b/volume_manager/Process.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _PROCESS_H
+#define _PROCESS_H
+
+class Process {
+  public:
+    static int killProcessesWithOpenFiles(const char* path, int signal);
+    static int getPid(const char* s);
+    static int checkSymLink(int pid, const char* path, const char* name);
+    static int checkFileMaps(int pid, const char* path);
+    static int checkFileMaps(int pid, const char* path, char* openFilename, size_t max);
+    static int checkFileDescriptorSymLinks(int pid, const char* mountPoint);
+    static int checkFileDescriptorSymLinks(int pid, const char* mountPoint, char* openFilename,
+                                           size_t max);
+    static void getProcessName(int pid, std::string& out_name);
+
+  private:
+    static int readSymLink(const char* path, char* link, size_t max);
+    static int pathMatchesMountPoint(const char* path, const char* mountPoint);
+};
+
+#endif
diff --git a/volume_manager/PublicVolume.cpp b/volume_manager/PublicVolume.cpp
new file mode 100644
index 0000000..8fb3b96
--- /dev/null
+++ b/volume_manager/PublicVolume.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 "PublicVolume.h"
+#include <volume_manager/ResponseCode.h>
+#include <volume_manager/VolumeManager.h>
+#include "Utils.h"
+#include "fs/Exfat.h"
+#include "fs/Ext4.h"
+#include "fs/F2fs.h"
+#include "fs/Ntfs.h"
+#include "fs/Vfat.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/fs.h>
+#include <private/android_filesystem_config.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+
+PublicVolume::PublicVolume(dev_t device, const std::string& nickname,
+                           const std::string& fstype /* = "" */,
+                           const std::string& mntopts /* = "" */)
+    : VolumeBase(Type::kPublic), mDevice(device), mFsType(fstype), mMntOpts(mntopts) {
+    setId(StringPrintf("public:%u_%u", major(device), minor(device)));
+    setPartLabel(nickname);
+    mDevPath = StringPrintf("/dev/block/volmgr/%s", getId().c_str());
+}
+
+PublicVolume::~PublicVolume() {}
+
+status_t PublicVolume::readMetadata() {
+    std::string label;
+    status_t res = ReadMetadataUntrusted(mDevPath, mFsType, mFsUuid, label);
+    if (!label.empty()) {
+        setPartLabel(label);
+    }
+    VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeFsTypeChanged, mFsType);
+    VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeFsUuidChanged, mFsUuid);
+    return res;
+}
+
+status_t PublicVolume::doCreate() {
+    status_t res = CreateDeviceNode(mDevPath, mDevice);
+    if (res != OK) {
+        return res;
+    }
+    readMetadata();
+
+    // Use UUID as stable name, if available
+    std::string stableName = getId();
+    if (!mFsUuid.empty()) {
+        stableName = mFsUuid;
+    }
+    setPath(StringPrintf("/storage/%s", stableName.c_str()));
+
+    return OK;
+}
+
+status_t PublicVolume::doDestroy() {
+    return DestroyDeviceNode(mDevPath);
+}
+
+status_t PublicVolume::doMount() {
+    if (!IsFilesystemSupported(mFsType)) {
+        LOG(ERROR) << getId() << " unsupported filesystem " << mFsType;
+        return -EIO;
+    }
+
+    if (fs_prepare_dir(getPath().c_str(), 0700, AID_ROOT, AID_ROOT)) {
+        PLOG(ERROR) << getId() << " failed to create mount points";
+        return -errno;
+    }
+
+    int ret = 0;
+    if (mFsType == "exfat") {
+        ret = exfat::Mount(mDevPath, getPath(), AID_MEDIA_RW, AID_MEDIA_RW, 0007);
+    } else if (mFsType == "ext4") {
+        ret = ext4::Mount(mDevPath, getPath(), false, false, true, mMntOpts, false, true);
+    } else if (mFsType == "f2fs") {
+        ret = f2fs::Mount(mDevPath, getPath(), mMntOpts, false, true);
+    } else if (mFsType == "ntfs") {
+        ret =
+            ntfs::Mount(mDevPath, getPath(), false, false, false, AID_MEDIA_RW, AID_MEDIA_RW, 0007);
+    } else if (mFsType == "vfat") {
+        ret = vfat::Mount(mDevPath, getPath(), false, false, false, AID_MEDIA_RW, AID_MEDIA_RW,
+                          0007, true);
+    } else {
+        ret = ::mount(mDevPath.c_str(), getPath().c_str(), mFsType.c_str(), 0, nullptr);
+    }
+    if (ret) {
+        PLOG(ERROR) << getId() << " failed to mount " << mDevPath;
+        return -EIO;
+    }
+
+    return OK;
+}
+
+status_t PublicVolume::doUnmount(bool detach /* = false */) {
+    ForceUnmount(getPath(), detach);
+
+    rmdir(getPath().c_str());
+
+    return OK;
+}
+
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/PublicVolume.h b/volume_manager/PublicVolume.h
new file mode 100644
index 0000000..c740c34
--- /dev/null
+++ b/volume_manager/PublicVolume.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VOLMGR_PUBLIC_VOLUME_H
+#define ANDROID_VOLMGR_PUBLIC_VOLUME_H
+
+#include "VolumeBase.h"
+
+#include <cutils/multiuser.h>
+
+namespace android {
+namespace volmgr {
+
+/*
+ * Shared storage provided by public (vfat) partition.
+ *
+ * Knows how to mount itself and then spawn a FUSE daemon to synthesize
+ * permissions.
+ *
+ * This volume is not inherently multi-user aware, so it has two possible
+ * modes of operation:
+ * 1. If primary storage for the device, it only binds itself to the
+ * owner user.
+ * 2. If secondary storage, it binds itself for all users, but masks
+ * away the Android directory for secondary users.
+ */
+class PublicVolume : public VolumeBase {
+  public:
+    PublicVolume(dev_t device, const std::string& nickname, const std::string& mntopts = "",
+                 const std::string& fstype = "");
+    virtual ~PublicVolume();
+
+  protected:
+    status_t doCreate() override;
+    status_t doDestroy() override;
+    status_t doMount() override;
+    status_t doUnmount(bool detach = false) override;
+
+    status_t readMetadata();
+
+  private:
+    /* Kernel device representing partition */
+    dev_t mDevice;
+    /* Block device path */
+    std::string mDevPath;
+
+    /* Filesystem type */
+    std::string mFsType;
+    /* Filesystem UUID */
+    std::string mFsUuid;
+    /* Mount options */
+    std::string mMntOpts;
+
+    DISALLOW_COPY_AND_ASSIGN(PublicVolume);
+};
+
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/ResponseCode.cpp b/volume_manager/ResponseCode.cpp
new file mode 100644
index 0000000..5365295
--- /dev/null
+++ b/volume_manager/ResponseCode.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#define LOG_TAG "Vold"
+
+#include <cutils/log.h>
+
+#include <volume_manager/ResponseCode.h>
+
+int ResponseCode::convertFromErrno() {
+    if (errno == ENODEV) {
+        return (ResponseCode::OpFailedNoMedia);
+    } else if (errno == ENODATA) {
+        return (ResponseCode::OpFailedMediaBlank);
+    } else if (errno == EIO) {
+        return (ResponseCode::OpFailedMediaCorrupt);
+    } else if (errno == EBUSY) {
+        return (ResponseCode::OpFailedStorageBusy);
+    } else if (errno == ENOENT) {
+        return (ResponseCode::OpFailedStorageNotFound);
+    }
+
+    SLOGW("Returning OperationFailed - no handler for errno %d", errno);
+    return (ResponseCode::OperationFailed);
+}
diff --git a/volume_manager/Utils.cpp b/volume_manager/Utils.cpp
new file mode 100644
index 0000000..1a0a940
--- /dev/null
+++ b/volume_manager/Utils.cpp
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 "Utils.h"
+#include <volume_manager/VolumeManager.h>
+#include "Process.h"
+#include "sehandle.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+
+#include <cutils/fs.h>
+#include <private/android_filesystem_config.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <mutex>
+#include <thread>
+
+#ifndef UMOUNT_NOFOLLOW
+#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+#endif
+
+using android::base::ReadFileToString;
+using android::base::StringPrintf;
+
+using namespace std::chrono_literals;
+
+namespace android {
+namespace volmgr {
+
+char* sBlkidContext = nullptr;
+char* sBlkidUntrustedContext = nullptr;
+char* sFsckContext = nullptr;
+char* sFsckUntrustedContext = nullptr;
+
+#include <blkid/blkid.h>
+
+static const char* kProcFilesystems = "/proc/filesystems";
+
+status_t CreateDeviceNode(const std::string& path, dev_t dev) {
+    const char* cpath = path.c_str();
+    status_t res = 0;
+
+    char* secontext = nullptr;
+    if (sehandle) {
+        if (!selabel_lookup(sehandle, &secontext, cpath, S_IFBLK)) {
+            setfscreatecon(secontext);
+        }
+    }
+
+    mode_t mode = 0660 | S_IFBLK;
+    if (mknod(cpath, mode, dev) < 0) {
+        if (errno != EEXIST) {
+            PLOG(ERROR) << "Failed to create device node for " << major(dev) << ":" << minor(dev)
+                        << " at " << path;
+            res = -errno;
+        }
+    }
+
+    if (secontext) {
+        setfscreatecon(nullptr);
+        freecon(secontext);
+    }
+
+    return res;
+}
+
+status_t DestroyDeviceNode(const std::string& path) {
+    const char* cpath = path.c_str();
+    if (TEMP_FAILURE_RETRY(unlink(cpath))) {
+        return -errno;
+    } else {
+        return OK;
+    }
+}
+
+status_t PrepareDir(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
+    const char* cpath = path.c_str();
+
+    char* secontext = nullptr;
+    if (sehandle) {
+        if (!selabel_lookup(sehandle, &secontext, cpath, S_IFDIR)) {
+            setfscreatecon(secontext);
+        }
+    }
+
+    int res = fs_prepare_dir(cpath, mode, uid, gid);
+
+    if (secontext) {
+        setfscreatecon(nullptr);
+        freecon(secontext);
+    }
+
+    if (res == 0) {
+        return OK;
+    } else {
+        return -errno;
+    }
+}
+
+status_t ForceUnmount(const std::string& path, bool detach /* = false */) {
+    const char* cpath = path.c_str();
+    if (detach) {
+        if (!umount2(path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) || errno == EINVAL ||
+            errno == ENOENT) {
+            return OK;
+        }
+        PLOG(WARNING) << "Failed to unmount " << path;
+        return -errno;
+    }
+    if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+        return OK;
+    }
+    sleep(1);
+
+    Process::killProcessesWithOpenFiles(cpath, SIGINT);
+    sleep(1);
+    if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+        return OK;
+    }
+
+    Process::killProcessesWithOpenFiles(cpath, SIGTERM);
+    sleep(1);
+    if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+        return OK;
+    }
+
+    Process::killProcessesWithOpenFiles(cpath, SIGKILL);
+    sleep(1);
+    if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+        return OK;
+    }
+
+    return -errno;
+}
+
+status_t KillProcessesUsingPath(const std::string& path) {
+    const char* cpath = path.c_str();
+    if (Process::killProcessesWithOpenFiles(cpath, SIGINT) == 0) {
+        return OK;
+    }
+    sleep(1);
+
+    if (Process::killProcessesWithOpenFiles(cpath, SIGTERM) == 0) {
+        return OK;
+    }
+    sleep(1);
+
+    if (Process::killProcessesWithOpenFiles(cpath, SIGKILL) == 0) {
+        return OK;
+    }
+    sleep(1);
+
+    // Send SIGKILL a second time to determine if we've
+    // actually killed everyone with open files
+    if (Process::killProcessesWithOpenFiles(cpath, SIGKILL) == 0) {
+        return OK;
+    }
+    PLOG(ERROR) << "Failed to kill processes using " << path;
+    return -EBUSY;
+}
+
+status_t BindMount(const std::string& source, const std::string& target) {
+    if (::mount(source.c_str(), target.c_str(), "", MS_BIND, NULL)) {
+        PLOG(WARNING) << "Failed to bind mount " << source << " to " << target;
+        return -errno;
+    }
+    return OK;
+}
+
+static status_t readMetadata(const std::string& path, std::string& fsType, std::string& fsUuid,
+                             std::string& fsLabel) {
+    char* val = NULL;
+    val = blkid_get_tag_value(NULL, "TYPE", path.c_str());
+    if (val) {
+        fsType = val;
+    }
+    val = blkid_get_tag_value(NULL, "UUID", path.c_str());
+    if (val) {
+        fsUuid = val;
+    }
+    val = blkid_get_tag_value(NULL, "LABEL", path.c_str());
+    if (val) {
+        fsLabel = val;
+    }
+
+    return OK;
+}
+
+status_t ReadMetadata(const std::string& path, std::string& fsType, std::string& fsUuid,
+                      std::string& fsLabel) {
+    return readMetadata(path, fsType, fsUuid, fsLabel);
+}
+
+status_t ReadMetadataUntrusted(const std::string& path, std::string& fsType, std::string& fsUuid,
+                               std::string& fsLabel) {
+    return readMetadata(path, fsType, fsUuid, fsLabel);
+}
+
+status_t ForkExecvp(const std::vector<std::string>& args) {
+    return ForkExecvp(args, nullptr);
+}
+
+status_t ForkExecvp(const std::vector<std::string>& args, char* context) {
+    std::vector<std::string> output;
+    size_t argc = args.size();
+    char** argv = (char**)calloc(argc + 1, sizeof(char*));
+    for (size_t i = 0; i < argc; i++) {
+        argv[i] = (char*)args[i].c_str();
+        if (i == 0) {
+            LOG(VERBOSE) << args[i];
+        } else {
+            LOG(VERBOSE) << "    " << args[i];
+        }
+    }
+
+    if (setexeccon(context)) {
+        LOG(ERROR) << "Failed to setexeccon";
+        abort();
+    }
+
+    pid_t pid = fork();
+    int fork_errno = errno;
+    if (pid == 0) {
+        close(STDIN_FILENO);
+        close(STDOUT_FILENO);
+        close(STDERR_FILENO);
+
+        if (execvp(argv[0], argv)) {
+            PLOG(ERROR) << "Failed to exec";
+        }
+
+        _exit(1);
+    }
+
+    if (setexeccon(nullptr)) {
+        LOG(ERROR) << "Failed to setexeccon";
+        abort();
+    }
+
+    if (pid == -1) {
+        PLOG(ERROR) << "Failed to exec";
+        return -fork_errno;
+    }
+
+    waitpid(pid, nullptr, 0);
+
+    free(argv);
+    return OK;
+}
+
+bool IsFilesystemSupported(const std::string& fsType) {
+    std::string supported;
+    if (!ReadFileToString(kProcFilesystems, &supported)) {
+        PLOG(ERROR) << "Failed to read supported filesystems";
+        return false;
+    }
+
+    /* fuse filesystems */
+    supported.append("fuse\tntfs\n");
+
+    return supported.find(fsType + "\n") != std::string::npos;
+}
+
+status_t WipeBlockDevice(const std::string& path) {
+    status_t res = -1;
+    const char* c_path = path.c_str();
+    unsigned long nr_sec = 0;
+    unsigned long long range[2];
+
+    int fd = TEMP_FAILURE_RETRY(open(c_path, O_RDWR | O_CLOEXEC));
+    if (fd == -1) {
+        PLOG(ERROR) << "Failed to open " << path;
+        goto done;
+    }
+
+    if ((ioctl(fd, BLKGETSIZE, &nr_sec)) == -1) {
+        PLOG(ERROR) << "Failed to determine size of " << path;
+        goto done;
+    }
+
+    range[0] = 0;
+    range[1] = (unsigned long long)nr_sec * 512;
+
+    LOG(INFO) << "About to discard " << range[1] << " on " << path;
+    if (ioctl(fd, BLKDISCARD, &range) == 0) {
+        LOG(INFO) << "Discard success on " << path;
+        res = 0;
+    } else {
+        PLOG(ERROR) << "Discard failure on " << path;
+    }
+
+done:
+    close(fd);
+    return res;
+}
+
+dev_t GetDevice(const std::string& path) {
+    struct stat sb;
+    if (stat(path.c_str(), &sb)) {
+        PLOG(WARNING) << "Failed to stat " << path;
+        return 0;
+    } else {
+        return sb.st_dev;
+    }
+}
+
+bool IsRunningInEmulator() {
+    return android::base::GetBoolProperty("ro.kernel.qemu", false);
+}
+
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/Utils.h b/volume_manager/Utils.h
new file mode 100644
index 0000000..0bb4a71
--- /dev/null
+++ b/volume_manager/Utils.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VOLMGR_UTILS_H
+#define ANDROID_VOLMGR_UTILS_H
+
+#include <cutils/multiuser.h>
+#include <selinux/selinux.h>
+#include <utils/Errors.h>
+
+#include <chrono>
+#include <string>
+#include <vector>
+
+// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. It goes in the private:
+// declarations in a class.
+#if !defined(DISALLOW_COPY_AND_ASSIGN)
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+    TypeName(const TypeName&) = delete;    \
+    void operator=(const TypeName&) = delete
+#endif
+
+struct DIR;
+
+namespace android {
+namespace volmgr {
+
+/* SELinux contexts used depending on the block device type */
+extern char* sBlkidContext;
+extern char* sBlkidUntrustedContext;
+extern char* sFsckContext;
+extern char* sFsckUntrustedContext;
+
+status_t CreateDeviceNode(const std::string& path, dev_t dev);
+status_t DestroyDeviceNode(const std::string& path);
+
+/* fs_prepare_dir wrapper that creates with SELinux context */
+status_t PrepareDir(const std::string& path, mode_t mode, uid_t uid, gid_t gid);
+
+/* Really unmounts the path, killing active processes along the way */
+status_t ForceUnmount(const std::string& path, bool detach = false);
+
+/* Kills any processes using given path */
+status_t KillProcessesUsingPath(const std::string& path);
+
+/* Creates bind mount from source to target */
+status_t BindMount(const std::string& source, const std::string& target);
+
+/* Reads filesystem metadata from device at path */
+status_t ReadMetadata(const std::string& path, std::string& fsType, std::string& fsUuid,
+                      std::string& fsLabel);
+
+/* Reads filesystem metadata from untrusted device at path */
+status_t ReadMetadataUntrusted(const std::string& path, std::string& fsType, std::string& fsUuid,
+                               std::string& fsLabel);
+
+/* Returns either WEXITSTATUS() status, or a negative errno */
+status_t ForkExecvp(const std::vector<std::string>& args);
+status_t ForkExecvp(const std::vector<std::string>& args, char* context);
+
+bool IsFilesystemSupported(const std::string& fsType);
+
+/* Wipes contents of block device at given path */
+status_t WipeBlockDevice(const std::string& path);
+
+dev_t GetDevice(const std::string& path);
+
+/* Checks if Android is running in QEMU */
+bool IsRunningInEmulator();
+
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/VolumeBase.cpp b/volume_manager/VolumeBase.cpp
new file mode 100644
index 0000000..bd7c29c
--- /dev/null
+++ b/volume_manager/VolumeBase.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 "VolumeBase.h"
+#include <volume_manager/ResponseCode.h>
+#include <volume_manager/VolumeManager.h>
+#include "Utils.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+
+VolumeBase::VolumeBase(Type type)
+    : mType(type), mMountFlags(0), mCreated(false), mState(State::kUnmounted), mSilent(false), mMountable(false) {}
+
+VolumeBase::~VolumeBase() {
+    CHECK(!mCreated);
+}
+
+void VolumeBase::setState(State state) {
+    mState = state;
+    VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeStateChanged,
+                                           StringPrintf("%d", mState));
+}
+
+status_t VolumeBase::setDiskId(const std::string& diskId) {
+    if (mCreated) {
+        LOG(WARNING) << getId() << " diskId change requires destroyed";
+        return -EBUSY;
+    }
+
+    mDiskId = diskId;
+    return OK;
+}
+
+status_t VolumeBase::setPartGuid(const std::string& partGuid) {
+    if (mCreated) {
+        LOG(WARNING) << getId() << " partGuid change requires destroyed";
+        return -EBUSY;
+    }
+
+    mPartGuid = partGuid;
+    return OK;
+}
+
+status_t VolumeBase::setPartLabel(const std::string& partLabel) {
+    if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
+        LOG(WARNING) << getId() << " partLabel change requires state unmounted or unmountable";
+        LOG(WARNING) << getId() << " state=" << (int)mState;
+    }
+
+    mPartLabel = partLabel;
+    return OK;
+}
+
+status_t VolumeBase::setMountFlags(int mountFlags) {
+    if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
+        LOG(WARNING) << getId() << " flags change requires state unmounted or unmountable";
+        return -EBUSY;
+    }
+
+    mMountFlags = mountFlags;
+    return OK;
+}
+
+status_t VolumeBase::setSilent(bool silent) {
+    if (mCreated) {
+        LOG(WARNING) << getId() << " silence change requires destroyed";
+        return -EBUSY;
+    }
+
+    mSilent = silent;
+    return OK;
+}
+
+status_t VolumeBase::setId(const std::string& id) {
+    if (mCreated) {
+        LOG(WARNING) << getId() << " id change requires not created";
+        return -EBUSY;
+    }
+
+    mId = id;
+    return OK;
+}
+
+status_t VolumeBase::setPath(const std::string& path) {
+    mPath = path;
+    VolumeManager::Instance()->notifyEvent(ResponseCode::VolumePathChanged, mPath);
+    return OK;
+}
+
+status_t VolumeBase::create() {
+    if (mCreated) {
+        return BAD_VALUE;
+    }
+
+    mCreated = true;
+    std::vector<std::string> argv;
+    argv.push_back(StringPrintf("%d", mType));
+    argv.push_back(mDiskId);
+    argv.push_back(mPartGuid);
+    status_t res = doCreate();
+    if (res == OK) {
+        VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeCreated, argv);
+        if (mPartLabel.size() > 0) {
+            VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeFsLabelChanged, mPartLabel);
+        }
+    }
+    setState(State::kUnmounted);
+
+    mMountable = detectMountable();
+
+    return res;
+}
+
+bool VolumeBase::detectMountable() {
+    bool mountable = false;
+    if (doMount() == OK) {
+        mountable = true;
+        doUnmount();
+    }
+    return mountable;
+}
+
+status_t VolumeBase::doCreate() {
+    return OK;
+}
+
+status_t VolumeBase::destroy() {
+    if (!mCreated) {
+        return NO_INIT;
+    }
+
+    if (mState == State::kMounted) {
+        unmount(true /* detatch */);
+        setState(State::kBadRemoval);
+    } else {
+        setState(State::kRemoved);
+    }
+
+    VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeDestroyed);
+    status_t res = doDestroy();
+    mCreated = false;
+    return res;
+}
+
+status_t VolumeBase::doDestroy() {
+    return OK;
+}
+
+status_t VolumeBase::mount() {
+    if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
+        LOG(WARNING) << getId() << " mount requires state unmounted or unmountable";
+        return -EBUSY;
+    }
+
+    setState(State::kChecking);
+    status_t res = doMount();
+    if (res == OK) {
+        setState(State::kMounted);
+    } else {
+        setState(State::kUnmountable);
+    }
+
+    return res;
+}
+
+status_t VolumeBase::unmount(bool detach /* = false */) {
+    setState(State::kEjecting);
+
+    status_t res = doUnmount(detach);
+    setState(State::kUnmounted);
+    return res;
+}
+
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/VolumeBase.h b/volume_manager/VolumeBase.h
new file mode 100644
index 0000000..acfff45
--- /dev/null
+++ b/volume_manager/VolumeBase.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VOLMGR_VOLUMEBASE_H
+#define ANDROID_VOLMGR_VOLUMEBASE_H
+
+#include "Utils.h"
+
+#include <cutils/multiuser.h>
+#include <utils/Errors.h>
+
+#include <sys/types.h>
+#include <list>
+#include <string>
+
+namespace android {
+namespace volmgr {
+
+/*
+ * Representation of a mounted volume ready for presentation.
+ *
+ * Various subclasses handle the different mounting prerequisites, such as
+ * encryption details, etc.  Volumes can also be "stacked" above other
+ * volumes to help communicate dependencies.  For example, an ASEC volume
+ * can be stacked on a vfat volume.
+ *
+ * Mounted volumes can be asked to manage bind mounts to present themselves
+ * to specific users on the device.
+ *
+ * When an unmount is requested, the volume recursively unmounts any stacked
+ * volumes and removes any bind mounts before finally unmounting itself.
+ */
+class VolumeBase {
+  public:
+    virtual ~VolumeBase();
+
+    enum class Type {
+        kPublic = 0,
+        kPrivate,
+        kEmulated,
+        kAsec,
+        kObb,
+    };
+
+    enum MountFlags {
+        /* Flag that volume is primary external storage */
+        kPrimary = 1 << 0,
+        /* Flag that volume is visible to normal apps */
+        kVisible = 1 << 1,
+    };
+
+    enum class State {
+        kUnmounted = 0,
+        kChecking,
+        kMounted,
+        kMountedReadOnly,
+        kEjecting,
+        kUnmountable,
+        kRemoved,
+        kBadRemoval,
+    };
+
+    const std::string& getId() const { return mId; }
+    const std::string& getDiskId() const { return mDiskId; }
+    const std::string& getPartGuid() const { return mPartGuid; }
+    const std::string& getPartLabel() const { return mPartLabel; }
+    Type getType() const { return mType; }
+    int getMountFlags() const { return mMountFlags; }
+    State getState() const { return mState; }
+    const std::string& getPath() const { return mPath; }
+    bool isMountable() const { return mMountable; }
+
+    status_t setDiskId(const std::string& diskId);
+    status_t setPartGuid(const std::string& partGuid);
+    status_t setPartLabel(const std::string& partLabel);
+    status_t setMountFlags(int mountFlags);
+    status_t setSilent(bool silent);
+
+    status_t create();
+    status_t destroy();
+    status_t mount();
+    status_t unmount(bool detach = false);
+
+  protected:
+    explicit VolumeBase(Type type);
+
+    virtual status_t doCreate();
+    virtual status_t doDestroy();
+    virtual status_t doMount() = 0;
+    virtual status_t doUnmount(bool detach = false) = 0;
+
+    status_t setId(const std::string& id);
+    status_t setPath(const std::string& path);
+
+  private:
+    /* ID that uniquely references volume while alive */
+    std::string mId;
+    /* ID that uniquely references parent disk while alive */
+    std::string mDiskId;
+    /* Partition GUID of this volume */
+    std::string mPartGuid;
+    /* Partition label of this volume */
+    std::string mPartLabel;
+    /* Volume type */
+    Type mType;
+    /* Flags used when mounting this volume */
+    int mMountFlags;
+    /* Flag indicating object is created */
+    bool mCreated;
+    /* Current state of volume */
+    State mState;
+    /* Path to mounted volume */
+    std::string mPath;
+    /* Flag indicating that volume should emit no events */
+    bool mSilent;
+
+    bool mMountable;
+    virtual bool detectMountable();
+
+    void setState(State state);
+
+    DISALLOW_COPY_AND_ASSIGN(VolumeBase);
+};
+
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/VolumeManager.cpp b/volume_manager/VolumeManager.cpp
new file mode 100644
index 0000000..ded6565
--- /dev/null
+++ b/volume_manager/VolumeManager.cpp
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 <blkid/blkid.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <fs_mgr.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+#define LOG_TAG "VolumeManager"
+
+#include <android-base/logging.h>
+#include <cutils/properties.h>
+
+#include <sysutils/NetlinkEvent.h>
+
+#include <volume_manager/VolumeManager.h>
+#include <fstab/fstab.h>
+#include "Disk.h"
+#include "DiskPartition.h"
+#include "EmulatedVolume.h"
+#include "VolumeBase.h"
+#include "NetlinkManager.h"
+
+#include "sehandle.h"
+
+struct selabel_handle* sehandle;
+
+using android::fs_mgr::Fstab;
+using android::fs_mgr::FstabEntry;
+
+static const unsigned int kMajorBlockMmc = 179;
+static const unsigned int kMajorBlockExperimentalMin = 240;
+static const unsigned int kMajorBlockExperimentalMax = 254;
+
+namespace android {
+namespace volmgr {
+
+static void do_coldboot(DIR* d, int lvl) {
+    struct dirent* de;
+    int dfd, fd;
+
+    dfd = dirfd(d);
+
+    fd = openat(dfd, "uevent", O_WRONLY | O_CLOEXEC);
+    if (fd >= 0) {
+        write(fd, "add\n", 4);
+        close(fd);
+    }
+
+    while ((de = readdir(d))) {
+        DIR* d2;
+
+        if (de->d_name[0] == '.') continue;
+
+        if (de->d_type != DT_DIR && lvl > 0) continue;
+
+        fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+        if (fd < 0) continue;
+
+        d2 = fdopendir(fd);
+        if (d2 == 0)
+            close(fd);
+        else {
+            do_coldboot(d2, lvl + 1);
+            closedir(d2);
+        }
+    }
+}
+
+static void coldboot(const char* path) {
+    DIR* d = opendir(path);
+    if (d) {
+        do_coldboot(d, 0);
+        closedir(d);
+    }
+}
+
+static int process_config(VolumeManager* vm, FstabEntry* data_recp) {
+    Fstab fstab;
+    if (!ReadDefaultFstab(&fstab)) {
+        PLOG(ERROR) << "Failed to open default fstab";
+        return -1;
+    }
+
+    /* Loop through entries looking for ones that vold manages */
+    for (int i = 0; i < fstab.size(); i++) {
+        if (fstab[i].fs_mgr_flags.vold_managed) {
+            std::string sysPattern(fstab[i].blk_device);
+            std::string fstype = fstab[i].fs_type;
+            std::string mntopts = fstab[i].fs_options;
+            std::string nickname = fstab[i].label;
+            int partnum = fstab[i].partnum;
+            int flags = 0;
+
+            if (fstab[i].is_encryptable()) {
+                flags |= android::volmgr::Disk::Flags::kAdoptable;
+            }
+            if (fstab[i].fs_mgr_flags.no_emulated_sd ||
+                property_get_bool("vold.debug.default_primary", false)) {
+                flags |= android::volmgr::Disk::Flags::kDefaultPrimary;
+            }
+            if (fstab[i].fs_mgr_flags.nonremovable) {
+                flags |= android::volmgr::Disk::Flags::kNonRemovable;
+            }
+
+            vm->addDiskSource(new VolumeManager::DiskSource(sysPattern, nickname, partnum, flags,
+                                                            fstype, mntopts));
+        } else {
+            if (data_recp->fs_type.empty() && fstab[i].mount_point == "/data") {
+                char* detected_fs_type =
+                    blkid_get_tag_value(nullptr, "TYPE", fstab[i].blk_device.c_str());
+                if (!detected_fs_type || fstab[i].fs_type == detected_fs_type) {
+                    *data_recp = fstab[i];
+                }
+            }
+        }
+    }
+    return 0;
+}
+
+VolumeInfo::VolumeInfo(const VolumeBase* vol)
+    : mId(vol->getId()), mLabel(vol->getPartLabel()), mPath(vol->getPath()), mMountable(vol->isMountable()) {
+    // Empty
+}
+
+VolumeManager* VolumeManager::sInstance = nullptr;
+
+VolumeManager* VolumeManager::Instance(void) {
+    if (!sInstance) {
+        sInstance = new VolumeManager();
+    }
+    return sInstance;
+}
+
+VolumeManager::VolumeManager(void)
+    : mWatcher(nullptr), mNetlinkManager(NetlinkManager::Instance()), mInternalEmulated(nullptr) {
+    // Empty
+}
+
+VolumeManager::~VolumeManager(void) {
+    stop();
+}
+
+bool VolumeManager::start(VolumeWatcher* watcher) {
+    setenv("BLKID_FILE", "/tmp/vold_blkid.tab", 1);
+
+    sehandle = selinux_android_file_context_handle();
+    if (sehandle) {
+        selinux_android_set_sehandle(sehandle);
+    }
+
+    mkdir("/dev/block/volmgr", 0755);
+
+    mWatcher = watcher;
+
+    FstabEntry data_rec;
+    if (process_config(this, &data_rec) != 0) {
+        LOG(ERROR) << "Error reading configuration... continuing anyway";
+    }
+
+    if (!data_rec.fs_type.empty()) {
+        mInternalEmulated = new EmulatedVolume(&data_rec, "media/0");
+        mInternalEmulated->create();
+    }
+
+    if (!mNetlinkManager->start()) {
+        LOG(ERROR) << "Unable to start NetlinkManager";
+        return false;
+    }
+
+    coldboot("/sys/block");
+
+    unmountAll();
+
+    LOG(INFO) << "VolumeManager initialized";
+    return true;
+}
+
+void VolumeManager::stop(void) {
+    for (auto& disk : mDisks) {
+        disk->destroy();
+        delete disk;
+    }
+    mDisks.clear();
+    for (auto& source : mDiskSources) {
+        delete source;
+    }
+    mDiskSources.clear();
+
+    mNetlinkManager->stop();
+    mWatcher = nullptr;
+}
+
+bool VolumeManager::reset(void) {
+    return false;
+}
+
+bool VolumeManager::unmountAll(void) {
+    std::lock_guard<std::mutex> lock(mLock);
+
+    if (mInternalEmulated) {
+        mInternalEmulated->unmount();
+    }
+
+    for (auto& disk : mDisks) {
+        disk->unmountAll();
+    }
+
+    return true;
+}
+
+void VolumeManager::getVolumeInfo(std::vector<VolumeInfo>& info) {
+    std::lock_guard<std::mutex> lock(mLock);
+
+    info.clear();
+    if (mInternalEmulated) {
+        info.push_back(VolumeInfo(mInternalEmulated));
+    }
+    for (const auto& disk : mDisks) {
+        disk->getVolumeInfo(info);
+    }
+}
+
+VolumeBase* VolumeManager::findVolume(const std::string& id) {
+    if (mInternalEmulated && mInternalEmulated->getId() == id) {
+        return mInternalEmulated;
+    }
+    for (const auto& disk : mDisks) {
+        auto vol = disk->findVolume(id);
+        if (vol != nullptr) {
+            return vol.get();
+        }
+    }
+    return nullptr;
+}
+
+bool VolumeManager::volumeMount(const std::string& id) {
+    std::lock_guard<std::mutex> lock(mLock);
+    auto vol = findVolume(id);
+    if (!vol) {
+        return false;
+    }
+    status_t res = vol->mount();
+    return (res == OK);
+}
+
+bool VolumeManager::volumeUnmount(const std::string& id, bool detach /* = false */) {
+    std::lock_guard<std::mutex> lock(mLock);
+    auto vol = findVolume(id);
+    if (!vol) {
+        return false;
+    }
+    status_t res = vol->unmount(detach);
+    return (res == OK);
+}
+
+void VolumeManager::addDiskSource(DiskSource* source) {
+    std::lock_guard<std::mutex> lock(mLock);
+
+    mDiskSources.push_back(source);
+}
+
+void VolumeManager::handleBlockEvent(NetlinkEvent* evt) {
+    std::lock_guard<std::mutex> lock(mLock);
+
+    const char* param;
+    param = evt->findParam("DEVTYPE");
+    std::string devType(param ? param : "");
+    if (devType != "disk") {
+        return;
+    }
+    param = evt->findParam("DEVPATH");
+    std::string eventPath(param ? param : "");
+
+    int major = atoi(evt->findParam("MAJOR"));
+    int minor = atoi(evt->findParam("MINOR"));
+    dev_t device = makedev(major, minor);
+
+    switch (evt->getAction()) {
+        case NetlinkEvent::Action::kAdd: {
+            for (const auto& source : mDiskSources) {
+                if (source->matches(eventPath)) {
+                    // For now, assume that MMC, virtio-blk (the latter is
+                    // emulator-specific; see Disk.cpp for details) and UFS card
+                    // devices are SD, and that everything else is USB
+                    int flags = source->getFlags();
+                    if (major == kMajorBlockMmc || (eventPath.find("ufs") != std::string::npos) ||
+                        (IsRunningInEmulator() && major >= (int)kMajorBlockExperimentalMin &&
+                         major <= (int)kMajorBlockExperimentalMax)) {
+                        flags |= Disk::Flags::kSd;
+                    } else {
+                        flags |= Disk::Flags::kUsb;
+                    }
+
+                    Disk* disk = (source->getPartNum() == -1)
+                                     ? new Disk(eventPath, device, source->getNickname(), flags)
+                                     : new DiskPartition(eventPath, device, source->getNickname(),
+                                                         flags, source->getPartNum(),
+                                                         source->getFsType(), source->getMntOpts());
+                    disk->create();
+                    mDisks.push_back(disk);
+                    break;
+                }
+            }
+            break;
+        }
+        case NetlinkEvent::Action::kChange: {
+            LOG(DEBUG) << "Disk at " << major << ":" << minor << " changed";
+            for (const auto& disk : mDisks) {
+                if (disk->getDevice() == device) {
+                    disk->readMetadata();
+                    disk->readPartitions();
+                }
+            }
+            break;
+        }
+        case NetlinkEvent::Action::kRemove: {
+            auto i = mDisks.begin();
+            while (i != mDisks.end()) {
+                if ((*i)->getDevice() == device) {
+                    (*i)->destroy();
+                    i = mDisks.erase(i);
+                } else {
+                    ++i;
+                }
+            }
+            break;
+        }
+        default: {
+            LOG(WARNING) << "Unexpected block event action " << (int)evt->getAction();
+            break;
+        }
+    }
+}
+
+void VolumeManager::notifyEvent(int code) {
+    std::vector<std::string> argv;
+    notifyEvent(code, argv);
+}
+
+void VolumeManager::notifyEvent(int code, const std::string& arg) {
+    std::vector<std::string> argv;
+    argv.push_back(arg);
+    notifyEvent(code, argv);
+}
+
+void VolumeManager::notifyEvent(int code, const std::vector<std::string>& argv) {
+    mWatcher->handleEvent(code, argv);
+}
+
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/fs/Exfat.cpp b/volume_manager/fs/Exfat.cpp
new file mode 100644
index 0000000..56ae430
--- /dev/null
+++ b/volume_manager/fs/Exfat.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 "Exfat.h"
+#include "Utils.h"
+
+#define LOG_TAG "Vold"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <cutils/log.h>
+
+#include <logwrap/logwrap.h>
+
+#include <string>
+#include <vector>
+
+#include <sys/mount.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+namespace exfat {
+
+status_t Mount(const std::string& source, const std::string& target, int ownerUid, int ownerGid,
+               int permMask) {
+    int mountFlags = MS_NODEV | MS_NOSUID | MS_DIRSYNC | MS_NOATIME | MS_NOEXEC;
+    auto mountData = android::base::StringPrintf("uid=%d,gid=%d,fmask=%o,dmask=%o", ownerUid,
+                                                 ownerGid, permMask, permMask);
+
+    if (mount(source.c_str(), target.c_str(), "exfat", mountFlags, mountData.c_str()) == 0) {
+        return 0;
+    }
+    PLOG(ERROR) << "Mount failed; attempting read-only";
+    mountFlags |= MS_RDONLY;
+    if (mount(source.c_str(), target.c_str(), "exfat", mountFlags, mountData.c_str()) == 0) {
+        return 0;
+    }
+
+    return -1;
+}
+
+}  // namespace exfat
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/fs/Exfat.h b/volume_manager/fs/Exfat.h
new file mode 100644
index 0000000..80bb289
--- /dev/null
+++ b/volume_manager/fs/Exfat.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef VOLMGR_EXFAT_H
+#define VOLMGR_EXFAT_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+namespace android {
+namespace volmgr {
+namespace exfat {
+
+status_t Mount(const std::string& source, const std::string& target, int ownerUid, int ownerGid,
+               int permMask);
+
+}  // namespace exfat
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/fs/Ext4.cpp b/volume_manager/fs/Ext4.cpp
new file mode 100644
index 0000000..48d848c
--- /dev/null
+++ b/volume_manager/fs/Ext4.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <linux/kdev_t.h>
+
+#define LOG_TAG "Vold"
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <logwrap/logwrap.h>
+#include <private/android_filesystem_config.h>
+#include <selinux/selinux.h>
+
+#include "Ext4.h"
+#include "Utils.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+namespace ext4 {
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+               bool executable, const std::string& opts /* = "" */, bool trusted, bool portable) {
+    int rc;
+    unsigned long flags;
+
+    std::string data(opts);
+
+    if (portable) {
+        if (!data.empty()) {
+            data += ",";
+        }
+        data += "context=u:object_r:sdcard_posix:s0";
+    }
+    const char* c_source = source.c_str();
+    const char* c_target = target.c_str();
+    const char* c_data = data.c_str();
+
+    flags = MS_NOATIME | MS_NODEV | MS_NOSUID;
+
+    // Only use MS_DIRSYNC if we're not mounting adopted storage
+    if (!trusted) {
+        flags |= MS_DIRSYNC;
+    }
+
+    flags |= (executable ? 0 : MS_NOEXEC);
+    flags |= (ro ? MS_RDONLY : 0);
+    flags |= (remount ? MS_REMOUNT : 0);
+
+    rc = mount(c_source, c_target, "ext4", flags, c_data);
+    if (portable && rc == 0) {
+        chown(c_target, AID_MEDIA_RW, AID_MEDIA_RW);
+        chmod(c_target, 0775);
+    }
+
+    if (rc && errno == EROFS) {
+        SLOGE("%s appears to be a read only filesystem - retrying mount RO", c_source);
+        flags |= MS_RDONLY;
+        rc = mount(c_source, c_target, "ext4", flags, c_data);
+    }
+
+    return rc;
+}
+
+}  // namespace ext4
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/fs/Ext4.h b/volume_manager/fs/Ext4.h
new file mode 100644
index 0000000..01add43
--- /dev/null
+++ b/volume_manager/fs/Ext4.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef VOLMGR_EXT4_H
+#define VOLMGR_EXT4_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+namespace android {
+namespace volmgr {
+namespace ext4 {
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+               bool executable, const std::string& opts = "", bool trusted = false,
+               bool portable = false);
+
+}  // namespace ext4
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/fs/F2fs.cpp b/volume_manager/fs/F2fs.cpp
new file mode 100644
index 0000000..fb50fb3
--- /dev/null
+++ b/volume_manager/fs/F2fs.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 "F2fs.h"
+#include "Utils.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <private/android_filesystem_config.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <string>
+#include <vector>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+namespace f2fs {
+
+status_t Mount(const std::string& source, const std::string& target,
+               const std::string& opts /* = "" */, bool trusted, bool portable) {
+    std::string data(opts);
+
+    if (portable) {
+        if (!data.empty()) {
+            data += ",";
+        }
+        data += "context=u:object_r:sdcard_posix:s0";
+    }
+
+    const char* c_source = source.c_str();
+    const char* c_target = target.c_str();
+    const char* c_data = data.c_str();
+
+    unsigned long flags = MS_NOATIME | MS_NODEV | MS_NOSUID;
+
+    // Only use MS_DIRSYNC if we're not mounting adopted storage
+    if (!trusted) {
+        flags |= MS_DIRSYNC;
+    }
+
+    int res = mount(c_source, c_target, "f2fs", flags, c_data);
+    if (portable && res == 0) {
+        chown(c_target, AID_MEDIA_RW, AID_MEDIA_RW);
+        chmod(c_target, 0775);
+    }
+
+    if (res != 0) {
+        PLOG(ERROR) << "Failed to mount " << source;
+        if (errno == EROFS) {
+            res = mount(c_source, c_target, "f2fs", flags | MS_RDONLY, c_data);
+            if (res != 0) {
+                PLOG(ERROR) << "Failed to mount read-only " << source;
+            }
+        }
+    }
+
+    return res;
+}
+
+}  // namespace f2fs
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/fs/F2fs.h b/volume_manager/fs/F2fs.h
new file mode 100644
index 0000000..e96b39f
--- /dev/null
+++ b/volume_manager/fs/F2fs.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef VOLMGR_F2FS_H
+#define VOLMGR_F2FS_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+namespace android {
+namespace volmgr {
+namespace f2fs {
+
+status_t Mount(const std::string& source, const std::string& target, const std::string& opts = "",
+               bool trusted = false, bool portable = false);
+
+}  // namespace f2fs
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/fs/Ntfs.cpp b/volume_manager/fs/Ntfs.cpp
new file mode 100644
index 0000000..45661ec
--- /dev/null
+++ b/volume_manager/fs/Ntfs.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 "Ntfs.h"
+#include "Utils.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <string>
+#include <vector>
+
+#include <sys/mount.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+namespace ntfs {
+
+static const char* kMountPath = "/sbin/mount.ntfs";
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+               bool executable, int ownerUid, int ownerGid, int permMask) {
+    char mountData[255];
+
+    const char* c_source = source.c_str();
+    const char* c_target = target.c_str();
+
+    sprintf(mountData,
+            "utf8,uid=%d,gid=%d,fmask=%o,dmask=%o,"
+            "shortname=mixed,nodev,nosuid,dirsync",
+            ownerUid, ownerGid, permMask, permMask);
+
+    if (!executable) strlcat(mountData, ",noexec", sizeof(mountData));
+    if (ro) strlcat(mountData, ",ro", sizeof(mountData));
+    if (remount) strlcat(mountData, ",remount", sizeof(mountData));
+
+    std::vector<std::string> cmd;
+    cmd.push_back(kMountPath);
+    cmd.push_back("-o");
+    cmd.push_back(mountData);
+    cmd.push_back(c_source);
+    cmd.push_back(c_target);
+
+    return ForkExecvp(cmd);
+}
+
+}  // namespace ntfs
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/fs/Ntfs.h b/volume_manager/fs/Ntfs.h
new file mode 100644
index 0000000..e6d152a
--- /dev/null
+++ b/volume_manager/fs/Ntfs.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VOLD_NTFS_H
+#define ANDROID_VOLD_NTFS_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+namespace android {
+namespace volmgr {
+namespace ntfs {
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+               bool executable, int ownerUid, int ownerGid, int permMask);
+
+}  // namespace ntfs
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/fs/Vfat.cpp b/volume_manager/fs/Vfat.cpp
new file mode 100644
index 0000000..395664a
--- /dev/null
+++ b/volume_manager/fs/Vfat.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS 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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <linux/kdev_t.h>
+
+#define LOG_TAG "Vold"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <selinux/selinux.h>
+
+#include <logwrap/logwrap.h>
+
+#include "Utils.h"
+#include "Vfat.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+namespace vfat {
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+               bool executable, int ownerUid, int ownerGid, int permMask, bool createLost) {
+    int rc;
+    unsigned long flags;
+    char mountData[255];
+
+    const char* c_source = source.c_str();
+    const char* c_target = target.c_str();
+
+    flags = MS_NODEV | MS_NOSUID | MS_DIRSYNC | MS_NOATIME;
+
+    flags |= (executable ? 0 : MS_NOEXEC);
+    flags |= (ro ? MS_RDONLY : 0);
+    flags |= (remount ? MS_REMOUNT : 0);
+
+    snprintf(mountData, sizeof(mountData), "utf8,uid=%d,gid=%d,fmask=%o,dmask=%o,shortname=mixed",
+             ownerUid, ownerGid, permMask, permMask);
+
+    rc = mount(c_source, c_target, "vfat", flags, mountData);
+
+    if (rc && errno == EROFS) {
+        SLOGE("%s appears to be a read only filesystem - retrying mount RO", c_source);
+        flags |= MS_RDONLY;
+        rc = mount(c_source, c_target, "vfat", flags, mountData);
+    }
+
+    if (rc == 0 && createLost) {
+        char* lost_path;
+        asprintf(&lost_path, "%s/LOST.DIR", c_target);
+        if (access(lost_path, F_OK)) {
+            /*
+             * Create a LOST.DIR in the root so we have somewhere to put
+             * lost cluster chains (fsck_msdos doesn't currently do this)
+             */
+            if (mkdir(lost_path, 0755)) {
+                SLOGE("Unable to create LOST.DIR (%s)", strerror(errno));
+            }
+        }
+        free(lost_path);
+    }
+
+    return rc;
+}
+
+}  // namespace vfat
+}  // namespace volmgr
+}  // namespace android
diff --git a/volume_manager/fs/Vfat.h b/volume_manager/fs/Vfat.h
new file mode 100644
index 0000000..fc1c50b
--- /dev/null
+++ b/volume_manager/fs/Vfat.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef VOLMGR_VFAT_H
+#define VOLMGR_VFAT_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+namespace android {
+namespace volmgr {
+namespace vfat {
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+               bool executable, int ownerUid, int ownerGid, int permMask, bool createLost);
+
+}  // namespace vfat
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/include/volume_manager/ResponseCode.h b/volume_manager/include/volume_manager/ResponseCode.h
new file mode 100644
index 0000000..47b1401
--- /dev/null
+++ b/volume_manager/include/volume_manager/ResponseCode.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _RESPONSECODE_H
+#define _RESPONSECODE_H
+
+class ResponseCode {
+  public:
+    // 100 series - Requestion action was initiated; expect another reply
+    // before proceeding with a new command.
+    static const int ActionInitiated = 100;
+
+    static const int VolumeListResult = 110;
+    static const int AsecListResult = 111;
+    static const int StorageUsersListResult = 112;
+    static const int CryptfsGetfieldResult = 113;
+
+    // 200 series - Requested action has been successfully completed
+    static const int CommandOkay = 200;
+    static const int ShareStatusResult = 210;
+    static const int AsecPathResult = 211;
+    static const int ShareEnabledResult = 212;
+    static const int PasswordTypeResult = 213;
+
+    // 400 series - The command was accepted but the requested action
+    // did not take place.
+    static const int OperationFailed = 400;
+    static const int OpFailedNoMedia = 401;
+    static const int OpFailedMediaBlank = 402;
+    static const int OpFailedMediaCorrupt = 403;
+    static const int OpFailedVolNotMounted = 404;
+    static const int OpFailedStorageBusy = 405;
+    static const int OpFailedStorageNotFound = 406;
+
+    // 500 series - The command was not accepted and the requested
+    // action did not take place.
+    static const int CommandSyntaxError = 500;
+    static const int CommandParameterError = 501;
+    static const int CommandNoPermission = 502;
+
+    // 600 series - Unsolicited broadcasts
+    static const int UnsolicitedInformational = 600;
+    static const int VolumeStateChange = 605;
+    static const int VolumeMountFailedBlank = 610;
+    static const int VolumeMountFailedDamaged = 611;
+    static const int VolumeMountFailedNoMedia = 612;
+    static const int VolumeUuidChange = 613;
+    static const int VolumeUserLabelChange = 614;
+
+    static const int ShareAvailabilityChange = 620;
+
+    static const int VolumeDiskInserted = 630;
+    static const int VolumeDiskRemoved = 631;
+    static const int VolumeBadRemoval = 632;
+
+    static const int DiskCreated = 640;
+    static const int DiskSizeChanged = 641;
+    static const int DiskLabelChanged = 642;
+    static const int DiskScanned = 643;
+    static const int DiskSysPathChanged = 644;
+    static const int DiskDestroyed = 649;
+
+    static const int VolumeCreated = 650;
+    static const int VolumeStateChanged = 651;
+    static const int VolumeFsTypeChanged = 652;
+    static const int VolumeFsUuidChanged = 653;
+    static const int VolumeFsLabelChanged = 654;
+    static const int VolumePathChanged = 655;
+    static const int VolumeDestroyed = 659;
+
+    static const int MoveStatus = 660;
+    static const int BenchmarkResult = 661;
+    static const int TrimResult = 662;
+
+    static int convertFromErrno();
+};
+#endif
diff --git a/volume_manager/include/volume_manager/VolumeManager.h b/volume_manager/include/volume_manager/VolumeManager.h
new file mode 100644
index 0000000..c2cb8dd
--- /dev/null
+++ b/volume_manager/include/volume_manager/VolumeManager.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _VOLMGR_VOLUME_MANAGER_H
+#define _VOLMGR_VOLUME_MANAGER_H
+
+#include <fnmatch.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+#include <list>
+#include <mutex>
+#include <string>
+
+struct selabel_handle;
+class NetlinkManager;
+class NetlinkEvent;
+
+class VolumeWatcher {
+  public:
+    virtual ~VolumeWatcher(void) {}
+    virtual void handleEvent(int code, const std::vector<std::string>& argv) = 0;
+};
+
+namespace android {
+namespace volmgr {
+
+class Disk;
+class VolumeBase;
+
+class VolumeInfo {
+  public:
+    explicit VolumeInfo(const VolumeBase* vol);
+
+    std::string mId;
+    std::string mLabel;
+    std::string mPath;
+    bool        mMountable;
+};
+
+class VolumeManager {
+  private:
+    VolumeManager(const VolumeManager&);
+    VolumeManager& operator=(const VolumeManager&);
+
+  public:
+    static VolumeManager* Instance(void);
+
+  private:
+    static VolumeManager* sInstance;
+
+  public:
+    class DiskSource {
+      public:
+        DiskSource(const std::string& sysPattern, const std::string& nickname, int partnum,
+                   int flags, const std::string& fstype, const std::string mntopts)
+            : mSysPattern(sysPattern),
+              mNickname(nickname),
+              mPartNum(partnum),
+              mFlags(flags),
+              mFsType(fstype),
+              mMntOpts(mntopts) {}
+
+        bool matches(const std::string& sysPath) {
+            return !fnmatch(mSysPattern.c_str(), sysPath.c_str(), 0);
+        }
+
+        const std::string& getNickname() { return mNickname; }
+        int getPartNum() { return mPartNum; }
+        int getFlags() { return mFlags; }
+        const std::string& getFsType() { return mFsType; }
+        const std::string& getMntOpts() { return mMntOpts; }
+
+      private:
+        std::string mSysPattern;
+        std::string mNickname;
+        int mPartNum;
+        int mFlags;
+        std::string mFsType;
+        std::string mMntOpts;
+    };
+
+  public:
+    VolumeManager(void);
+    ~VolumeManager(void);
+
+    bool start(VolumeWatcher* watcher);
+    void stop(void);
+
+    bool reset(void);
+    bool unmountAll(void);
+
+    void getVolumeInfo(std::vector<VolumeInfo>& info);
+
+    VolumeBase* findVolume(const std::string& id);
+
+    bool volumeMount(const std::string& id);
+    bool volumeUnmount(const std::string& id, bool detach = false);
+    bool volumeFormat(const std::string& id, const std::string& fsType);
+
+  public:
+    void addDiskSource(DiskSource* source);
+    void handleBlockEvent(NetlinkEvent* evt);
+
+    void notifyEvent(int code);
+    void notifyEvent(int code, const std::string& arg);
+    void notifyEvent(int code, const std::vector<std::string>& argv);
+
+  private:
+    VolumeWatcher* mWatcher;
+    NetlinkManager* mNetlinkManager;
+    std::mutex mLock;
+    VolumeBase* mInternalEmulated;
+    std::list<DiskSource*> mDiskSources;
+    std::list<Disk*> mDisks;
+};
+
+}  // namespace volmgr
+}  // namespace android
+
+#endif
diff --git a/volume_manager/sehandle.h b/volume_manager/sehandle.h
new file mode 100644
index 0000000..d9e969d
--- /dev/null
+++ b/volume_manager/sehandle.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _SEHANDLE_H
+#define _SEHANDLE_H
+
+#include <selinux/android.h>
+
+extern struct selabel_handle* sehandle;
+
+#endif