DO NOT MERGE - Skip pi-platform-release (PPRL.190505.001) in stage-aosp-master

Bug: 132622481
Change-Id: Ic07faa9aa968d11ae8f6b44d548d724fd194e047
diff --git a/.clang-format b/.clang-format
index 0e0f4d1..4a3bd2f 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,3 +1,33 @@
+# bootable/recovery project uses repohook to apply `clang-format` to the changed lines, with the
+# local style file in `.clang-format`. This will be triggered automatically with `repo upload`.
+# Alternatively, one can stage and format a change with `git clang-format` directly.
+#
+#   $ git add <files>
+#   $ git clang-format --style file
+#
+# Or to format a committed change.
+#
+#   $ git clang-format --style file HEAD~1
+#
+# `--style file` will pick up the local style file in `.clang-format`. This can be configured as the
+# default behavior for bootable/recovery project.
+#
+#   $ git config --local clangFormat.style file
+#
+# Note that `repo upload` calls the `clang-format` binary in Android repo (i.e.
+# `$ANDROID_BUILD_TOP/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format`), which might
+# give slightly different results from the one installed in host machine (e.g.
+# `/usr/bin/clang-format`). Specifying the file with `--binary` will ensure consistent results.
+#
+#  $ git clang-format --binary \
+#      /path/to/aosp-master/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format
+#
+# Or to do one-time setup to make it default.
+#
+#   $ git config --local clangFormat.binary \
+#       /path/to/aosp-master/prebuilts/clang/host/linux-x86/clang-stable/bin/clang-format
+#
+
 BasedOnStyle: Google
 AllowShortBlocksOnASingleLine: false
 AllowShortFunctionsOnASingleLine: Empty
diff --git a/Android.bp b/Android.bp
index f8c6a4b..0eb5fd9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,8 +1,191 @@
-subdirs = [
-    "applypatch",
-    "bootloader_message",
-    "edify",
-    "otafault",
-    "otautil",
-    "uncrypt",
-]
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+    name: "recovery_defaults",
+
+    cflags: [
+        "-D_FILE_OFFSET_BITS=64",
+
+        // Must be the same as RECOVERY_API_VERSION.
+        "-DRECOVERY_API_VERSION=3",
+
+        "-Wall",
+        "-Werror",
+    ],
+}
+
+cc_library_static {
+    name: "librecovery_fastboot",
+    recovery_available: true,
+    defaults: [
+        "recovery_defaults",
+    ],
+
+    srcs: [
+        "fastboot/fastboot.cpp",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libbootloader_message",
+        "libcutils",
+        "liblog",
+        "librecovery_ui",
+    ],
+
+    static_libs: [
+        "librecovery_ui_default",
+    ],
+}
+
+cc_defaults {
+    name: "librecovery_defaults",
+
+    defaults: [
+        "recovery_defaults",
+    ],
+
+    shared_libs: [
+        "android.hardware.health@2.0",
+        "libbase",
+        "libbootloader_message",
+        "libcrypto",
+        "libcutils",
+        "libfs_mgr",
+        "liblog",
+        "libziparchive",
+    ],
+
+    static_libs: [
+        "libinstall",
+        "librecovery_fastboot",
+        "libminui",
+        "libotautil",
+
+        // external dependencies
+        "libhealthhalutils",
+    ],
+}
+
+cc_library_static {
+    name: "librecovery",
+    recovery_available: true,
+
+    defaults: [
+        "librecovery_defaults",
+    ],
+
+    srcs: [
+        "fsck_unshare_blocks.cpp",
+        "recovery.cpp",
+    ],
+
+    shared_libs: [
+        "librecovery_ui",
+    ],
+}
+
+cc_binary {
+    name: "recovery",
+    recovery: true,
+
+    defaults: [
+        "libinstall_defaults",
+        "librecovery_defaults",
+    ],
+
+    srcs: [
+        "recovery_main.cpp",
+    ],
+
+    shared_libs: [
+        "librecovery_ui",
+    ],
+
+    static_libs: [
+        "librecovery",
+        "librecovery_ui_default",
+    ],
+
+    required: [
+        "e2fsdroid.recovery",
+        "librecovery_ui_ext",
+        "minadbd",
+        "mke2fs.conf.recovery",
+        "mke2fs.recovery",
+        "recovery_deps",
+    ],
+}
+
+// The dynamic executable that runs after /data mounts.
+cc_binary {
+    name: "recovery-persist",
+
+    defaults: [
+        "recovery_defaults",
+    ],
+
+    srcs: [
+        "recovery-persist.cpp",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libmetricslogger",
+    ],
+
+    static_libs: [
+        "libotautil",
+    ],
+
+    init_rc: [
+        "recovery-persist.rc",
+    ],
+}
+
+// The dynamic executable that runs at init.
+cc_binary {
+    name: "recovery-refresh",
+
+    defaults: [
+        "recovery_defaults",
+    ],
+
+    srcs: [
+        "recovery-refresh.cpp",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+
+    static_libs: [
+        "libotautil",
+    ],
+
+    init_rc: [
+        "recovery-refresh.rc",
+    ],
+}
+
+filegroup {
+    name: "res-testdata",
+
+    srcs: [
+        "res-*/images/*_text.png",
+    ],
+}
diff --git a/Android.mk b/Android.mk
index 7e0ad12..9806d10 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,267 +14,65 @@
 
 LOCAL_PATH := $(call my-dir)
 
-# Needed by build/make/core/Makefile.
+# Needed by build/make/core/Makefile. Must be consistent with the value in Android.bp.
 RECOVERY_API_VERSION := 3
 RECOVERY_FSTAB_VERSION := 2
 
-# libfusesideload (static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := fuse_sideload.cpp
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
-LOCAL_MODULE := libfusesideload
-LOCAL_STATIC_LIBRARIES := \
-    libcrypto \
-    libbase
-include $(BUILD_STATIC_LIBRARY)
+# TARGET_RECOVERY_UI_LIB should be one of librecovery_ui_{default,wear,vr} or a device-specific
+# module that defines make_device() and the exact RecoveryUI class for the target. It defaults to
+# librecovery_ui_default, which uses ScreenRecoveryUI.
+TARGET_RECOVERY_UI_LIB ?= librecovery_ui_default
 
-# libmounts (static library)
-# ===============================
+# librecovery_ui_ext (shared library)
+# ===================================
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := mounts.cpp
-LOCAL_CFLAGS := \
-    -Wall \
-    -Werror
-LOCAL_MODULE := libmounts
-LOCAL_STATIC_LIBRARIES := libbase
-include $(BUILD_STATIC_LIBRARY)
 
-# librecovery (static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
-    install.cpp
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
+LOCAL_MODULE := librecovery_ui_ext
 
-ifeq ($(AB_OTA_UPDATER),true)
-    LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+# LOCAL_MODULE_PATH for shared libraries is unsupported in multiarch builds.
+LOCAL_MULTILIB := first
+
+ifeq ($(TARGET_IS_64_BIT),true)
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib64
+else
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib
 endif
 
-LOCAL_MODULE := librecovery
-LOCAL_STATIC_LIBRARIES := \
-    libminui \
-    libotautil \
-    libvintf_recovery \
-    libcrypto_utils \
-    libcrypto \
+LOCAL_WHOLE_STATIC_LIBRARIES := \
+    $(TARGET_RECOVERY_UI_LIB)
+
+LOCAL_SHARED_LIBRARIES := \
     libbase \
-    libziparchive \
+    liblog \
+    librecovery_ui.recovery
 
-include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_SHARED_LIBRARY)
 
-# recovery (static executable)
-# ===============================
+# recovery_deps: A phony target that's depended on by `recovery`, which
+# builds additional modules conditionally based on Makefile variables.
+# ======================================================================
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := \
-    adb_install.cpp \
-    device.cpp \
-    fuse_sdcard_provider.cpp \
-    recovery.cpp \
-    roots.cpp \
-    rotate_logs.cpp \
-    screen_ui.cpp \
-    ui.cpp \
-    vr_ui.cpp \
-    wear_ui.cpp \
-
-LOCAL_MODULE := recovery
-
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-
-LOCAL_REQUIRED_MODULES := e2fsdroid_static mke2fs_static mke2fs.conf
+LOCAL_MODULE := recovery_deps
 
 ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
 ifeq ($(HOST_OS),linux)
-LOCAL_REQUIRED_MODULES += sload.f2fs mkfs.f2fs
+LOCAL_REQUIRED_MODULES += \
+    make_f2fs.recovery \
+    sload_f2fs.recovery
 endif
 endif
 
-LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
-LOCAL_CFLAGS += -Wall -Werror
-
-ifneq ($(TARGET_RECOVERY_UI_MARGIN_HEIGHT),)
-LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_HEIGHT=$(TARGET_RECOVERY_UI_MARGIN_HEIGHT)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_HEIGHT=0
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_MARGIN_WIDTH),)
-LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_WIDTH=$(TARGET_RECOVERY_UI_MARGIN_WIDTH)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_WIDTH=0
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD),)
-LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=50
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD),)
-LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=90
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE),)
-LOCAL_CFLAGS += -DRECOVERY_UI_PROGRESS_BAR_BASELINE=$(TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_PROGRESS_BAR_BASELINE=259
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_ANIMATION_FPS),)
-LOCAL_CFLAGS += -DRECOVERY_UI_ANIMATION_FPS=$(TARGET_RECOVERY_UI_ANIMATION_FPS)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_ANIMATION_FPS=30
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_MENU_UNUSABLE_ROWS),)
-LOCAL_CFLAGS += -DRECOVERY_UI_MENU_UNUSABLE_ROWS=$(TARGET_RECOVERY_UI_MENU_UNUSABLE_ROWS)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_MENU_UNUSABLE_ROWS=9
-endif
-
-ifneq ($(TARGET_RECOVERY_UI_VR_STEREO_OFFSET),)
-LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=$(TARGET_RECOVERY_UI_VR_STEREO_OFFSET)
-else
-LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=0
-endif
-
-LOCAL_C_INCLUDES += \
-    system/vold \
-
-# Health HAL dependency
-LOCAL_STATIC_LIBRARIES := \
-    android.hardware.health@2.0-impl \
-    android.hardware.health@2.0 \
-    android.hardware.health@1.0 \
-    android.hardware.health@1.0-convert \
-    libhealthstoragedefault \
-    libhidltransport \
-    libhidlbase \
-    libhwbinder_noltopgo \
-    libvndksupport \
-    libbatterymonitor
-
-LOCAL_STATIC_LIBRARIES += \
-    librecovery \
-    libverifier \
-    libbootloader_message \
-    libfs_mgr \
-    libext4_utils \
-    libsparse \
-    libziparchive \
-    libotautil \
-    libmounts \
-    libminadbd \
-    libasyncio \
-    libfusesideload \
-    libminui \
-    libpng \
-    libcrypto_utils \
-    libcrypto \
-    libvintf_recovery \
-    libvintf \
-    libhidl-gen-utils \
-    libtinyxml2 \
-    libbase \
-    libutils \
-    libcutils \
-    liblog \
-    libselinux \
-    libz
-
-LOCAL_HAL_STATIC_LIBRARIES := libhealthd
-
-ifeq ($(AB_OTA_UPDATER),true)
-    LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
-endif
-
-LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
-
-ifeq ($(TARGET_RECOVERY_UI_LIB),)
-  LOCAL_SRC_FILES += default_device.cpp
-else
-  LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB)
-endif
-
+# On A/B devices recovery-persist reads the recovery related file from the persist storage and
+# copies them into /data/misc/recovery. Then, for both A/B and non-A/B devices, recovery-persist
+# parses the last_install file and reports the embedded update metrics. Also, the last_install file
+# will be deteleted after the report.
+LOCAL_REQUIRED_MODULES += recovery-persist
 ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),)
-LOCAL_REQUIRED_MODULES += recovery-persist recovery-refresh
+LOCAL_REQUIRED_MODULES += recovery-refresh
 endif
 
-include $(BUILD_EXECUTABLE)
-
-# recovery-persist (system partition dynamic executable run after /data mounts)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
-    recovery-persist.cpp \
-    rotate_logs.cpp
-LOCAL_MODULE := recovery-persist
-LOCAL_SHARED_LIBRARIES := liblog libbase
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_INIT_RC := recovery-persist.rc
-include $(BUILD_EXECUTABLE)
-
-# recovery-refresh (system partition dynamic executable run at init)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
-    recovery-refresh.cpp \
-    rotate_logs.cpp
-LOCAL_MODULE := recovery-refresh
-LOCAL_SHARED_LIBRARIES := liblog libbase
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_INIT_RC := recovery-refresh.rc
-include $(BUILD_EXECUTABLE)
-
-# libverifier (static library)
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_MODULE := libverifier
-LOCAL_SRC_FILES := \
-    asn1_decoder.cpp \
-    verifier.cpp
-LOCAL_STATIC_LIBRARIES := \
-    libotautil \
-    libcrypto_utils \
-    libcrypto \
-    libbase
-LOCAL_CFLAGS := -Wall -Werror
-include $(BUILD_STATIC_LIBRARY)
-
-# Wear default device
-# ===============================
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := wear_device.cpp
-LOCAL_CFLAGS := -Wall -Werror
-
-# Should match TARGET_RECOVERY_UI_LIB in BoardConfig.mk.
-LOCAL_MODULE := librecovery_ui_wear
-
-include $(BUILD_STATIC_LIBRARY)
-
-# vr headset default device
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := vr_device.cpp
-LOCAL_CFLAGS := -Wall -Werror
-
-# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk
-LOCAL_MODULE := librecovery_ui_vr
-
-include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_PHONY_PACKAGE)
 
 include \
-    $(LOCAL_PATH)/boot_control/Android.mk \
-    $(LOCAL_PATH)/minadbd/Android.mk \
-    $(LOCAL_PATH)/minui/Android.mk \
-    $(LOCAL_PATH)/tests/Android.mk \
-    $(LOCAL_PATH)/tools/Android.mk \
     $(LOCAL_PATH)/updater/Android.mk \
-    $(LOCAL_PATH)/update_verifier/Android.mk \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index e2d97d4..6bd1eb1 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -44,8 +44,17 @@
 #$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
 #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
 
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/sbin)
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinstall.recovery_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/system/lib64/libinstall.so)
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest/recovery_component_test)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/nativetest64/recovery_component_test)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/testcases/recovery_component_test)
+
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes)
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index b5f5f03..28aa06f 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -4,3 +4,7 @@
 [Builtin Hooks Options]
 # Handle native codes only.
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+                  --file_whitelist tools/ updater_sample/
diff --git a/README.md b/README.md
index 0aeadae..0ccc10b 100644
--- a/README.md
+++ b/README.md
@@ -22,11 +22,9 @@
 
     # 32-bit device
     adb shell /data/nativetest/recovery_unit_test/recovery_unit_test
-    adb shell /data/nativetest/recovery_component_test/recovery_component_test
 
     # Or 64-bit device
     adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test
-    adb shell /data/nativetest64/recovery_component_test/recovery_component_test
 
 Running the manual tests
 ------------------------
@@ -41,13 +39,6 @@
   contents of pmsg buffer into /data/misc/recovery/inject.txt. Test will pass if
   this file has expected contents.
 
-`ResourceTest` validates whether the png files are qualified as background text
-image under recovery.
-
-    1. `adb sync data` to make sure the test-dir has the images to test.
-    2. The test will automatically pickup and verify all `_text.png` files in
-       the test dir.
-
 Using `adb` under recovery
 --------------------------
 
@@ -60,10 +51,10 @@
     List of devices attached
     1234567890abcdef    recovery
 
-Although `/sbin/adbd` shares the same binary between normal boot and recovery images, only a subset
-of `adb` commands are meaningful under recovery, such as `adb root`, `adb shell`, `adb push`, `adb
-pull` etc. `adb shell` works only after manually mounting `/system` from recovery menu (assuming a
-valid system image on device).
+Although `/system/bin/adbd` is built from the same code base as the one in the normal boot, only a
+subset of `adb` commands are meaningful under recovery, such as `adb root`, `adb shell`, `adb push`,
+`adb pull` etc. Since Android Q, `adb shell` no longer requires manually mounting `/system` from
+recovery menu.
 
 ## Troubleshooting
 
@@ -74,8 +65,8 @@
 
  * Ensure `adbd` is built and running.
 
-By default, `adbd` is always included into recovery image, as `/sbin/adbd`. `init` starts `adbd`
-service automatically only in debuggable builds. This behavior is controlled by the recovery
+By default, `adbd` is always included into recovery image, as `/system/bin/adbd`. `init` starts
+`adbd` service automatically only in debuggable builds. This behavior is controlled by the recovery
 specific `/init.rc`, whose source code is at `bootable/recovery/etc/init.rc`.
 
 The best way to confirm a running `adbd` is by checking the serial output, which shows a service
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..a304582
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,14 @@
+{
+  "presubmit": [
+    {
+      "name": "minadbd_test"
+    },
+    {
+      "name": "recovery_unit_test"
+    },
+    {
+      "name": "recovery_host_test",
+      "host": true
+    }
+  ]
+}
diff --git a/adb_install.cpp b/adb_install.cpp
deleted file mode 100644
index ac01306..0000000
--- a/adb_install.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2012 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 "adb_install.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/unique_fd.h>
-
-#include "common.h"
-#include "fuse_sideload.h"
-#include "install.h"
-#include "ui.h"
-
-static void set_usb_driver(bool enabled) {
-  // USB configfs doesn't use /s/c/a/a/enable.
-  if (android::base::GetBoolProperty("sys.usb.configfs", false)) {
-    return;
-  }
-
-  static constexpr const char* USB_DRIVER_CONTROL = "/sys/class/android_usb/android0/enable";
-  android::base::unique_fd fd(open(USB_DRIVER_CONTROL, O_WRONLY));
-  if (fd == -1) {
-    PLOG(ERROR) << "Failed to open driver control";
-    return;
-  }
-  // Not using android::base::WriteStringToFile since that will open with O_CREAT and give EPERM
-  // when USB_DRIVER_CONTROL doesn't exist. When it gives EPERM, we don't know whether that's due
-  // to non-existent USB_DRIVER_CONTROL or indeed a permission issue.
-  if (!android::base::WriteStringToFd(enabled ? "1" : "0", fd)) {
-    PLOG(ERROR) << "Failed to set driver control";
-  }
-}
-
-static void stop_adbd() {
-  ui->Print("Stopping adbd...\n");
-  android::base::SetProperty("ctl.stop", "adbd");
-  set_usb_driver(false);
-}
-
-static void maybe_restart_adbd() {
-  if (is_ro_debuggable()) {
-    ui->Print("Restarting adbd...\n");
-    set_usb_driver(true);
-    android::base::SetProperty("ctl.start", "adbd");
-  }
-}
-
-int apply_from_adb(bool* wipe_cache, const char* install_file) {
-  modified_flash = true;
-
-  stop_adbd();
-  set_usb_driver(true);
-
-  ui->Print(
-      "\n\nNow send the package you want to apply\n"
-      "to the device with \"adb sideload <filename>\"...\n");
-
-  pid_t child;
-  if ((child = fork()) == 0) {
-    execl("/sbin/recovery", "recovery", "--adbd", nullptr);
-    _exit(EXIT_FAILURE);
-  }
-
-  // How long (in seconds) we wait for the host to start sending us a package, before timing out.
-  static constexpr int ADB_INSTALL_TIMEOUT = 300;
-
-  // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host connects and starts serving a
-  // package. Poll for its appearance. (Note that inotify doesn't work with FUSE.)
-  int result = INSTALL_ERROR;
-  int status;
-  bool waited = false;
-  for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {
-    if (waitpid(child, &status, WNOHANG) != 0) {
-      result = INSTALL_ERROR;
-      waited = true;
-      break;
-    }
-
-    struct stat st;
-    if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {
-      if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) {
-        sleep(1);
-        continue;
-      } else {
-        ui->Print("\nTimed out waiting for package.\n\n");
-        result = INSTALL_ERROR;
-        kill(child, SIGKILL);
-        break;
-      }
-    }
-    result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false, 0);
-    break;
-  }
-
-  if (!waited) {
-    // Calling stat() on this magic filename signals the minadbd subprocess to shut down.
-    struct stat st;
-    stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
-
-    // TODO: there should be a way to cancel waiting for a package (by pushing some button combo on
-    // the device). For now you just have to 'adb sideload' a file that's not a valid package, like
-    // "/dev/null".
-    waitpid(child, &status, 0);
-  }
-
-  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-    if (WEXITSTATUS(status) == 3) {
-      ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n");
-    } else if (!WIFSIGNALED(status)) {
-      ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status));
-    }
-  }
-
-  set_usb_driver(false);
-  maybe_restart_adbd();
-
-  return result;
-}
diff --git a/applypatch/Android.bp b/applypatch/Android.bp
index cb0b367..620ca6c 100644
--- a/applypatch/Android.bp
+++ b/applypatch/Android.bp
@@ -53,7 +53,6 @@
         "libbz",
         "libcrypto",
         "libedify",
-        "libotafault",
         "libotautil",
         "libz",
     ],
@@ -100,7 +99,6 @@
         "libapplypatch_modes",
         "libapplypatch",
         "libedify",
-        "libotafault",
         "libotautil",
         "libbspatch",
     ],
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 7645a40..90d8e86 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -23,261 +23,167 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
-#include <sys/statfs.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <algorithm>
 #include <functional>
 #include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <openssl/sha.h>
 
 #include "edify/expr.h"
-#include "otafault/ota_io.h"
-#include "otautil/cache_location.h"
+#include "otautil/paths.h"
 #include "otautil/print_sha1.h"
 
-static int LoadPartitionContents(const std::string& filename, FileContents* file);
-static size_t FileSink(const unsigned char* data, size_t len, int fd);
-static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch,
-                          const std::string& target_filename,
-                          const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data);
+using namespace std::string_literals;
 
-// Read a file into memory; store the file contents and associated metadata in *file.
-// Return 0 on success.
-int LoadFileContents(const char* filename, FileContents* file) {
-  // A special 'filename' beginning with "EMMC:" means to load the contents of a partition.
-  if (strncmp(filename, "EMMC:", 5) == 0) {
-    return LoadPartitionContents(filename, file);
+static bool GenerateTarget(const Partition& target, const FileContents& source_file,
+                           const Value& patch, const Value* bonus_data);
+
+bool LoadFileContents(const std::string& filename, FileContents* file) {
+  // No longer allow loading contents from eMMC partitions.
+  if (android::base::StartsWith(filename, "EMMC:")) {
+    return false;
   }
 
-  struct stat sb;
-  if (stat(filename, &sb) == -1) {
-    printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
-    return -1;
+  std::string data;
+  if (!android::base::ReadFileToString(filename, &data)) {
+    PLOG(ERROR) << "Failed to read \"" << filename << "\"";
+    return false;
   }
 
-  std::vector<unsigned char> data(sb.st_size);
-  unique_file f(ota_fopen(filename, "rb"));
-  if (!f) {
-    printf("failed to open \"%s\": %s\n", filename, strerror(errno));
-    return -1;
-  }
-
-  size_t bytes_read = ota_fread(data.data(), 1, data.size(), f.get());
-  if (bytes_read != data.size()) {
-    printf("short read of \"%s\" (%zu bytes of %zu)\n", filename, bytes_read, data.size());
-    return -1;
-  }
-  file->data = std::move(data);
+  file->data = std::vector<unsigned char>(data.begin(), data.end());
   SHA1(file->data.data(), file->data.size(), file->sha1);
-  return 0;
+  return true;
 }
 
-// Load the contents of an EMMC partition into the provided
-// FileContents.  filename should be a string of the form
-// "EMMC:<partition_device>:...".  The smallest size_n bytes for
-// which that prefix of the partition contents has the corresponding
-// sha1 hash will be loaded.  It is acceptable for a size value to be
-// repeated with different sha1s.  Will return 0 on success.
-//
-// This complexity is needed because if an OTA installation is
-// interrupted, the partition might contain either the source or the
-// target data, which might be of different lengths.  We need to know
-// the length in order to read from a partition (there is no
-// "end-of-file" marker), so the caller must specify the possible
-// lengths and the hash of the data, and we'll do the load expecting
-// to find one of those hashes.
-static int LoadPartitionContents(const std::string& filename, FileContents* file) {
-  std::vector<std::string> pieces = android::base::Split(filename, ":");
-  if (pieces.size() < 4 || pieces.size() % 2 != 0 || pieces[0] != "EMMC") {
-    printf("LoadPartitionContents called with bad filename \"%s\"\n", filename.c_str());
-    return -1;
+// Reads the contents of a Partition to the given FileContents buffer.
+static bool ReadPartitionToBuffer(const Partition& partition, FileContents* out,
+                                  bool check_backup) {
+  uint8_t expected_sha1[SHA_DIGEST_LENGTH];
+  if (ParseSha1(partition.hash, expected_sha1) != 0) {
+    LOG(ERROR) << "Failed to parse target hash \"" << partition.hash << "\"";
+    return false;
   }
 
-  size_t pair_count = (pieces.size() - 2) / 2;  // # of (size, sha1) pairs in filename
-  std::vector<std::pair<size_t, std::string>> pairs;
-  for (size_t i = 0; i < pair_count; ++i) {
-    size_t size;
-    if (!android::base::ParseUint(pieces[i * 2 + 2], &size) || size == 0) {
-      printf("LoadPartitionContents called with bad size \"%s\"\n", pieces[i * 2 + 2].c_str());
-      return -1;
-    }
-    pairs.push_back({ size, pieces[i * 2 + 3] });
-  }
-
-  // Sort the pairs array so that they are in order of increasing size.
-  std::sort(pairs.begin(), pairs.end());
-
-  const char* partition = pieces[1].c_str();
-  unique_file dev(ota_fopen(partition, "rb"));
-  if (!dev) {
-    printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno));
-    return -1;
-  }
-
-  SHA_CTX sha_ctx;
-  SHA1_Init(&sha_ctx);
-
-  // Allocate enough memory to hold the largest size.
-  std::vector<unsigned char> buffer(pairs[pair_count - 1].first);
-  unsigned char* buffer_ptr = buffer.data();
-  size_t buffer_size = 0;  // # bytes read so far
-  bool found = false;
-
-  for (const auto& pair : pairs) {
-    size_t current_size = pair.first;
-    const std::string& current_sha1 = pair.second;
-
-    // Read enough additional bytes to get us up to the next size. (Again,
-    // we're trying the possibilities in order of increasing size).
-    size_t next = current_size - buffer_size;
-    if (next > 0) {
-      size_t read = ota_fread(buffer_ptr, 1, next, dev.get());
-      if (next != read) {
-        printf("short read (%zu bytes of %zu) for partition \"%s\"\n", read, next, partition);
-        return -1;
+  android::base::unique_fd dev(open(partition.name.c_str(), O_RDONLY));
+  if (dev == -1) {
+    PLOG(ERROR) << "Failed to open eMMC partition \"" << partition << "\"";
+  } else {
+    std::vector<unsigned char> buffer(partition.size);
+    if (!android::base::ReadFully(dev, buffer.data(), buffer.size())) {
+      PLOG(ERROR) << "Failed to read " << buffer.size() << " bytes of data for partition "
+                  << partition;
+    } else {
+      SHA1(buffer.data(), buffer.size(), out->sha1);
+      if (memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
+        out->data = std::move(buffer);
+        return true;
       }
-      SHA1_Update(&sha_ctx, buffer_ptr, read);
-      buffer_size += read;
-      buffer_ptr += read;
-    }
-
-    // Duplicate the SHA context and finalize the duplicate so we can
-    // check it against this pair's expected hash.
-    SHA_CTX temp_ctx;
-    memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX));
-    uint8_t sha_so_far[SHA_DIGEST_LENGTH];
-    SHA1_Final(sha_so_far, &temp_ctx);
-
-    uint8_t parsed_sha[SHA_DIGEST_LENGTH];
-    if (ParseSha1(current_sha1.c_str(), parsed_sha) != 0) {
-      printf("failed to parse SHA-1 %s in %s\n", current_sha1.c_str(), filename.c_str());
-      return -1;
-    }
-
-    if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) {
-      // We have a match. Stop reading the partition; we'll return the data we've read so far.
-      printf("partition read matched size %zu SHA-1 %s\n", current_size, current_sha1.c_str());
-      found = true;
-      break;
     }
   }
 
-  if (!found) {
-    // Ran off the end of the list of (size, sha1) pairs without finding a match.
-    printf("contents of partition \"%s\" didn't match %s\n", partition, filename.c_str());
-    return -1;
+  if (!check_backup) {
+    LOG(ERROR) << "Partition contents don't have the expected checksum";
+    return false;
   }
 
-  SHA1_Final(file->sha1, &sha_ctx);
+  if (LoadFileContents(Paths::Get().cache_temp_source(), out) &&
+      memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
+    return true;
+  }
 
-  buffer.resize(buffer_size);
-  file->data = std::move(buffer);
-
-  return 0;
+  LOG(ERROR) << "Both of partition contents and backup don't have the expected checksum";
+  return false;
 }
 
-// Save the contents of the given FileContents object under the given
-// filename.  Return 0 on success.
-int SaveFileContents(const char* filename, const FileContents* file) {
-  unique_fd fd(ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR));
+bool SaveFileContents(const std::string& filename, const FileContents* file) {
+  android::base::unique_fd fd(
+      open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR));
   if (fd == -1) {
-    printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno));
-    return -1;
+    PLOG(ERROR) << "Failed to open \"" << filename << "\" for write";
+    return false;
   }
 
-  size_t bytes_written = FileSink(file->data.data(), file->data.size(), fd);
-  if (bytes_written != file->data.size()) {
-    printf("short write of \"%s\" (%zd bytes of %zu): %s\n", filename, bytes_written,
-           file->data.size(), strerror(errno));
-    return -1;
-  }
-  if (ota_fsync(fd) != 0) {
-    printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno));
-    return -1;
-  }
-  if (ota_close(fd) != 0) {
-    printf("close of \"%s\" failed: %s\n", filename, strerror(errno));
-    return -1;
+  if (!android::base::WriteFully(fd, file->data.data(), file->data.size())) {
+    PLOG(ERROR) << "Failed to write " << file->data.size() << " bytes of data to " << filename;
+    return false;
   }
 
-  return 0;
+  if (fsync(fd) != 0) {
+    PLOG(ERROR) << "Failed to fsync \"" << filename << "\"";
+    return false;
+  }
+
+  if (close(fd.release()) != 0) {
+    PLOG(ERROR) << "Failed to close \"" << filename << "\"";
+    return false;
+  }
+
+  return true;
 }
 
-// Write a memory buffer to 'target' partition, a string of the form
-// "EMMC:<partition_device>[:...]". The target name
-// might contain multiple colons, but WriteToPartition() only uses the first
-// two and ignores the rest. Return 0 on success.
-int WriteToPartition(const unsigned char* data, size_t len, const std::string& target) {
-  std::vector<std::string> pieces = android::base::Split(target, ":");
-  if (pieces.size() < 2 || pieces[0] != "EMMC") {
-    printf("WriteToPartition called with bad target (%s)\n", target.c_str());
-    return -1;
-  }
-
-  const char* partition = pieces[1].c_str();
-  unique_fd fd(ota_open(partition, O_RDWR));
-  if (fd == -1) {
-    printf("failed to open %s: %s\n", partition, strerror(errno));
-    return -1;
-  }
-
+// Writes a memory buffer to 'target' Partition.
+static bool WriteBufferToPartition(const FileContents& file_contents, const Partition& partition) {
+  const unsigned char* data = file_contents.data.data();
+  size_t len = file_contents.data.size();
   size_t start = 0;
   bool success = false;
   for (size_t attempt = 0; attempt < 2; ++attempt) {
-    if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
-      printf("failed seek on %s: %s\n", partition, strerror(errno));
-      return -1;
-    }
-    while (start < len) {
-      size_t to_write = len - start;
-      if (to_write > 1 << 20) to_write = 1 << 20;
-
-      ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data + start, to_write));
-      if (written == -1) {
-        printf("failed write writing to %s: %s\n", partition, strerror(errno));
-        return -1;
-      }
-      start += written;
-    }
-
-    if (ota_fsync(fd) != 0) {
-      printf("failed to sync to %s: %s\n", partition, strerror(errno));
-      return -1;
-    }
-    if (ota_close(fd) != 0) {
-      printf("failed to close %s: %s\n", partition, strerror(errno));
-      return -1;
-    }
-
-    fd.reset(ota_open(partition, O_RDONLY));
+    android::base::unique_fd fd(open(partition.name.c_str(), O_RDWR));
     if (fd == -1) {
-      printf("failed to reopen %s for verify: %s\n", partition, strerror(errno));
-      return -1;
+      PLOG(ERROR) << "Failed to open \"" << partition << "\"";
+      return false;
+    }
+
+    if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
+      PLOG(ERROR) << "Failed to seek to " << start << " on \"" << partition << "\"";
+      return false;
+    }
+
+    if (!android::base::WriteFully(fd, data + start, len - start)) {
+      PLOG(ERROR) << "Failed to write " << len - start << " bytes to \"" << partition << "\"";
+      return false;
+    }
+
+    if (fsync(fd) != 0) {
+      PLOG(ERROR) << "Failed to sync \"" << partition << "\"";
+      return false;
+    }
+    if (close(fd.release()) != 0) {
+      PLOG(ERROR) << "Failed to close \"" << partition << "\"";
+      return false;
+    }
+
+    fd.reset(open(partition.name.c_str(), O_RDONLY));
+    if (fd == -1) {
+      PLOG(ERROR) << "Failed to reopen \"" << partition << "\" for verification";
+      return false;
     }
 
     // Drop caches so our subsequent verification read won't just be reading the cache.
     sync();
-    unique_fd dc(ota_open("/proc/sys/vm/drop_caches", O_WRONLY));
-    if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) {
-      printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno));
+    std::string drop_cache = "/proc/sys/vm/drop_caches";
+    if (!android::base::WriteStringToFile("3\n", drop_cache)) {
+      PLOG(ERROR) << "Failed to write to " << drop_cache;
     } else {
-      printf("  caches dropped\n");
+      LOG(INFO) << "  caches dropped";
     }
-    ota_close(dc);
     sleep(1);
 
     // Verify.
     if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
-      printf("failed to seek back to beginning of %s: %s\n", partition, strerror(errno));
-      return -1;
+      PLOG(ERROR) << "Failed to seek to 0 on " << partition;
+      return false;
     }
 
     unsigned char buffer[4096];
@@ -288,386 +194,262 @@
         to_read = sizeof(buffer);
       }
 
-      size_t so_far = 0;
-      while (so_far < to_read) {
-        ssize_t read_count = TEMP_FAILURE_RETRY(ota_read(fd, buffer + so_far, to_read - so_far));
-        if (read_count == -1) {
-          printf("verify read error %s at %zu: %s\n", partition, p, strerror(errno));
-          return -1;
-        } else if (read_count == 0) {
-          printf("verify read reached unexpected EOF, %s at %zu\n", partition, p);
-          return -1;
-        }
-        if (static_cast<size_t>(read_count) < to_read) {
-          printf("short verify read %s at %zu: %zd %zu\n", partition, p, read_count, to_read);
-        }
-        so_far += read_count;
+      if (!android::base::ReadFully(fd, buffer, to_read)) {
+        PLOG(ERROR) << "Failed to verify-read " << partition << " at " << p;
+        return false;
       }
 
       if (memcmp(buffer, data + p, to_read) != 0) {
-        printf("verification failed starting at %zu\n", p);
+        LOG(ERROR) << "Verification failed starting at " << p;
         start = p;
         break;
       }
     }
 
     if (start == len) {
-      printf("verification read succeeded (attempt %zu)\n", attempt + 1);
+      LOG(INFO) << "Verification read succeeded (attempt " << attempt + 1 << ")";
       success = true;
       break;
     }
 
-    if (ota_close(fd) != 0) {
-      printf("failed to close %s: %s\n", partition, strerror(errno));
-      return -1;
-    }
-
-    fd.reset(ota_open(partition, O_RDWR));
-    if (fd == -1) {
-      printf("failed to reopen %s for retry write && verify: %s\n", partition, strerror(errno));
-      return -1;
+    if (close(fd.release()) != 0) {
+      PLOG(ERROR) << "Failed to close " << partition;
+      return false;
     }
   }
 
   if (!success) {
-    printf("failed to verify after all attempts\n");
-    return -1;
+    LOG(ERROR) << "Failed to verify after all attempts";
+    return false;
   }
 
-  if (ota_close(fd) == -1) {
-    printf("error closing %s: %s\n", partition, strerror(errno));
-    return -1;
-  }
   sync();
 
+  return true;
+}
+
+int ParseSha1(const std::string& str, uint8_t* digest) {
+  const char* ps = str.c_str();
+  uint8_t* pd = digest;
+  for (int i = 0; i < SHA_DIGEST_LENGTH * 2; ++i, ++ps) {
+    int digit;
+    if (*ps >= '0' && *ps <= '9') {
+      digit = *ps - '0';
+    } else if (*ps >= 'a' && *ps <= 'f') {
+      digit = *ps - 'a' + 10;
+    } else if (*ps >= 'A' && *ps <= 'F') {
+      digit = *ps - 'A' + 10;
+    } else {
+      return -1;
+    }
+    if (i % 2 == 0) {
+      *pd = digit << 4;
+    } else {
+      *pd |= digit;
+      ++pd;
+    }
+  }
+  if (*ps != '\0') return -1;
   return 0;
 }
 
-// Take a string 'str' of 40 hex digits and parse it into the 20
-// byte array 'digest'.  'str' may contain only the digest or be of
-// the form "<digest>:<anything>".  Return 0 on success, -1 on any
-// error.
-int ParseSha1(const char* str, uint8_t* digest) {
-    const char* ps = str;
-    uint8_t* pd = digest;
-    for (int i = 0; i < SHA_DIGEST_LENGTH * 2; ++i, ++ps) {
-        int digit;
-        if (*ps >= '0' && *ps <= '9') {
-            digit = *ps - '0';
-        } else if (*ps >= 'a' && *ps <= 'f') {
-            digit = *ps - 'a' + 10;
-        } else if (*ps >= 'A' && *ps <= 'F') {
-            digit = *ps - 'A' + 10;
-        } else {
-            return -1;
-        }
-        if (i % 2 == 0) {
-            *pd = digit << 4;
-        } else {
-            *pd |= digit;
-            ++pd;
-        }
-    }
-    if (*ps != '\0') return -1;
-    return 0;
-}
-
-// Search an array of sha1 strings for one matching the given sha1.
-// Return the index of the match on success, or -1 if no match is
-// found.
-static int FindMatchingPatch(uint8_t* sha1, const std::vector<std::string>& patch_sha1_str) {
-  for (size_t i = 0; i < patch_sha1_str.size(); ++i) {
-    uint8_t patch_sha1[SHA_DIGEST_LENGTH];
-    if (ParseSha1(patch_sha1_str[i].c_str(), patch_sha1) == 0 &&
-        memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) {
-      return i;
-    }
-  }
-  return -1;
-}
-
-// Returns 0 if the contents of the file (argv[2]) or the cached file
-// match any of the sha1's on the command line (argv[3:]).  Returns
-// nonzero otherwise.
-int applypatch_check(const char* filename, const std::vector<std::string>& patch_sha1_str) {
-  FileContents file;
-
-  // It's okay to specify no sha1s; the check will pass if the
-  // LoadFileContents is successful.  (Useful for reading
-  // partitions, where the filename encodes the sha1s; no need to
-  // check them twice.)
-  if (LoadFileContents(filename, &file) != 0 ||
-      (!patch_sha1_str.empty() && FindMatchingPatch(file.sha1, patch_sha1_str) < 0)) {
-    printf("file \"%s\" doesn't have any of expected sha1 sums; checking cache\n", filename);
-
-    // If the source file is missing or corrupted, it might be because we were killed in the middle
-    // of patching it.  A copy of it should have been made in cache_temp_source.  If that file
-    // exists and matches the sha1 we're looking for, the check still passes.
-    if (LoadFileContents(CacheLocation::location().cache_temp_source().c_str(), &file) != 0) {
-      printf("failed to load cache file\n");
-      return 1;
-    }
-
-    if (FindMatchingPatch(file.sha1, patch_sha1_str) < 0) {
-      printf("cache bits don't match any sha1 for \"%s\"\n", filename);
-      return 1;
-    }
-  }
-  return 0;
+bool PatchPartitionCheck(const Partition& target, const Partition& source) {
+  FileContents target_file;
+  FileContents source_file;
+  return (ReadPartitionToBuffer(target, &target_file, false) ||
+          ReadPartitionToBuffer(source, &source_file, true));
 }
 
 int ShowLicenses() {
-    ShowBSDiffLicense();
-    return 0;
-}
-
-static size_t FileSink(const unsigned char* data, size_t len, int fd) {
-  size_t done = 0;
-  while (done < len) {
-    ssize_t wrote = TEMP_FAILURE_RETRY(ota_write(fd, data + done, len - done));
-    if (wrote == -1) {
-      printf("error writing %zd bytes: %s\n", (len - done), strerror(errno));
-      return done;
-    }
-    done += wrote;
-  }
-  return done;
-}
-
-// Return the amount of free space (in bytes) on the filesystem
-// containing filename.  filename must exist.  Return -1 on error.
-size_t FreeSpaceForFile(const char* filename) {
-    struct statfs sf;
-    if (statfs(filename, &sf) != 0) {
-        printf("failed to statfs %s: %s\n", filename, strerror(errno));
-        return -1;
-    }
-    return sf.f_bsize * sf.f_bavail;
-}
-
-int CacheSizeCheck(size_t bytes) {
-    if (MakeFreeSpaceOnCache(bytes) < 0) {
-        printf("unable to make %zu bytes available on /cache\n", bytes);
-        return 1;
-    }
-    return 0;
-}
-
-// This function applies binary patches to EMMC target files in a way that is safe (the original
-// file is not touched until we have the desired replacement for it) and idempotent (it's okay to
-// run this program multiple times).
-//
-// - If the SHA-1 hash of <target_filename> is <target_sha1_string>, does nothing and exits
-//   successfully.
-//
-// - Otherwise, if the SHA-1 hash of <source_filename> is one of the entries in <patch_sha1_str>,
-//   the corresponding patch from <patch_data> (which must be a VAL_BLOB) is applied to produce a
-//   new file (the type of patch is automatically detected from the blob data). If that new file
-//   has SHA-1 hash <target_sha1_str>, moves it to replace <target_filename>, and exits
-//   successfully. Note that if <source_filename> and <target_filename> are not the same,
-//   <source_filename> is NOT deleted on success. <target_filename> may be the string "-" to mean
-//   "the same as <source_filename>".
-//
-// - Otherwise, or if any error is encountered, exits with non-zero status.
-//
-// <source_filename> must refer to an EMMC partition to read the source data. See the comments for
-// the LoadPartitionContents() function above for the format of such a filename. <target_size> has
-// become obsolete since we have dropped the support for patching non-EMMC targets (EMMC targets
-// have the size embedded in the filename).
-int applypatch(const char* source_filename, const char* target_filename,
-               const char* target_sha1_str, size_t /* target_size */,
-               const std::vector<std::string>& patch_sha1_str,
-               const std::vector<std::unique_ptr<Value>>& patch_data, const Value* bonus_data) {
-  printf("patch %s: ", source_filename);
-
-  if (target_filename[0] == '-' && target_filename[1] == '\0') {
-    target_filename = source_filename;
-  }
-
-  if (strncmp(target_filename, "EMMC:", 5) != 0) {
-    printf("Supporting patching EMMC targets only.\n");
-    return 1;
-  }
-
-  uint8_t target_sha1[SHA_DIGEST_LENGTH];
-  if (ParseSha1(target_sha1_str, target_sha1) != 0) {
-    printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
-    return 1;
-  }
-
-  // We try to load the target file into the source_file object.
-  FileContents source_file;
-  if (LoadFileContents(target_filename, &source_file) == 0) {
-    if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
-      // The early-exit case: the patch was already applied, this file has the desired hash, nothing
-      // for us to do.
-      printf("already %s\n", short_sha1(target_sha1).c_str());
-      return 0;
-    }
-  }
-
-  if (source_file.data.empty() ||
-      (target_filename != source_filename && strcmp(target_filename, source_filename) != 0)) {
-    // Need to load the source file: either we failed to load the target file, or we did but it's
-    // different from the expected.
-    source_file.data.clear();
-    LoadFileContents(source_filename, &source_file);
-  }
-
-  if (!source_file.data.empty()) {
-    int to_use = FindMatchingPatch(source_file.sha1, patch_sha1_str);
-    if (to_use != -1) {
-      return GenerateTarget(source_file, patch_data[to_use], target_filename, target_sha1,
-                            bonus_data);
-    }
-  }
-
-  printf("source file is bad; trying copy\n");
-
-  FileContents copy_file;
-  if (LoadFileContents(CacheLocation::location().cache_temp_source().c_str(), &copy_file) < 0) {
-    printf("failed to read copy file\n");
-    return 1;
-  }
-
-  int to_use = FindMatchingPatch(copy_file.sha1, patch_sha1_str);
-  if (to_use == -1) {
-    printf("copy file doesn't match source SHA-1s either\n");
-    return 1;
-  }
-
-  return GenerateTarget(copy_file, patch_data[to_use], target_filename, target_sha1, bonus_data);
-}
-
-/*
- * This function flashes a given image to the target partition. It verifies
- * the target cheksum first, and will return if target has the desired hash.
- * It checks the checksum of the given source image before flashing, and
- * verifies the target partition afterwards. The function is idempotent.
- * Returns zero on success.
- */
-int applypatch_flash(const char* source_filename, const char* target_filename,
-                     const char* target_sha1_str, size_t target_size) {
-  printf("flash %s: ", target_filename);
-
-  uint8_t target_sha1[SHA_DIGEST_LENGTH];
-  if (ParseSha1(target_sha1_str, target_sha1) != 0) {
-    printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str);
-    return 1;
-  }
-
-  std::string target_str(target_filename);
-  std::vector<std::string> pieces = android::base::Split(target_str, ":");
-  if (pieces.size() != 2 || pieces[0] != "EMMC") {
-    printf("invalid target name \"%s\"", target_filename);
-    return 1;
-  }
-
-  // Load the target into the source_file object to see if already applied.
-  pieces.push_back(std::to_string(target_size));
-  pieces.push_back(target_sha1_str);
-  std::string fullname = android::base::Join(pieces, ':');
-  FileContents source_file;
-  if (LoadPartitionContents(fullname, &source_file) == 0 &&
-      memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) {
-    // The early-exit case: the image was already applied, this partition
-    // has the desired hash, nothing for us to do.
-    printf("already %s\n", short_sha1(target_sha1).c_str());
-    return 0;
-  }
-
-  if (LoadFileContents(source_filename, &source_file) == 0) {
-    if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
-      // The source doesn't have desired checksum.
-      printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename);
-      printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(),
-             short_sha1(source_file.sha1).c_str());
-      return 1;
-    }
-  }
-
-  if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) {
-    printf("write of copied data to %s failed\n", target_filename);
-    return 1;
-  }
+  ShowBSDiffLicense();
   return 0;
 }
 
-static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch,
-                          const std::string& target_filename,
-                          const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data) {
-  if (patch->type != VAL_BLOB) {
-    printf("patch is not a blob\n");
-    return 1;
+bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
+                    const Value* bonus) {
+  LOG(INFO) << "Patching " << target.name;
+
+  // We try to load and check against the target hash first.
+  FileContents target_file;
+  if (ReadPartitionToBuffer(target, &target_file, false)) {
+    // The early-exit case: the patch was already applied, this file has the desired hash, nothing
+    // for us to do.
+    LOG(INFO) << "  already " << target.hash.substr(0, 8);
+    return true;
   }
 
-  const char* header = &patch->data[0];
-  size_t header_bytes_read = patch->data.size();
+  FileContents source_file;
+  if (ReadPartitionToBuffer(source, &source_file, true)) {
+    return GenerateTarget(target, source_file, patch, bonus);
+  }
+
+  LOG(ERROR) << "Failed to find any match";
+  return false;
+}
+
+bool FlashPartition(const Partition& partition, const std::string& source_filename) {
+  LOG(INFO) << "Flashing " << partition;
+
+  // We try to load and check against the target hash first.
+  FileContents target_file;
+  if (ReadPartitionToBuffer(partition, &target_file, false)) {
+    // The early-exit case: the patch was already applied, this file has the desired hash, nothing
+    // for us to do.
+    LOG(INFO) << "  already " << partition.hash.substr(0, 8);
+    return true;
+  }
+
+  FileContents source_file;
+  if (!LoadFileContents(source_filename, &source_file)) {
+    LOG(ERROR) << "Failed to load source file";
+    return false;
+  }
+
+  uint8_t expected_sha1[SHA_DIGEST_LENGTH];
+  if (ParseSha1(partition.hash, expected_sha1) != 0) {
+    LOG(ERROR) << "Failed to parse source hash \"" << partition.hash << "\"";
+    return false;
+  }
+
+  if (memcmp(source_file.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
+    // The source doesn't have desired checksum.
+    LOG(ERROR) << "source \"" << source_filename << "\" doesn't have expected SHA-1 sum";
+    LOG(ERROR) << "expected: " << partition.hash.substr(0, 8)
+               << ", found: " << short_sha1(source_file.sha1);
+    return false;
+  }
+  if (!WriteBufferToPartition(source_file, partition)) {
+    LOG(ERROR) << "Failed to write to " << partition;
+    return false;
+  }
+  return true;
+}
+
+static bool GenerateTarget(const Partition& target, const FileContents& source_file,
+                           const Value& patch, const Value* bonus_data) {
+  uint8_t expected_sha1[SHA_DIGEST_LENGTH];
+  if (ParseSha1(target.hash, expected_sha1) != 0) {
+    LOG(ERROR) << "Failed to parse target hash \"" << target.hash << "\"";
+    return false;
+  }
+
+  if (patch.type != Value::Type::BLOB) {
+    LOG(ERROR) << "patch is not a blob";
+    return false;
+  }
+
+  const char* header = patch.data.data();
+  size_t header_bytes_read = patch.data.size();
   bool use_bsdiff = false;
   if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) {
     use_bsdiff = true;
   } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) {
     use_bsdiff = false;
   } else {
-    printf("Unknown patch file format\n");
-    return 1;
+    LOG(ERROR) << "Unknown patch file format";
+    return false;
   }
 
-  CHECK(android::base::StartsWith(target_filename, "EMMC:"));
-
-  // We still write the original source to cache, in case the partition write is interrupted.
-  if (MakeFreeSpaceOnCache(source_file.data.size()) < 0) {
-    printf("not enough free space on /cache\n");
-    return 1;
+  // We write the original source to cache, in case the partition write is interrupted.
+  if (!CheckAndFreeSpaceOnCache(source_file.data.size())) {
+    LOG(ERROR) << "Not enough free space on /cache";
+    return false;
   }
-  if (SaveFileContents(CacheLocation::location().cache_temp_source().c_str(), &source_file) < 0) {
-    printf("failed to back up source file\n");
-    return 1;
+  if (!SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) {
+    LOG(ERROR) << "Failed to back up source file";
+    return false;
   }
 
   // We store the decoded output in memory.
-  std::string memory_sink_str;  // Don't need to reserve space.
-  SinkFn sink = [&memory_sink_str](const unsigned char* data, size_t len) {
-    memory_sink_str.append(reinterpret_cast<const char*>(data), len);
+  FileContents patched;
+  SHA_CTX ctx;
+  SHA1_Init(&ctx);
+  SinkFn sink = [&patched, &ctx](const unsigned char* data, size_t len) {
+    SHA1_Update(&ctx, data, len);
+    patched.data.insert(patched.data.end(), data, data + len);
     return len;
   };
 
-  SHA_CTX ctx;
-  SHA1_Init(&ctx);
-
   int result;
   if (use_bsdiff) {
-    result =
-        ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), *patch, 0, sink, &ctx);
+    result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch, 0, sink);
   } else {
-    result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), *patch, sink, &ctx,
-                             bonus_data);
+    result =
+        ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch, sink, bonus_data);
   }
 
   if (result != 0) {
-    printf("applying patch failed\n");
-    return 1;
+    LOG(ERROR) << "Failed to apply the patch: " << result;
+    return false;
   }
 
-  uint8_t current_target_sha1[SHA_DIGEST_LENGTH];
-  SHA1_Final(current_target_sha1, &ctx);
-  if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) {
-    printf("patch did not produce expected sha1\n");
-    return 1;
-  } else {
-    printf("now %s\n", short_sha1(target_sha1).c_str());
+  SHA1_Final(patched.sha1, &ctx);
+  if (memcmp(patched.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
+    LOG(ERROR) << "Patching did not produce the expected SHA-1 of " << short_sha1(expected_sha1);
+
+    LOG(ERROR) << "target size " << patched.data.size() << " SHA-1 " << short_sha1(patched.sha1);
+    LOG(ERROR) << "source size " << source_file.data.size() << " SHA-1 "
+               << short_sha1(source_file.sha1);
+
+    uint8_t patch_digest[SHA_DIGEST_LENGTH];
+    SHA1(reinterpret_cast<const uint8_t*>(patch.data.data()), patch.data.size(), patch_digest);
+    LOG(ERROR) << "patch size " << patch.data.size() << " SHA-1 " << short_sha1(patch_digest);
+
+    if (bonus_data != nullptr) {
+      uint8_t bonus_digest[SHA_DIGEST_LENGTH];
+      SHA1(reinterpret_cast<const uint8_t*>(bonus_data->data.data()), bonus_data->data.size(),
+           bonus_digest);
+      LOG(ERROR) << "bonus size " << bonus_data->data.size() << " SHA-1 "
+                 << short_sha1(bonus_digest);
+    }
+
+    return false;
   }
 
+  LOG(INFO) << "  now " << short_sha1(expected_sha1);
+
   // Write back the temp file to the partition.
-  if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()),
-                       memory_sink_str.size(), target_filename) != 0) {
-    printf("write of patched data to %s failed\n", target_filename.c_str());
-    return 1;
+  if (!WriteBufferToPartition(patched, target)) {
+    LOG(ERROR) << "Failed to write patched data to " << target.name;
+    return false;
   }
 
   // Delete the backup copy of the source.
-  unlink(CacheLocation::location().cache_temp_source().c_str());
+  unlink(Paths::Get().cache_temp_source().c_str());
 
   // Success!
-  return 0;
+  return true;
+}
+
+bool CheckPartition(const Partition& partition) {
+  FileContents target_file;
+  return ReadPartitionToBuffer(partition, &target_file, false);
+}
+
+Partition Partition::Parse(const std::string& input_str, std::string* err) {
+  std::vector<std::string> pieces = android::base::Split(input_str, ":");
+  if (pieces.size() != 4 || pieces[0] != "EMMC") {
+    *err = "Invalid number of tokens or non-eMMC target";
+    return {};
+  }
+
+  size_t size;
+  if (!android::base::ParseUint(pieces[2], &size) || size == 0) {
+    *err = "Failed to parse \"" + pieces[2] + "\" as byte count";
+    return {};
+  }
+
+  return Partition(pieces[1], size, pieces[3]);
+}
+
+std::string Partition::ToString() const {
+  if (*this) {
+    return "EMMC:"s + name + ":" + std::to_string(size) + ":" + hash;
+  }
+  return "<invalid-partition>";
+}
+
+std::ostream& operator<<(std::ostream& os, const Partition& partition) {
+  os << partition.ToString();
+  return os;
 }
diff --git a/applypatch/applypatch_main.cpp b/applypatch/applypatch_main.cpp
index 197077c..92d2b3f 100644
--- a/applypatch/applypatch_main.cpp
+++ b/applypatch/applypatch_main.cpp
@@ -16,13 +16,10 @@
 
 #include "applypatch_modes.h"
 
-// This program (applypatch) applies binary patches to files in a way that
-// is safe (the original file is not touched until we have the desired
-// replacement for it) and idempotent (it's okay to run this program
-// multiple times).
-//
-// See the comments to applypatch_modes() function.
+#include <android-base/logging.h>
 
+// See the comments for applypatch() function.
 int main(int argc, char** argv) {
-    return applypatch_modes(argc, const_cast<const char**>(argv));
+  android::base::InitLogging(argv);
+  return applypatch_modes(argc, argv);
 }
diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp
index aa32d57..b466598 100644
--- a/applypatch/applypatch_modes.cpp
+++ b/applypatch/applypatch_modes.cpp
@@ -16,6 +16,7 @@
 
 #include "applypatch_modes.h"
 
+#include <getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -25,6 +26,8 @@
 #include <string>
 #include <vector>
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
 #include <openssl/sha.h>
@@ -32,157 +35,153 @@
 #include "applypatch/applypatch.h"
 #include "edify/expr.h"
 
-static int CheckMode(int argc, const char** argv) {
-    if (argc < 3) {
+static int CheckMode(const std::string& target_emmc) {
+  std::string err;
+  auto target = Partition::Parse(target_emmc, &err);
+  if (!target) {
+    LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
+    return 2;
+  }
+  return CheckPartition(target) ? 0 : 1;
+}
+
+static int FlashMode(const std::string& target_emmc, const std::string& source_file) {
+  std::string err;
+  auto target = Partition::Parse(target_emmc, &err);
+  if (!target) {
+    LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
+    return 2;
+  }
+  return FlashPartition(target, source_file) ? 0 : 1;
+}
+
+static int PatchMode(const std::string& target_emmc, const std::string& source_emmc,
+                     const std::string& patch_file, const std::string& bonus_file) {
+  std::string err;
+  auto target = Partition::Parse(target_emmc, &err);
+  if (!target) {
+    LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
+    return 2;
+  }
+
+  auto source = Partition::Parse(source_emmc, &err);
+  if (!source) {
+    LOG(ERROR) << "Failed to parse source \"" << source_emmc << "\": " << err;
+    return 2;
+  }
+
+  std::string patch_contents;
+  if (!android::base::ReadFileToString(patch_file, &patch_contents)) {
+    PLOG(ERROR) << "Failed to read patch file \"" << patch_file << "\"";
+    return 1;
+  }
+
+  Value patch(Value::Type::BLOB, std::move(patch_contents));
+  std::unique_ptr<Value> bonus;
+  if (!bonus_file.empty()) {
+    std::string bonus_contents;
+    if (!android::base::ReadFileToString(bonus_file, &bonus_contents)) {
+      PLOG(ERROR) << "Failed to read bonus file \"" << bonus_file << "\"";
+      return 1;
+    }
+    bonus = std::make_unique<Value>(Value::Type::BLOB, std::move(bonus_contents));
+  }
+
+  return PatchPartition(target, source, patch, bonus.get()) ? 0 : 1;
+}
+
+static void Usage() {
+  printf(
+      "Usage: \n"
+      "check mode\n"
+      "  applypatch --check EMMC:<target-file>:<target-size>:<target-sha1>\n\n"
+      "flash mode\n"
+      "  applypatch --flash <source-file>\n"
+      "             --target EMMC:<target-file>:<target-size>:<target-sha1>\n\n"
+      "patch mode\n"
+      "  applypatch [--bonus <bonus-file>]\n"
+      "             --patch <patch-file>\n"
+      "             --target EMMC:<target-file>:<target-size>:<target-sha1>\n"
+      "             --source EMMC:<source-file>:<source-size>:<source-sha1>\n\n"
+      "show license\n"
+      "  applypatch --license\n"
+      "\n\n");
+}
+
+int applypatch_modes(int argc, char* argv[]) {
+  static constexpr struct option OPTIONS[]{
+    // clang-format off
+    { "bonus", required_argument, nullptr, 0 },
+    { "check", required_argument, nullptr, 0 },
+    { "flash", required_argument, nullptr, 0 },
+    { "license", no_argument, nullptr, 0 },
+    { "patch", required_argument, nullptr, 0 },
+    { "source", required_argument, nullptr, 0 },
+    { "target", required_argument, nullptr, 0 },
+    { nullptr, 0, nullptr, 0 },
+    // clang-format on
+  };
+
+  std::string check_target;
+  std::string source;
+  std::string target;
+  std::string patch;
+  std::string bonus;
+
+  bool check_mode = false;
+  bool flash_mode = false;
+  bool patch_mode = false;
+
+  optind = 1;
+
+  int arg;
+  int option_index;
+  while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
+    switch (arg) {
+      case 0: {
+        std::string option = OPTIONS[option_index].name;
+        if (option == "bonus") {
+          bonus = optarg;
+        } else if (option == "check") {
+          check_target = optarg;
+          check_mode = true;
+        } else if (option == "flash") {
+          source = optarg;
+          flash_mode = true;
+        } else if (option == "license") {
+          return ShowLicenses();
+        } else if (option == "patch") {
+          patch = optarg;
+          patch_mode = true;
+        } else if (option == "source") {
+          source = optarg;
+        } else if (option == "target") {
+          target = optarg;
+        }
+        break;
+      }
+      case '?':
+      default:
+        LOG(ERROR) << "Invalid argument";
+        Usage();
         return 2;
     }
-    std::vector<std::string> sha1;
-    for (int i = 3; i < argc; i++) {
-        sha1.push_back(argv[i]);
+  }
+
+  if (check_mode) {
+    return CheckMode(check_target);
+  }
+  if (flash_mode) {
+    if (!bonus.empty()) {
+      LOG(ERROR) << "bonus file not supported in flash mode";
+      return 1;
     }
+    return FlashMode(target, source);
+  }
+  if (patch_mode) {
+    return PatchMode(target, source, patch, bonus);
+  }
 
-    return applypatch_check(argv[2], sha1);
-}
-
-// Parse arguments (which should be of the form "<sha1>:<filename>" into the
-// new parallel arrays *sha1s and *files. Returns true on success.
-static bool ParsePatchArgs(int argc, const char** argv, std::vector<std::string>* sha1s,
-                           std::vector<FileContents>* files) {
-    if (sha1s == nullptr) {
-        return false;
-    }
-    for (int i = 0; i < argc; ++i) {
-        std::vector<std::string> pieces = android::base::Split(argv[i], ":");
-        if (pieces.size() != 2) {
-            printf("failed to parse patch argument \"%s\"\n", argv[i]);
-            return false;
-        }
-
-        uint8_t digest[SHA_DIGEST_LENGTH];
-        if (ParseSha1(pieces[0].c_str(), digest) != 0) {
-            printf("failed to parse sha1 \"%s\"\n", argv[i]);
-            return false;
-        }
-
-        sha1s->push_back(pieces[0]);
-        FileContents fc;
-        if (LoadFileContents(pieces[1].c_str(), &fc) != 0) {
-            return false;
-        }
-        files->push_back(std::move(fc));
-    }
-    return true;
-}
-
-static int FlashMode(const char* src_filename, const char* tgt_filename,
-                     const char* tgt_sha1, size_t tgt_size) {
-    return applypatch_flash(src_filename, tgt_filename, tgt_sha1, tgt_size);
-}
-
-static int PatchMode(int argc, const char** argv) {
-    FileContents bonusFc;
-    Value bonus(VAL_INVALID, "");
-
-    if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
-        if (LoadFileContents(argv[2], &bonusFc) != 0) {
-            printf("failed to load bonus file %s\n", argv[2]);
-            return 1;
-        }
-        bonus.type = VAL_BLOB;
-        bonus.data = std::string(bonusFc.data.cbegin(), bonusFc.data.cend());
-        argc -= 2;
-        argv += 2;
-    }
-
-    if (argc < 4) {
-        return 2;
-    }
-
-    size_t target_size;
-    if (!android::base::ParseUint(argv[4], &target_size) || target_size == 0) {
-        printf("can't parse \"%s\" as byte count\n\n", argv[4]);
-        return 1;
-    }
-
-    // If no <src-sha1>:<patch> is provided, it is in flash mode.
-    if (argc == 5) {
-        if (bonus.type != VAL_INVALID) {
-            printf("bonus file not supported in flash mode\n");
-            return 1;
-        }
-        return FlashMode(argv[1], argv[2], argv[3], target_size);
-    }
-
-    std::vector<std::string> sha1s;
-    std::vector<FileContents> files;
-    if (!ParsePatchArgs(argc-5, argv+5, &sha1s, &files)) {
-        printf("failed to parse patch args\n");
-        return 1;
-    }
-
-    std::vector<std::unique_ptr<Value>> patches;
-    for (size_t i = 0; i < files.size(); ++i) {
-        patches.push_back(std::make_unique<Value>(
-                VAL_BLOB, std::string(files[i].data.cbegin(), files[i].data.cend())));
-    }
-    return applypatch(argv[1], argv[2], argv[3], target_size, sha1s, patches, &bonus);
-}
-
-// This program (applypatch) applies binary patches to files in a way that
-// is safe (the original file is not touched until we have the desired
-// replacement for it) and idempotent (it's okay to run this program
-// multiple times).
-//
-// - if the sha1 hash of <tgt-file> is <tgt-sha1>, does nothing and exits
-//   successfully.
-//
-// - otherwise, if no <src-sha1>:<patch> is provided, flashes <tgt-file> with
-//   <src-file>. <tgt-file> must be a partition name, while <src-file> must
-//   be a regular image file. <src-file> will not be deleted on success.
-//
-// - otherwise, if the sha1 hash of <src-file> is <src-sha1>, applies the
-//   bsdiff <patch> to <src-file> to produce a new file (the type of patch
-//   is automatically detected from the file header).  If that new
-//   file has sha1 hash <tgt-sha1>, moves it to replace <tgt-file>, and
-//   exits successfully.  Note that if <src-file> and <tgt-file> are
-//   not the same, <src-file> is NOT deleted on success.  <tgt-file>
-//   may be the string "-" to mean "the same as src-file".
-//
-// - otherwise, or if any error is encountered, exits with non-zero
-//   status.
-//
-// <src-file> (or <file> in check mode) may refer to an EMMC partition
-// to read the source data.  See the comments for the
-// LoadPartitionContents() function for the format of such a filename.
-
-int applypatch_modes(int argc, const char** argv) {
-    if (argc < 2) {
-      usage:
-        printf(
-            "usage: %s [-b <bonus-file>] <src-file> <tgt-file> <tgt-sha1> <tgt-size> "
-            "[<src-sha1>:<patch> ...]\n"
-            "   or  %s -c <file> [<sha1> ...]\n"
-            "   or  %s -l\n"
-            "\n"
-            "Filenames may be of the form\n"
-            "  EMMC:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n"
-            "to specify reading from or writing to an EMMC partition.\n\n",
-            argv[0], argv[0], argv[0]);
-        return 2;
-    }
-
-    int result;
-
-    if (strncmp(argv[1], "-l", 3) == 0) {
-        result = ShowLicenses();
-    } else if (strncmp(argv[1], "-c", 3) == 0) {
-        result = CheckMode(argc, argv);
-    } else {
-        result = PatchMode(argc, argv);
-    }
-
-    if (result == 2) {
-        goto usage;
-    }
-    return result;
+  Usage();
+  return 2;
 }
diff --git a/applypatch/applypatch_modes.h b/applypatch/applypatch_modes.h
index 3d9d08d..aa60a43 100644
--- a/applypatch/applypatch_modes.h
+++ b/applypatch/applypatch_modes.h
@@ -17,6 +17,6 @@
 #ifndef _APPLYPATCH_MODES_H
 #define _APPLYPATCH_MODES_H
 
-int applypatch_modes(int argc, const char** argv);
+int applypatch_modes(int argc, char* argv[]);
 
 #endif // _APPLYPATCH_MODES_H
diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp
index 912dbbd..ba33c3a 100644
--- a/applypatch/bspatch.cpp
+++ b/applypatch/bspatch.cpp
@@ -66,18 +66,12 @@
 }
 
 int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch,
-                     size_t patch_offset, SinkFn sink, SHA_CTX* ctx) {
-  auto sha_sink = [&sink, &ctx](const uint8_t* data, size_t len) {
-    len = sink(data, len);
-    if (ctx) SHA1_Update(ctx, data, len);
-    return len;
-  };
-
+                     size_t patch_offset, SinkFn sink) {
   CHECK_LE(patch_offset, patch.data.size());
 
   int result = bsdiff::bspatch(old_data, old_size,
                                reinterpret_cast<const uint8_t*>(&patch.data[patch_offset]),
-                               patch.data.size() - patch_offset, sha_sink);
+                               patch.data.size() - patch_offset, sink);
   if (result != 0) {
     LOG(ERROR) << "bspatch failed, result: " << result;
     // print SHA1 of the patch in the case of a data error.
diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp
index ea364d8..3868ef2 100644
--- a/applypatch/freecache.cpp
+++ b/applypatch/freecache.cpp
@@ -14,31 +14,35 @@
  * limitations under the License.
  */
 
+#include <dirent.h>
 #include <errno.h>
-#include <libgen.h>
+#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/statfs.h>
 #include <unistd.h>
-#include <dirent.h>
-#include <ctype.h>
 
+#include <algorithm>
+#include <limits>
 #include <memory>
 #include <set>
 #include <string>
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 
 #include "applypatch/applypatch.h"
-#include "otautil/cache_location.h"
+#include "otautil/paths.h"
 
-static int EliminateOpenFiles(std::set<std::string>* files) {
+static int EliminateOpenFiles(const std::string& dirname, std::set<std::string>* files) {
   std::unique_ptr<DIR, decltype(&closedir)> d(opendir("/proc"), closedir);
   if (!d) {
-    printf("error opening /proc: %s\n", strerror(errno));
+    PLOG(ERROR) << "Failed to open /proc";
     return -1;
   }
   struct dirent* de;
@@ -52,7 +56,7 @@
     struct dirent* fdde;
     std::unique_ptr<DIR, decltype(&closedir)> fdd(opendir(path.c_str()), closedir);
     if (!fdd) {
-      printf("error opening %s: %s\n", path.c_str(), strerror(errno));
+      PLOG(ERROR) << "Failed to open " << path;
       continue;
     }
     while ((fdde = readdir(fdd.get())) != 0) {
@@ -62,9 +66,9 @@
       int count = readlink(fd_path.c_str(), link, sizeof(link)-1);
       if (count >= 0) {
         link[count] = '\0';
-        if (strncmp(link, "/cache/", 7) == 0) {
+        if (android::base::StartsWith(link, dirname)) {
           if (files->erase(link) > 0) {
-            printf("%s is open by %s\n", link, de->d_name);
+            LOG(INFO) << link << " is open by " << de->d_name;
           }
         }
       }
@@ -73,77 +77,171 @@
   return 0;
 }
 
-static std::set<std::string> FindExpendableFiles() {
-  std::set<std::string> files;
-  // We're allowed to delete unopened regular files in any of these
-  // directories.
-  const char* dirs[2] = {"/cache", "/cache/recovery/otatest"};
+static std::vector<std::string> FindExpendableFiles(
+    const std::string& dirname, const std::function<bool(const std::string&)>& name_filter) {
+  std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dirname.c_str()), closedir);
+  if (!d) {
+    PLOG(ERROR) << "Failed to open " << dirname;
+    return {};
+  }
 
-  for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); ++i) {
-    std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dirs[i]), closedir);
-    if (!d) {
-      printf("error opening %s: %s\n", dirs[i], strerror(errno));
+  // Look for regular files in the directory (not in any subdirectories).
+  std::set<std::string> files;
+  struct dirent* de;
+  while ((de = readdir(d.get())) != 0) {
+    std::string path = dirname + "/" + de->d_name;
+
+    // We can't delete cache_temp_source; if it's there we might have restarted during
+    // installation and could be depending on it to be there.
+    if (path == Paths::Get().cache_temp_source()) {
       continue;
     }
 
-    // Look for regular files in the directory (not in any subdirectories).
-    struct dirent* de;
-    while ((de = readdir(d.get())) != 0) {
-      std::string path = std::string(dirs[i]) + "/" + de->d_name;
+    // Do not delete the file if it doesn't have the expected format.
+    if (name_filter != nullptr && !name_filter(de->d_name)) {
+      continue;
+    }
 
-      // We can't delete cache_temp_source; if it's there we might have restarted during
-      // installation and could be depending on it to be there.
-      if (path == CacheLocation::location().cache_temp_source()) {
-        continue;
-      }
-
-      struct stat st;
-      if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
-        files.insert(path);
-      }
+    struct stat st;
+    if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
+      files.insert(path);
     }
   }
 
-  printf("%zu regular files in deletable directories\n", files.size());
-  if (EliminateOpenFiles(&files) < 0) {
-    return std::set<std::string>();
+  LOG(INFO) << files.size() << " regular files in deletable directory";
+  if (EliminateOpenFiles(dirname, &files) < 0) {
+    return {};
   }
-  return files;
+
+  return std::vector<std::string>(files.begin(), files.end());
 }
 
-int MakeFreeSpaceOnCache(size_t bytes_needed) {
-#ifndef __ANDROID__
-  // TODO (xunchang) implement a heuristic cache size check during host simulation.
-  printf("Skip making (%zu) bytes free space on cache; program is running on host\n", bytes_needed);
-  return 0;
-#endif
-
-  size_t free_now = FreeSpaceForFile("/cache");
-  printf("%zu bytes free on /cache (%zu needed)\n", free_now, bytes_needed);
-
-  if (free_now >= bytes_needed) {
+// Parses the index of given log file, e.g. 3 for last_log.3; returns max number if the log name
+// doesn't have the expected format so that we'll delete these ones first.
+static unsigned int GetLogIndex(const std::string& log_name) {
+  if (log_name == "last_log" || log_name == "last_kmsg") {
     return 0;
   }
-  std::set<std::string> files = FindExpendableFiles();
-  if (files.empty()) {
-    // nothing we can delete to free up space!
-    printf("no files can be deleted to free space on /cache\n");
+
+  unsigned int index;
+  if (sscanf(log_name.c_str(), "last_log.%u", &index) == 1 ||
+      sscanf(log_name.c_str(), "last_kmsg.%u", &index) == 1) {
+    return index;
+  }
+
+  return std::numeric_limits<unsigned int>::max();
+}
+
+// Returns the amount of free space (in bytes) on the filesystem containing filename, or -1 on
+// error.
+static int64_t FreeSpaceForFile(const std::string& filename) {
+  struct statfs sf;
+  if (statfs(filename.c_str(), &sf) == -1) {
+    PLOG(ERROR) << "Failed to statfs " << filename;
     return -1;
   }
 
-  // We could try to be smarter about which files to delete:  the
-  // biggest ones?  the smallest ones that will free up enough space?
-  // the oldest?  the newest?
-  //
-  // Instead, we'll be dumb.
+  auto f_bsize = static_cast<int64_t>(sf.f_bsize);
+  auto free_space = sf.f_bsize * sf.f_bavail;
+  if (f_bsize == 0 || free_space / f_bsize != static_cast<int64_t>(sf.f_bavail)) {
+    LOG(ERROR) << "Invalid block size or overflow (sf.f_bsize " << sf.f_bsize << ", sf.f_bavail "
+               << sf.f_bavail << ")";
+    return -1;
+  }
+  return free_space;
+}
 
-  for (const auto& file : files) {
-    unlink(file.c_str());
-    free_now = FreeSpaceForFile("/cache");
-    printf("deleted %s; now %zu bytes free\n", file.c_str(), free_now);
-    if (free_now < bytes_needed) {
-        break;
+bool CheckAndFreeSpaceOnCache(size_t bytes) {
+#ifndef __ANDROID__
+  // TODO(xunchang): Implement a heuristic cache size check during host simulation.
+  LOG(WARNING) << "Skipped making (" << bytes
+               << ") bytes free space on /cache; program is running on host";
+  return true;
+#endif
+
+  std::vector<std::string> dirs{ "/cache", Paths::Get().cache_log_directory() };
+  for (const auto& dirname : dirs) {
+    if (RemoveFilesInDirectory(bytes, dirname, FreeSpaceForFile)) {
+      return true;
     }
   }
-  return (free_now >= bytes_needed) ? 0 : -1;
+
+  return false;
+}
+
+bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
+                            const std::function<int64_t(const std::string&)>& space_checker) {
+  // The requested size cannot exceed max int64_t.
+  if (static_cast<uint64_t>(bytes_needed) >
+      static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
+    LOG(ERROR) << "Invalid arg of bytes_needed: " << bytes_needed;
+    return false;
+  }
+
+  struct stat st;
+  if (stat(dirname.c_str(), &st) == -1) {
+    PLOG(ERROR) << "Failed to stat " << dirname;
+    return false;
+  }
+  if (!S_ISDIR(st.st_mode)) {
+    LOG(ERROR) << dirname << " is not a directory";
+    return false;
+  }
+
+  int64_t free_now = space_checker(dirname);
+  if (free_now == -1) {
+    LOG(ERROR) << "Failed to check free space for " << dirname;
+    return false;
+  }
+  LOG(INFO) << free_now << " bytes free on " << dirname << " (" << bytes_needed << " needed)";
+
+  if (free_now >= static_cast<int64_t>(bytes_needed)) {
+    return true;
+  }
+
+  std::vector<std::string> files;
+  if (dirname == Paths::Get().cache_log_directory()) {
+    // Deletes the log files only.
+    auto log_filter = [](const std::string& file_name) {
+      return android::base::StartsWith(file_name, "last_log") ||
+             android::base::StartsWith(file_name, "last_kmsg");
+    };
+
+    files = FindExpendableFiles(dirname, log_filter);
+
+    // Older logs will come to the top of the queue.
+    auto comparator = [](const std::string& name1, const std::string& name2) -> bool {
+      unsigned int index1 = GetLogIndex(android::base::Basename(name1));
+      unsigned int index2 = GetLogIndex(android::base::Basename(name2));
+      if (index1 == index2) {
+        return name1 < name2;
+      }
+
+      return index1 > index2;
+    };
+
+    std::sort(files.begin(), files.end(), comparator);
+  } else {
+    // We're allowed to delete unopened regular files in the directory.
+    files = FindExpendableFiles(dirname, nullptr);
+  }
+
+  for (const auto& file : files) {
+    if (unlink(file.c_str()) == -1) {
+      PLOG(ERROR) << "Failed to delete " << file;
+      continue;
+    }
+
+    free_now = space_checker(dirname);
+    if (free_now == -1) {
+      LOG(ERROR) << "Failed to check free space for " << dirname;
+      return false;
+    }
+    LOG(INFO) << "Deleted " << file << "; now " << free_now << " bytes free";
+    if (free_now >= static_cast<int64_t>(bytes_needed)) {
+      return true;
+    }
+  }
+
+  return false;
 }
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index 674cc2b..cb34d46 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -462,12 +462,12 @@
       target_len_(tgt.GetRawDataLength()),
       target_uncompressed_len_(tgt.DataLengthForPatch()),
       target_compress_level_(tgt.GetCompressLevel()),
-      data_(tgt.DataForPatch(), tgt.DataForPatch() + tgt.DataLengthForPatch()) {}
+      data_(tgt.GetRawData(), tgt.GetRawData() + tgt.GetRawDataLength()) {}
 
 // Return true if raw data is smaller than the patch size.
 bool PatchChunk::RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size) {
   size_t target_len = tgt.GetRawDataLength();
-  return (tgt.GetType() == CHUNK_NORMAL && (target_len <= 160 || target_len < patch_size));
+  return target_len < patch_size || (tgt.GetType() == CHUNK_NORMAL && target_len <= 160);
 }
 
 void PatchChunk::UpdateSourceOffset(const SortedRangeSet& src_range) {
@@ -675,7 +675,7 @@
 // Iterate the zip entries and compose the image chunks accordingly.
 bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandle handle) {
   void* cookie;
-  int ret = StartIteration(handle, &cookie, nullptr, nullptr);
+  int ret = StartIteration(handle, &cookie);
   if (ret != 0) {
     LOG(ERROR) << "Failed to iterate over entries in " << filename << ": " << ErrorCodeString(ret);
     return false;
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index 3682d61..f4c33e5 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -38,6 +38,7 @@
 #include <zlib.h>
 
 #include "edify/expr.h"
+#include "otautil/print_sha1.h"
 
 static inline int64_t Read8(const void *address) {
   return android::base::get_unaligned<int64_t>(address);
@@ -51,8 +52,9 @@
 // patched data and stream the deflated data to output.
 static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_len,
                                             const Value& patch, size_t patch_offset,
-                                            const char* deflate_header, SinkFn sink, SHA_CTX* ctx) {
+                                            const char* deflate_header, SinkFn sink) {
   size_t expected_target_length = static_cast<size_t>(Read8(deflate_header + 32));
+  CHECK_GT(expected_target_length, static_cast<size_t>(0));
   int level = Read4(deflate_header + 40);
   int method = Read4(deflate_header + 44);
   int window_bits = Read4(deflate_header + 48);
@@ -77,7 +79,7 @@
   size_t total_written = 0;
   static constexpr size_t buffer_size = 32768;
   auto compression_sink = [&strm, &actual_target_length, &expected_target_length, &total_written,
-                           &ret, &ctx, &sink](const uint8_t* data, size_t len) -> size_t {
+                           &ret, &sink](const uint8_t* data, size_t len) -> size_t {
     // The input patch length for an update never exceeds INT_MAX.
     strm.avail_in = len;
     strm.next_in = data;
@@ -102,15 +104,13 @@
         LOG(ERROR) << "Failed to write " << have << " compressed bytes to output.";
         return 0;
       }
-      if (ctx) SHA1_Update(ctx, buffer.data(), have);
     } while ((strm.avail_in != 0 || strm.avail_out == 0) && ret != Z_STREAM_END);
 
     actual_target_length += len;
     return len;
   };
 
-  int bspatch_result =
-      ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr);
+  int bspatch_result = ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink);
   deflateEnd(&strm);
 
   if (bspatch_result != 0) {
@@ -127,19 +127,20 @@
                << actual_target_length;
     return false;
   }
-  LOG(DEBUG) << "bspatch writes " << total_written << " bytes in total to streaming output.";
+  LOG(DEBUG) << "bspatch wrote " << total_written << " bytes in total to streaming output.";
 
   return true;
 }
 
 int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data,
                     size_t patch_size, SinkFn sink) {
-  Value patch(VAL_BLOB, std::string(reinterpret_cast<const char*>(patch_data), patch_size));
-  return ApplyImagePatch(old_data, old_size, patch, sink, nullptr, nullptr);
+  Value patch(Value::Type::BLOB,
+              std::string(reinterpret_cast<const char*>(patch_data), patch_size));
+  return ApplyImagePatch(old_data, old_size, patch, sink, nullptr);
 }
 
 int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
-                    SHA_CTX* ctx, const Value* bonus_data) {
+                    const Value* bonus_data) {
   if (patch.data.size() < 12) {
     printf("patch too short to contain header\n");
     return -1;
@@ -180,10 +181,12 @@
         printf("source data too short\n");
         return -1;
       }
-      if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx) != 0) {
+      if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink) != 0) {
         printf("Failed to apply bsdiff patch.\n");
         return -1;
       }
+
+      LOG(DEBUG) << "Processed chunk type normal";
     } else if (type == CHUNK_RAW) {
       const char* raw_header = patch_header + pos;
       pos += 4;
@@ -198,14 +201,13 @@
         printf("failed to read chunk %d raw data\n", i);
         return -1;
       }
-      if (ctx) {
-        SHA1_Update(ctx, patch_header + pos, data_len);
-      }
       if (sink(reinterpret_cast<const unsigned char*>(patch_header + pos), data_len) != data_len) {
         printf("failed to write chunk %d raw data\n", i);
         return -1;
       }
       pos += data_len;
+
+      LOG(DEBUG) << "Processed chunk type raw";
     } else if (type == CHUNK_DEFLATE) {
       // deflate chunks have an additional 60 bytes in their chunk header.
       const char* deflate_header = patch_header + pos;
@@ -228,11 +230,10 @@
       // Decompress the source data; the chunk header tells us exactly
       // how big we expect it to be when decompressed.
 
-      // Note: expanded_len will include the bonus data size if
-      // the patch was constructed with bonus data.  The
-      // deflation will come up 'bonus_size' bytes short; these
-      // must be appended from the bonus_data value.
-      size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->data.size() : 0;
+      // Note: expanded_len will include the bonus data size if the patch was constructed with
+      // bonus data. The deflation will come up 'bonus_size' bytes short; these must be appended
+      // from the bonus_data value.
+      size_t bonus_size = (i == 1 && bonus_data != nullptr) ? bonus_data->data.size() : 0;
 
       std::vector<unsigned char> expanded_source(expanded_len);
 
@@ -270,17 +271,18 @@
         inflateEnd(&strm);
 
         if (bonus_size) {
-          memcpy(expanded_source.data() + (expanded_len - bonus_size), &bonus_data->data[0],
+          memcpy(expanded_source.data() + (expanded_len - bonus_size), bonus_data->data.data(),
                  bonus_size);
         }
       }
 
       if (!ApplyBSDiffPatchAndStreamOutput(expanded_source.data(), expanded_len, patch,
-                                           patch_offset, deflate_header, sink, ctx)) {
+                                           patch_offset, deflate_header, sink)) {
         LOG(ERROR) << "Fail to apply streaming bspatch.";
         return -1;
       }
 
+      LOG(DEBUG) << "Processed chunk type deflate";
     } else {
       printf("patch chunk %d is unknown type %d\n", i, type);
       return -1;
diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h
index 912ead1..6fc6f0f 100644
--- a/applypatch/include/applypatch/applypatch.h
+++ b/applypatch/include/applypatch/applypatch.h
@@ -21,6 +21,7 @@
 
 #include <functional>
 #include <memory>
+#include <ostream>
 #include <string>
 #include <vector>
 
@@ -39,45 +40,91 @@
 // applypatch.cpp
 
 int ShowLicenses();
-size_t FreeSpaceForFile(const char* filename);
-int CacheSizeCheck(size_t bytes);
-int ParseSha1(const char* str, uint8_t* digest);
 
-int applypatch(const char* source_filename,
-               const char* target_filename,
-               const char* target_sha1_str,
-               size_t target_size,
-               const std::vector<std::string>& patch_sha1_str,
-               const std::vector<std::unique_ptr<Value>>& patch_data,
-               const Value* bonus_data);
-int applypatch_check(const char* filename,
-                     const std::vector<std::string>& patch_sha1_str);
-int applypatch_flash(const char* source_filename, const char* target_filename,
-                     const char* target_sha1_str, size_t target_size);
+// Parses a given string of 40 hex digits into 20-byte array 'digest'. 'str' may contain only the
+// digest or be of the form "<digest>:<anything>". Returns 0 on success, or -1 on any error.
+int ParseSha1(const std::string& str, uint8_t* digest);
 
-int LoadFileContents(const char* filename, FileContents* file);
-int SaveFileContents(const char* filename, const FileContents* file);
+struct Partition {
+  Partition() = default;
+
+  Partition(const std::string& name, size_t size, const std::string& hash)
+      : name(name), size(size), hash(hash) {}
+
+  // Parses and returns the given string into a Partition object. The input string is of the form
+  // "EMMC:<device>:<size>:<hash>". Returns the parsed Partition, or an empty object on error.
+  static Partition Parse(const std::string& partition, std::string* err);
+
+  std::string ToString() const;
+
+  // Returns whether the current Partition object is valid.
+  explicit operator bool() const {
+    return !name.empty();
+  }
+
+  std::string name;
+  size_t size;
+  std::string hash;
+};
+
+std::ostream& operator<<(std::ostream& os, const Partition& partition);
+
+// Applies the given 'patch' to the 'source' Partition, verifies then writes the patching result to
+// the 'target' Partition. While patching, it will backup the data on the source partition to
+// /cache, so that the patching could be resumed on interruption even if both of the source and
+// target partitions refer to the same device. The function is idempotent if called multiple times.
+// An optional arg 'bonus' can be provided, if the patch was generated with a bonus output.
+// Returns the patching result.
+bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
+                    const Value* bonus);
+
+// Returns whether the contents of the eMMC target or the cached file match the embedded hash.
+// It will look for the backup on /cache if the given partition doesn't match the checksum.
+bool PatchPartitionCheck(const Partition& target, const Partition& source);
+
+// Checks whether the contents of the given partition has the desired hash. It will NOT look for
+// the backup on /cache if the given partition doesn't have the expected checksum.
+bool CheckPartition(const Partition& target);
+
+// Flashes a given image in 'source_filename' to the eMMC target partition. It verifies the target
+// checksum first, and will return if target already has the desired hash. Otherwise it checks the
+// checksum of the given source image, flashes, and verifies the target partition afterwards. The
+// function is idempotent. Returns the flashing result.
+bool FlashPartition(const Partition& target, const std::string& source_filename);
+
+// Reads a file into memory; stores the file contents and associated metadata in *file.
+bool LoadFileContents(const std::string& filename, FileContents* file);
+
+// Saves the given FileContents object to the given filename.
+bool SaveFileContents(const std::string& filename, const FileContents* file);
 
 // bspatch.cpp
 
 void ShowBSDiffLicense();
 
 // Applies the bsdiff-patch given in 'patch' (from offset 'patch_offset' to the end) to the source
-// data given by (old_data, old_size). Writes the patched output through the given 'sink', and
-// updates the SHA-1 context with the output data. Returns 0 on success.
+// data given by (old_data, old_size). Writes the patched output through the given 'sink'. Returns
+// 0 on success.
 int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch,
-                     size_t patch_offset, SinkFn sink, SHA_CTX* ctx);
+                     size_t patch_offset, SinkFn sink);
 
 // imgpatch.cpp
 
 // Applies the imgdiff-patch given in 'patch' to the source data given by (old_data, old_size), with
-// the optional bonus data. Writes the patched output through the given 'sink', and updates the
-// SHA-1 context with the output data. Returns 0 on success.
+// the optional bonus data. Writes the patched output through the given 'sink'. Returns 0 on
+// success.
 int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
-                    SHA_CTX* ctx, const Value* bonus_data);
+                    const Value* bonus_data);
 
 // freecache.cpp
 
-int MakeFreeSpaceOnCache(size_t bytes_needed);
+// Checks whether /cache partition has at least 'bytes'-byte free space. Returns true immediately
+// if so. Otherwise, it will try to free some space by removing older logs, checks again and
+// returns the checking result.
+bool CheckAndFreeSpaceOnCache(size_t bytes);
 
+// Removes the files in |dirname| until we have at least |bytes_needed| bytes of free space on the
+// partition. |space_checker| should return the size of the free space, or -1 on error.
+bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
+                            const std::function<int64_t(const std::string&)>& space_checker);
 #endif
diff --git a/applypatch/include/applypatch/imgdiff_image.h b/applypatch/include/applypatch/imgdiff_image.h
index 0848072..6716051 100644
--- a/applypatch/include/applypatch/imgdiff_image.h
+++ b/applypatch/include/applypatch/imgdiff_image.h
@@ -44,6 +44,8 @@
   int GetType() const {
     return type_;
   }
+
+  const uint8_t* GetRawData() const;
   size_t GetRawDataLength() const {
     return raw_data_len_;
   }
@@ -99,7 +101,6 @@
                         bsdiff::SuffixArrayIndexInterface** bsdiff_cache);
 
  private:
-  const uint8_t* GetRawData() const;
   bool TryReconstruction(int level);
 
   int type_;                                    // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
diff --git a/boot_control/Android.bp b/boot_control/Android.bp
new file mode 100644
index 0000000..7720ead
--- /dev/null
+++ b/boot_control/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_library_shared {
+    name: "bootctrl.default",
+    recovery_available: true,
+    relative_install_path: "hw",
+
+    srcs: ["boot_control.cpp"],
+
+    cflags: [
+        "-D_FILE_OFFSET_BITS=64",
+        "-Werror",
+        "-Wall",
+        "-Wextra",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libbootloader_message",
+        "libfs_mgr",
+        "liblog",
+    ],
+}
diff --git a/boot_control/Android.mk b/boot_control/Android.mk
deleted file mode 100644
index 9814d71..0000000
--- a/boot_control/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := bootctrl.bcb
-LOCAL_MODULE_RELATIVE_PATH := hw
-LOCAL_SRC_FILES := boot_control.cpp
-LOCAL_CFLAGS := \
-  -D_FILE_OFFSET_BITS=64 \
-  -Werror \
-  -Wall \
-  -Wextra
-LOCAL_SHARED_LIBRARIES := liblog
-LOCAL_STATIC_LIBRARIES := libbootloader_message libfs_mgr libbase
-LOCAL_POST_INSTALL_CMD := \
-  $(hide) mkdir -p $(TARGET_OUT_SHARED_LIBRARIES)/hw && \
-  ln -sf bootctrl.bcb.so $(TARGET_OUT_SHARED_LIBRARIES)/hw/bootctrl.default.so
-include $(BUILD_SHARED_LIBRARY)
diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp
index c81c67b..e76305f 100644
--- a/bootloader_message/Android.bp
+++ b/bootloader_message/Android.bp
@@ -14,16 +14,36 @@
 // limitations under the License.
 //
 
-cc_library_static {
+cc_library {
     name: "libbootloader_message",
+    recovery_available: true,
+    host_supported: true,
     srcs: ["bootloader_message.cpp"],
     cflags: [
         "-Wall",
         "-Werror",
     ],
-    static_libs: [
+    shared_libs: [
         "libbase",
-        "libfs_mgr",
     ],
     export_include_dirs: ["include"],
+
+    target: {
+        android: {
+            shared_libs: [
+                "libfs_mgr",
+            ],
+        },
+        host: {
+            shared_libs: [
+                "libcutils", // for strlcpy
+            ],
+            static_libs: [
+                "libfstab",
+            ],
+        },
+        darwin: {
+            enabled: false,
+        },
+    }
 }
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index aaeffdc..331a42b 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -27,21 +27,29 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
-#include <fs_mgr.h>
+#include <fstab/fstab.h>
+
+#ifndef __ANDROID__
+#include <cutils/memory.h>  // for strlcpy
+#endif
+
+using android::fs_mgr::Fstab;
+using android::fs_mgr::ReadDefaultFstab;
 
 static std::string get_misc_blk_device(std::string* err) {
-  std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
-                                                             fs_mgr_free_fstab);
-  if (!fstab) {
+  Fstab fstab;
+  if (!ReadDefaultFstab(&fstab)) {
     *err = "failed to read default fstab";
     return "";
   }
-  fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc");
-  if (record == nullptr) {
-    *err = "failed to find /misc partition";
-    return "";
+  for (const auto& entry : fstab) {
+    if (entry.mount_point == "/misc") {
+      return entry.blk_device;
+    }
   }
-  return record->blk_device;
+
+  *err = "failed to find /misc partition";
+  return "";
 }
 
 // In recovery mode, recovery can get started and try to access the misc
@@ -164,6 +172,14 @@
   return write_bootloader_message(boot, err);
 }
 
+bool write_bootloader_message_to(const std::vector<std::string>& options,
+                                 const std::string& misc_blk_device, std::string* err) {
+  bootloader_message boot = {};
+  update_bootloader_message_in_struct(&boot, options);
+
+  return write_bootloader_message_to(boot, misc_blk_device, err);
+}
+
 bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) {
   bootloader_message boot;
   if (!read_bootloader_message(&boot, err)) {
@@ -182,13 +198,15 @@
   memset(boot->recovery, 0, sizeof(boot->recovery));
 
   strlcpy(boot->command, "boot-recovery", sizeof(boot->command));
-  strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery));
+
+  std::string recovery = "recovery\n";
   for (const auto& s : options) {
-    strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery));
+    recovery += s;
     if (s.back() != '\n') {
-      strlcat(boot->recovery, "\n", sizeof(boot->recovery));
+      recovery += '\n';
     }
   }
+  strlcpy(boot->recovery, recovery.c_str(), sizeof(boot->recovery));
   return true;
 }
 
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index 95c19ae..2207d4c 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -207,6 +207,11 @@
 // set the command and recovery fields, and reset the rest.
 bool write_bootloader_message(const std::vector<std::string>& options, std::string* err);
 
+// Write bootloader message (boots into recovery with the options) to the specific BCB device. Will
+// set the command and recovery fields, and reset the rest.
+bool write_bootloader_message_to(const std::vector<std::string>& options,
+                                 const std::string& misc_blk_device, std::string* err);
+
 // Update bootloader message (boots into recovery with the options) to BCB. Will
 // only update the command and recovery fields.
 bool update_bootloader_message(const std::vector<std::string>& options, std::string* err);
diff --git a/common.h b/common.h
index 8b336f8..a524a41 100644
--- a/common.h
+++ b/common.h
@@ -14,11 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef RECOVERY_COMMON_H
-#define RECOVERY_COMMON_H
-
-#include <stdio.h>
-#include <stdarg.h>
+#pragma once
 
 #include <string>
 
@@ -27,9 +23,11 @@
 static constexpr int kRecoveryApiVersion = 3;
 
 class RecoveryUI;
+struct selabel_handle;
 
+extern struct selabel_handle* sehandle;
 extern RecoveryUI* ui;
-extern bool modified_flash;
+extern bool has_cache;
 
 // The current stage, e.g. "1/2".
 extern std::string stage;
@@ -37,13 +35,4 @@
 // The reason argument provided in "--reason=".
 extern const char* reason;
 
-// fopen a file, mounting volumes and making parent dirs as necessary.
-FILE* fopen_path(const char *path, const char *mode);
-
-void ui_print(const char* format, ...);
-
 bool is_ro_debuggable();
-
-bool reboot(const std::string& command);
-
-#endif  // RECOVERY_COMMON_H
diff --git a/device.cpp b/device.cpp
deleted file mode 100644
index f881daf..0000000
--- a/device.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "device.h"
-
-static const char* MENU_ITEMS[] = {
-  "Reboot system now",
-  "Reboot to bootloader",
-  "Apply update from ADB",
-  "Apply update from SD card",
-  "Wipe data/factory reset",
-#ifndef AB_OTA_UPDATER
-  "Wipe cache partition",
-#endif  // !AB_OTA_UPDATER
-  "Mount /system",
-  "View recovery logs",
-  "Run graphics test",
-  "Run locale test",
-  "Power off",
-  nullptr,
-};
-
-static const Device::BuiltinAction MENU_ACTIONS[] = {
-  Device::REBOOT,
-  Device::REBOOT_BOOTLOADER,
-  Device::APPLY_ADB_SIDELOAD,
-  Device::APPLY_SDCARD,
-  Device::WIPE_DATA,
-#ifndef AB_OTA_UPDATER
-  Device::WIPE_CACHE,
-#endif  // !AB_OTA_UPDATER
-  Device::MOUNT_SYSTEM,
-  Device::VIEW_RECOVERY_LOGS,
-  Device::RUN_GRAPHICS_TEST,
-  Device::RUN_LOCALE_TEST,
-  Device::SHUTDOWN,
-};
-
-static_assert(sizeof(MENU_ITEMS) / sizeof(MENU_ITEMS[0]) ==
-              sizeof(MENU_ACTIONS) / sizeof(MENU_ACTIONS[0]) + 1,
-              "MENU_ITEMS and MENU_ACTIONS should have the same length, "
-              "except for the extra NULL entry in MENU_ITEMS.");
-
-const char* const* Device::GetMenuItems() {
-  return MENU_ITEMS;
-}
-
-Device::BuiltinAction Device::InvokeMenuItem(int menu_position) {
-  return menu_position < 0 ? NO_ACTION : MENU_ACTIONS[menu_position];
-}
-
-int Device::HandleMenuKey(int key, bool visible) {
-  if (!visible) {
-    return kNoAction;
-  }
-
-  switch (key) {
-    case KEY_DOWN:
-    case KEY_VOLUMEDOWN:
-      return kHighlightDown;
-
-    case KEY_UP:
-    case KEY_VOLUMEUP:
-      return kHighlightUp;
-
-    case KEY_ENTER:
-    case KEY_POWER:
-      return kInvokeItem;
-
-    default:
-      // If you have all of the above buttons, any other buttons
-      // are ignored. Otherwise, any button cycles the highlight.
-      return ui_->HasThreeButtons() ? kNoAction : kHighlightDown;
-  }
-}
diff --git a/device.h b/device.h
deleted file mode 100644
index 74745b3..0000000
--- a/device.h
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2011 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 _RECOVERY_DEVICE_H
-#define _RECOVERY_DEVICE_H
-
-#include "ui.h"
-
-class Device {
- public:
-  explicit Device(RecoveryUI* ui) : ui_(ui) {}
-  virtual ~Device() {}
-
-  // Called to obtain the UI object that should be used to display the recovery user interface for
-  // this device. You should not have called Init() on the UI object already, the caller will do
-  // that after this method returns.
-  virtual RecoveryUI* GetUI() {
-    return ui_;
-  }
-
-  // Called when recovery starts up (after the UI has been obtained and initialized and after the
-  // arguments have been parsed, but before anything else).
-  virtual void StartRecovery() {};
-
-  // Called from the main thread when recovery is at the main menu and waiting for input, and a key
-  // is pressed. (Note that "at" the main menu does not necessarily mean the menu is visible;
-  // recovery will be at the main menu with it invisible after an unsuccessful operation [ie OTA
-  // package failure], or if recovery is started with no command.)
-  //
-  // 'key' is the code of the key just pressed. (You can call IsKeyPressed() on the RecoveryUI
-  // object you returned from GetUI if you want to find out if other keys are held down.)
-  //
-  // 'visible' is true if the menu is visible.
-  //
-  // Returns one of the defined constants below in order to:
-  //
-  //   - move the menu highlight (kHighlight{Up,Down})
-  //   - invoke the highlighted item (kInvokeItem)
-  //   - do nothing (kNoAction)
-  //   - invoke a specific action (a menu position: any non-negative number)
-  virtual int HandleMenuKey(int key, bool visible);
-
-  enum BuiltinAction {
-    NO_ACTION = 0,
-    REBOOT = 1,
-    APPLY_SDCARD = 2,
-    // APPLY_CACHE was 3.
-    APPLY_ADB_SIDELOAD = 4,
-    WIPE_DATA = 5,
-    WIPE_CACHE = 6,
-    REBOOT_BOOTLOADER = 7,
-    SHUTDOWN = 8,
-    VIEW_RECOVERY_LOGS = 9,
-    MOUNT_SYSTEM = 10,
-    RUN_GRAPHICS_TEST = 11,
-    RUN_LOCALE_TEST = 12,
-  };
-
-  // Return the list of menu items (an array of strings, NULL-terminated). The menu_position passed
-  // to InvokeMenuItem will correspond to the indexes into this array.
-  virtual const char* const* GetMenuItems();
-
-  // Perform a recovery action selected from the menu. 'menu_position' will be the item number of
-  // the selected menu item, or a non-negative number returned from HandleMenuKey(). The menu will
-  // be hidden when this is called; implementations can call ui_print() to print information to the
-  // screen. If the menu position is one of the builtin actions, you can just return the
-  // corresponding enum value. If it is an action specific to your device, you actually perform it
-  // here and return NO_ACTION.
-  virtual BuiltinAction InvokeMenuItem(int menu_position);
-
-  static const int kNoAction = -1;
-  static const int kHighlightUp = -2;
-  static const int kHighlightDown = -3;
-  static const int kInvokeItem = -4;
-
-  // Called before and after we do a wipe data/factory reset operation, either via a reboot from the
-  // main system with the --wipe_data flag, or when the user boots into recovery image manually and
-  // selects the option from the menu, to perform whatever device-specific wiping actions as needed.
-  // Returns true on success; returning false from PreWipeData will prevent the regular wipe, and
-  // returning false from PostWipeData will cause the wipe to be considered a failure.
-  virtual bool PreWipeData() {
-    return true;
-  }
-
-  virtual bool PostWipeData() {
-    return true;
-  }
-
- private:
-  RecoveryUI* ui_;
-};
-
-// The device-specific library must define this function (or the default one will be used, if there
-// is no device-specific library). It returns the Device object that recovery should use.
-Device* make_device();
-
-#endif  // _DEVICE_H
diff --git a/edify/expr.cpp b/edify/expr.cpp
index 6823b73..c090eb2 100644
--- a/edify/expr.cpp
+++ b/edify/expr.cpp
@@ -51,9 +51,9 @@
     if (!v) {
         return false;
     }
-    if (v->type != VAL_STRING) {
-        ErrorAbort(state, kArgsParsingFailure, "expecting string, got value type %d", v->type);
-        return false;
+    if (v->type != Value::Type::STRING) {
+      ErrorAbort(state, kArgsParsingFailure, "expecting string, got value type %d", v->type);
+      return false;
     }
 
     *result = v->data;
@@ -68,7 +68,7 @@
     if (str == nullptr) {
         return nullptr;
     }
-    return new Value(VAL_STRING, str);
+    return new Value(Value::Type::STRING, str);
 }
 
 Value* StringValue(const std::string& str) {
diff --git a/edify/include/edify/expr.h b/edify/include/edify/expr.h
index 770d1cf..5cbd5e1 100644
--- a/edify/include/edify/expr.h
+++ b/edify/include/edify/expr.h
@@ -53,19 +53,16 @@
   bool is_retry = false;
 };
 
-enum ValueType {
-    VAL_INVALID = -1,
-    VAL_STRING = 1,
-    VAL_BLOB = 2,
-};
-
 struct Value {
-    ValueType type;
-    std::string data;
+  enum class Type {
+    STRING = 1,
+    BLOB = 2,
+  };
 
-    Value(ValueType type, const std::string& str) :
-        type(type),
-        data(str) {}
+  Value(Type type, const std::string& str) : type(type), data(str) {}
+
+  Type type;
+  std::string data;
 };
 
 struct Expr;
@@ -156,6 +153,6 @@
 
 Value* StringValue(const std::string& str);
 
-int parse_string(const char* str, std::unique_ptr<Expr>* root, int* error_count);
+int ParseString(const std::string& str, std::unique_ptr<Expr>* root, int* error_count);
 
 #endif  // _EXPRESSION_H
diff --git a/edify/parser.yy b/edify/parser.yy
index bd2e010..3a63c37 100644
--- a/edify/parser.yy
+++ b/edify/parser.yy
@@ -138,7 +138,7 @@
   ++*error_count;
 }
 
-int parse_string(const char* str, std::unique_ptr<Expr>* root, int* error_count) {
-    yy_switch_to_buffer(yy_scan_string(str));
-    return yyparse(root, error_count);
+int ParseString(const std::string& str, std::unique_ptr<Expr>* root, int* error_count) {
+  yy_switch_to_buffer(yy_scan_string(str.c_str()));
+  return yyparse(root, error_count);
 }
diff --git a/etc/init.rc b/etc/init.rc
index 0fc6c4c..0822aba 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -6,11 +6,17 @@
 
     start ueventd
 
+    setprop sys.usb.configfs 0
+
 on init
     export ANDROID_ROOT /system
     export ANDROID_DATA /data
     export EXTERNAL_STORAGE /sdcard
 
+    symlink /proc/self/fd/0 /dev/stdin
+    symlink /proc/self/fd/1 /dev/stdout
+    symlink /proc/self/fd/2 /dev/stderr
+
     symlink /system/bin /bin
     symlink /system/etc /etc
 
@@ -22,6 +28,7 @@
     mkdir /data
     mkdir /cache
     mkdir /sideload
+    mkdir /mnt/system
     mount tmpfs tmpfs /tmp
 
     chown root shell /tmp
@@ -30,20 +37,6 @@
     write /proc/sys/kernel/panic_on_oops 1
     write /proc/sys/vm/max_map_count 1000000
 
-on fs
-    write /sys/class/android_usb/android0/f_ffs/aliases adb
-    mkdir /dev/usb-ffs 0770 shell shell
-    mkdir /dev/usb-ffs/adb 0770 shell shell
-    mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000
-
-    write /sys/class/android_usb/android0/enable 0
-    write /sys/class/android_usb/android0/idVendor 18D1
-    write /sys/class/android_usb/android0/idProduct D001
-    write /sys/class/android_usb/android0/functions adb
-    write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer}
-    write /sys/class/android_usb/android0/iProduct ${ro.product.model}
-    write /sys/class/android_usb/android0/iSerial ${ro.serialno}
-
 on boot
     ifup lo
     hostname localhost
@@ -76,29 +69,119 @@
     trigger early-boot
     trigger boot
 
-service ueventd /sbin/ueventd
+service ueventd /system/bin/ueventd
     critical
     seclabel u:r:ueventd:s0
 
-service charger /charger -r
+service charger /system/bin/charger
     critical
     seclabel u:r:charger:s0
 
-service recovery /sbin/recovery
+service recovery /system/bin/recovery
+    socket recovery stream 422 system system
     seclabel u:r:recovery:s0
 
-service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery
+service adbd /system/bin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery
     disabled
     socket adbd stream 660 system system
     seclabel u:r:adbd:s0
 
-# Always start adbd on userdebug and eng builds
-on property:ro.debuggable=1
-    write /sys/class/android_usb/android0/enable 1
-    start adbd
+service fastbootd /system/bin/fastbootd
+    disabled
+    group system
+    seclabel u:r:fastbootd:s0
 
 # Restart adbd so it can run as root
 on property:service.adb.root=1
-    write /sys/class/android_usb/android0/enable 0
     restart adbd
+
+# Always start adbd on userdebug and eng builds
+on fs && property:ro.debuggable=1
+    setprop sys.usb.config adb
+
+on fs && property:sys.usb.configfs=1
+    mount configfs none /config
+    mkdir /config/usb_gadget/g1 0770 shell shell
+    write /config/usb_gadget/g1/idVendor 0x18D1
+    mkdir /config/usb_gadget/g1/strings/0x409 0770
+    write /config/usb_gadget/g1/strings/0x409/serialnumber ${ro.serialno}
+    write /config/usb_gadget/g1/strings/0x409/manufacturer ${ro.product.manufacturer}
+    write /config/usb_gadget/g1/strings/0x409/product ${ro.product.model}
+    mkdir /config/usb_gadget/g1/functions/ffs.adb
+    mkdir /config/usb_gadget/g1/functions/ffs.fastboot
+    mkdir /config/usb_gadget/g1/configs/b.1 0777 shell shell
+    mkdir /config/usb_gadget/g1/configs/b.1/strings/0x409 0770 shell shell
+
+on fs && property:sys.usb.configfs=0
+    write /sys/class/android_usb/android0/f_ffs/aliases adb,fastboot
+    write /sys/class/android_usb/android0/idVendor 18D1
+    write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer}
+    write /sys/class/android_usb/android0/iProduct ${ro.product.model}
+    write /sys/class/android_usb/android0/iSerial ${ro.serialno}
+
+on fs
+    mkdir /dev/usb-ffs 0775 shell shell
+    mkdir /dev/usb-ffs/adb 0770 shell shell
+    mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000
+    mkdir /dev/usb-ffs/fastboot 0770 system system
+    mount functionfs fastboot /dev/usb-ffs/fastboot rmode=0770,fmode=0660,uid=1000,gid=1000
+
+on property:sys.usb.config=adb
+    start adbd
+
+on property:sys.usb.config=fastboot
+    start fastbootd
+
+on property:sys.usb.config=none && property:sys.usb.configfs=0
+    stop adbd
+    stop fastboot
+    write /sys/class/android_usb/android0/enable 0
+    setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=adb && property:sys.usb.configfs=0
+    write /sys/class/android_usb/android0/idProduct D001
+    write /sys/class/android_usb/android0/functions adb
     write /sys/class/android_usb/android0/enable 1
+    setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=sideload && property:sys.usb.configfs=0
+    write /sys/class/android_usb/android0/idProduct D001
+    write /sys/class/android_usb/android0/functions adb
+    write /sys/class/android_usb/android0/enable 1
+    setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=fastboot && property:sys.usb.configfs=0
+    write /sys/class/android_usb/android0/idProduct 4EE0
+    write /sys/class/android_usb/android0/functions fastboot
+    write /sys/class/android_usb/android0/enable 1
+    setprop sys.usb.state ${sys.usb.config}
+
+# Configfs triggers
+on property:sys.usb.config=none && property:sys.usb.configfs=1
+    write /config/usb_gadget/g1/UDC "none"
+    stop adbd
+    stop fastbootd
+    setprop sys.usb.ffs.ready 0
+    rm /config/usb_gadget/g1/configs/b.1/f1
+    setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=sideload && property:sys.usb.ffs.ready=1 && property:sys.usb.configfs=1
+    write /config/usb_gadget/g1/idProduct 0xD001
+    write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "adb"
+    symlink /config/usb_gadget/g1/functions/ffs.adb /config/usb_gadget/g1/configs/b.1/f1
+    write /config/usb_gadget/g1/UDC ${sys.usb.controller}
+    setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=adb && property:sys.usb.ffs.ready=1 && property:sys.usb.configfs=1
+    write /config/usb_gadget/g1/idProduct 0xD001
+    write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "adb"
+    symlink /config/usb_gadget/g1/functions/ffs.adb /config/usb_gadget/g1/configs/b.1/f1
+    write /config/usb_gadget/g1/UDC ${sys.usb.controller}
+    setprop sys.usb.state ${sys.usb.config}
+
+on property:sys.usb.config=fastboot && property:sys.usb.ffs.ready=1 && property:sys.usb.configfs=1
+    write /config/usb_gadget/g1/idProduct 0x4EE0
+    write /config/usb_gadget/g1/configs/b.1/strings/0x409/configuration "fastboot"
+    symlink /config/usb_gadget/g1/functions/ffs.fastboot /config/usb_gadget/g1/configs/b.1/f1
+    write /config/usb_gadget/g1/UDC ${sys.usb.controller}
+    setprop sys.usb.state ${sys.usb.config}
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
new file mode 100644
index 0000000..14f5e4b
--- /dev/null
+++ b/fastboot/fastboot.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fastboot.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <bootloader_message/bootloader_message.h>
+
+#include "recovery_ui/ui.h"
+
+static const std::vector<std::pair<std::string, Device::BuiltinAction>> kFastbootMenuActions{
+  { "Reboot system now", Device::REBOOT },
+  { "Enter recovery", Device::ENTER_RECOVERY },
+  { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
+  { "Power off", Device::SHUTDOWN },
+};
+
+Device::BuiltinAction StartFastboot(Device* device, const std::vector<std::string>& /* args */) {
+  RecoveryUI* ui = device->GetUI();
+
+  std::vector<std::string> title_lines = { "Android Fastboot" };
+  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", ""));
+  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", ""));
+
+  ui->ResetKeyInterruptStatus();
+  ui->SetTitle(title_lines);
+  ui->ShowText(true);
+
+  // Reset to normal system boot so recovery won't cycle indefinitely.
+  // TODO(b/112277594) Clear only if 'recovery' field of BCB is empty. If not,
+  // set the 'command' field of BCB to 'boot-recovery' so the next boot is into recovery
+  // to finish any interrupted tasks.
+  std::string err;
+  if (!clear_bootloader_message(&err)) {
+    LOG(ERROR) << "Failed to clear BCB message: " << err;
+  }
+
+  std::vector<std::string> fastboot_menu_items;
+  std::transform(kFastbootMenuActions.cbegin(), kFastbootMenuActions.cend(),
+                 std::back_inserter(fastboot_menu_items),
+                 [](const auto& entry) { return entry.first; });
+
+  auto chosen_item = ui->ShowMenu(
+      {}, fastboot_menu_items, 0, false,
+      std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+  if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
+    return Device::KEY_INTERRUPTED;
+  }
+  if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::TIMED_OUT)) {
+    return Device::BuiltinAction::NO_ACTION;
+  }
+  return kFastbootMenuActions[chosen_item].second;
+}
diff --git a/default_device.cpp b/fastboot/fastboot.h
similarity index 70%
copy from default_device.cpp
copy to fastboot/fastboot.h
index a971866..1aa7de6 100644
--- a/default_device.cpp
+++ b/fastboot/fastboot.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-#include "device.h"
-#include "screen_ui.h"
+#pragma once
 
-Device* make_device() {
-  return new Device(new ScreenRecoveryUI);
-}
+#include <string>
+#include <vector>
+
+#include "recovery_ui/device.h"
+
+Device::BuiltinAction StartFastboot(Device* device, const std::vector<std::string>& args);
diff --git a/fsck_unshare_blocks.cpp b/fsck_unshare_blocks.cpp
new file mode 100644
index 0000000..9dc0fd8
--- /dev/null
+++ b/fsck_unshare_blocks.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fsck_unshare_blocks.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <spawn.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <fs_mgr/roots.h>
+
+#include "otautil/roots.h"
+
+static constexpr const char* SYSTEM_E2FSCK_BIN = "/system/bin/e2fsck_static";
+static constexpr const char* TMP_E2FSCK_BIN = "/tmp/e2fsck.bin";
+
+static bool copy_file(const char* source, const char* dest) {
+  android::base::unique_fd source_fd(open(source, O_RDONLY));
+  if (source_fd < 0) {
+    PLOG(ERROR) << "open %s failed" << source;
+    return false;
+  }
+
+  android::base::unique_fd dest_fd(open(dest, O_CREAT | O_WRONLY, S_IRWXU));
+  if (dest_fd < 0) {
+    PLOG(ERROR) << "open %s failed" << dest;
+    return false;
+  }
+
+  for (;;) {
+    char buf[4096];
+    ssize_t rv = read(source_fd, buf, sizeof(buf));
+    if (rv < 0) {
+      PLOG(ERROR) << "read failed";
+      return false;
+    }
+    if (rv == 0) {
+      break;
+    }
+    if (write(dest_fd, buf, rv) != rv) {
+      PLOG(ERROR) << "write failed";
+      return false;
+    }
+  }
+  return true;
+}
+
+static bool run_e2fsck(const std::string& partition) {
+  Volume* volume = volume_for_mount_point(partition);
+  if (!volume) {
+    LOG(INFO) << "No fstab entry for " << partition << ", skipping.";
+    return true;
+  }
+
+  LOG(INFO) << "Running e2fsck on device " << volume->blk_device;
+
+  std::vector<std::string> args = { TMP_E2FSCK_BIN, "-p", "-E", "unshare_blocks",
+                                    volume->blk_device };
+  std::vector<char*> argv(args.size());
+  std::transform(args.cbegin(), args.cend(), argv.begin(),
+                 [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
+  argv.push_back(nullptr);
+
+  pid_t child;
+  char* env[] = { nullptr };
+  if (posix_spawn(&child, argv[0], nullptr, nullptr, argv.data(), env)) {
+    PLOG(ERROR) << "posix_spawn failed";
+    return false;
+  }
+
+  int status = 0;
+  int ret = TEMP_FAILURE_RETRY(waitpid(child, &status, 0));
+  if (ret < 0) {
+    PLOG(ERROR) << "waitpid failed";
+    return false;
+  }
+  if (!WIFEXITED(status)) {
+    LOG(ERROR) << "e2fsck exited abnormally: " << status;
+    return false;
+  }
+  int return_code = WEXITSTATUS(status);
+  if (return_code >= 8) {
+    LOG(ERROR) << "e2fsck could not unshare blocks: " << return_code;
+    return false;
+  }
+
+  LOG(INFO) << "Successfully unshared blocks on " << partition;
+  return true;
+}
+
+bool do_fsck_unshare_blocks() {
+  // List of partitions we will try to e2fsck -E unshare_blocks.
+  std::vector<std::string> partitions = { "/odm", "/oem", "/product", "/vendor" };
+
+  // Temporarily mount system so we can copy e2fsck_static.
+  auto system_root = android::fs_mgr::GetSystemRoot();
+  bool mounted = ensure_path_mounted_at(system_root, "/mnt/system") != -1;
+  partitions.push_back(system_root);
+
+  if (!mounted) {
+    LOG(ERROR) << "Failed to mount system image.";
+    return false;
+  }
+  if (!copy_file(SYSTEM_E2FSCK_BIN, TMP_E2FSCK_BIN)) {
+    LOG(ERROR) << "Could not copy e2fsck to /tmp.";
+    return false;
+  }
+  if (umount("/mnt/system") < 0) {
+    PLOG(ERROR) << "umount failed";
+    return false;
+  }
+
+  bool ok = true;
+  for (const auto& partition : partitions) {
+    ok &= run_e2fsck(partition);
+  }
+
+  if (ok) {
+    LOG(INFO) << "Finished running e2fsck.";
+  } else {
+    LOG(ERROR) << "Finished running e2fsck, but not all partitions succceeded.";
+  }
+  return ok;
+}
diff --git a/default_device.cpp b/fsck_unshare_blocks.h
similarity index 76%
copy from default_device.cpp
copy to fsck_unshare_blocks.h
index a971866..9de8ef9 100644
--- a/default_device.cpp
+++ b/fsck_unshare_blocks.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include "device.h"
-#include "screen_ui.h"
+#ifndef _FILESYSTEM_CMDS_H
+#define _FILESYSTEM_CMDS_H
 
-Device* make_device() {
-  return new Device(new ScreenRecoveryUI);
-}
+bool do_fsck_unshare_blocks();
+
+#endif  // _FILESYSTEM_CMDS_H
diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp
deleted file mode 100644
index 46bdf17..0000000
--- a/fuse_sdcard_provider.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2014 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 "fuse_sdcard_provider.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <functional>
-
-#include <android-base/file.h>
-
-#include "fuse_sideload.h"
-
-struct file_data {
-  int fd;  // the underlying sdcard file
-
-  uint64_t file_size;
-  uint32_t block_size;
-};
-
-static int read_block_file(const file_data& fd, uint32_t block, uint8_t* buffer,
-                           uint32_t fetch_size) {
-  off64_t offset = static_cast<off64_t>(block) * fd.block_size;
-  if (TEMP_FAILURE_RETRY(lseek64(fd.fd, offset, SEEK_SET)) == -1) {
-    fprintf(stderr, "seek on sdcard failed: %s\n", strerror(errno));
-    return -EIO;
-  }
-
-  if (!android::base::ReadFully(fd.fd, buffer, fetch_size)) {
-    fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno));
-    return -EIO;
-  }
-
-  return 0;
-}
-
-bool start_sdcard_fuse(const char* path) {
-  struct stat sb;
-  if (stat(path, &sb) == -1) {
-    fprintf(stderr, "failed to stat %s: %s\n", path, strerror(errno));
-    return false;
-  }
-
-  file_data fd;
-  fd.fd = open(path, O_RDONLY);
-  if (fd.fd == -1) {
-    fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno));
-    return false;
-  }
-  fd.file_size = sb.st_size;
-  fd.block_size = 65536;
-
-  provider_vtab vtab;
-  vtab.read_block = std::bind(&read_block_file, fd, std::placeholders::_1, std::placeholders::_2,
-                              std::placeholders::_3);
-  vtab.close = [&fd]() { close(fd.fd); };
-
-  // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so
-  // that our open file continues to work but new references see it as unmounted.
-  umount2("/sdcard", MNT_DETACH);
-
-  return run_fuse_sideload(vtab, fd.file_size, fd.block_size) == 0;
-}
diff --git a/fuse_sdcard_provider.h b/fuse_sdcard_provider.h
deleted file mode 100644
index bdc60f2..0000000
--- a/fuse_sdcard_provider.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2014 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 __FUSE_SDCARD_PROVIDER_H
-#define __FUSE_SDCARD_PROVIDER_H
-
-bool start_sdcard_fuse(const char* path);
-
-#endif
diff --git a/fuse_sideload/Android.bp b/fuse_sideload/Android.bp
new file mode 100644
index 0000000..9bf19eb
--- /dev/null
+++ b/fuse_sideload/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+    name: "libfusesideload",
+    recovery_available: true,
+
+    defaults: [
+        "recovery_defaults",
+    ],
+
+    cflags: [
+        "-D_XOPEN_SOURCE",
+        "-D_GNU_SOURCE",
+    ],
+
+    srcs: [
+        "fuse_provider.cpp",
+        "fuse_sideload.cpp",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    static_libs: [
+        "libotautil",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libcrypto",
+    ],
+}
diff --git a/fuse_sideload/fuse_provider.cpp b/fuse_sideload/fuse_provider.cpp
new file mode 100644
index 0000000..5ee6e24
--- /dev/null
+++ b/fuse_sideload/fuse_provider.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fuse_provider.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <functional>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "fuse_sideload.h"
+#include "otautil/sysutil.h"
+
+FuseFileDataProvider::FuseFileDataProvider(const std::string& path, uint32_t block_size) {
+  struct stat sb;
+  if (stat(path.c_str(), &sb) == -1) {
+    fprintf(stderr, "failed to stat %s: %s\n", path.c_str(), strerror(errno));
+    return;
+  }
+
+  fd_.reset(open(path.c_str(), O_RDONLY));
+  if (fd_ == -1) {
+    fprintf(stderr, "failed to open %s: %s\n", path.c_str(), strerror(errno));
+    return;
+  }
+  file_size_ = sb.st_size;
+  fuse_block_size_ = block_size;
+}
+
+bool FuseFileDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                                                uint32_t start_block) const {
+  uint64_t offset = static_cast<uint64_t>(start_block) * fuse_block_size_;
+  if (fetch_size > file_size_ || offset > file_size_ - fetch_size) {
+    fprintf(stderr,
+            "Out of bound read, start block: %" PRIu32 ", fetch size: %" PRIu32
+            ", file size %" PRIu64 "\n",
+            start_block, fetch_size, file_size_);
+    return false;
+  }
+
+  if (!android::base::ReadFullyAtOffset(fd_, buffer, fetch_size, offset)) {
+    fprintf(stderr, "Failed to read fetch size: %" PRIu32 " bytes data at offset %" PRIu64 ": %s\n",
+            fetch_size, offset, strerror(errno));
+    return false;
+  }
+
+  return true;
+}
+
+void FuseFileDataProvider::Close() {
+  fd_.reset();
+}
+
+FuseBlockDataProvider::FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size,
+                                             android::base::unique_fd&& fd,
+                                             uint32_t source_block_size, RangeSet ranges)
+    : FuseDataProvider(file_size, fuse_block_size),
+      fd_(std::move(fd)),
+      source_block_size_(source_block_size),
+      ranges_(std::move(ranges)) {
+  // Make sure the offset is also aligned with the blocks on the block device when we call
+  // ReadBlockAlignedData().
+  CHECK_EQ(0, fuse_block_size_ % source_block_size_);
+}
+
+bool FuseBlockDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                                                 uint32_t start_block) const {
+  uint64_t offset = static_cast<uint64_t>(start_block) * fuse_block_size_;
+  if (fetch_size > file_size_ || offset > file_size_ - fetch_size) {
+    LOG(ERROR) << "Out of bound read, offset: " << offset << ", fetch size: " << fetch_size
+               << ", file size " << file_size_;
+    return false;
+  }
+
+  auto read_ranges =
+      ranges_.GetSubRanges(offset / source_block_size_, fetch_size / source_block_size_);
+  if (!read_ranges) {
+    return false;
+  }
+
+  uint8_t* next_out = buffer;
+  for (const auto& [range_start, range_end] : read_ranges.value()) {
+    uint64_t bytes_start = static_cast<uint64_t>(range_start) * source_block_size_;
+    uint64_t bytes_to_read = static_cast<uint64_t>(range_end - range_start) * source_block_size_;
+    if (!android::base::ReadFullyAtOffset(fd_, next_out, bytes_to_read, bytes_start)) {
+      PLOG(ERROR) << "Failed to read " << bytes_to_read << " bytes at offset " << bytes_start;
+      return false;
+    }
+
+    next_out += bytes_to_read;
+  }
+
+  if (uint64_t tailing_bytes = fetch_size % source_block_size_; tailing_bytes != 0) {
+    // Calculate the offset to last partial block.
+    uint64_t tailing_offset =
+        read_ranges.value()
+            ? static_cast<uint64_t>((read_ranges->cend() - 1)->second) * source_block_size_
+            : static_cast<uint64_t>(start_block) * source_block_size_;
+    if (!android::base::ReadFullyAtOffset(fd_, next_out, tailing_bytes, tailing_offset)) {
+      PLOG(ERROR) << "Failed to read tailing " << tailing_bytes << " bytes at offset "
+                  << tailing_offset;
+      return false;
+    }
+  }
+  return true;
+}
+
+std::unique_ptr<FuseBlockDataProvider> FuseBlockDataProvider::CreateFromBlockMap(
+    const std::string& block_map_path, uint32_t fuse_block_size) {
+  auto block_map = BlockMapData::ParseBlockMapFile(block_map_path);
+  if (!block_map) {
+    return nullptr;
+  }
+
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_map.path().c_str(), O_RDONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open " << block_map.path();
+    return nullptr;
+  }
+
+  return std::unique_ptr<FuseBlockDataProvider>(
+      new FuseBlockDataProvider(block_map.file_size(), fuse_block_size, std::move(fd),
+                                block_map.block_size(), block_map.block_ranges()));
+}
+
+void FuseBlockDataProvider::Close() {
+  fd_.reset();
+}
diff --git a/fuse_sideload.cpp b/fuse_sideload/fuse_sideload.cpp
similarity index 96%
rename from fuse_sideload.cpp
rename to fuse_sideload/fuse_sideload.cpp
index 1c7e98f..3d94803 100644
--- a/fuse_sideload.cpp
+++ b/fuse_sideload/fuse_sideload.cpp
@@ -76,7 +76,7 @@
 struct fuse_data {
   android::base::unique_fd ffd;  // file descriptor for the fuse socket
 
-  provider_vtab vtab;
+  FuseDataProvider* provider;  // Provider of the source data.
 
   uint64_t file_size;  // bytes
 
@@ -236,7 +236,7 @@
     return 0;
   }
 
-  size_t fetch_size = fd->block_size;
+  uint32_t fetch_size = fd->block_size;
   if (block * fd->block_size + fetch_size > fd->file_size) {
     // If we're reading the last (partial) block of the file, expect a shorter response from the
     // host, and pad the rest of the block with zeroes.
@@ -244,8 +244,9 @@
     memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size);
   }
 
-  int result = fd->vtab.read_block(block, fd->block_data, fetch_size);
-  if (result < 0) return result;
+  if (!fd->provider->ReadBlockAlignedData(fd->block_data, fetch_size, block)) {
+    return -EIO;
+  }
 
   fd->curr_block = block;
 
@@ -340,12 +341,14 @@
   return NO_STATUS;
 }
 
-int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size,
-                      const char* mount_point) {
+int run_fuse_sideload(std::unique_ptr<FuseDataProvider>&& provider, const char* mount_point) {
   // If something's already mounted on our mountpoint, try to remove it. (Mostly in case of a
   // previous abnormal exit.)
   umount2(mount_point, MNT_FORCE);
 
+  uint64_t file_size = provider->file_size();
+  uint32_t block_size = provider->fuse_block_size();
+
   // fs/fuse/inode.c in kernel code uses the greater of 4096 and the passed-in max_read.
   if (block_size < 4096) {
     fprintf(stderr, "block size (%u) is too small\n", block_size);
@@ -357,7 +360,7 @@
   }
 
   fuse_data fd = {};
-  fd.vtab = vtab;
+  fd.provider = provider.get();
   fd.file_size = file_size;
   fd.block_size = block_size;
   fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1);
@@ -389,7 +392,7 @@
   }
 
   fd.ffd.reset(open("/dev/fuse", O_RDWR));
-  if (!fd.ffd) {
+  if (fd.ffd == -1) {
     perror("open /dev/fuse");
     result = -1;
     goto done;
@@ -479,7 +482,7 @@
   }
 
 done:
-  fd.vtab.close();
+  provider->Close();
 
   if (umount2(mount_point, MNT_DETACH) == -1) {
     fprintf(stderr, "fuse_sideload umount failed: %s\n", strerror(errno));
diff --git a/fuse_sideload/include/fuse_provider.h b/fuse_sideload/include/fuse_provider.h
new file mode 100644
index 0000000..8d4ea40
--- /dev/null
+++ b/fuse_sideload/include/fuse_provider.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include <android-base/unique_fd.h>
+
+#include "otautil/rangeset.h"
+
+// This is the base class to read data from source and provide the data to FUSE.
+class FuseDataProvider {
+ public:
+  FuseDataProvider(uint64_t file_size, uint32_t block_size)
+      : file_size_(file_size), fuse_block_size_(block_size) {}
+
+  virtual ~FuseDataProvider() = default;
+
+  uint64_t file_size() const {
+    return file_size_;
+  }
+  uint32_t fuse_block_size() const {
+    return fuse_block_size_;
+  }
+
+  // Reads |fetch_size| bytes data starting from |start_block|. Puts the result in |buffer|.
+  virtual bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                                    uint32_t start_block) const = 0;
+
+  virtual void Close() {}
+
+ protected:
+  FuseDataProvider() = default;
+
+  // Size in bytes of the file to read.
+  uint64_t file_size_ = 0;
+  // Block size passed to the fuse, this is different from the block size of the block device.
+  uint32_t fuse_block_size_ = 0;
+};
+
+// This class reads data from a file.
+class FuseFileDataProvider : public FuseDataProvider {
+ public:
+  FuseFileDataProvider(const std::string& path, uint32_t block_size);
+
+  bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                            uint32_t start_block) const override;
+
+  bool Valid() const {
+    return fd_ != -1;
+  }
+
+  void Close() override;
+
+ private:
+  // The underlying source to read data from.
+  android::base::unique_fd fd_;
+};
+
+// This class parses a block map and reads data from the underlying block device.
+class FuseBlockDataProvider : public FuseDataProvider {
+ public:
+  // Constructs the fuse provider from the block map.
+  static std::unique_ptr<FuseBlockDataProvider> CreateFromBlockMap(
+      const std::string& block_map_path, uint32_t fuse_block_size);
+
+  RangeSet ranges() const {
+    return ranges_;
+  }
+  bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                            uint32_t start_block) const override;
+  void Close() override;
+
+ private:
+  FuseBlockDataProvider(uint64_t file_size, uint32_t fuse_block_size, android::base::unique_fd&& fd,
+                        uint32_t source_block_size, RangeSet ranges);
+  // The underlying block device to read data from.
+  android::base::unique_fd fd_;
+  // The block size of the source block device.
+  uint32_t source_block_size_;
+  // The block ranges from the source block device that consist of the file
+  RangeSet ranges_;
+};
diff --git a/fuse_sideload.h b/fuse_sideload/include/fuse_sideload.h
similarity index 80%
rename from fuse_sideload.h
rename to fuse_sideload/include/fuse_sideload.h
index 1b34cbd..1b7759a 100644
--- a/fuse_sideload.h
+++ b/fuse_sideload/include/fuse_sideload.h
@@ -17,7 +17,9 @@
 #ifndef __FUSE_SIDELOAD_H
 #define __FUSE_SIDELOAD_H
 
-#include <functional>
+#include <memory>
+
+#include "fuse_provider.h"
 
 // Define the filenames created by the sideload FUSE filesystem.
 static constexpr const char* FUSE_SIDELOAD_HOST_MOUNTPOINT = "/sideload";
@@ -26,15 +28,7 @@
 static constexpr const char* FUSE_SIDELOAD_HOST_EXIT_FLAG = "exit";
 static constexpr const char* FUSE_SIDELOAD_HOST_EXIT_PATHNAME = "/sideload/exit";
 
-struct provider_vtab {
-  // read a block
-  std::function<int(uint32_t block, uint8_t* buffer, uint32_t fetch_size)> read_block;
-
-  // close down
-  std::function<void(void)> close;
-};
-
-int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size,
+int run_fuse_sideload(std::unique_ptr<FuseDataProvider>&& provider,
                       const char* mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT);
 
 #endif
diff --git a/install.h b/install.h
deleted file mode 100644
index f3fda30..0000000
--- a/install.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef RECOVERY_INSTALL_H_
-#define RECOVERY_INSTALL_H_
-
-#include <string>
-#include <ziparchive/zip_archive.h>
-
-enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED,
-        INSTALL_RETRY };
-
-// Installs the given update package. If INSTALL_SUCCESS is returned and *wipe_cache is true on
-// exit, caller should wipe the cache partition.
-int install_package(const std::string& package, bool* wipe_cache, const std::string& install_file,
-                    bool needs_mount, int retry_count);
-
-// Verify the package by ota keys. Return true if the package is verified successfully,
-// otherwise return false.
-bool verify_package(const unsigned char* package_data, size_t package_size);
-
-// Read meta data file of the package, write its content in the string pointed by meta_data.
-// Return true if succeed, otherwise return false.
-bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata);
-
-// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
-// entry doesn't exist.
-bool verify_package_compatibility(ZipArchiveHandle package_zip);
-
-#endif  // RECOVERY_INSTALL_H_
diff --git a/install/Android.bp b/install/Android.bp
new file mode 100644
index 0000000..4696e50
--- /dev/null
+++ b/install/Android.bp
@@ -0,0 +1,83 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+    name: "libinstall_defaults",
+
+    defaults: [
+        "recovery_defaults",
+    ],
+
+    header_libs: [
+        "libminadbd_headers",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libbootloader_message",
+        "libcrypto",
+        "libext4_utils",
+        "libfs_mgr",
+        "libfusesideload",
+        "libhidl-gen-utils",
+        "libhidlbase",
+        "libhidltransport",
+        "liblog",
+        "libselinux",
+        "libtinyxml2",
+        "libutils",
+        "libz",
+        "libziparchive",
+    ],
+
+    static_libs: [
+        "libotautil",
+
+        // external dependencies
+        "libvintf_recovery",
+        "libvintf",
+    ],
+}
+
+cc_library_static {
+    name: "libinstall",
+    recovery_available: true,
+
+    defaults: [
+        "libinstall_defaults",
+    ],
+
+    srcs: [
+        "adb_install.cpp",
+        "asn1_decoder.cpp",
+        "fuse_sdcard_install.cpp",
+        "install.cpp",
+        "package.cpp",
+        "verifier.cpp",
+        "wipe_data.cpp",
+        "wipe_device.cpp",
+    ],
+
+    shared_libs: [
+        "librecovery_ui",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    export_shared_lib_headers: [
+        "librecovery_ui",
+    ],
+}
diff --git a/install/adb_install.cpp b/install/adb_install.cpp
new file mode 100644
index 0000000..2de1075
--- /dev/null
+++ b/install/adb_install.cpp
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "install/adb_install.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <functional>
+#include <map>
+#include <utility>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/memory.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+#include "fuse_sideload.h"
+#include "install/install.h"
+#include "install/wipe_data.h"
+#include "minadbd_types.h"
+#include "otautil/sysutil.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+// A CommandFunction returns a pair of (result, should_continue), which indicates the command
+// execution result and whether it should proceed to the next iteration. The execution result will
+// always be sent to the minadbd side.
+using CommandFunction = std::function<std::pair<bool, bool>()>;
+
+static bool SetUsbConfig(const std::string& state) {
+  android::base::SetProperty("sys.usb.config", state);
+  return android::base::WaitForProperty("sys.usb.state", state);
+}
+
+// Parses the minadbd command in |message|; returns MinadbdCommand::kError upon errors.
+static MinadbdCommand ParseMinadbdCommand(const std::string& message) {
+  if (!android::base::StartsWith(message, kMinadbdCommandPrefix)) {
+    LOG(ERROR) << "Failed to parse command in message " << message;
+    return MinadbdCommand::kError;
+  }
+
+  auto cmd_code_string = message.substr(strlen(kMinadbdCommandPrefix));
+  auto cmd_code = android::base::get_unaligned<uint32_t>(cmd_code_string.c_str());
+  if (cmd_code >= static_cast<uint32_t>(MinadbdCommand::kError)) {
+    LOG(ERROR) << "Unsupported command code: " << cmd_code;
+    return MinadbdCommand::kError;
+  }
+
+  return static_cast<MinadbdCommand>(cmd_code);
+}
+
+static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) {
+  char message[kMinadbdMessageSize];
+  memcpy(message, kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix));
+  android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), status);
+
+  if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) {
+    PLOG(ERROR) << "Failed to write message " << message;
+    return false;
+  }
+  return true;
+}
+
+// Installs the package from FUSE. Returns the installation result and whether it should continue
+// waiting for new commands.
+static auto AdbInstallPackageHandler(RecoveryUI* ui, InstallResult* result) {
+  // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long
+  // because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME
+  // will start to exist once the host connects and starts serving a package. Poll for its
+  // appearance. (Note that inotify doesn't work with FUSE.)
+  constexpr int ADB_INSTALL_TIMEOUT = 15;
+  bool should_continue = true;
+  *result = INSTALL_ERROR;
+  for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {
+    struct stat st;
+    if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {
+      if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) {
+        sleep(1);
+        continue;
+      } else {
+        should_continue = false;
+        ui->Print("\nTimed out waiting for fuse to be ready.\n\n");
+        break;
+      }
+    }
+    *result = InstallPackage(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0, ui);
+    break;
+  }
+
+  // Calling stat() on this magic filename signals the FUSE to exit.
+  struct stat st;
+  stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
+  return std::make_pair(*result == INSTALL_SUCCESS, should_continue);
+}
+
+static auto AdbRebootHandler(MinadbdCommand command, InstallResult* result,
+                             Device::BuiltinAction* reboot_action) {
+  // Use Device::REBOOT_{FASTBOOT,RECOVERY,RESCUE}, instead of the ones with ENTER_. This allows
+  // rebooting back into fastboot/recovery/rescue mode through bootloader, which may use a newly
+  // installed bootloader/recovery image.
+  switch (command) {
+    case MinadbdCommand::kRebootBootloader:
+      *reboot_action = Device::REBOOT_BOOTLOADER;
+      break;
+    case MinadbdCommand::kRebootFastboot:
+      *reboot_action = Device::REBOOT_FASTBOOT;
+      break;
+    case MinadbdCommand::kRebootRecovery:
+      *reboot_action = Device::REBOOT_RECOVERY;
+      break;
+    case MinadbdCommand::kRebootRescue:
+      *reboot_action = Device::REBOOT_RESCUE;
+      break;
+    case MinadbdCommand::kRebootAndroid:
+    default:
+      *reboot_action = Device::REBOOT;
+      break;
+  }
+  *result = INSTALL_REBOOT;
+  return std::make_pair(true, false);
+}
+
+// Parses and executes the command from minadbd. Returns whether the caller should keep waiting for
+// next command.
+static bool HandleMessageFromMinadbd(int socket_fd,
+                                     const std::map<MinadbdCommand, CommandFunction>& command_map) {
+  char buffer[kMinadbdMessageSize];
+  if (!android::base::ReadFully(socket_fd, buffer, kMinadbdMessageSize)) {
+    PLOG(ERROR) << "Failed to read message from minadbd";
+    return false;
+  }
+
+  std::string message(buffer, buffer + kMinadbdMessageSize);
+  auto command_type = ParseMinadbdCommand(message);
+  if (command_type == MinadbdCommand::kError) {
+    return false;
+  }
+  if (command_map.find(command_type) == command_map.end()) {
+    LOG(ERROR) << "Unsupported command: "
+               << android::base::get_unaligned<unsigned int>(
+                      message.substr(strlen(kMinadbdCommandPrefix)).c_str());
+    return false;
+  }
+
+  // We have received a valid command, execute the corresponding function.
+  const auto& command_func = command_map.at(command_type);
+  const auto [result, should_continue] = command_func();
+  LOG(INFO) << "Command " << static_cast<uint32_t>(command_type) << " finished with " << result;
+  if (!WriteStatusToFd(result ? MinadbdCommandStatus::kSuccess : MinadbdCommandStatus::kFailure,
+                       socket_fd)) {
+    return false;
+  }
+  return should_continue;
+}
+
+// TODO(xunchang) add a wrapper function and kill the minadbd service there.
+static void ListenAndExecuteMinadbdCommands(
+    RecoveryUI* ui, pid_t minadbd_pid, android::base::unique_fd&& socket_fd,
+    const std::map<MinadbdCommand, CommandFunction>& command_map) {
+  android::base::unique_fd epoll_fd(epoll_create1(O_CLOEXEC));
+  if (epoll_fd == -1) {
+    PLOG(ERROR) << "Failed to create epoll";
+    kill(minadbd_pid, SIGKILL);
+    return;
+  }
+
+  constexpr int EPOLL_MAX_EVENTS = 10;
+  struct epoll_event ev = {};
+  ev.events = EPOLLIN | EPOLLHUP;
+  ev.data.fd = socket_fd.get();
+  struct epoll_event events[EPOLL_MAX_EVENTS];
+  if (epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, socket_fd.get(), &ev) == -1) {
+    PLOG(ERROR) << "Failed to add socket fd to epoll";
+    kill(minadbd_pid, SIGKILL);
+    return;
+  }
+
+  // Set the timeout to be 300s when waiting for minadbd commands.
+  constexpr int TIMEOUT_MILLIS = 300 * 1000;
+  while (true) {
+    // Reset the progress bar and the background image before each command.
+    ui->SetProgressType(RecoveryUI::EMPTY);
+    ui->SetBackground(RecoveryUI::NO_COMMAND);
+
+    // Poll for the status change of the socket_fd, and handle the message if the fd is ready to
+    // read.
+    int event_count =
+        TEMP_FAILURE_RETRY(epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, TIMEOUT_MILLIS));
+    if (event_count == -1) {
+      PLOG(ERROR) << "Failed to wait for epoll events";
+      kill(minadbd_pid, SIGKILL);
+      return;
+    }
+    if (event_count == 0) {
+      LOG(ERROR) << "Timeout waiting for messages from minadbd";
+      kill(minadbd_pid, SIGKILL);
+      return;
+    }
+
+    for (int n = 0; n < event_count; n++) {
+      if (events[n].events & EPOLLHUP) {
+        LOG(INFO) << "Socket has been closed";
+        kill(minadbd_pid, SIGKILL);
+        return;
+      }
+      if (!HandleMessageFromMinadbd(socket_fd.get(), command_map)) {
+        kill(minadbd_pid, SIGKILL);
+        return;
+      }
+    }
+  }
+}
+
+// Recovery starts minadbd service as a child process, and spawns another thread to listen for the
+// message from minadbd through a socket pair. Here is an example to execute one command from adb
+// host.
+//  a. recovery                    b. listener thread               c. minadbd service
+//
+//  a1. create socket pair
+//  a2. fork minadbd service
+//                                                                c3. wait for the adb commands
+//                                                                    from host
+//                                                                c4. after receiving host commands:
+//                                                                  1) set up pre-condition (i.e.
+//                                                                     start fuse for adb sideload)
+//                                                                  2) issue command through
+//                                                                     socket.
+//                                                                  3) wait for result
+//  a5. start listener thread
+//                               b6. listen for message from
+//                                   minadbd in a loop.
+//                               b7. After receiving a minadbd
+//                                   command from socket
+//                                 1) execute the command function
+//                                 2) send the result back to
+//                                    minadbd
+//  ......
+//                                                                 c8. exit upon receiving the
+//                                                                     result
+// a9.  wait for listener thread
+//      to exit.
+//
+// a10. wait for minadbd to
+//      exit
+//                               b11. exit the listening loop
+//
+static void CreateMinadbdServiceAndExecuteCommands(
+    RecoveryUI* ui, const std::map<MinadbdCommand, CommandFunction>& command_map,
+    bool rescue_mode) {
+  signal(SIGPIPE, SIG_IGN);
+
+  android::base::unique_fd recovery_socket;
+  android::base::unique_fd minadbd_socket;
+  if (!android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &recovery_socket, &minadbd_socket)) {
+    PLOG(ERROR) << "Failed to create socket";
+    return;
+  }
+
+  pid_t child = fork();
+  if (child == -1) {
+    PLOG(ERROR) << "Failed to fork child process";
+    return;
+  }
+  if (child == 0) {
+    recovery_socket.reset();
+    std::vector<std::string> minadbd_commands = {
+      "/system/bin/minadbd",
+      "--socket_fd",
+      std::to_string(minadbd_socket.release()),
+    };
+    if (rescue_mode) {
+      minadbd_commands.push_back("--rescue");
+    }
+    auto exec_args = StringVectorToNullTerminatedArray(minadbd_commands);
+    execv(exec_args[0], exec_args.data());
+    _exit(EXIT_FAILURE);
+  }
+
+  minadbd_socket.reset();
+
+  // We need to call SetUsbConfig() after forking minadbd service. Because the function waits for
+  // the usb state to be updated, which depends on sys.usb.ffs.ready=1 set in the adb daemon.
+  if (!SetUsbConfig("sideload")) {
+    LOG(ERROR) << "Failed to set usb config to sideload";
+    return;
+  }
+
+  std::thread listener_thread(ListenAndExecuteMinadbdCommands, ui, child,
+                              std::move(recovery_socket), std::ref(command_map));
+  if (listener_thread.joinable()) {
+    listener_thread.join();
+  }
+
+  int status;
+  waitpid(child, &status, 0);
+  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+    if (WEXITSTATUS(status) == MinadbdErrorCode::kMinadbdAdbVersionError) {
+      LOG(ERROR) << "\nYou need adb 1.0.32 or newer to sideload\nto this device.\n";
+    } else if (!WIFSIGNALED(status)) {
+      LOG(ERROR) << "\n(adbd status " << WEXITSTATUS(status) << ")";
+    }
+  }
+
+  signal(SIGPIPE, SIG_DFL);
+}
+
+InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) {
+  // Save the usb state to restore after the sideload operation.
+  std::string usb_state = android::base::GetProperty("sys.usb.state", "none");
+  // Clean up state and stop adbd.
+  if (usb_state != "none" && !SetUsbConfig("none")) {
+    LOG(ERROR) << "Failed to clear USB config";
+    return INSTALL_ERROR;
+  }
+
+  RecoveryUI* ui = device->GetUI();
+
+  InstallResult install_result = INSTALL_ERROR;
+  std::map<MinadbdCommand, CommandFunction> command_map{
+    { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) },
+    { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid,
+                                                &install_result, reboot_action) },
+    { MinadbdCommand::kRebootBootloader,
+      std::bind(&AdbRebootHandler, MinadbdCommand::kRebootBootloader, &install_result,
+                reboot_action) },
+    { MinadbdCommand::kRebootFastboot, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootFastboot,
+                                                 &install_result, reboot_action) },
+    { MinadbdCommand::kRebootRecovery, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRecovery,
+                                                 &install_result, reboot_action) },
+    { MinadbdCommand::kRebootRescue,
+      std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) },
+  };
+
+  if (!rescue_mode) {
+    ui->Print(
+        "\n\nNow send the package you want to apply\n"
+        "to the device with \"adb sideload <filename>\"...\n");
+  } else {
+    ui->Print("\n\nWaiting for rescue commands...\n");
+    command_map.emplace(MinadbdCommand::kWipeData, [&device]() {
+      bool result = WipeData(device, false);
+      return std::make_pair(result, true);
+    });
+  }
+
+  CreateMinadbdServiceAndExecuteCommands(ui, 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.
+  if (!SetUsbConfig("none")) {
+    LOG(ERROR) << "Failed to clear USB config";
+  }
+
+  if (usb_state != "none") {
+    if (!SetUsbConfig(usb_state)) {
+      LOG(ERROR) << "Failed to set USB config to " << usb_state;
+    }
+  }
+
+  return install_result;
+}
diff --git a/asn1_decoder.cpp b/install/asn1_decoder.cpp
similarity index 98%
rename from asn1_decoder.cpp
rename to install/asn1_decoder.cpp
index 285214f..2d81a6e 100644
--- a/asn1_decoder.cpp
+++ b/install/asn1_decoder.cpp
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-#include "asn1_decoder.h"
-
-#include <stdint.h>
+#include "private/asn1_decoder.h"
 
 int asn1_context::peek_byte() const {
   if (length_ == 0) {
diff --git a/install/fuse_sdcard_install.cpp b/install/fuse_sdcard_install.cpp
new file mode 100644
index 0000000..a5caa6e
--- /dev/null
+++ b/install/fuse_sdcard_install.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "install/fuse_sdcard_install.h"
+
+#include <dirent.h>
+#include <signal.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "bootloader_message/bootloader_message.h"
+#include "fuse_provider.h"
+#include "fuse_sideload.h"
+#include "install/install.h"
+#include "otautil/roots.h"
+
+static constexpr const char* SDCARD_ROOT = "/sdcard";
+// How long (in seconds) we wait for the fuse-provided package file to
+// appear, before timing out.
+static constexpr int SDCARD_INSTALL_TIMEOUT = 10;
+
+// Set the BCB to reboot back into recovery (it won't resume the install from
+// sdcard though).
+static void SetSdcardUpdateBootloaderMessage() {
+  std::vector<std::string> options;
+  std::string err;
+  if (!update_bootloader_message(options, &err)) {
+    LOG(ERROR) << "Failed to set BCB message: " << err;
+  }
+}
+
+// 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;
+    return "";
+  }
+
+  std::vector<std::string> dirs;
+  std::vector<std::string> entries{ "../" };  // "../" is always the first entry.
+
+  dirent* de;
+  while ((de = readdir(d.get())) != nullptr) {
+    std::string name(de->d_name);
+
+    if (de->d_type == DT_DIR) {
+      // Skip "." and ".." entries.
+      if (name == "." || name == "..") continue;
+      dirs.push_back(name + "/");
+    } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) {
+      entries.push_back(name);
+    }
+  }
+
+  std::sort(dirs.begin(), dirs.end());
+  std::sort(entries.begin(), entries.end());
+
+  // Append dirs to the entries list.
+  entries.insert(entries.end(), dirs.begin(), dirs.end());
+
+  std::vector<std::string> headers{ "Choose a package to install:", path };
+
+  size_t chosen_item = 0;
+  while (true) {
+    chosen_item = ui->ShowMenu(
+        headers, entries, chosen_item, true,
+        std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+    // Return if WaitKey() was interrupted.
+    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).
+      return "";
+    }
+
+    std::string new_path = path + "/" + item;
+    if (new_path.back() == '/') {
+      // Recurse down into a subdirectory.
+      new_path.pop_back();
+      std::string result = BrowseDirectory(new_path, device, ui);
+      if (!result.empty()) return result;
+    } else {
+      // Selected a zip file: return the path to the caller.
+      return new_path;
+    }
+  }
+
+  // Unreachable.
+}
+
+static bool StartSdcardFuse(const std::string& path) {
+  auto file_data_reader = std::make_unique<FuseFileDataProvider>(path, 65536);
+
+  if (!file_data_reader->Valid()) {
+    return false;
+  }
+
+  // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so
+  // that our open file continues to work but new references see it as unmounted.
+  umount2("/sdcard", MNT_DETACH);
+
+  return run_fuse_sideload(std::move(file_data_reader)) == 0;
+}
+
+InstallResult ApplyFromSdcard(Device* device, RecoveryUI* ui) {
+  if (ensure_path_mounted(SDCARD_ROOT) != 0) {
+    LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n";
+    return INSTALL_ERROR;
+  }
+
+  std::string path = BrowseDirectory(SDCARD_ROOT, device, ui);
+  if (path.empty()) {
+    LOG(ERROR) << "\n-- No package file selected.\n";
+    ensure_path_unmounted(SDCARD_ROOT);
+    return INSTALL_ERROR;
+  }
+
+  ui->Print("\n-- Install %s ...\n", path.c_str());
+  SetSdcardUpdateBootloaderMessage();
+
+  // We used to use fuse in a thread as opposed to a process. Since accessing
+  // through fuse involves going from kernel to userspace to kernel, it leads
+  // to deadlock when a page fault occurs. (Bug: 26313124)
+  pid_t child;
+  if ((child = fork()) == 0) {
+    bool status = StartSdcardFuse(path);
+
+    _exit(status ? EXIT_SUCCESS : EXIT_FAILURE);
+  }
+
+  // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child process is ready.
+  InstallResult result = INSTALL_ERROR;
+  int status;
+  bool waited = false;
+  for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) {
+    if (waitpid(child, &status, WNOHANG) == -1) {
+      result = INSTALL_ERROR;
+      waited = true;
+      break;
+    }
+
+    struct stat sb;
+    if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &sb) == -1) {
+      if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT - 1) {
+        sleep(1);
+        continue;
+      } else {
+        LOG(ERROR) << "Timed out waiting for the fuse-provided package.";
+        result = INSTALL_ERROR;
+        kill(child, SIGKILL);
+        break;
+      }
+    }
+
+    result = InstallPackage(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /* retry_count */, ui);
+    break;
+  }
+
+  if (!waited) {
+    // Calling stat() on this magic filename signals the fuse
+    // filesystem to shut down.
+    struct stat sb;
+    stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &sb);
+
+    waitpid(child, &status, 0);
+  }
+
+  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+    LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status);
+  }
+
+  ensure_path_unmounted(SDCARD_ROOT);
+  return result;
+}
diff --git a/adb_install.h b/install/include/install/adb_install.h
similarity index 61%
rename from adb_install.h
rename to install/include/install/adb_install.h
index e654c89..8800223 100644
--- a/adb_install.h
+++ b/install/include/install/adb_install.h
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef _ADB_INSTALL_H
-#define _ADB_INSTALL_H
+#pragma once
 
-int apply_from_adb(bool* wipe_cache, const char* install_file);
+#include "install/install.h"
+#include "recovery_ui/device.h"
 
-#endif
+// Applies a package via `adb sideload` or `adb rescue`. Returns the install result. When a reboot
+// has been requested, INSTALL_REBOOT will be the return value, with the reboot target set in
+// reboot_action.
+InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action);
diff --git a/default_device.cpp b/install/include/install/fuse_sdcard_install.h
similarity index 71%
copy from default_device.cpp
copy to install/include/install/fuse_sdcard_install.h
index a971866..e5bb01f 100644
--- a/default_device.cpp
+++ b/install/include/install/fuse_sdcard_install.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-#include "device.h"
-#include "screen_ui.h"
+#pragma once
 
-Device* make_device() {
-  return new Device(new ScreenRecoveryUI);
-}
+#include "install/install.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+InstallResult ApplyFromSdcard(Device* device, RecoveryUI* ui);
diff --git a/install/include/install/install.h b/install/include/install/install.h
new file mode 100644
index 0000000..44a5cde
--- /dev/null
+++ b/install/include/install/install.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+#include "package.h"
+#include "recovery_ui/ui.h"
+
+enum InstallResult {
+  INSTALL_SUCCESS,
+  INSTALL_ERROR,
+  INSTALL_CORRUPT,
+  INSTALL_NONE,
+  INSTALL_SKIPPED,
+  INSTALL_RETRY,
+  INSTALL_KEY_INTERRUPTED,
+  INSTALL_REBOOT,
+};
+
+enum class OtaType {
+  AB,
+  BLOCK,
+  BRICK,
+};
+
+// Installs the given update package. This function should also wipe the cache partition after a
+// successful installation if |should_wipe_cache| is true or an updater command asks to wipe the
+// cache.
+InstallResult InstallPackage(const std::string& package, bool should_wipe_cache, bool needs_mount,
+                             int retry_count, RecoveryUI* ui);
+
+// Verifies the package by ota keys. Returns true if the package is verified successfully,
+// otherwise returns false.
+bool verify_package(Package* package, RecoveryUI* ui);
+
+// Reads meta data file of the package; parses each line in the format "key=value"; and writes the
+// result to |metadata|. Return true if succeed, otherwise return false.
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata);
+
+// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
+// entry doesn't exist.
+bool verify_package_compatibility(ZipArchiveHandle package_zip);
+
+// Checks if the metadata in the OTA package has expected values. Mandatory checks: ota-type,
+// pre-device and serial number (if presents). A/B OTA specific checks: pre-build version,
+// fingerprint, timestamp.
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
diff --git a/install/include/install/package.h b/install/include/install/package.h
new file mode 100644
index 0000000..cd44d10
--- /dev/null
+++ b/install/include/install/package.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+#include "verifier.h"
+
+// This class serves as a wrapper for an OTA update package. It aims to provide the common
+// interface for both packages loaded in memory and packages read from fd.
+class Package : public VerifierInterface {
+ public:
+  static std::unique_ptr<Package> CreateMemoryPackage(
+      const std::string& path, const std::function<void(float)>& set_progress);
+  static std::unique_ptr<Package> CreateMemoryPackage(
+      std::vector<uint8_t> content, const std::function<void(float)>& set_progress);
+  static std::unique_ptr<Package> CreateFilePackage(const std::string& path,
+                                                    const std::function<void(float)>& set_progress);
+
+  virtual ~Package() = default;
+
+  // Opens the package as a zip file and returns the ZipArchiveHandle.
+  virtual ZipArchiveHandle GetZipArchiveHandle() = 0;
+
+  // Updates the progress in fraction during package verification.
+  void SetProgress(float progress) override;
+
+ protected:
+  // An optional function to update the progress.
+  std::function<void(float)> set_progress_;
+};
diff --git a/install/include/install/verifier.h b/install/include/install/verifier.h
new file mode 100644
index 0000000..f9e9475
--- /dev/null
+++ b/install/include/install/verifier.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <openssl/ec_key.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+
+constexpr size_t MiB = 1024 * 1024;
+
+using HasherUpdateCallback = std::function<void(const uint8_t* addr, uint64_t size)>;
+
+struct RSADeleter {
+  void operator()(RSA* rsa) const {
+    RSA_free(rsa);
+  }
+};
+
+struct ECKEYDeleter {
+  void operator()(EC_KEY* ec_key) const {
+    EC_KEY_free(ec_key);
+  }
+};
+
+struct Certificate {
+  typedef enum {
+    KEY_TYPE_RSA,
+    KEY_TYPE_EC,
+  } KeyType;
+
+  Certificate(int hash_len_, KeyType key_type_, std::unique_ptr<RSA, RSADeleter>&& rsa_,
+              std::unique_ptr<EC_KEY, ECKEYDeleter>&& ec_)
+      : hash_len(hash_len_), key_type(key_type_), rsa(std::move(rsa_)), ec(std::move(ec_)) {}
+
+  // SHA_DIGEST_LENGTH (SHA-1) or SHA256_DIGEST_LENGTH (SHA-256)
+  int hash_len;
+  KeyType key_type;
+  std::unique_ptr<RSA, RSADeleter> rsa;
+  std::unique_ptr<EC_KEY, ECKEYDeleter> ec;
+};
+
+class VerifierInterface {
+ public:
+  virtual ~VerifierInterface() = default;
+
+  // Returns the package size in bytes.
+  virtual uint64_t GetPackageSize() const = 0;
+
+  // Reads |byte_count| data starting from |offset|, and puts the result in |buffer|.
+  virtual bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) = 0;
+
+  // Updates the hash contexts for |length| bytes data starting from |start|.
+  virtual bool UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers, uint64_t start,
+                                  uint64_t length) = 0;
+
+  // Updates the progress in fraction during package verification.
+  virtual void SetProgress(float progress) = 0;
+};
+
+//  Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip.
+//  Verifies that it matches one of the given public keys. Returns VERIFY_SUCCESS or
+//  VERIFY_FAILURE (if any error is encountered or no key matches the signature).
+int verify_file(VerifierInterface* package, const std::vector<Certificate>& keys);
+
+// Checks that the RSA key has a modulus of 2048 or 4096 bits long, and public exponent is 3 or
+// 65537.
+bool CheckRSAKey(const std::unique_ptr<RSA, RSADeleter>& rsa);
+
+// Checks that the field size of the curve for the EC key is 256 bits.
+bool CheckECKey(const std::unique_ptr<EC_KEY, ECKEYDeleter>& ec_key);
+
+// Parses a PEM-encoded x509 certificate from the given buffer and saves it into |cert|. Returns
+// false if there is a parsing failure or the signature's encryption algorithm is not supported.
+bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certificate* cert);
+
+// Iterates over the zip entries with the suffix "x509.pem" and returns a list of recognized
+// certificates. Returns an empty list if we fail to parse any of the entries.
+std::vector<Certificate> LoadKeysFromZipfile(const std::string& zip_name);
+
+#define VERIFY_SUCCESS 0
+#define VERIFY_FAILURE 1
diff --git a/default_device.cpp b/install/include/install/wipe_data.h
similarity index 61%
copy from default_device.cpp
copy to install/include/install/wipe_data.h
index a971866..b34891f 100644
--- a/default_device.cpp
+++ b/install/include/install/wipe_data.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,9 +14,17 @@
  * limitations under the License.
  */
 
-#include "device.h"
-#include "screen_ui.h"
+#pragma once
 
-Device* make_device() {
-  return new Device(new ScreenRecoveryUI);
-}
+#include <functional>
+
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+struct selabel_handle;
+
+// Returns true on success.
+bool WipeCache(RecoveryUI* ui, const std::function<bool()>& confirm);
+
+// Returns true on success.
+bool WipeData(Device* device, bool convert_fbe);
diff --git a/install/include/install/wipe_device.h b/install/include/install/wipe_device.h
new file mode 100644
index 0000000..c60b999
--- /dev/null
+++ b/install/include/install/wipe_device.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "install/package.h"
+#include "recovery_ui/device.h"
+
+// Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE.
+bool WipeAbDevice(Device* device, size_t wipe_package_size);
+
+// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe.
+std::vector<std::string> GetWipePartitionList(Package* wipe_package);
diff --git a/asn1_decoder.h b/install/include/private/asn1_decoder.h
similarity index 98%
rename from asn1_decoder.h
rename to install/include/private/asn1_decoder.h
index 3e99211..e5337d9 100644
--- a/asn1_decoder.h
+++ b/install/include/private/asn1_decoder.h
@@ -17,6 +17,7 @@
 #ifndef ASN1_DECODER_H_
 #define ASN1_DECODER_H_
 
+#include <stddef.h>
 #include <stdint.h>
 
 class asn1_context {
diff --git a/install/include/private/setup_commands.h b/install/include/private/setup_commands.h
new file mode 100644
index 0000000..dcff761
--- /dev/null
+++ b/install/include/private/setup_commands.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Private headers exposed for testing purpose only.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+// Sets up the commands for a non-A/B update. Extracts the updater binary from the open zip archive
+// |zip| located at |package|. Stores the command line that should be called into |cmd|. The
+// |status_fd| is the file descriptor the child process should use to report back the progress of
+// the update.
+bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
+                              int status_fd, std::vector<std::string>* cmd);
+
+// Sets up the commands for an A/B update. Extracts the needed entries from the open zip archive
+// |zip| located at |package|. Stores the command line that should be called into |cmd|. The
+// |status_fd| is the file descriptor the child process should use to report back the progress of
+// the update. Note that since this applies to the sideloading flow only, it takes one less
+// parameter |retry_count| than the non-A/B version.
+bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+                           std::vector<std::string>* cmd);
diff --git a/install.cpp b/install/install.cpp
similarity index 60%
rename from install.cpp
rename to install/install.cpp
index d058931..1b51b42 100644
--- a/install.cpp
+++ b/install/install.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "install.h"
+#include "install/install.h"
 
 #include <ctype.h>
 #include <errno.h>
@@ -32,9 +32,7 @@
 #include <condition_variable>
 #include <functional>
 #include <limits>
-#include <map>
 #include <mutex>
-#include <string>
 #include <thread>
 #include <vector>
 
@@ -45,147 +43,117 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <vintf/VintfObjectRecovery.h>
-#include <ziparchive/zip_archive.h>
 
-#include "common.h"
-#include "otautil/SysUtil.h"
-#include "otautil/ThermalUtil.h"
+#include "install/package.h"
+#include "install/verifier.h"
+#include "install/wipe_data.h"
 #include "otautil/error_code.h"
-#include "private/install.h"
-#include "roots.h"
-#include "ui.h"
-#include "verifier.h"
+#include "otautil/paths.h"
+#include "otautil/roots.h"
+#include "otautil/sysutil.h"
+#include "otautil/thermalutil.h"
+#include "private/setup_commands.h"
+#include "recovery_ui/ui.h"
 
 using namespace std::chrono_literals;
 
+static constexpr int kRecoveryApiVersion = 3;
+// Assert the version defined in code and in Android.mk are consistent.
+static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions.");
+
 // Default allocation of progress bar segments to operations
 static constexpr int VERIFICATION_PROGRESS_TIME = 60;
 static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
 
 static std::condition_variable finish_log_temperature;
 
-// This function parses and returns the build.version.incremental
-static std::string parse_build_number(const std::string& str) {
-    size_t pos = str.find('=');
-    if (pos != std::string::npos) {
-        return android::base::Trim(str.substr(pos+1));
-    }
-
-    LOG(ERROR) << "Failed to parse build number in " << str;
-    return "";
-}
-
-bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) {
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata) {
   CHECK(metadata != nullptr);
 
   static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
-  ZipString path(METADATA_PATH);
   ZipEntry entry;
-  if (FindEntry(zip, path, &entry) != 0) {
+  if (FindEntry(zip, METADATA_PATH, &entry) != 0) {
     LOG(ERROR) << "Failed to find " << METADATA_PATH;
     return false;
   }
 
   uint32_t length = entry.uncompressed_length;
-  metadata->resize(length, '\0');
-  int32_t err = ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&(*metadata)[0]), length);
+  std::string metadata_string(length, '\0');
+  int32_t err =
+      ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&metadata_string[0]), length);
   if (err != 0) {
     LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err);
     return false;
   }
+
+  for (const std::string& line : android::base::Split(metadata_string, "\n")) {
+    size_t eq = line.find('=');
+    if (eq != std::string::npos) {
+      metadata->emplace(android::base::Trim(line.substr(0, eq)),
+                        android::base::Trim(line.substr(eq + 1)));
+    }
+  }
+
   return true;
 }
 
-// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
-static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>* log_buffer) {
-  std::string metadata;
-  if (!read_metadata_from_package(zip, &metadata)) {
-    return;
-  }
-  // Examples of the pre-build and post-build strings in metadata:
-  //   pre-build-incremental=2943039
-  //   post-build-incremental=2951741
-  std::vector<std::string> lines = android::base::Split(metadata, "\n");
-  for (const std::string& line : lines) {
-    std::string str = android::base::Trim(line);
-    if (android::base::StartsWith(str, "pre-build-incremental")) {
-      std::string source_build = parse_build_number(str);
-      if (!source_build.empty()) {
-        log_buffer->push_back("source_build: " + source_build);
-      }
-    } else if (android::base::StartsWith(str, "post-build-incremental")) {
-      std::string target_build = parse_build_number(str);
-      if (!target_build.empty()) {
-        log_buffer->push_back("target_build: " + target_build);
-      }
-    }
+// Gets the value for the given key in |metadata|. Returns an emtpy string if the key isn't
+// present.
+static std::string get_value(const std::map<std::string, std::string>& metadata,
+                             const std::string& key) {
+  const auto& it = metadata.find(key);
+  return (it == metadata.end()) ? "" : it->second;
+}
+
+static std::string OtaTypeToString(OtaType type) {
+  switch (type) {
+    case OtaType::AB:
+      return "AB";
+    case OtaType::BLOCK:
+      return "BLOCK";
+    case OtaType::BRICK:
+      return "BRICK";
   }
 }
 
-#ifdef AB_OTA_UPDATER
-
-// Parses the metadata of the OTA package in |zip| and checks whether we are
-// allowed to accept this A/B package. Downgrading is not allowed unless
-// explicitly enabled in the package and only for incremental packages.
-static int check_newer_ab_build(ZipArchiveHandle zip) {
-  std::string metadata_str;
-  if (!read_metadata_from_package(zip, &metadata_str)) {
-    return INSTALL_CORRUPT;
-  }
-  std::map<std::string, std::string> metadata;
-  for (const std::string& line : android::base::Split(metadata_str, "\n")) {
-    size_t eq = line.find('=');
-    if (eq != std::string::npos) {
-      metadata[line.substr(0, eq)] = line.substr(eq + 1);
-    }
+// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
+static void ReadSourceTargetBuild(const std::map<std::string, std::string>& metadata,
+                                  std::vector<std::string>* log_buffer) {
+  // Examples of the pre-build and post-build strings in metadata:
+  //   pre-build-incremental=2943039
+  //   post-build-incremental=2951741
+  auto source_build = get_value(metadata, "pre-build-incremental");
+  if (!source_build.empty()) {
+    log_buffer->push_back("source_build: " + source_build);
   }
 
-  std::string value = android::base::GetProperty("ro.product.device", "");
-  const std::string& pkg_device = metadata["pre-device"];
-  if (pkg_device != value || pkg_device.empty()) {
-    LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value;
-    return INSTALL_ERROR;
+  auto target_build = get_value(metadata, "post-build-incremental");
+  if (!target_build.empty()) {
+    log_buffer->push_back("target_build: " + target_build);
   }
+}
 
-  // We allow the package to not have any serialno; and we also allow it to carry multiple serial
-  // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
-  // verification if the device's serialno doesn't match any of these carried numbers.
-  value = android::base::GetProperty("ro.serialno", "");
-  const std::string& pkg_serial_no = metadata["serialno"];
-  if (!pkg_serial_no.empty()) {
-    bool match = false;
-    for (const std::string& number : android::base::Split(pkg_serial_no, "|")) {
-      if (value == android::base::Trim(number)) {
-        match = true;
-        break;
-      }
-    }
-    if (!match) {
-      LOG(ERROR) << "Package is for serial " << pkg_serial_no;
-      return INSTALL_ERROR;
-    }
-  }
-
-  if (metadata["ota-type"] != "AB") {
-    LOG(ERROR) << "Package is not A/B";
-    return INSTALL_ERROR;
-  }
-
+// 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) {
   // Incremental updates should match the current build.
-  value = android::base::GetProperty("ro.build.version.incremental", "");
-  const std::string& pkg_pre_build = metadata["pre-build-incremental"];
-  if (!pkg_pre_build.empty() && pkg_pre_build != value) {
-    LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value;
-    return INSTALL_ERROR;
+  auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", "");
+  auto pkg_pre_build = get_value(metadata, "pre-build-incremental");
+  if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) {
+    LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected "
+               << device_pre_build;
+    return false;
   }
 
-  value = android::base::GetProperty("ro.build.fingerprint", "");
-  const std::string& pkg_pre_build_fingerprint = metadata["pre-build"];
-  if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != value) {
+  auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", "");
+  auto pkg_pre_build_fingerprint = get_value(metadata, "pre-build");
+  if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) {
     LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected "
-               << value;
-    return INSTALL_ERROR;
+               << device_fingerprint;
+    return false;
   }
 
   // Check for downgrade version.
@@ -194,42 +162,83 @@
   int64_t pkg_post_timestamp = 0;
   // We allow to full update to the same version we are running, in case there
   // is a problem with the current copy of that version.
-  if (metadata["post-timestamp"].empty() ||
-      !android::base::ParseInt(metadata["post-timestamp"].c_str(), &pkg_post_timestamp) ||
+  auto pkg_post_timestamp_string = get_value(metadata, "post-timestamp");
+  if (pkg_post_timestamp_string.empty() ||
+      !android::base::ParseInt(pkg_post_timestamp_string, &pkg_post_timestamp) ||
       pkg_post_timestamp < build_timestamp) {
-    if (metadata["ota-downgrade"] != "yes") {
+    if (get_value(metadata, "ota-downgrade") != "yes") {
       LOG(ERROR) << "Update package is older than the current build, expected a build "
                     "newer than timestamp "
                  << build_timestamp << " but package has timestamp " << pkg_post_timestamp
                  << " and downgrade not allowed.";
-      return INSTALL_ERROR;
+      return false;
     }
     if (pkg_pre_build_fingerprint.empty()) {
       LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
-      return INSTALL_ERROR;
+      return false;
     }
   }
 
-  return 0;
+  return true;
 }
 
-int update_binary_command(const std::string& package, ZipArchiveHandle zip,
-                          const std::string& binary_path, int /* retry_count */, int status_fd,
-                          std::vector<std::string>* cmd) {
-  CHECK(cmd != nullptr);
-  int ret = check_newer_ab_build(zip);
-  if (ret != 0) {
-    return ret;
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
+  auto package_ota_type = get_value(metadata, "ota-type");
+  auto expected_ota_type = OtaTypeToString(ota_type);
+  if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) {
+    LOG(INFO) << "Skip package metadata check for ota type " << expected_ota_type;
+    return true;
   }
 
+  if (package_ota_type != expected_ota_type) {
+    LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual "
+               << package_ota_type;
+    return false;
+  }
+
+  auto device = android::base::GetProperty("ro.product.device", "");
+  auto pkg_device = get_value(metadata, "pre-device");
+  if (pkg_device != device || pkg_device.empty()) {
+    LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device;
+    return false;
+  }
+
+  // We allow the package to not have any serialno; and we also allow it to carry multiple serial
+  // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
+  // verification if the device's serialno doesn't match any of these carried numbers.
+  auto pkg_serial_no = get_value(metadata, "serialno");
+  if (!pkg_serial_no.empty()) {
+    auto device_serial_no = android::base::GetProperty("ro.serialno", "");
+    bool serial_number_match = false;
+    for (const auto& number : android::base::Split(pkg_serial_no, "|")) {
+      if (device_serial_no == android::base::Trim(number)) {
+        serial_number_match = true;
+      }
+    }
+    if (!serial_number_match) {
+      LOG(ERROR) << "Package is for serial " << pkg_serial_no;
+      return false;
+    }
+  }
+
+  if (ota_type == OtaType::AB) {
+    return CheckAbSpecificMetadata(metadata);
+  }
+
+  return true;
+}
+
+bool SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+                           std::vector<std::string>* cmd) {
+  CHECK(cmd != nullptr);
+
   // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset
   // in the zip file.
   static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
-  ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES);
   ZipEntry properties_entry;
-  if (FindEntry(zip, property_name, &properties_entry) != 0) {
+  if (FindEntry(zip, AB_OTA_PAYLOAD_PROPERTIES, &properties_entry) != 0) {
     LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES;
-    return INSTALL_CORRUPT;
+    return false;
   }
   uint32_t properties_entry_length = properties_entry.uncompressed_length;
   std::vector<uint8_t> payload_properties(properties_entry_length);
@@ -237,57 +246,57 @@
       ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length);
   if (err != 0) {
     LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err);
-    return INSTALL_CORRUPT;
+    return false;
   }
 
   static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
-  ZipString payload_name(AB_OTA_PAYLOAD);
   ZipEntry payload_entry;
-  if (FindEntry(zip, payload_name, &payload_entry) != 0) {
+  if (FindEntry(zip, AB_OTA_PAYLOAD, &payload_entry) != 0) {
     LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD;
-    return INSTALL_CORRUPT;
+    return false;
   }
   long payload_offset = payload_entry.offset;
   *cmd = {
-    binary_path,
+    "/system/bin/update_engine_sideload",
     "--payload=file://" + package,
     android::base::StringPrintf("--offset=%ld", payload_offset),
     "--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
     android::base::StringPrintf("--status_fd=%d", status_fd),
   };
-  return 0;
+  return true;
 }
 
-#else  // !AB_OTA_UPDATER
-
-int update_binary_command(const std::string& package, ZipArchiveHandle zip,
-                          const std::string& binary_path, int retry_count, int status_fd,
-                          std::vector<std::string>* cmd) {
+bool SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
+                              int status_fd, std::vector<std::string>* cmd) {
   CHECK(cmd != nullptr);
 
-  // On traditional updates we extract the update binary from the package.
+  // In non-A/B updates we extract the update binary from the package.
   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
-  ZipString binary_name(UPDATE_BINARY_NAME);
   ZipEntry binary_entry;
-  if (FindEntry(zip, binary_name, &binary_entry) != 0) {
+  if (FindEntry(zip, UPDATE_BINARY_NAME, &binary_entry) != 0) {
     LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
-    return INSTALL_CORRUPT;
+    return false;
   }
 
+  const std::string binary_path = Paths::Get().temporary_update_binary();
   unlink(binary_path.c_str());
-  int fd = open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755);
+  android::base::unique_fd fd(
+      open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755));
   if (fd == -1) {
     PLOG(ERROR) << "Failed to create " << binary_path;
-    return INSTALL_ERROR;
+    return false;
   }
 
-  int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
-  close(fd);
-  if (error != 0) {
+  if (auto error = ExtractEntryToFile(zip, &binary_entry, fd); error != 0) {
     LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
-    return INSTALL_ERROR;
+    return false;
   }
 
+  // When executing the update binary contained in the package, the arguments passed are:
+  //   - the version number for this interface
+  //   - an FD to which the program can write in order to update the progress bar.
+  //   - the name of the package zip file.
+  //   - an optional argument "retry" if this update is a retry of a failed update attempt.
   *cmd = {
     binary_path,
     std::to_string(kRecoveryApiVersion),
@@ -297,9 +306,8 @@
   if (retry_count > 0) {
     cmd->push_back("retry");
   }
-  return 0;
+  return true;
 }
-#endif  // !AB_OTA_UPDATER
 
 static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) {
   CHECK(max_temperature != nullptr);
@@ -312,89 +320,76 @@
 }
 
 // If the package contains an update binary, extract it and run it.
-static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
-                             std::vector<std::string>* log_buffer, int retry_count,
-                             int* max_temperature) {
-  read_source_target_build(zip, log_buffer);
-
-  int pipefd[2];
-  pipe(pipefd);
-
-  std::vector<std::string> args;
-#ifdef AB_OTA_UPDATER
-  int ret = update_binary_command(package, zip, "/sbin/update_engine_sideload", retry_count,
-                                  pipefd[1], &args);
-#else
-  int ret = update_binary_command(package, zip, "/tmp/update-binary", retry_count, pipefd[1],
-                                  &args);
-#endif
-  if (ret) {
-    close(pipefd[0]);
-    close(pipefd[1]);
-    log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
-    return ret;
+static InstallResult TryUpdateBinary(const std::string& package, ZipArchiveHandle zip,
+                                     bool* wipe_cache, std::vector<std::string>* log_buffer,
+                                     int retry_count, int* max_temperature, RecoveryUI* ui) {
+  std::map<std::string, std::string> metadata;
+  if (!ReadMetadataFromPackage(zip, &metadata)) {
+    LOG(ERROR) << "Failed to parse metadata in the zip file";
+    return INSTALL_CORRUPT;
   }
 
-  // When executing the update binary contained in the package, the
-  // arguments passed are:
+  bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
+  // Verify against the metadata in the package first.
+  if (is_ab && !CheckPackageMetadata(metadata, OtaType::AB)) {
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
+    return INSTALL_ERROR;
+  }
+
+  ReadSourceTargetBuild(metadata, log_buffer);
+
+  // The updater in child process writes to the pipe to communicate with recovery.
+  android::base::unique_fd pipe_read, pipe_write;
+  // Explicitly disable O_CLOEXEC using 0 as the flags (last) parameter to Pipe
+  // so that the child updater process will recieve a non-closed fd.
+  if (!android::base::Pipe(&pipe_read, &pipe_write, 0)) {
+    PLOG(ERROR) << "Failed to create pipe for updater-recovery communication";
+    return INSTALL_CORRUPT;
+  }
+
+  // The updater-recovery communication protocol.
   //
-  //   - the version number for this interface
+  //   progress <frac> <secs>
+  //       fill up the next <frac> part of of the progress bar over <secs> seconds. If <secs> is
+  //       zero, use `set_progress` commands to manually control the progress of this segment of the
+  //       bar.
   //
-  //   - an FD to which the program can write in order to update the
-  //     progress bar.  The program can write single-line commands:
+  //   set_progress <frac>
+  //       <frac> should be between 0.0 and 1.0; sets the progress bar within the segment defined by
+  //       the most recent progress command.
   //
-  //        progress <frac> <secs>
-  //            fill up the next <frac> part of of the progress bar
-  //            over <secs> seconds.  If <secs> is zero, use
-  //            set_progress commands to manually control the
-  //            progress of this segment of the bar.
+  //   ui_print <string>
+  //       display <string> on the screen.
   //
-  //        set_progress <frac>
-  //            <frac> should be between 0.0 and 1.0; sets the
-  //            progress bar within the segment defined by the most
-  //            recent progress command.
+  //   wipe_cache
+  //       a wipe of cache will be performed following a successful installation.
   //
-  //        ui_print <string>
-  //            display <string> on the screen.
+  //   clear_display
+  //       turn off the text display.
   //
-  //        wipe_cache
-  //            a wipe of cache will be performed following a successful
-  //            installation.
+  //   enable_reboot
+  //       packages can explicitly request that they want the user to be able to reboot during
+  //       installation (useful for debugging packages that don't exit).
   //
-  //        clear_display
-  //            turn off the text display.
+  //   retry_update
+  //       updater encounters some issue during the update. It requests a reboot to retry the same
+  //       package automatically.
   //
-  //        enable_reboot
-  //            packages can explicitly request that they want the user
-  //            to be able to reboot during installation (useful for
-  //            debugging packages that don't exit).
-  //
-  //        retry_update
-  //            updater encounters some issue during the update. It requests
-  //            a reboot to retry the same package automatically.
-  //
-  //        log <string>
-  //            updater requests logging the string (e.g. cause of the
-  //            failure).
-  //
-  //   - the name of the package zip file.
-  //
-  //   - an optional argument "retry" if this update is a retry of a failed
-  //   update attempt.
+  //   log <string>
+  //       updater requests logging the string (e.g. cause of the failure).
   //
 
-  // Convert the vector to a NULL-terminated char* array suitable for execv.
-  const char* chr_args[args.size() + 1];
-  chr_args[args.size()] = nullptr;
-  for (size_t i = 0; i < args.size(); i++) {
-    chr_args[i] = args[i].c_str();
+  std::vector<std::string> args;
+  if (auto setup_result =
+          is_ab ? SetUpAbUpdateCommands(package, zip, pipe_write.get(), &args)
+                : SetUpNonAbUpdateCommands(package, zip, retry_count, pipe_write.get(), &args);
+      !setup_result) {
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
+    return INSTALL_CORRUPT;
   }
 
   pid_t pid = fork();
-
   if (pid == -1) {
-    close(pipefd[0]);
-    close(pipefd[1]);
     PLOG(ERROR) << "Failed to fork update binary";
     log_buffer->push_back(android::base::StringPrintf("error: %d", kForkUpdateBinaryFailure));
     return INSTALL_ERROR;
@@ -402,16 +397,18 @@
 
   if (pid == 0) {
     umask(022);
-    close(pipefd[0]);
-    execv(chr_args[0], const_cast<char**>(chr_args));
-    // Bug: 34769056
-    // We shouldn't use LOG/PLOG in the forked process, since they may cause
-    // the child process to hang. This deadlock results from an improperly
-    // copied mutex in the ui functions.
+    pipe_read.reset();
+
+    // Convert the std::string vector to a NULL-terminated char* vector suitable for execv.
+    auto chr_args = StringVectorToNullTerminatedArray(args);
+    execv(chr_args[0], chr_args.data());
+    // We shouldn't use LOG/PLOG in the forked process, since they may cause the child process to
+    // hang. This deadlock results from an improperly copied mutex in the ui functions.
+    // (Bug: 34769056)
     fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
     _exit(EXIT_FAILURE);
   }
-  close(pipefd[1]);
+  pipe_write.reset();
 
   std::atomic<bool> logger_finished(false);
   std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished));
@@ -420,7 +417,7 @@
   bool retry_update = false;
 
   char buffer[1024];
-  FILE* from_child = fdopen(pipefd[0], "r");
+  FILE* from_child = android::base::Fdopen(std::move(pipe_read), "r");
   while (fgets(buffer, sizeof(buffer), from_child) != nullptr) {
     std::string line(buffer);
     size_t space = line.find_first_of(" \n");
@@ -485,9 +482,16 @@
   if (retry_update) {
     return INSTALL_RETRY;
   }
-  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-    LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")";
+  if (WIFEXITED(status)) {
+    if (WEXITSTATUS(status) != EXIT_SUCCESS) {
+      LOG(ERROR) << "Error in " << package << " (status " << WEXITSTATUS(status) << ")";
+      return INSTALL_ERROR;
+    }
+  } else if (WIFSIGNALED(status)) {
+    LOG(ERROR) << "Error in " << package << " (killed by signal " << WTERMSIG(status) << ")";
     return INSTALL_ERROR;
+  } else {
+    LOG(FATAL) << "Invalid status code " << status;
   }
 
   return INSTALL_SUCCESS;
@@ -500,9 +504,8 @@
   LOG(INFO) << "Verifying package compatibility...";
 
   static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip";
-  ZipString compatibility_entry_name(COMPATIBILITY_ZIP_ENTRY);
   ZipEntry compatibility_entry;
-  if (FindEntry(package_zip, compatibility_entry_name, &compatibility_entry) != 0) {
+  if (FindEntry(package_zip, COMPATIBILITY_ZIP_ENTRY, &compatibility_entry) != 0) {
     LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry";
     return true;
   }
@@ -526,7 +529,7 @@
 
   // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents.
   void* cookie;
-  ret = StartIteration(zip_handle, &cookie, nullptr, nullptr);
+  ret = StartIteration(zip_handle, &cookie);
   if (ret != 0) {
     LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret);
     CloseArchive(zip_handle);
@@ -561,9 +564,10 @@
   return false;
 }
 
-static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
-                                  std::vector<std::string>* log_buffer, int retry_count,
-                                  int* max_temperature) {
+static InstallResult VerifyAndInstallPackage(const std::string& path, bool* wipe_cache,
+                                             bool needs_mount, std::vector<std::string>* log_buffer,
+                                             int retry_count, int* max_temperature,
+                                             RecoveryUI* ui) {
   ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
   ui->Print("Finding update package...\n");
   // Give verification half the progress bar...
@@ -576,40 +580,35 @@
 
   if (needs_mount) {
     if (path[0] == '@') {
-      ensure_path_mounted(path.substr(1).c_str());
+      ensure_path_mounted(path.substr(1));
     } else {
-      ensure_path_mounted(path.c_str());
+      ensure_path_mounted(path);
     }
   }
 
-  MemMapping map;
-  if (!map.MapFile(path)) {
-    LOG(ERROR) << "failed to map file";
+  auto package = Package::CreateMemoryPackage(
+      path, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
+  if (!package) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
     return INSTALL_CORRUPT;
   }
 
   // Verify package.
-  if (!verify_package(map.addr, map.length)) {
+  if (!verify_package(package.get(), ui)) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
     return INSTALL_CORRUPT;
   }
 
   // Try to open the package.
-  ZipArchiveHandle zip;
-  int err = OpenArchiveFromMemory(map.addr, map.length, path.c_str(), &zip);
-  if (err != 0) {
-    LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err);
+  ZipArchiveHandle zip = package->GetZipArchiveHandle();
+  if (!zip) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
-
-    CloseArchive(zip);
     return INSTALL_CORRUPT;
   }
 
-  // Additionally verify the compatibility of the package.
-  if (!verify_package_compatibility(zip)) {
+  // Additionally verify the compatibility of the package if it's a fresh install.
+  if (retry_count == 0 && !verify_package_compatibility(zip)) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
-    CloseArchive(zip);
     return INSTALL_CORRUPT;
   }
 
@@ -619,34 +618,33 @@
     ui->Print("Retry attempt: %d\n", retry_count);
   }
   ui->SetEnableReboot(false);
-  int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature);
+  auto result =
+      TryUpdateBinary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
   ui->SetEnableReboot(true);
   ui->Print("\n");
 
-  CloseArchive(zip);
   return result;
 }
 
-int install_package(const std::string& path, bool* wipe_cache, const std::string& install_file,
-                    bool needs_mount, int retry_count) {
+InstallResult InstallPackage(const std::string& path, bool should_wipe_cache, bool needs_mount,
+                             int retry_count, RecoveryUI* ui) {
   CHECK(!path.empty());
-  CHECK(!install_file.empty());
-  CHECK(wipe_cache != nullptr);
 
-  modified_flash = true;
   auto start = std::chrono::system_clock::now();
 
   int start_temperature = GetMaxValueFromThermalZone();
   int max_temperature = start_temperature;
 
-  int result;
+  InstallResult result;
   std::vector<std::string> log_buffer;
   if (setup_install_mounts() != 0) {
     LOG(ERROR) << "failed to set up expected mounts for install; aborting";
     result = INSTALL_ERROR;
   } else {
-    result = really_install_package(path, wipe_cache, needs_mount, &log_buffer, retry_count,
-                                    &max_temperature);
+    bool updater_wipe_cache = false;
+    result = VerifyAndInstallPackage(path, &updater_wipe_cache, needs_mount, &log_buffer,
+                                     retry_count, &max_temperature, ui);
+    should_wipe_cache = should_wipe_cache || updater_wipe_cache;
   }
 
   // Measure the time spent to apply OTA update in seconds.
@@ -693,6 +691,7 @@
 
   std::string log_content =
       android::base::Join(log_header, "\n") + "\n" + android::base::Join(log_buffer, "\n") + "\n";
+  const std::string& install_file = Paths::Get().temporary_install_file();
   if (!android::base::WriteStringToFile(log_content, install_file)) {
     PLOG(ERROR) << "failed to write " << install_file;
   }
@@ -700,23 +699,28 @@
   // Write a copy into last_log.
   LOG(INFO) << log_content;
 
+  if (result == INSTALL_SUCCESS && should_wipe_cache) {
+    if (!WipeCache(ui, nullptr)) {
+      result = INSTALL_ERROR;
+    }
+  }
+
   return result;
 }
 
-bool verify_package(const unsigned char* package_data, size_t package_size) {
-  static constexpr const char* PUBLIC_KEYS_FILE = "/res/keys";
-  std::vector<Certificate> loadedKeys;
-  if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
+bool verify_package(Package* package, RecoveryUI* ui) {
+  static constexpr const char* CERTIFICATE_ZIP_FILE = "/system/etc/security/otacerts.zip";
+  std::vector<Certificate> loaded_keys = LoadKeysFromZipfile(CERTIFICATE_ZIP_FILE);
+  if (loaded_keys.empty()) {
     LOG(ERROR) << "Failed to load keys";
     return false;
   }
-  LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE;
+  LOG(INFO) << loaded_keys.size() << " key(s) loaded from " << CERTIFICATE_ZIP_FILE;
 
   // Verify package.
   ui->Print("Verifying update package...\n");
   auto t0 = std::chrono::system_clock::now();
-  int err = verify_file(package_data, package_size, loadedKeys,
-                        std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
+  int err = verify_file(package, loaded_keys);
   std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
   ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
   if (err != VERIFY_SUCCESS) {
diff --git a/install/package.cpp b/install/package.cpp
new file mode 100644
index 0000000..4402f48
--- /dev/null
+++ b/install/package.cpp
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "install/package.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+
+#include "otautil/error_code.h"
+#include "otautil/sysutil.h"
+
+// This class wraps the package in memory, i.e. a memory mapped package, or a package loaded
+// to a string/vector.
+class MemoryPackage : public Package {
+ public:
+  // Constructs the class from a file. We will memory maps the file later.
+  MemoryPackage(const std::string& path, std::unique_ptr<MemMapping> map,
+                const std::function<void(float)>& set_progress);
+
+  // Constructs the class from the package bytes in |content|.
+  MemoryPackage(std::vector<uint8_t> content, const std::function<void(float)>& set_progress);
+
+  ~MemoryPackage() override;
+
+  // Memory maps the package file if necessary. Initializes the start address and size of the
+  // package.
+  uint64_t GetPackageSize() const override {
+    return package_size_;
+  }
+
+  bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override;
+
+  ZipArchiveHandle GetZipArchiveHandle() override;
+
+  bool UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers, uint64_t start,
+                          uint64_t length) override;
+
+ private:
+  const uint8_t* addr_;    // Start address of the package in memory.
+  uint64_t package_size_;  // Package size in bytes.
+
+  // The memory mapped package.
+  std::unique_ptr<MemMapping> map_;
+  // A copy of the package content, valid only if we create the class with the exact bytes of
+  // the package.
+  std::vector<uint8_t> package_content_;
+  // The physical path to the package, empty if we create the class with the package content.
+  std::string path_;
+
+  // The ZipArchiveHandle of the package.
+  ZipArchiveHandle zip_handle_;
+};
+
+void Package::SetProgress(float progress) {
+  if (set_progress_) {
+    set_progress_(progress);
+  }
+}
+
+class FilePackage : public Package {
+ public:
+  FilePackage(android::base::unique_fd&& fd, uint64_t file_size, const std::string& path,
+              const std::function<void(float)>& set_progress);
+
+  ~FilePackage() override;
+
+  uint64_t GetPackageSize() const override {
+    return package_size_;
+  }
+
+  bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override;
+
+  ZipArchiveHandle GetZipArchiveHandle() override;
+
+  bool UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers, uint64_t start,
+                          uint64_t length) override;
+
+ private:
+  android::base::unique_fd fd_;  // The underlying fd to the open package.
+  uint64_t package_size_;
+  std::string path_;  // The physical path to the package.
+
+  ZipArchiveHandle zip_handle_;
+};
+
+std::unique_ptr<Package> Package::CreateMemoryPackage(
+    const std::string& path, const std::function<void(float)>& set_progress) {
+  std::unique_ptr<MemMapping> mmap = std::make_unique<MemMapping>();
+  if (!mmap->MapFile(path)) {
+    LOG(ERROR) << "failed to map file";
+    return nullptr;
+  }
+
+  return std::make_unique<MemoryPackage>(path, std::move(mmap), set_progress);
+}
+
+std::unique_ptr<Package> Package::CreateFilePackage(
+    const std::string& path, const std::function<void(float)>& set_progress) {
+  android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open " << path;
+    return nullptr;
+  }
+
+  off64_t file_size = lseek64(fd.get(), 0, SEEK_END);
+  if (file_size == -1) {
+    PLOG(ERROR) << "Failed to get the package size";
+    return nullptr;
+  }
+
+  return std::make_unique<FilePackage>(std::move(fd), file_size, path, set_progress);
+}
+
+std::unique_ptr<Package> Package::CreateMemoryPackage(
+    std::vector<uint8_t> content, const std::function<void(float)>& set_progress) {
+  return std::make_unique<MemoryPackage>(std::move(content), set_progress);
+}
+
+MemoryPackage::MemoryPackage(const std::string& path, std::unique_ptr<MemMapping> map,
+                             const std::function<void(float)>& set_progress)
+    : map_(std::move(map)), path_(path), zip_handle_(nullptr) {
+  addr_ = map_->addr;
+  package_size_ = map_->length;
+  set_progress_ = set_progress;
+}
+
+MemoryPackage::MemoryPackage(std::vector<uint8_t> content,
+                             const std::function<void(float)>& set_progress)
+    : package_content_(std::move(content)), zip_handle_(nullptr) {
+  CHECK(!package_content_.empty());
+  addr_ = package_content_.data();
+  package_size_ = package_content_.size();
+  set_progress_ = set_progress;
+}
+
+MemoryPackage::~MemoryPackage() {
+  if (zip_handle_) {
+    CloseArchive(zip_handle_);
+  }
+}
+
+bool MemoryPackage::ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) {
+  if (byte_count > package_size_ || offset > package_size_ - byte_count) {
+    LOG(ERROR) << "Out of bound read, offset: " << offset << ", size: " << byte_count
+               << ", total package_size: " << package_size_;
+    return false;
+  }
+  memcpy(buffer, addr_ + offset, byte_count);
+  return true;
+}
+
+bool MemoryPackage::UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers,
+                                       uint64_t start, uint64_t length) {
+  if (length > package_size_ || start > package_size_ - length) {
+    LOG(ERROR) << "Out of bound read, offset: " << start << ", size: " << length
+               << ", total package_size: " << package_size_;
+    return false;
+  }
+
+  for (const auto& hasher : hashers) {
+    hasher(addr_ + start, length);
+  }
+  return true;
+}
+
+ZipArchiveHandle MemoryPackage::GetZipArchiveHandle() {
+  if (zip_handle_) {
+    return zip_handle_;
+  }
+
+  if (auto err = OpenArchiveFromMemory(const_cast<uint8_t*>(addr_), package_size_, path_.c_str(),
+                                       &zip_handle_);
+      err != 0) {
+    LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err);
+    return nullptr;
+  }
+
+  return zip_handle_;
+}
+
+FilePackage::FilePackage(android::base::unique_fd&& fd, uint64_t file_size, const std::string& path,
+                         const std::function<void(float)>& set_progress)
+    : fd_(std::move(fd)), package_size_(file_size), path_(path), zip_handle_(nullptr) {
+  set_progress_ = set_progress;
+}
+
+FilePackage::~FilePackage() {
+  if (zip_handle_) {
+    CloseArchive(zip_handle_);
+  }
+}
+
+bool FilePackage::ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) {
+  if (byte_count > package_size_ || offset > package_size_ - byte_count) {
+    LOG(ERROR) << "Out of bound read, offset: " << offset << ", size: " << byte_count
+               << ", total package_size: " << package_size_;
+    return false;
+  }
+
+  if (!android::base::ReadFullyAtOffset(fd_.get(), buffer, byte_count, offset)) {
+    PLOG(ERROR) << "Failed to read " << byte_count << " bytes data at offset " << offset;
+    return false;
+  }
+
+  return true;
+}
+
+bool FilePackage::UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers,
+                                     uint64_t start, uint64_t length) {
+  if (length > package_size_ || start > package_size_ - length) {
+    LOG(ERROR) << "Out of bound read, offset: " << start << ", size: " << length
+               << ", total package_size: " << package_size_;
+    return false;
+  }
+
+  uint64_t so_far = 0;
+  while (so_far < length) {
+    uint64_t read_size = std::min<uint64_t>(length - so_far, 16 * MiB);
+    std::vector<uint8_t> buffer(read_size);
+    if (!ReadFullyAtOffset(buffer.data(), read_size, start + so_far)) {
+      return false;
+    }
+
+    for (const auto& hasher : hashers) {
+      hasher(buffer.data(), read_size);
+    }
+    so_far += read_size;
+  }
+
+  return true;
+}
+
+ZipArchiveHandle FilePackage::GetZipArchiveHandle() {
+  if (zip_handle_) {
+    return zip_handle_;
+  }
+
+  if (auto err = OpenArchiveFd(fd_.get(), path_.c_str(), &zip_handle_); err != 0) {
+    LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err);
+    return nullptr;
+  }
+
+  return zip_handle_;
+}
diff --git a/install/verifier.cpp b/install/verifier.cpp
new file mode 100644
index 0000000..3de58e4
--- /dev/null
+++ b/install/verifier.cpp
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "install/verifier.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <openssl/bio.h>
+#include <openssl/bn.h>
+#include <openssl/ecdsa.h>
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <ziparchive/zip_archive.h>
+
+#include "otautil/print_sha1.h"
+#include "private/asn1_decoder.h"
+
+/*
+ * Simple version of PKCS#7 SignedData extraction. This extracts the
+ * signature OCTET STRING to be used for signature verification.
+ *
+ * For full details, see http://www.ietf.org/rfc/rfc3852.txt
+ *
+ * The PKCS#7 structure looks like:
+ *
+ *   SEQUENCE (ContentInfo)
+ *     OID (ContentType)
+ *     [0] (content)
+ *       SEQUENCE (SignedData)
+ *         INTEGER (version CMSVersion)
+ *         SET (DigestAlgorithmIdentifiers)
+ *         SEQUENCE (EncapsulatedContentInfo)
+ *         [0] (CertificateSet OPTIONAL)
+ *         [1] (RevocationInfoChoices OPTIONAL)
+ *         SET (SignerInfos)
+ *           SEQUENCE (SignerInfo)
+ *             INTEGER (CMSVersion)
+ *             SEQUENCE (SignerIdentifier)
+ *             SEQUENCE (DigestAlgorithmIdentifier)
+ *             SEQUENCE (SignatureAlgorithmIdentifier)
+ *             OCTET STRING (SignatureValue)
+ */
+static bool read_pkcs7(const uint8_t* pkcs7_der, size_t pkcs7_der_len,
+                       std::vector<uint8_t>* sig_der) {
+  CHECK(sig_der != nullptr);
+  sig_der->clear();
+
+  asn1_context ctx(pkcs7_der, pkcs7_der_len);
+
+  std::unique_ptr<asn1_context> pkcs7_seq(ctx.asn1_sequence_get());
+  if (pkcs7_seq == nullptr || !pkcs7_seq->asn1_sequence_next()) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> signed_data_app(pkcs7_seq->asn1_constructed_get());
+  if (signed_data_app == nullptr) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> signed_data_seq(signed_data_app->asn1_sequence_get());
+  if (signed_data_seq == nullptr || !signed_data_seq->asn1_sequence_next() ||
+      !signed_data_seq->asn1_sequence_next() || !signed_data_seq->asn1_sequence_next() ||
+      !signed_data_seq->asn1_constructed_skip_all()) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> sig_set(signed_data_seq->asn1_set_get());
+  if (sig_set == nullptr) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> sig_seq(sig_set->asn1_sequence_get());
+  if (sig_seq == nullptr || !sig_seq->asn1_sequence_next() || !sig_seq->asn1_sequence_next() ||
+      !sig_seq->asn1_sequence_next() || !sig_seq->asn1_sequence_next()) {
+    return false;
+  }
+
+  const uint8_t* sig_der_ptr;
+  size_t sig_der_length;
+  if (!sig_seq->asn1_octet_string_get(&sig_der_ptr, &sig_der_length)) {
+    return false;
+  }
+
+  sig_der->resize(sig_der_length);
+  std::copy(sig_der_ptr, sig_der_ptr + sig_der_length, sig_der->begin());
+  return true;
+}
+
+int verify_file(VerifierInterface* package, const std::vector<Certificate>& keys) {
+  CHECK(package);
+  package->SetProgress(0.0);
+
+  // An archive with a whole-file signature will end in six bytes:
+  //
+  //   (2-byte signature start) $ff $ff (2-byte comment size)
+  //
+  // (As far as the ZIP format is concerned, these are part of the archive comment.) We start by
+  // reading this footer, this tells us how far back from the end we have to start reading to find
+  // the whole comment.
+
+#define FOOTER_SIZE 6
+  uint64_t length = package->GetPackageSize();
+
+  if (length < FOOTER_SIZE) {
+    LOG(ERROR) << "not big enough to contain footer";
+    return VERIFY_FAILURE;
+  }
+
+  uint8_t footer[FOOTER_SIZE];
+  if (!package->ReadFullyAtOffset(footer, FOOTER_SIZE, length - FOOTER_SIZE)) {
+    LOG(ERROR) << "Failed to read footer";
+    return VERIFY_FAILURE;
+  }
+
+  if (footer[2] != 0xff || footer[3] != 0xff) {
+    LOG(ERROR) << "footer is wrong";
+    return VERIFY_FAILURE;
+  }
+
+  size_t comment_size = footer[4] + (footer[5] << 8);
+  size_t signature_start = footer[0] + (footer[1] << 8);
+  LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start
+            << " bytes from end";
+
+  if (signature_start > comment_size) {
+    LOG(ERROR) << "signature start: " << signature_start
+               << " is larger than comment size: " << comment_size;
+    return VERIFY_FAILURE;
+  }
+
+  if (signature_start <= FOOTER_SIZE) {
+    LOG(ERROR) << "Signature start is in the footer";
+    return VERIFY_FAILURE;
+  }
+
+#define EOCD_HEADER_SIZE 22
+
+  // The end-of-central-directory record is 22 bytes plus any comment length.
+  size_t eocd_size = comment_size + EOCD_HEADER_SIZE;
+
+  if (length < eocd_size) {
+    LOG(ERROR) << "not big enough to contain EOCD";
+    return VERIFY_FAILURE;
+  }
+
+  // Determine how much of the file is covered by the signature. This is everything except the
+  // signature data and length, which includes all of the EOCD except for the comment length field
+  // (2 bytes) and the comment data.
+  uint64_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2;
+
+  uint8_t eocd[eocd_size];
+  if (!package->ReadFullyAtOffset(eocd, eocd_size, length - eocd_size)) {
+    LOG(ERROR) << "Failed to read EOCD of " << eocd_size << " bytes";
+    return VERIFY_FAILURE;
+  }
+
+  // If this is really is the EOCD record, it will begin with the magic number $50 $4b $05 $06.
+  if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) {
+    LOG(ERROR) << "signature length doesn't match EOCD marker";
+    return VERIFY_FAILURE;
+  }
+
+  for (size_t i = 4; i < eocd_size - 3; ++i) {
+    if (eocd[i] == 0x50 && eocd[i + 1] == 0x4b && eocd[i + 2] == 0x05 && eocd[i + 3] == 0x06) {
+      // If the sequence $50 $4b $05 $06 appears anywhere after the real one, libziparchive will
+      // find the later (wrong) one, which could be exploitable. Fail the verification if this
+      // sequence occurs anywhere after the real one.
+      LOG(ERROR) << "EOCD marker occurs after start of EOCD";
+      return VERIFY_FAILURE;
+    }
+  }
+
+  bool need_sha1 = false;
+  bool need_sha256 = false;
+  for (const auto& key : keys) {
+    switch (key.hash_len) {
+      case SHA_DIGEST_LENGTH:
+        need_sha1 = true;
+        break;
+      case SHA256_DIGEST_LENGTH:
+        need_sha256 = true;
+        break;
+    }
+  }
+
+  SHA_CTX sha1_ctx;
+  SHA256_CTX sha256_ctx;
+  SHA1_Init(&sha1_ctx);
+  SHA256_Init(&sha256_ctx);
+
+  std::vector<HasherUpdateCallback> hashers;
+  if (need_sha1) {
+    hashers.emplace_back(
+        std::bind(&SHA1_Update, &sha1_ctx, std::placeholders::_1, std::placeholders::_2));
+  }
+  if (need_sha256) {
+    hashers.emplace_back(
+        std::bind(&SHA256_Update, &sha256_ctx, std::placeholders::_1, std::placeholders::_2));
+  }
+
+  double frac = -1.0;
+  uint64_t so_far = 0;
+  while (so_far < signed_len) {
+    // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a 1196MiB full OTA and
+    // 60% for an 89MiB incremental OTA. http://b/28135231.
+    uint64_t read_size = std::min<uint64_t>(signed_len - so_far, 16 * MiB);
+    package->UpdateHashAtOffset(hashers, so_far, read_size);
+    so_far += read_size;
+
+    double f = so_far / static_cast<double>(signed_len);
+    if (f > frac + 0.02 || read_size == so_far) {
+      package->SetProgress(f);
+      frac = f;
+    }
+  }
+
+  uint8_t sha1[SHA_DIGEST_LENGTH];
+  SHA1_Final(sha1, &sha1_ctx);
+  uint8_t sha256[SHA256_DIGEST_LENGTH];
+  SHA256_Final(sha256, &sha256_ctx);
+
+  const uint8_t* signature = eocd + eocd_size - signature_start;
+  size_t signature_size = signature_start - FOOTER_SIZE;
+
+  LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start)
+            << ", length: " << signature_size << "): " << print_hex(signature, signature_size);
+
+  std::vector<uint8_t> sig_der;
+  if (!read_pkcs7(signature, signature_size, &sig_der)) {
+    LOG(ERROR) << "Could not find signature DER block";
+    return VERIFY_FAILURE;
+  }
+
+  // Check to make sure at least one of the keys matches the signature. Since any key can match,
+  // we need to try each before determining a verification failure has happened.
+  size_t i = 0;
+  for (const auto& key : keys) {
+    const uint8_t* hash;
+    int hash_nid;
+    switch (key.hash_len) {
+      case SHA_DIGEST_LENGTH:
+        hash = sha1;
+        hash_nid = NID_sha1;
+        break;
+      case SHA256_DIGEST_LENGTH:
+        hash = sha256;
+        hash_nid = NID_sha256;
+        break;
+      default:
+        continue;
+    }
+
+    // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that the signing tool appends
+    // after the signature itself.
+    if (key.key_type == Certificate::KEY_TYPE_RSA) {
+      if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der.data(), sig_der.size(),
+                      key.rsa.get())) {
+        LOG(INFO) << "failed to verify against RSA key " << i;
+        continue;
+      }
+
+      LOG(INFO) << "whole-file signature verified against RSA key " << i;
+      return VERIFY_SUCCESS;
+    } else if (key.key_type == Certificate::KEY_TYPE_EC && key.hash_len == SHA256_DIGEST_LENGTH) {
+      if (!ECDSA_verify(0, hash, key.hash_len, sig_der.data(), sig_der.size(), key.ec.get())) {
+        LOG(INFO) << "failed to verify against EC key " << i;
+        continue;
+      }
+
+      LOG(INFO) << "whole-file signature verified against EC key " << i;
+      return VERIFY_SUCCESS;
+    } else {
+      LOG(INFO) << "Unknown key type " << key.key_type;
+    }
+    i++;
+  }
+
+  if (need_sha1) {
+    LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH);
+  }
+  if (need_sha256) {
+    LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH);
+  }
+  LOG(ERROR) << "failed to verify whole-file signature";
+  return VERIFY_FAILURE;
+}
+
+static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) {
+  void* cookie;
+  int32_t iter_status = StartIteration(handle, &cookie, "", "x509.pem");
+  if (iter_status != 0) {
+    LOG(ERROR) << "Failed to iterate over entries in the certificate zipfile: "
+               << ErrorCodeString(iter_status);
+    return {};
+  }
+
+  std::vector<Certificate> result;
+
+  ZipString name;
+  ZipEntry entry;
+  while ((iter_status = Next(cookie, &entry, &name)) == 0) {
+    std::vector<uint8_t> pem_content(entry.uncompressed_length);
+    if (int32_t extract_status =
+            ExtractToMemory(handle, &entry, pem_content.data(), pem_content.size());
+        extract_status != 0) {
+      LOG(ERROR) << "Failed to extract " << std::string(name.name, name.name + name.name_length);
+      return {};
+    }
+
+    Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+    // Aborts the parsing if we fail to load one of the key file.
+    if (!LoadCertificateFromBuffer(pem_content, &cert)) {
+      LOG(ERROR) << "Failed to load keys from "
+                 << std::string(name.name, name.name + name.name_length);
+      return {};
+    }
+
+    result.emplace_back(std::move(cert));
+  }
+
+  if (iter_status != -1) {
+    LOG(ERROR) << "Error while iterating over zip entries: " << ErrorCodeString(iter_status);
+    return {};
+  }
+
+  return result;
+}
+
+std::vector<Certificate> LoadKeysFromZipfile(const std::string& zip_name) {
+  ZipArchiveHandle handle;
+  if (int32_t open_status = OpenArchive(zip_name.c_str(), &handle); open_status != 0) {
+    LOG(ERROR) << "Failed to open " << zip_name << ": " << ErrorCodeString(open_status);
+    return {};
+  }
+
+  std::vector<Certificate> result = IterateZipEntriesAndSearchForKeys(handle);
+  CloseArchive(handle);
+  return result;
+}
+
+bool CheckRSAKey(const std::unique_ptr<RSA, RSADeleter>& rsa) {
+  if (!rsa) {
+    return false;
+  }
+
+  const BIGNUM* out_n;
+  const BIGNUM* out_e;
+  RSA_get0_key(rsa.get(), &out_n, &out_e, nullptr /* private exponent */);
+  auto modulus_bits = BN_num_bits(out_n);
+  if (modulus_bits != 2048 && modulus_bits != 4096) {
+    LOG(ERROR) << "Modulus should be 2048 or 4096 bits long, actual: " << modulus_bits;
+    return false;
+  }
+
+  BN_ULONG exponent = BN_get_word(out_e);
+  if (exponent != 3 && exponent != 65537) {
+    LOG(ERROR) << "Public exponent should be 3 or 65537, actual: " << exponent;
+    return false;
+  }
+
+  return true;
+}
+
+bool CheckECKey(const std::unique_ptr<EC_KEY, ECKEYDeleter>& ec_key) {
+  if (!ec_key) {
+    return false;
+  }
+
+  const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key.get());
+  if (!ec_group) {
+    LOG(ERROR) << "Failed to get the ec_group from the ec_key";
+    return false;
+  }
+  auto degree = EC_GROUP_get_degree(ec_group);
+  if (degree != 256) {
+    LOG(ERROR) << "Field size of the ec key should be 256 bits long, actual: " << degree;
+    return false;
+  }
+
+  return true;
+}
+
+bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certificate* cert) {
+  std::unique_ptr<BIO, decltype(&BIO_free)> content(
+      BIO_new_mem_buf(pem_content.data(), pem_content.size()), BIO_free);
+
+  std::unique_ptr<X509, decltype(&X509_free)> x509(
+      PEM_read_bio_X509(content.get(), nullptr, nullptr, nullptr), X509_free);
+  if (!x509) {
+    LOG(ERROR) << "Failed to read x509 certificate";
+    return false;
+  }
+
+  int nid = X509_get_signature_nid(x509.get());
+  switch (nid) {
+    // SignApk has historically accepted md5WithRSA certificates, but treated them as
+    // sha1WithRSA anyway. Continue to do so for backwards compatibility.
+    case NID_md5WithRSA:
+    case NID_md5WithRSAEncryption:
+    case NID_sha1WithRSA:
+    case NID_sha1WithRSAEncryption:
+      cert->hash_len = SHA_DIGEST_LENGTH;
+      break;
+    case NID_sha256WithRSAEncryption:
+    case NID_ecdsa_with_SHA256:
+      cert->hash_len = SHA256_DIGEST_LENGTH;
+      break;
+    default:
+      LOG(ERROR) << "Unrecognized signature nid " << OBJ_nid2ln(nid);
+      return false;
+  }
+
+  std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> public_key(X509_get_pubkey(x509.get()),
+                                                                 EVP_PKEY_free);
+  if (!public_key) {
+    LOG(ERROR) << "Failed to extract the public key from x509 certificate";
+    return false;
+  }
+
+  int key_type = EVP_PKEY_id(public_key.get());
+  if (key_type == EVP_PKEY_RSA) {
+    cert->key_type = Certificate::KEY_TYPE_RSA;
+    cert->ec.reset();
+    cert->rsa.reset(EVP_PKEY_get1_RSA(public_key.get()));
+    if (!cert->rsa || !CheckRSAKey(cert->rsa)) {
+      LOG(ERROR) << "Failed to validate the rsa key info from public key";
+      return false;
+    }
+  } else if (key_type == EVP_PKEY_EC) {
+    cert->key_type = Certificate::KEY_TYPE_EC;
+    cert->rsa.reset();
+    cert->ec.reset(EVP_PKEY_get1_EC_KEY(public_key.get()));
+    if (!cert->ec || !CheckECKey(cert->ec)) {
+      LOG(ERROR) << "Failed to validate the ec key info from the public key";
+      return false;
+    }
+  } else {
+    LOG(ERROR) << "Unrecognized public key type " << OBJ_nid2ln(key_type);
+    return false;
+  }
+
+  return true;
+}
diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp
new file mode 100644
index 0000000..765a815
--- /dev/null
+++ b/install/wipe_data.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "install/wipe_data.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <functional>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include "otautil/dirutil.h"
+#include "otautil/logging.h"
+#include "otautil/roots.h"
+#include "recovery_ui/ui.h"
+
+constexpr const char* CACHE_ROOT = "/cache";
+constexpr const char* DATA_ROOT = "/data";
+constexpr const char* METADATA_ROOT = "/metadata";
+
+static bool EraseVolume(const char* volume, RecoveryUI* ui, bool convert_fbe) {
+  bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
+  bool is_data = (strcmp(volume, DATA_ROOT) == 0);
+
+  ui->SetBackground(RecoveryUI::ERASING);
+  ui->SetProgressType(RecoveryUI::INDETERMINATE);
+
+  std::vector<saved_log_file> log_files;
+  if (is_cache) {
+    // If we're reformatting /cache, we load any past logs (i.e. "/cache/recovery/last_*") and the
+    // current log ("/cache/recovery/log") into memory, so we can restore them after the reformat.
+    log_files = ReadLogFilesToMemory();
+  }
+
+  ui->Print("Formatting %s...\n", volume);
+
+  ensure_path_unmounted(volume);
+
+  int result;
+  if (is_data && convert_fbe) {
+    constexpr const char* CONVERT_FBE_DIR = "/tmp/convert_fbe";
+    constexpr const char* CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe";
+    // Create convert_fbe breadcrumb file to signal init to convert to file based encryption, not
+    // full disk encryption.
+    if (mkdir(CONVERT_FBE_DIR, 0700) != 0) {
+      PLOG(ERROR) << "Failed to mkdir " << CONVERT_FBE_DIR;
+      return false;
+    }
+    FILE* f = fopen(CONVERT_FBE_FILE, "wbe");
+    if (!f) {
+      PLOG(ERROR) << "Failed to convert to file encryption";
+      return false;
+    }
+    fclose(f);
+    result = format_volume(volume, CONVERT_FBE_DIR);
+    remove(CONVERT_FBE_FILE);
+    rmdir(CONVERT_FBE_DIR);
+  } else {
+    result = format_volume(volume);
+  }
+
+  if (is_cache) {
+    RestoreLogFilesAfterFormat(log_files);
+  }
+
+  return (result == 0);
+}
+
+bool WipeCache(RecoveryUI* ui, const std::function<bool()>& confirm_func) {
+  bool has_cache = volume_for_mount_point("/cache") != nullptr;
+  if (!has_cache) {
+    ui->Print("No /cache partition found.\n");
+    return false;
+  }
+
+  if (confirm_func && !confirm_func()) {
+    return false;
+  }
+
+  ui->Print("\n-- Wiping cache...\n");
+  bool success = EraseVolume("/cache", ui, false);
+  ui->Print("Cache wipe %s.\n", success ? "complete" : "failed");
+  return success;
+}
+
+bool WipeData(Device* device, bool convert_fbe) {
+  RecoveryUI* ui = device->GetUI();
+  ui->Print("\n-- Wiping data...\n");
+  bool success = device->PreWipeData();
+  if (success) {
+    success &= EraseVolume(DATA_ROOT, ui, convert_fbe);
+    bool has_cache = volume_for_mount_point("/cache") != nullptr;
+    if (has_cache) {
+      success &= EraseVolume(CACHE_ROOT, ui, false);
+    }
+    if (volume_for_mount_point(METADATA_ROOT) != nullptr) {
+      success &= EraseVolume(METADATA_ROOT, ui, false);
+    }
+  }
+  if (success) {
+    success &= device->PostWipeData();
+  }
+  ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
+  return success;
+}
\ No newline at end of file
diff --git a/install/wipe_device.cpp b/install/wipe_device.cpp
new file mode 100644
index 0000000..89d5d31
--- /dev/null
+++ b/install/wipe_device.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "install/wipe_device.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ziparchive/zip_archive.h>
+
+#include "bootloader_message/bootloader_message.h"
+#include "install/install.h"
+#include "install/package.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+std::vector<std::string> GetWipePartitionList(Package* wipe_package) {
+  ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
+  if (!zip) {
+    LOG(ERROR) << "Failed to get ZipArchiveHandle";
+    return {};
+  }
+
+  constexpr char RECOVERY_WIPE_ENTRY_NAME[] = "recovery.wipe";
+
+  std::string partition_list_content;
+  ZipEntry entry;
+  if (FindEntry(zip, RECOVERY_WIPE_ENTRY_NAME, &entry) == 0) {
+    uint32_t length = entry.uncompressed_length;
+    partition_list_content = std::string(length, '\0');
+    if (auto err = ExtractToMemory(
+            zip, &entry, reinterpret_cast<uint8_t*>(partition_list_content.data()), length);
+        err != 0) {
+      LOG(ERROR) << "Failed to extract " << RECOVERY_WIPE_ENTRY_NAME << ": "
+                 << ErrorCodeString(err);
+      return {};
+    }
+  } else {
+    LOG(INFO) << "Failed to find " << RECOVERY_WIPE_ENTRY_NAME
+              << ", falling back to use the partition list on device.";
+
+    constexpr char RECOVERY_WIPE_ON_DEVICE[] = "/etc/recovery.wipe";
+    if (!android::base::ReadFileToString(RECOVERY_WIPE_ON_DEVICE, &partition_list_content)) {
+      PLOG(ERROR) << "failed to read \"" << RECOVERY_WIPE_ON_DEVICE << "\"";
+      return {};
+    }
+  }
+
+  std::vector<std::string> result;
+  auto lines = android::base::Split(partition_list_content, "\n");
+  for (const auto& line : lines) {
+    auto partition = android::base::Trim(line);
+    // Ignore '#' comment or empty lines.
+    if (android::base::StartsWith(partition, "#") || partition.empty()) {
+      continue;
+    }
+    result.push_back(line);
+  }
+
+  return result;
+}
+
+// Secure-wipes a given partition. It uses BLKSECDISCARD, if supported. Otherwise, it goes with
+// BLKDISCARD (if device supports BLKDISCARDZEROES) or BLKZEROOUT.
+static bool SecureWipePartition(const std::string& partition) {
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(partition.c_str(), O_WRONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open \"" << partition << "\"";
+    return false;
+  }
+
+  uint64_t range[2] = { 0, 0 };
+  if (ioctl(fd, BLKGETSIZE64, &range[1]) == -1 || range[1] == 0) {
+    PLOG(ERROR) << "Failed to get partition size";
+    return false;
+  }
+  LOG(INFO) << "Secure-wiping \"" << partition << "\" from " << range[0] << " to " << range[1];
+
+  LOG(INFO) << "  Trying BLKSECDISCARD...";
+  if (ioctl(fd, BLKSECDISCARD, &range) == -1) {
+    PLOG(WARNING) << "  Failed";
+
+    // Use BLKDISCARD if it zeroes out blocks, otherwise use BLKZEROOUT.
+    unsigned int zeroes;
+    if (ioctl(fd, BLKDISCARDZEROES, &zeroes) == 0 && zeroes != 0) {
+      LOG(INFO) << "  Trying BLKDISCARD...";
+      if (ioctl(fd, BLKDISCARD, &range) == -1) {
+        PLOG(ERROR) << "  Failed";
+        return false;
+      }
+    } else {
+      LOG(INFO) << "  Trying BLKZEROOUT...";
+      if (ioctl(fd, BLKZEROOUT, &range) == -1) {
+        PLOG(ERROR) << "  Failed";
+        return false;
+      }
+    }
+  }
+
+  LOG(INFO) << "  Done";
+  return true;
+}
+
+static std::unique_ptr<Package> ReadWipePackage(size_t wipe_package_size) {
+  if (wipe_package_size == 0) {
+    LOG(ERROR) << "wipe_package_size is zero";
+    return nullptr;
+  }
+
+  std::string wipe_package;
+  if (std::string err_str; !read_wipe_package(&wipe_package, wipe_package_size, &err_str)) {
+    PLOG(ERROR) << "Failed to read wipe package" << err_str;
+    return nullptr;
+  }
+
+  return Package::CreateMemoryPackage(
+      std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr);
+}
+
+// Checks if the wipe package matches expectation. If the check passes, reads the list of
+// partitions to wipe from the package. Checks include
+// 1. verify the package.
+// 2. check metadata (ota-type, pre-device and serial number if having one).
+static bool CheckWipePackage(Package* wipe_package, RecoveryUI* ui) {
+  if (!verify_package(wipe_package, ui)) {
+    LOG(ERROR) << "Failed to verify package";
+    return false;
+  }
+
+  ZipArchiveHandle zip = wipe_package->GetZipArchiveHandle();
+  if (!zip) {
+    LOG(ERROR) << "Failed to get ZipArchiveHandle";
+    return false;
+  }
+
+  std::map<std::string, std::string> metadata;
+  if (!ReadMetadataFromPackage(zip, &metadata)) {
+    LOG(ERROR) << "Failed to parse metadata in the zip file";
+    return false;
+  }
+
+  return CheckPackageMetadata(metadata, OtaType::BRICK);
+}
+
+bool WipeAbDevice(Device* device, size_t wipe_package_size) {
+  auto ui = device->GetUI();
+  ui->SetBackground(RecoveryUI::ERASING);
+  ui->SetProgressType(RecoveryUI::INDETERMINATE);
+
+  auto wipe_package = ReadWipePackage(wipe_package_size);
+  if (!wipe_package) {
+    LOG(ERROR) << "Failed to open wipe package";
+    return false;
+  }
+
+  if (!CheckWipePackage(wipe_package.get(), ui)) {
+    LOG(ERROR) << "Failed to verify wipe package";
+    return false;
+  }
+
+  auto partition_list = GetWipePartitionList(wipe_package.get());
+  if (partition_list.empty()) {
+    LOG(ERROR) << "Empty wipe ab partition list";
+    return false;
+  }
+
+  for (const auto& partition : partition_list) {
+    // Proceed anyway even if it fails to wipe some partition.
+    SecureWipePartition(partition);
+  }
+  return true;
+}
diff --git a/minadbd/Android.bp b/minadbd/Android.bp
new file mode 100644
index 0000000..afd57ad
--- /dev/null
+++ b/minadbd/Android.bp
@@ -0,0 +1,117 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+    name: "minadbd_defaults",
+
+    cflags: [
+        "-DADB_HOST=0",
+        "-Wall",
+        "-Werror",
+    ],
+
+    cpp_std: "experimental",
+
+    include_dirs: [
+        "system/core/adb",
+    ],
+}
+
+// `libminadbd_services` is analogous to the `libadbd_services` for regular `adbd`, but providing
+// the sideload service only.
+cc_library {
+    name: "libminadbd_services",
+    recovery_available: true,
+
+    defaults: [
+        "minadbd_defaults",
+    ],
+
+    srcs: [
+        "fuse_adb_provider.cpp",
+        "minadbd_services.cpp",
+    ],
+
+    static_libs: [
+        "libotautil",
+    ],
+
+    shared_libs: [
+        "libadbd",
+        "libbase",
+        "libcrypto",
+        "libfusesideload",
+    ],
+}
+
+cc_library_headers {
+    name: "libminadbd_headers",
+    recovery_available: true,
+    // TODO create a include dir
+    export_include_dirs: [
+        ".",
+    ],
+}
+
+cc_binary {
+    name: "minadbd",
+    recovery: true,
+
+    defaults: [
+        "minadbd_defaults",
+    ],
+
+    srcs: [
+        "minadbd.cpp",
+    ],
+
+    shared_libs: [
+        "libadbd",
+        "libbase",
+        "libcrypto",
+        "libminadbd_services",
+    ],
+}
+
+cc_test {
+    name: "minadbd_test",
+    isolated: true,
+
+    defaults: [
+        "minadbd_defaults",
+    ],
+
+    srcs: [
+        "fuse_adb_provider_test.cpp",
+        "minadbd_services_test.cpp",
+    ],
+
+    static_libs: [
+        "libminadbd_services",
+        "libfusesideload",
+        "libotautil",
+        "libadbd",
+        "libcrypto",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+    ],
+
+    test_suites: [
+        "device-tests",
+    ],
+}
diff --git a/minadbd/Android.mk b/minadbd/Android.mk
deleted file mode 100644
index 50e3b34..0000000
--- a/minadbd/Android.mk
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright 2005 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-minadbd_cflags := \
-    -Wall -Werror \
-    -DADB_HOST=0 \
-
-# libminadbd (static library)
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    fuse_adb_provider.cpp \
-    minadbd.cpp \
-    minadbd_services.cpp \
-
-LOCAL_MODULE := libminadbd
-LOCAL_CFLAGS := $(minadbd_cflags)
-LOCAL_C_INCLUDES := bootable/recovery system/core/adb
-LOCAL_WHOLE_STATIC_LIBRARIES := libadbd
-LOCAL_STATIC_LIBRARIES := libcrypto libbase
-
-include $(BUILD_STATIC_LIBRARY)
-
-# minadbd_test (native test)
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := minadbd_test
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_SRC_FILES := fuse_adb_provider_test.cpp
-LOCAL_CFLAGS := $(minadbd_cflags)
-LOCAL_C_INCLUDES := $(LOCAL_PATH) system/core/adb
-LOCAL_STATIC_LIBRARIES := \
-    libBionicGtestMain \
-    libminadbd
-LOCAL_SHARED_LIBRARIES := \
-    liblog \
-    libbase \
-    libcutils
-
-include $(BUILD_NATIVE_TEST)
diff --git a/minadbd/fuse_adb_provider.cpp b/minadbd/fuse_adb_provider.cpp
index 9bd3f23..47719b0 100644
--- a/minadbd/fuse_adb_provider.cpp
+++ b/minadbd/fuse_adb_provider.cpp
@@ -18,39 +18,22 @@
 
 #include <errno.h>
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
-#include <functional>
-
 #include "adb.h"
 #include "adb_io.h"
-#include "fuse_sideload.h"
 
-int read_block_adb(const adb_data& ad, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
-  if (!WriteFdFmt(ad.sfd, "%08u", block)) {
+bool FuseAdbDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                                               uint32_t start_block) const {
+  if (!WriteFdFmt(fd_, "%08u", start_block)) {
     fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno));
-    return -EIO;
+    return false;
   }
 
-  if (!ReadFdExactly(ad.sfd, buffer, fetch_size)) {
+  if (!ReadFdExactly(fd_, buffer, fetch_size)) {
     fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno));
-    return -EIO;
+    return false;
   }
 
-  return 0;
-}
-
-int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size) {
-  adb_data ad;
-  ad.sfd = sfd;
-  ad.file_size = file_size;
-  ad.block_size = block_size;
-
-  provider_vtab vtab;
-  vtab.read_block = std::bind(read_block_adb, ad, std::placeholders::_1, std::placeholders::_2,
-                              std::placeholders::_3);
-  vtab.close = [&ad]() { WriteFdExactly(ad.sfd, "DONEDONE"); };
-
-  return run_fuse_sideload(vtab, file_size, block_size);
+  return true;
 }
diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h
index 36d86d5..c5561e5 100644
--- a/minadbd/fuse_adb_provider.h
+++ b/minadbd/fuse_adb_provider.h
@@ -14,19 +14,22 @@
  * limitations under the License.
  */
 
-#ifndef __FUSE_ADB_PROVIDER_H
-#define __FUSE_ADB_PROVIDER_H
+#pragma once
 
 #include <stdint.h>
 
-struct adb_data {
-  int sfd;  // file descriptor for the adb channel
+#include "fuse_provider.h"
 
-  uint64_t file_size;
-  uint32_t block_size;
+// This class reads data from adb server.
+class FuseAdbDataProvider : public FuseDataProvider {
+ public:
+  FuseAdbDataProvider(int fd, uint64_t file_size, uint32_t block_size)
+      : FuseDataProvider(file_size, block_size), fd_(fd) {}
+
+  bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size,
+                            uint32_t start_block) const override;
+
+ private:
+  // The underlying source to read data from (i.e. the one that talks to the host).
+  int fd_;
 };
-
-int read_block_adb(const adb_data& ad, uint32_t block, uint8_t* buffer, uint32_t fetch_size);
-int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size);
-
-#endif
diff --git a/minadbd/fuse_adb_provider_test.cpp b/minadbd/fuse_adb_provider_test.cpp
index 00250e5..0b09712 100644
--- a/minadbd/fuse_adb_provider_test.cpp
+++ b/minadbd/fuse_adb_provider_test.cpp
@@ -21,19 +21,19 @@
 
 #include <string>
 
+#include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
 
 #include "adb_io.h"
 #include "fuse_adb_provider.h"
 
 TEST(fuse_adb_provider, read_block_adb) {
-  adb_data data = {};
-  int sockets[2];
+  android::base::unique_fd device_socket;
+  android::base::unique_fd host_socket;
 
-  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets));
-  data.sfd = sockets[0];
+  ASSERT_TRUE(android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &device_socket, &host_socket));
+  FuseAdbDataProvider data(std::move(device_socket), 0, 0);
 
-  int host_socket = sockets[1];
   fcntl(host_socket, F_SETFL, O_NONBLOCK);
 
   const char expected_data[] = "foobar";
@@ -46,8 +46,8 @@
 
   uint32_t block = 1234U;
   const char expected_block[] = "00001234";
-  ASSERT_EQ(0, read_block_adb(data, block, reinterpret_cast<uint8_t*>(block_data),
-                              sizeof(expected_data) - 1));
+  ASSERT_TRUE(data.ReadBlockAlignedData(reinterpret_cast<uint8_t*>(block_data),
+                                        sizeof(expected_data) - 1, block));
 
   // Check that read_block_adb requested the right block.
   char block_req[sizeof(expected_block)] = {};
@@ -65,26 +65,21 @@
   errno = 0;
   ASSERT_EQ(-1, read(host_socket, &tmp, 1));
   ASSERT_EQ(EWOULDBLOCK, errno);
-
-  close(sockets[0]);
-  close(sockets[1]);
 }
 
 TEST(fuse_adb_provider, read_block_adb_fail_write) {
-  adb_data data = {};
-  int sockets[2];
+  android::base::unique_fd device_socket;
+  android::base::unique_fd host_socket;
 
-  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets));
-  data.sfd = sockets[0];
+  ASSERT_TRUE(android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &device_socket, &host_socket));
+  FuseAdbDataProvider data(std::move(device_socket), 0, 0);
 
-  ASSERT_EQ(0, close(sockets[1]));
+  host_socket.reset();
 
   // write(2) raises SIGPIPE since the reading end has been closed. Ignore the signal to avoid
   // failing the test.
   signal(SIGPIPE, SIG_IGN);
 
   char buf[1];
-  ASSERT_EQ(-EIO, read_block_adb(data, 0, reinterpret_cast<uint8_t*>(buf), 1));
-
-  close(sockets[0]);
+  ASSERT_FALSE(data.ReadBlockAlignedData(reinterpret_cast<uint8_t*>(buf), 1, 0));
 }
diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp
index 349189c..c80d549 100644
--- a/minadbd/minadbd.cpp
+++ b/minadbd/minadbd.cpp
@@ -14,30 +14,62 @@
  * limitations under the License.
  */
 
-#include "minadbd.h"
-
 #include <errno.h>
+#include <fcntl.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <strings.h>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
 
 #include "adb.h"
 #include "adb_auth.h"
 #include "transport.h"
 
-int minadbd_main() {
+#include "minadbd_services.h"
+#include "minadbd_types.h"
+
+using namespace std::string_literals;
+
+int main(int argc, char** argv) {
+  android::base::InitLogging(argv, &android::base::StderrLogger);
+  // TODO(xunchang) implement a command parser
+  if ((argc != 3 && argc != 4) || argv[1] != "--socket_fd"s ||
+      (argc == 4 && argv[3] != "--rescue"s)) {
+    LOG(ERROR) << "minadbd has invalid arguments, argc: " << argc;
+    exit(kMinadbdArgumentsParsingError);
+  }
+
+  int socket_fd;
+  if (!android::base::ParseInt(argv[2], &socket_fd)) {
+    LOG(ERROR) << "Failed to parse int in " << argv[2];
+    exit(kMinadbdArgumentsParsingError);
+  }
+  if (fcntl(socket_fd, F_GETFD, 0) == -1) {
+    PLOG(ERROR) << "Failed to get minadbd socket";
+    exit(kMinadbdSocketIOError);
+  }
+  SetMinadbdSocketFd(socket_fd);
+
+  if (argc == 4) {
+    SetMinadbdRescueMode(true);
+    adb_device_banner = "rescue";
+  } else {
     adb_device_banner = "sideload";
+  }
 
-    signal(SIGPIPE, SIG_IGN);
+  signal(SIGPIPE, SIG_IGN);
 
-    // We can't require authentication for sideloading. http://b/22025550.
-    auth_required = false;
+  // We can't require authentication for sideloading. http://b/22025550.
+  auth_required = false;
 
-    init_transport_registration();
-    usb_init();
+  init_transport_registration();
+  usb_init();
 
-    VLOG(ADB) << "Event loop starting";
-    fdevent_loop();
+  VLOG(ADB) << "Event loop starting";
+  fdevent_loop();
 
-    return 0;
+  return 0;
 }
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
index 043c51a..8f2b71a 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "minadbd_services.h"
+
 #include <errno.h>
 #include <inttypes.h>
 #include <stdio.h>
@@ -21,57 +23,256 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <functional>
+#include <memory>
 #include <string>
+#include <string_view>
 #include <thread>
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/memory.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 
 #include "adb.h"
+#include "adb_unique_fd.h"
+#include "adb_utils.h"
 #include "fdevent.h"
 #include "fuse_adb_provider.h"
+#include "fuse_sideload.h"
+#include "minadbd_types.h"
+#include "services.h"
 #include "sysdeps.h"
 
-static void sideload_host_service(int sfd, const std::string& args) {
-    int file_size;
-    int block_size;
-    if (sscanf(args.c_str(), "%d:%d", &file_size, &block_size) != 2) {
-        printf("bad sideload-host arguments: %s\n", args.c_str());
-        exit(1);
-    }
+static int minadbd_socket = -1;
+static bool rescue_mode = false;
+static std::string sideload_mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT;
 
-    printf("sideload-host file size %d block size %d\n", file_size, block_size);
-
-    int result = run_adb_fuse(sfd, file_size, block_size);
-
-    printf("sideload_host finished\n");
-    exit(result == 0 ? 0 : 1);
+void SetMinadbdSocketFd(int socket_fd) {
+  minadbd_socket = socket_fd;
 }
 
-static int create_service_thread(void (*func)(int, const std::string&), const std::string& args) {
-    int s[2];
-    if (adb_socketpair(s)) {
-        printf("cannot create service socket pair\n");
-        return -1;
-    }
-
-    std::thread([s, func, args]() { func(s[1], args); }).detach();
-
-    VLOG(SERVICES) << "service thread started, " << s[0] << ":" << s[1];
-    return s[0];
+void SetMinadbdRescueMode(bool rescue) {
+  rescue_mode = rescue;
 }
 
-int service_to_fd(const char* name, atransport* /* transport */) {
-  int ret = -1;
+void SetSideloadMountPoint(const std::string& path) {
+  sideload_mount_point = path;
+}
 
-  if (!strncmp(name, "sideload:", 9)) {
-    // this exit status causes recovery to print a special error
-    // message saying to use a newer adb (that supports
-    // sideload-host).
-    exit(3);
-  } else if (!strncmp(name, "sideload-host:", 14)) {
-    std::string arg(name + 14);
-    ret = create_service_thread(sideload_host_service, arg);
+static bool WriteCommandToFd(MinadbdCommand cmd, int fd) {
+  char message[kMinadbdMessageSize];
+  memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix));
+  android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), cmd);
+
+  if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) {
+    PLOG(ERROR) << "Failed to write message " << message;
+    return false;
   }
-  if (ret >= 0) {
-    close_on_exec(ret);
+  return true;
+}
+
+// Blocks and reads the command status from |fd|. Returns false if the received message has a
+// format error.
+static bool WaitForCommandStatus(int fd, MinadbdCommandStatus* status) {
+  char buffer[kMinadbdMessageSize];
+  if (!android::base::ReadFully(fd, buffer, kMinadbdMessageSize)) {
+    PLOG(ERROR) << "Failed to response status from socket";
+    exit(kMinadbdSocketIOError);
   }
-  return ret;
+
+  std::string message(buffer, buffer + kMinadbdMessageSize);
+  if (!android::base::StartsWith(message, kMinadbdStatusPrefix)) {
+    LOG(ERROR) << "Failed to parse status in " << message;
+    return false;
+  }
+
+  *status = android::base::get_unaligned<MinadbdCommandStatus>(
+      message.substr(strlen(kMinadbdStatusPrefix)).c_str());
+  return true;
+}
+
+static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args,
+                                           MinadbdCommandStatus* status) {
+  auto pieces = android::base::Split(args, ":");
+  int64_t file_size;
+  int block_size;
+  if (pieces.size() != 2 || !android::base::ParseInt(pieces[0], &file_size) || file_size <= 0 ||
+      !android::base::ParseInt(pieces[1], &block_size) || block_size <= 0) {
+    LOG(ERROR) << "bad sideload-host arguments: " << args;
+    return kMinadbdHostCommandArgumentError;
+  }
+
+  LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size;
+
+  if (!WriteCommandToFd(MinadbdCommand::kInstall, minadbd_socket)) {
+    return kMinadbdSocketIOError;
+  }
+
+  auto adb_data_reader = std::make_unique<FuseAdbDataProvider>(sfd, file_size, block_size);
+  if (int result = run_fuse_sideload(std::move(adb_data_reader), sideload_mount_point.c_str());
+      result != 0) {
+    LOG(ERROR) << "Failed to start fuse";
+    return kMinadbdFuseStartError;
+  }
+
+  if (!WaitForCommandStatus(minadbd_socket, status)) {
+    return kMinadbdMessageFormatError;
+  }
+
+  // Signal host-side adb to stop. For sideload mode, we always send kMinadbdServicesExitSuccess
+  // (i.e. "DONEDONE") regardless of the install result. For rescue mode, we send failure message on
+  // install error.
+  if (!rescue_mode || *status == MinadbdCommandStatus::kSuccess) {
+    if (!android::base::WriteFully(sfd, kMinadbdServicesExitSuccess,
+                                   strlen(kMinadbdServicesExitSuccess))) {
+      return kMinadbdHostSocketIOError;
+    }
+  } else {
+    if (!android::base::WriteFully(sfd, kMinadbdServicesExitFailure,
+                                   strlen(kMinadbdServicesExitFailure))) {
+      return kMinadbdHostSocketIOError;
+    }
+  }
+
+  return kMinadbdSuccess;
+}
+
+// Sideload service always exits after serving an install command.
+static void SideloadHostService(unique_fd sfd, const std::string& args) {
+  MinadbdCommandStatus status;
+  exit(RunAdbFuseSideload(sfd.get(), args, &status));
+}
+
+// Rescue service waits for the next command after an install command.
+static void RescueInstallHostService(unique_fd sfd, const std::string& args) {
+  MinadbdCommandStatus status;
+  if (auto result = RunAdbFuseSideload(sfd.get(), args, &status); result != kMinadbdSuccess) {
+    exit(result);
+  }
+}
+
+static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) {
+  static const std::unordered_set<std::string> kGetpropAllowedProps = {
+    "ro.build.fingerprint",
+    "ro.build.date.utc",
+  };
+  auto allowed = kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end();
+  if (!allowed) {
+    return;
+  }
+
+  auto result = android::base::GetProperty(prop, "");
+  if (result.empty()) {
+    return;
+  }
+  if (!android::base::WriteFully(sfd, result.data(), result.size())) {
+    exit(kMinadbdHostSocketIOError);
+  }
+}
+
+// Reboots into the given target. We don't reboot directly from minadbd, but going through recovery
+// instead. This allows recovery to finish all the pending works (clear BCB, save logs etc) before
+// the reboot.
+static void RebootHostService(unique_fd /* sfd */, const std::string& target) {
+  MinadbdCommand command;
+  if (target == "bootloader") {
+    command = MinadbdCommand::kRebootBootloader;
+  } else if (target == "rescue") {
+    command = MinadbdCommand::kRebootRescue;
+  } else if (target == "recovery") {
+    command = MinadbdCommand::kRebootRecovery;
+  } else if (target == "fastboot") {
+    command = MinadbdCommand::kRebootFastboot;
+  } else {
+    command = MinadbdCommand::kRebootAndroid;
+  }
+  if (!WriteCommandToFd(command, minadbd_socket)) {
+    exit(kMinadbdSocketIOError);
+  }
+  MinadbdCommandStatus status;
+  if (!WaitForCommandStatus(minadbd_socket, &status)) {
+    exit(kMinadbdMessageFormatError);
+  }
+}
+
+static void WipeDeviceService(unique_fd fd, const std::string& args) {
+  auto pieces = android::base::Split(args, ":");
+  if (pieces.size() != 2 || pieces[0] != "userdata") {
+    LOG(ERROR) << "Failed to parse wipe device command arguments " << args;
+    exit(kMinadbdHostCommandArgumentError);
+  }
+
+  size_t message_size;
+  if (!android::base::ParseUint(pieces[1], &message_size) ||
+      message_size < strlen(kMinadbdServicesExitSuccess)) {
+    LOG(ERROR) << "Failed to parse wipe device message size in " << args;
+    exit(kMinadbdHostCommandArgumentError);
+  }
+
+  WriteCommandToFd(MinadbdCommand::kWipeData, minadbd_socket);
+  MinadbdCommandStatus status;
+  if (!WaitForCommandStatus(minadbd_socket, &status)) {
+    exit(kMinadbdMessageFormatError);
+  }
+
+  std::string response = (status == MinadbdCommandStatus::kSuccess) ? kMinadbdServicesExitSuccess
+                                                                    : kMinadbdServicesExitFailure;
+  response += std::string(message_size - response.size(), '\0');
+  if (!android::base::WriteFully(fd, response.c_str(), response.size())) {
+    exit(kMinadbdHostSocketIOError);
+  }
+}
+
+unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) {
+  // Common services that are supported both in sideload and rescue modes.
+  if (android::base::ConsumePrefix(&name, "reboot:")) {
+    // "reboot:<target>", where target must be one of the following.
+    std::string args(name);
+    if (args.empty() || args == "bootloader" || args == "rescue" || args == "recovery" ||
+        args == "fastboot") {
+      return create_service_thread("reboot",
+                                   std::bind(RebootHostService, std::placeholders::_1, args));
+    }
+    return unique_fd{};
+  }
+
+  // Rescue-specific services.
+  if (rescue_mode) {
+    if (android::base::ConsumePrefix(&name, "rescue-install:")) {
+      // rescue-install:<file-size>:<block-size>
+      std::string args(name);
+      return create_service_thread(
+          "rescue-install", std::bind(RescueInstallHostService, std::placeholders::_1, args));
+    } else if (android::base::ConsumePrefix(&name, "rescue-getprop:")) {
+      // rescue-getprop:<prop>
+      std::string args(name);
+      return create_service_thread(
+          "rescue-getprop", std::bind(RescueGetpropHostService, std::placeholders::_1, args));
+    } else if (android::base::ConsumePrefix(&name, "rescue-wipe:")) {
+      // rescue-wipe:target:<message-size>
+      std::string args(name);
+      return create_service_thread("rescue-wipe",
+                                   std::bind(WipeDeviceService, std::placeholders::_1, args));
+    }
+
+    return unique_fd{};
+  }
+
+  // Sideload-specific services.
+  if (name.starts_with("sideload:")) {
+    // This exit status causes recovery to print a special error message saying to use a newer adb
+    // (that supports sideload-host).
+    exit(kMinadbdAdbVersionError);
+  } else if (android::base::ConsumePrefix(&name, "sideload-host:")) {
+    // sideload-host:<file-size>:<block-size>
+    std::string args(name);
+    return create_service_thread("sideload-host",
+                                 std::bind(SideloadHostService, std::placeholders::_1, args));
+  }
+  return unique_fd{};
 }
diff --git a/minadbd/minadbd.h b/minadbd/minadbd_services.h
similarity index 79%
copy from minadbd/minadbd.h
copy to minadbd/minadbd_services.h
index 3570a5d..5575c6b 100644
--- a/minadbd/minadbd.h
+++ b/minadbd/minadbd_services.h
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef MINADBD_H__
-#define MINADBD_H__
+#pragma once
 
-int minadbd_main();
+#include <string>
 
-#endif
+void SetMinadbdSocketFd(int socket_fd);
+
+void SetMinadbdRescueMode(bool);
+
+void SetSideloadMountPoint(const std::string& path);
diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp
new file mode 100644
index 0000000..f878737
--- /dev/null
+++ b/minadbd/minadbd_services_test.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <strings.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+#include "adb.h"
+#include "adb_io.h"
+#include "fuse_adb_provider.h"
+#include "fuse_sideload.h"
+#include "minadbd_services.h"
+#include "minadbd_types.h"
+#include "socket.h"
+
+class MinadbdServicesTest : public ::testing::Test {
+ protected:
+  static constexpr int EXIT_TIME_OUT = 10;
+
+  void SetUp() override {
+    ASSERT_TRUE(
+        android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &minadbd_socket_, &recovery_socket_));
+    SetMinadbdSocketFd(minadbd_socket_);
+    SetSideloadMountPoint(mount_point_.path);
+
+    package_path_ = std::string(mount_point_.path) + "/" + FUSE_SIDELOAD_HOST_FILENAME;
+    exit_flag_ = std::string(mount_point_.path) + "/" + FUSE_SIDELOAD_HOST_EXIT_FLAG;
+
+    signal(SIGPIPE, SIG_IGN);
+  }
+
+  void TearDown() override {
+    // Umount in case the test fails. Ignore the result.
+    umount(mount_point_.path);
+
+    signal(SIGPIPE, SIG_DFL);
+  }
+
+  void ReadAndCheckCommandMessage(int fd, MinadbdCommand expected_command) {
+    std::vector<uint8_t> received(kMinadbdMessageSize, '\0');
+    ASSERT_TRUE(android::base::ReadFully(fd, received.data(), kMinadbdMessageSize));
+
+    std::vector<uint8_t> expected(kMinadbdMessageSize, '\0');
+    memcpy(expected.data(), kMinadbdCommandPrefix, strlen(kMinadbdCommandPrefix));
+    memcpy(expected.data() + strlen(kMinadbdCommandPrefix), &expected_command,
+           sizeof(expected_command));
+    ASSERT_EQ(expected, received);
+  }
+
+  void WaitForFusePath() {
+    constexpr int TIME_OUT = 10;
+    for (int i = 0; i < TIME_OUT; ++i) {
+      struct stat sb;
+      if (stat(package_path_.c_str(), &sb) == 0) {
+        return;
+      }
+
+      if (errno == ENOENT) {
+        sleep(1);
+        continue;
+      }
+      FAIL() << "Timed out waiting for the fuse-provided package " << strerror(errno);
+    }
+  }
+
+  void StatExitFlagAndExitProcess(int exit_code) {
+    struct stat sb;
+    if (stat(exit_flag_.c_str(), &sb) != 0) {
+      PLOG(ERROR) << "Failed to stat " << exit_flag_;
+    }
+
+    exit(exit_code);
+  }
+
+  void WriteMinadbdCommandStatus(MinadbdCommandStatus status) {
+    std::string status_message(kMinadbdMessageSize, '\0');
+    memcpy(status_message.data(), kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix));
+    memcpy(status_message.data() + strlen(kMinadbdStatusPrefix), &status, sizeof(status));
+    ASSERT_TRUE(
+        android::base::WriteFully(recovery_socket_, status_message.data(), kMinadbdMessageSize));
+  }
+
+  void ExecuteCommandAndWaitForExit(const std::string& command) {
+    unique_fd fd = daemon_service_to_fd(command, nullptr);
+    ASSERT_NE(-1, fd);
+    sleep(EXIT_TIME_OUT);
+  }
+
+  android::base::unique_fd minadbd_socket_;
+  android::base::unique_fd recovery_socket_;
+
+  TemporaryDir mount_point_;
+  std::string package_path_;
+  std::string exit_flag_;
+};
+
+TEST_F(MinadbdServicesTest, SideloadHostService_wrong_size_argument) {
+  ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:abc:4096"),
+              ::testing::ExitedWithCode(kMinadbdHostCommandArgumentError), "");
+}
+
+TEST_F(MinadbdServicesTest, SideloadHostService_wrong_block_size) {
+  ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:10:20"),
+              ::testing::ExitedWithCode(kMinadbdFuseStartError), "");
+}
+
+TEST_F(MinadbdServicesTest, SideloadHostService_broken_minadbd_socket) {
+  SetMinadbdSocketFd(-1);
+  ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:4096:4096"),
+              ::testing::ExitedWithCode(kMinadbdSocketIOError), "");
+}
+
+TEST_F(MinadbdServicesTest, SideloadHostService_broken_recovery_socket) {
+  recovery_socket_.reset();
+  ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:4096:4096"),
+              ::testing::ExitedWithCode(kMinadbdSocketIOError), "");
+}
+
+TEST_F(MinadbdServicesTest, SideloadHostService_wrong_command_format) {
+  auto test_body = [&](const std::string& command) {
+    unique_fd fd = daemon_service_to_fd(command, nullptr);
+    ASSERT_NE(-1, fd);
+    WaitForFusePath();
+    ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall);
+
+    struct stat sb;
+    ASSERT_EQ(0, stat(exit_flag_.c_str(), &sb));
+    ASSERT_TRUE(android::base::WriteStringToFd("12345678", recovery_socket_));
+    sleep(EXIT_TIME_OUT);
+  };
+
+  ASSERT_EXIT(test_body("sideload-host:4096:4096"),
+              ::testing::ExitedWithCode(kMinadbdMessageFormatError), "");
+}
+
+TEST_F(MinadbdServicesTest, SideloadHostService_read_data_from_fuse) {
+  auto test_body = [&]() {
+    std::vector<uint8_t> content(4096, 'a');
+    // Start a new process instead of a thread to read from the package mounted by FUSE. Because
+    // the test may not exit and report failures correctly when the thread blocks by a syscall.
+    pid_t pid = fork();
+    if (pid == 0) {
+      WaitForFusePath();
+      android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(package_path_.c_str(), O_RDONLY)));
+      // Do not use assertion here because we want to stat the exit flag and exit the process.
+      // Otherwise the test will wait for the time out instead of failing immediately.
+      if (fd == -1) {
+        PLOG(ERROR) << "Failed to open " << package_path_;
+        StatExitFlagAndExitProcess(1);
+      }
+      std::vector<uint8_t> content_from_fuse(4096);
+      if (!android::base::ReadFully(fd, content_from_fuse.data(), 4096)) {
+        PLOG(ERROR) << "Failed to read from " << package_path_;
+        StatExitFlagAndExitProcess(1);
+      }
+      if (content_from_fuse != content) {
+        LOG(ERROR) << "Content read from fuse doesn't match with the expected value";
+        StatExitFlagAndExitProcess(1);
+      }
+      StatExitFlagAndExitProcess(0);
+    }
+
+    unique_fd fd = daemon_service_to_fd("sideload-host:4096:4096", nullptr);
+    ASSERT_NE(-1, fd);
+    ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall);
+
+    // Mimic the response from adb host.
+    std::string adb_message(8, '\0');
+    ASSERT_TRUE(android::base::ReadFully(fd, adb_message.data(), 8));
+    ASSERT_EQ(android::base::StringPrintf("%08u", 0), adb_message);
+    ASSERT_TRUE(android::base::WriteFully(fd, content.data(), 4096));
+
+    // Check that we read the correct data from fuse.
+    int child_status;
+    waitpid(pid, &child_status, 0);
+    ASSERT_TRUE(WIFEXITED(child_status));
+    ASSERT_EQ(0, WEXITSTATUS(child_status));
+
+    WriteMinadbdCommandStatus(MinadbdCommandStatus::kSuccess);
+
+    // TODO(xunchang) check if adb host-side receives "DONEDONE", there's a race condition between
+    // receiving the message and exit of test body (by detached thread in minadbd service).
+    exit(kMinadbdSuccess);
+  };
+
+  ASSERT_EXIT(test_body(), ::testing::ExitedWithCode(kMinadbdSuccess), "");
+}
diff --git a/minadbd/minadbd_types.h b/minadbd/minadbd_types.h
new file mode 100644
index 0000000..99fd45e
--- /dev/null
+++ b/minadbd/minadbd_types.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+// The message between recovery and minadbd is 8 bytes in size unless the length is explicitly
+// specified. Both the command and status has the format |prefix(4 bytes) + encoded enum(4 bytes)|.
+constexpr size_t kMinadbdMessageSize = 8;
+constexpr char const kMinadbdCommandPrefix[] = "COMD";
+constexpr char const kMinadbdStatusPrefix[] = "STAT";
+
+enum MinadbdErrorCode : int {
+  kMinadbdSuccess = 0,
+  kMinadbdArgumentsParsingError = 1,
+  kMinadbdSocketIOError = 2,
+  kMinadbdMessageFormatError = 3,
+  kMinadbdAdbVersionError = 4,
+  kMinadbdHostCommandArgumentError = 5,
+  kMinadbdFuseStartError = 6,
+  kMinadbdUnsupportedCommandError = 7,
+  kMinadbdCommandExecutionError = 8,
+  kMinadbdErrorUnknown = 9,
+  kMinadbdHostSocketIOError = 10,
+};
+
+enum class MinadbdCommandStatus : uint32_t {
+  kSuccess = 0,
+  kFailure = 1,
+};
+
+enum class MinadbdCommand : uint32_t {
+  kInstall = 0,
+  kUiPrint = 1,
+  kRebootAndroid = 2,
+  kRebootBootloader = 3,
+  kRebootFastboot = 4,
+  kRebootRecovery = 5,
+  kRebootRescue = 6,
+  kWipeCache = 7,
+  kWipeData = 8,
+
+  // Last but invalid command.
+  kError,
+};
+
+static_assert(kMinadbdMessageSize == sizeof(kMinadbdCommandPrefix) - 1 + sizeof(MinadbdCommand));
+static_assert(kMinadbdMessageSize ==
+              sizeof(kMinadbdStatusPrefix) - 1 + sizeof(MinadbdCommandStatus));
diff --git a/minui/Android.bp b/minui/Android.bp
new file mode 100644
index 0000000..fff3a8e
--- /dev/null
+++ b/minui/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+    name: "libminui",
+    recovery_available: true,
+
+    defaults: [
+        "recovery_defaults",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    srcs: [
+        "events.cpp",
+        "graphics.cpp",
+        "graphics_adf.cpp",
+        "graphics_drm.cpp",
+        "graphics_fbdev.cpp",
+        "resources.cpp",
+    ],
+
+    whole_static_libs: [
+        "libadf",
+        "libdrm",
+        "libsync",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libpng",
+        "libz",
+    ],
+}
diff --git a/minui/Android.mk b/minui/Android.mk
deleted file mode 100644
index ae1552b..0000000
--- a/minui/Android.mk
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright (C) 2007 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-# libminui (static library)
-# ===============================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    events.cpp \
-    graphics.cpp \
-    graphics_adf.cpp \
-    graphics_drm.cpp \
-    graphics_fbdev.cpp \
-    resources.cpp \
-
-LOCAL_WHOLE_STATIC_LIBRARIES := \
-    libadf \
-    libdrm \
-    libsync_recovery
-
-LOCAL_STATIC_LIBRARIES := \
-    libpng \
-    libbase
-
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-
-LOCAL_MODULE := libminui
-
-# This used to compare against values in double-quotes (which are just
-# ordinary characters in this context).  Strip double-quotes from the
-# value so that either will work.
-
-ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),ABGR_8888)
-  LOCAL_CFLAGS += -DRECOVERY_ABGR
-endif
-ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888)
-  LOCAL_CFLAGS += -DRECOVERY_RGBX
-endif
-ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),BGRA_8888)
-  LOCAL_CFLAGS += -DRECOVERY_BGRA
-endif
-
-ifneq ($(TARGET_RECOVERY_OVERSCAN_PERCENT),)
-  LOCAL_CFLAGS += -DOVERSCAN_PERCENT=$(TARGET_RECOVERY_OVERSCAN_PERCENT)
-else
-  LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0
-endif
-
-ifneq ($(TARGET_RECOVERY_DEFAULT_ROTATION),)
-  LOCAL_CFLAGS += -DDEFAULT_ROTATION=$(TARGET_RECOVERY_DEFAULT_ROTATION)
-else
-  LOCAL_CFLAGS += -DDEFAULT_ROTATION=ROTATION_NONE
-endif
-
-include $(BUILD_STATIC_LIBRARY)
-
-# libminui (shared library)
-# ===============================
-# Used by OEMs for factory test images.
-include $(CLEAR_VARS)
-LOCAL_MODULE := libminui
-LOCAL_WHOLE_STATIC_LIBRARIES += libminui
-LOCAL_SHARED_LIBRARIES := \
-    libpng \
-    libbase
-
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-include $(BUILD_SHARED_LIBRARY)
diff --git a/minui/events.cpp b/minui/events.cpp
index 2894c3b..7d0250e 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -23,144 +23,148 @@
 #include <string.h>
 #include <sys/epoll.h>
 #include <sys/ioctl.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 #include <functional>
+#include <memory>
+
+#include <android-base/unique_fd.h>
 
 #include "minui/minui.h"
 
-#define MAX_DEVICES 16
-#define MAX_MISC_FDS 16
+constexpr size_t MAX_DEVICES = 16;
+constexpr size_t MAX_MISC_FDS = 16;
 
-#define BITS_PER_LONG (sizeof(unsigned long) * 8)
-#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG)
+constexpr size_t BITS_PER_LONG = sizeof(unsigned long) * 8;
+constexpr size_t BITS_TO_LONGS(size_t bits) {
+  return ((bits + BITS_PER_LONG - 1) / BITS_PER_LONG);
+}
 
-struct fd_info {
-  int fd;
+struct FdInfo {
+  android::base::unique_fd fd;
   ev_callback cb;
 };
 
-static int g_epoll_fd;
-static epoll_event polledevents[MAX_DEVICES + MAX_MISC_FDS];
-static int npolledevents;
+static android::base::unique_fd g_epoll_fd;
+static epoll_event g_polled_events[MAX_DEVICES + MAX_MISC_FDS];
+static int g_polled_events_count;
 
-static fd_info ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS];
+static FdInfo ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS];
 
-static unsigned ev_count = 0;
-static unsigned ev_dev_count = 0;
-static unsigned ev_misc_count = 0;
+static size_t g_ev_count = 0;
+static size_t g_ev_dev_count = 0;
+static size_t g_ev_misc_count = 0;
 
 static bool test_bit(size_t bit, unsigned long* array) { // NOLINT
-    return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
+  return (array[bit / BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
 }
 
 int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
-  g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
-  if (g_epoll_fd == -1) {
+  g_epoll_fd.reset();
+
+  android::base::unique_fd epoll_fd(epoll_create1(EPOLL_CLOEXEC));
+  if (epoll_fd == -1) {
     return -1;
   }
 
-  bool epollctlfail = false;
-  DIR* dir = opendir("/dev/input");
-  if (dir != nullptr) {
-    dirent* de;
-    while ((de = readdir(dir))) {
-      if (strncmp(de->d_name, "event", 5)) continue;
-      int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
-      if (fd == -1) continue;
+  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/dev/input"), closedir);
+  if (!dir) {
+    return -1;
+  }
 
-      // Use unsigned long to match ioctl's parameter type.
-      unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
+  bool epoll_ctl_failed = false;
+  dirent* de;
+  while ((de = readdir(dir.get())) != nullptr) {
+    if (strncmp(de->d_name, "event", 5)) continue;
+    android::base::unique_fd fd(openat(dirfd(dir.get()), de->d_name, O_RDONLY | O_CLOEXEC));
+    if (fd == -1) continue;
 
-      // Read the evbits of the input device.
-      if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
-        close(fd);
-        continue;
-      }
+    // Use unsigned long to match ioctl's parameter type.
+    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
 
-      // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
-      // allowed if allow_touch_inputs is set.
-      if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
-        if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
-          close(fd);
-          continue;
-        }
-      }
-
-      epoll_event ev;
-      ev.events = EPOLLIN | EPOLLWAKEUP;
-      ev.data.ptr = &ev_fdinfo[ev_count];
-      if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
-        close(fd);
-        epollctlfail = true;
-        continue;
-      }
-
-      ev_fdinfo[ev_count].fd = fd;
-      ev_fdinfo[ev_count].cb = std::move(input_cb);
-      ev_count++;
-      ev_dev_count++;
-      if (ev_dev_count == MAX_DEVICES) break;
+    // Read the evbits of the input device.
+    if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+      continue;
     }
 
-    closedir(dir);
+    // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
+    // allowed if allow_touch_inputs is set.
+    if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
+      if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
+        continue;
+      }
+    }
+
+    epoll_event ev;
+    ev.events = EPOLLIN | EPOLLWAKEUP;
+    ev.data.ptr = &ev_fdinfo[g_ev_count];
+    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
+      epoll_ctl_failed = true;
+      continue;
+    }
+
+    ev_fdinfo[g_ev_count].fd.reset(fd.release());
+    ev_fdinfo[g_ev_count].cb = input_cb;
+    g_ev_count++;
+    g_ev_dev_count++;
+    if (g_ev_dev_count == MAX_DEVICES) break;
   }
 
-  if (epollctlfail && !ev_count) {
-    close(g_epoll_fd);
-    g_epoll_fd = -1;
+  if (epoll_ctl_failed && !g_ev_count) {
     return -1;
   }
 
+  g_epoll_fd.reset(epoll_fd.release());
   return 0;
 }
 
 int ev_get_epollfd(void) {
-    return g_epoll_fd;
+  return g_epoll_fd.get();
 }
 
-int ev_add_fd(int fd, ev_callback cb) {
-  if (ev_misc_count == MAX_MISC_FDS || cb == NULL) {
+int ev_add_fd(android::base::unique_fd&& fd, ev_callback cb) {
+  if (g_ev_misc_count == MAX_MISC_FDS || cb == nullptr) {
     return -1;
   }
 
   epoll_event ev;
   ev.events = EPOLLIN | EPOLLWAKEUP;
-  ev.data.ptr = static_cast<void*>(&ev_fdinfo[ev_count]);
+  ev.data.ptr = static_cast<void*>(&ev_fdinfo[g_ev_count]);
   int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev);
   if (!ret) {
-    ev_fdinfo[ev_count].fd = fd;
-    ev_fdinfo[ev_count].cb = std::move(cb);
-    ev_count++;
-    ev_misc_count++;
+    ev_fdinfo[g_ev_count].fd.reset(fd.release());
+    ev_fdinfo[g_ev_count].cb = std::move(cb);
+    g_ev_count++;
+    g_ev_misc_count++;
   }
 
   return ret;
 }
 
 void ev_exit(void) {
-    while (ev_count > 0) {
-        close(ev_fdinfo[--ev_count].fd);
-    }
-    ev_misc_count = 0;
-    ev_dev_count = 0;
-    close(g_epoll_fd);
+  while (g_ev_count > 0) {
+    ev_fdinfo[--g_ev_count].fd.reset();
+  }
+  g_ev_misc_count = 0;
+  g_ev_dev_count = 0;
+  g_epoll_fd.reset();
 }
 
 int ev_wait(int timeout) {
-    npolledevents = epoll_wait(g_epoll_fd, polledevents, ev_count, timeout);
-    if (npolledevents <= 0) {
-        return -1;
-    }
-    return 0;
+  g_polled_events_count = epoll_wait(g_epoll_fd, g_polled_events, g_ev_count, timeout);
+  if (g_polled_events_count <= 0) {
+    return -1;
+  }
+  return 0;
 }
 
 void ev_dispatch(void) {
-  for (int n = 0; n < npolledevents; n++) {
-    fd_info* fdi = static_cast<fd_info*>(polledevents[n].data.ptr);
+  for (int n = 0; n < g_polled_events_count; n++) {
+    FdInfo* fdi = static_cast<FdInfo*>(g_polled_events[n].data.ptr);
     const ev_callback& cb = fdi->cb;
     if (cb) {
-      cb(fdi->fd, polledevents[n].events);
+      cb(fdi->fd, g_polled_events[n].events);
     }
   }
 }
@@ -180,7 +184,7 @@
   unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];    // NOLINT
   unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];  // NOLINT
 
-  for (size_t i = 0; i < ev_dev_count; ++i) {
+  for (size_t i = 0; i < g_ev_dev_count; ++i) {
     memset(ev_bits, 0, sizeof(ev_bits));
     memset(key_bits, 0, sizeof(key_bits));
 
@@ -205,37 +209,36 @@
 }
 
 void ev_iterate_available_keys(const std::function<void(int)>& f) {
-    // 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
+  // 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
 
-    for (size_t i = 0; i < ev_dev_count; ++i) {
-        memset(ev_bits, 0, sizeof(ev_bits));
-        memset(key_bits, 0, sizeof(key_bits));
+  for (size_t i = 0; i < g_ev_dev_count; ++i) {
+    memset(ev_bits, 0, sizeof(ev_bits));
+    memset(key_bits, 0, sizeof(key_bits));
 
-        // Does this device even have keys?
-        if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
-            continue;
-        }
-        if (!test_bit(EV_KEY, ev_bits)) {
-            continue;
-        }
-
-        int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits);
-        if (rc == -1) {
-            continue;
-        }
-
-        for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
-            if (test_bit(key_code, key_bits)) {
-                f(key_code);
-            }
-        }
+    // Does this device even have keys?
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+      continue;
     }
+    if (!test_bit(EV_KEY, ev_bits)) {
+      continue;
+    }
+
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits) == -1) {
+      continue;
+    }
+
+    for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
+      if (test_bit(key_code, key_bits)) {
+        f(key_code);
+      }
+    }
+  }
 }
 
 void ev_iterate_touch_inputs(const std::function<void(int)>& action) {
-  for (size_t i = 0; i < ev_dev_count; ++i) {
+  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
     if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
diff --git a/minui/font_10x18.h b/minui/font_10x18.h
deleted file mode 100644
index 30dfb9c..0000000
--- a/minui/font_10x18.h
+++ /dev/null
@@ -1,214 +0,0 @@
-struct {
-  unsigned width;
-  unsigned height;
-  unsigned char_width;
-  unsigned char_height;
-  unsigned char rundata[2973];
-} font = {
-  .width = 960,
-  .height = 18,
-  .char_width = 10,
-  .char_height = 18,
-  .rundata = {
-0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x55,0x82,0x06,0x82,0x02,0x82,0x10,0x82,
-0x11,0x83,0x08,0x82,0x0a,0x82,0x04,0x82,0x46,0x82,0x08,0x82,0x07,0x84,0x06,
-0x84,0x0a,0x81,0x03,0x88,0x04,0x84,0x04,0x88,0x04,0x84,0x06,0x84,0x1e,0x81,
-0x0e,0x81,0x0a,0x84,0x06,0x84,0x07,0x82,0x05,0x85,0x07,0x84,0x04,0x86,0x04,
-0x88,0x02,0x88,0x04,0x84,0x04,0x82,0x04,0x82,0x02,0x88,0x05,0x86,0x01,0x82,
-0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,
-0x86,0x06,0x84,0x04,0x86,0x06,0x84,0x04,0x88,0x02,0x82,0x04,0x82,0x02,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
-0x88,0x03,0x86,0x0e,0x86,0x06,0x82,0x11,0x82,0x10,0x82,0x18,0x82,0x0f,0x84,
-0x0d,0x82,0x1c,0x82,0x09,0x84,0x7f,0x16,0x84,0x05,0x82,0x05,0x84,0x07,0x83,
-0x02,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x03,0x86,0x04,
-0x83,0x02,0x82,0x03,0x82,0x01,0x82,0x07,0x82,0x09,0x82,0x06,0x82,0x3e,0x82,
-0x04,0x84,0x06,0x83,0x06,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x03,
-0x82,0x09,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,
-0x1c,0x82,0x0e,0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x05,0x84,0x04,
-0x82,0x02,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,
-0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x04,
-0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x03,0x82,0x02,0x82,
-0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02,
-0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
-0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x03,0x82,0x08,0x82,0x0c,
-0x82,0x05,0x84,0x11,0x82,0x0f,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,
-0x1c,0x82,0x0b,0x82,0x7f,0x15,0x82,0x08,0x82,0x08,0x82,0x05,0x82,0x01,0x82,
-0x01,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x02,0x82,0x01,
-0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x07,0x82,
-0x08,0x82,0x08,0x82,0x3d,0x82,0x03,0x82,0x02,0x82,0x04,0x84,0x05,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x06,0x83,0x03,0x82,0x08,0x82,0x04,0x81,0x09,0x82,
-0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x1a,0x82,0x10,0x82,0x06,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,
-0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,
-0x02,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
-0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x08,0x82,0x0c,0x82,0x04,0x82,0x02,0x82,
-0x11,0x82,0x0e,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,0x0b,0x82,0x0b,
-0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x05,0x82,
-0x02,0x83,0x1a,0x82,0x07,0x81,0x02,0x81,0x07,0x82,0x01,0x82,0x02,0x82,0x01,
-0x82,0x05,0x82,0x01,0x84,0x04,0x82,0x01,0x82,0x07,0x82,0x08,0x82,0x08,0x82,
-0x06,0x82,0x02,0x82,0x06,0x82,0x28,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,
-0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x84,0x03,0x82,0x08,0x82,
-0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x19,0x82,0x12,0x82,0x05,
-0x82,0x04,0x82,0x02,0x82,0x02,0x84,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,
-0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,
-0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,0x02,0x83,
-0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,0x05,0x82,0x04,0x82,0x02,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,
-0x82,0x04,0x82,0x09,0x82,0x0b,0x82,0x03,0x82,0x04,0x82,0x20,0x82,0x18,0x82,
-0x0e,0x82,0x10,0x82,0x0b,0x82,0x0b,0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,
-0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x88,0x01,0x82,0x01,0x82,0x06,0x83,
-0x01,0x82,0x04,0x84,0x08,0x81,0x08,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,
-0x82,0x28,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x08,0x82,0x04,0x82,
-0x01,0x82,0x03,0x82,0x08,0x82,0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x18,0x82,0x06,0x88,0x06,0x82,0x04,0x82,0x04,0x82,0x02,0x82,0x01,0x85,
-0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,
-0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,
-0x02,0x82,0x04,0x82,0x08,0x88,0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,
-0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,
-0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x84,0x06,
-0x84,0x08,0x82,0x05,0x82,0x09,0x82,0x0b,0x82,0x2b,0x82,0x18,0x82,0x0e,0x82,
-0x10,0x82,0x1c,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x26,
-0x82,0x11,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x09,0x82,0x06,0x82,0x12,0x82,
-0x0a,0x82,0x06,0x84,0x07,0x82,0x27,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,
-0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,0x83,0x04,0x82,0x01,0x83,
-0x08,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x83,0x07,0x83,0x05,
-0x82,0x16,0x82,0x08,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,
-0x02,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,
-0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,
-0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,
-0x0a,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,
-0x82,0x04,0x84,0x06,0x84,0x08,0x82,0x05,0x82,0x0a,0x82,0x0a,0x82,0x23,0x85,
-0x03,0x82,0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x84,0x04,0x86,0x05,
-0x85,0x01,0x81,0x02,0x82,0x01,0x83,0x05,0x84,0x09,0x84,0x02,0x82,0x03,0x82,
-0x06,0x82,0x05,0x81,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x83,0x06,0x84,0x04,
-0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x02,0x82,0x01,0x84,0x04,0x86,0x03,0x86,
-0x04,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x03,0x87,0x05,0x82,0x08,0x82,0x08,0x82,0x26,0x82,
-0x11,0x82,0x01,0x82,0x04,0x86,0x07,0x82,0x05,0x83,0x12,0x82,0x0a,0x82,0x04,
-0x88,0x02,0x88,0x0c,0x88,0x10,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,
-0x06,0x83,0x04,0x82,0x03,0x82,0x03,0x83,0x02,0x82,0x03,0x83,0x02,0x82,0x07,
-0x82,0x06,0x84,0x05,0x82,0x02,0x83,0x05,0x83,0x07,0x83,0x04,0x82,0x18,0x82,
-0x06,0x82,0x04,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x86,0x04,
-0x82,0x08,0x82,0x04,0x82,0x02,0x86,0x04,0x86,0x04,0x82,0x02,0x84,0x02,0x88,
-0x05,0x82,0x0a,0x82,0x03,0x85,0x05,0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,
-0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,
-0x04,0x82,0x02,0x82,0x03,0x82,0x05,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x03,
-0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x08,0x82,0x08,0x82,
-0x06,0x82,0x0a,0x82,0x0a,0x82,0x22,0x82,0x03,0x82,0x02,0x83,0x02,0x82,0x04,
-0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x02,0x82,0x05,0x82,0x06,0x82,
-0x03,0x83,0x02,0x83,0x02,0x82,0x06,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,0x07,
-0x82,0x05,0x88,0x02,0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,
-0x04,0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,
-0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,
-0x03,0x82,0x04,0x82,0x08,0x82,0x02,0x84,0x09,0x82,0x09,0x84,0x23,0x82,0x11,
-0x82,0x01,0x82,0x06,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x01,0x82,0x11,0x82,
-0x0a,0x82,0x06,0x84,0x07,0x82,0x26,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x08,
-0x83,0x09,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x02,0x82,0x04,0x82,0x05,0x82,
-0x06,0x82,0x02,0x82,0x05,0x83,0x01,0x82,0x17,0x82,0x16,0x82,0x06,0x82,0x05,
-0x82,0x01,0x82,0x01,0x82,0x02,0x88,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,
-0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
-0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82,
-0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x86,0x04,0x82,0x04,0x82,0x02,
-0x86,0x09,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,
-0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,0x82,0x27,
-0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
-0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,
-0x82,0x01,0x82,0x08,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,
-0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x07,
-0x82,0x0a,0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,
-0x04,0x84,0x04,0x82,0x04,0x82,0x07,0x82,0x06,0x82,0x08,0x82,0x08,0x82,0x26,
-0x82,0x0f,0x88,0x05,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x02,0x82,0x01,0x82,
-0x0d,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,0x82,0x26,0x82,0x05,0x82,0x04,
-0x82,0x05,0x82,0x07,0x82,0x0c,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,
-0x05,0x82,0x05,0x82,0x04,0x82,0x08,0x82,0x18,0x82,0x14,0x82,0x07,0x82,0x05,
-0x82,0x01,0x84,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,
-0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
-0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82,
-0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,
-0x82,0x02,0x82,0x0a,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,
-0x01,0x82,0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,
-0x82,0x22,0x87,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x88,
-0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,
-0x84,0x09,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x86,0x05,
-0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,
-0x05,0x82,0x05,0x82,0x04,0x82,0x06,0x82,0x07,0x82,0x08,0x82,0x08,0x82,0x26,
-0x82,0x10,0x82,0x01,0x82,0x07,0x82,0x01,0x82,0x04,0x82,0x01,0x83,0x02,0x82,
-0x03,0x83,0x0f,0x82,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x25,0x82,0x07,
-0x82,0x02,0x82,0x06,0x82,0x06,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x09,0x82,
-0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,0x82,0x08,0x82,0x19,0x82,0x05,
-0x88,0x05,0x82,0x08,0x82,0x05,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x02,0x82,
-0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
-0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,
-0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x03,0x82,0x09,0x82,0x05,0x82,
-0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x06,
-0x82,0x06,0x82,0x08,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,0x04,0x82,0x02,0x82,
-0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,0x82,0x06,0x82,0x03,
-0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x85,0x08,0x82,0x05,0x82,
-0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,0x06,0x82,0x04,0x82,
-0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,
-0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x38,0x82,0x01,0x82,0x04,0x82,0x01,0x82,
-0x01,0x82,0x04,0x84,0x01,0x82,0x01,0x82,0x03,0x82,0x10,0x82,0x08,0x82,0x30,
-0x83,0x06,0x82,0x07,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x08,0x82,0x04,0x82,
-0x07,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,
-0x82,0x03,0x81,0x04,0x82,0x1a,0x82,0x10,0x82,0x10,0x82,0x08,0x82,0x04,0x82,
-0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,
-0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,
-0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,
-0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x02,0x84,0x02,0x82,0x03,0x82,0x03,0x82,
-0x04,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x05,0x83,0x02,0x83,0x03,
-0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,
-0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,
-0x82,0x07,0x85,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,
-0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,
-0x06,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x04,0x84,0x04,
-0x82,0x04,0x82,0x04,0x82,0x09,0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x82,
-0x01,0x82,0x05,0x86,0x04,0x82,0x01,0x82,0x01,0x82,0x01,0x83,0x01,0x84,0x10,
-0x82,0x06,0x82,0x1d,0x83,0x11,0x83,0x05,0x82,0x09,0x84,0x07,0x82,0x05,0x82,
-0x09,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
-0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x06,0x83,0x07,0x83,0x09,0x82,
-0x0e,0x82,0x0a,0x82,0x06,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,
-0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x09,0x82,
-0x02,0x83,0x02,0x82,0x04,0x82,0x05,0x82,0x06,0x82,0x01,0x82,0x04,0x82,0x04,
-0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,
-0x03,0x82,0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x06,
-0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
-0x05,0x82,0x05,0x82,0x09,0x82,0x0d,0x82,0x07,0x82,0x21,0x82,0x04,0x82,0x02,
-0x83,0x02,0x82,0x04,0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x03,0x82,
-0x04,0x82,0x06,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x03,
-0x82,0x06,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x03,0x82,
-0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x07,0x82,0x04,
-0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x02,0x83,0x05,0x82,0x05,0x88,0x03,0x82,
-0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x0a,0x82,0x08,0x82,0x08,0x82,0x26,
-0x82,0x1c,0x82,0x06,0x82,0x02,0x83,0x03,0x84,0x02,0x82,0x10,0x82,0x04,0x82,
-0x1e,0x83,0x11,0x83,0x05,0x82,0x0a,0x82,0x05,0x88,0x02,0x88,0x04,0x84,0x09,
-0x82,0x05,0x84,0x06,0x84,0x05,0x82,0x09,0x84,0x06,0x84,0x07,0x83,0x07,0x83,
-0x0a,0x81,0x0e,0x81,0x0b,0x82,0x07,0x85,0x03,0x82,0x04,0x82,0x02,0x86,0x06,
-0x84,0x04,0x86,0x04,0x88,0x02,0x82,0x0a,0x84,0x01,0x81,0x02,0x82,0x04,0x82,
-0x02,0x88,0x04,0x83,0x05,0x82,0x04,0x82,0x02,0x88,0x02,0x82,0x04,0x82,0x02,
-0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x0a,0x85,0x03,0x82,0x04,0x82,0x04,0x84,
-0x07,0x82,0x07,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
-0x82,0x05,0x88,0x03,0x86,0x09,0x82,0x03,0x86,0x22,0x85,0x01,0x81,0x02,0x82,
-0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x85,0x05,0x82,0x07,0x86,0x03,
-0x82,0x04,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x88,0x02,0x82,
-0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x83,0x06,
-0x83,0x01,0x82,0x03,0x82,0x08,0x86,0x06,0x84,0x05,0x83,0x01,0x82,0x05,0x82,
-0x06,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x83,0x01,0x82,0x03,0x87,0x06,
-0x84,0x05,0x82,0x05,0x84,0x7f,0x15,0x83,0x7f,0x14,0x83,0x7f,0x5e,0x82,0x7f,
-0x05,0x89,0x47,0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x4e,
-0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,0x82,0x04,0x82,0x17,0x82,0x03,0x82,
-0x34,0x82,0x0e,0x82,0x48,0x82,0x04,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,
-0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x49,0x82,0x02,0x82,
-0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0c,0x86,0x19,0x85,0x35,0x82,0x0e,0x82,0x4a,
-0x84,0x3f,
-0x00,
-  }
-};
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index 56f471b..4d1f9b2 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -23,41 +23,57 @@
 
 #include <memory>
 
-#include "font_10x18.h"
+#include <android-base/properties.h>
+
 #include "graphics_adf.h"
 #include "graphics_drm.h"
 #include "graphics_fbdev.h"
 #include "minui/minui.h"
 
-static GRFont* gr_font = NULL;
+static GRFont* gr_font = nullptr;
 static MinuiBackend* gr_backend = nullptr;
 
-static int overscan_percent = OVERSCAN_PERCENT;
 static int overscan_offset_x = 0;
 static int overscan_offset_y = 0;
 
 static uint32_t gr_current = ~0;
 static constexpr uint32_t alpha_mask = 0xff000000;
 
-static GRSurface* gr_draw = NULL;
-static GRRotation rotation = ROTATION_NONE;
+// gr_draw is owned by backends.
+static GRSurface* gr_draw = nullptr;
+static GRRotation rotation = GRRotation::NONE;
+static PixelFormat pixel_format = PixelFormat::UNKNOWN;
 
 static bool outside(int x, int y) {
-  return x < 0 || x >= (rotation % 2 ? gr_draw->height : gr_draw->width) || y < 0 ||
-         y >= (rotation % 2 ? gr_draw->width : gr_draw->height);
+  auto swapped = (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT);
+  return x < 0 || x >= (swapped ? gr_draw->height : gr_draw->width) || y < 0 ||
+         y >= (swapped ? gr_draw->width : gr_draw->height);
 }
 
 const GRFont* gr_sys_font() {
   return gr_font;
 }
 
+PixelFormat gr_pixel_format() {
+  return pixel_format;
+}
+
 int gr_measure(const GRFont* font, const char* s) {
+  if (font == nullptr) {
+    return -1;
+  }
+
   return font->char_width * strlen(s);
 }
 
-void gr_font_size(const GRFont* font, int* x, int* y) {
+int gr_font_size(const GRFont* font, int* x, int* y) {
+  if (font == nullptr) {
+    return -1;
+  }
+
   *x = font->char_width;
   *y = font->char_height;
+  return 0;
 }
 
 // Blends gr_current onto pix value, assumes alpha as most significant byte.
@@ -78,47 +94,56 @@
   return (out_r & 0xff) | (out_g & 0xff00) | (out_b & 0xff0000) | (gr_current & 0xff000000);
 }
 
-// increments pixel pointer right, with current rotation.
+// Increments pixel pointer right, with current rotation.
 static void incr_x(uint32_t** p, int row_pixels) {
-  if (rotation % 2) {
-    *p = *p + (rotation == 1 ? 1 : -1) * row_pixels;
-  } else {
-    *p = *p + (rotation ? -1 : 1);
+  if (rotation == GRRotation::LEFT) {
+    *p = *p - row_pixels;
+  } else if (rotation == GRRotation::RIGHT) {
+    *p = *p + row_pixels;
+  } else if (rotation == GRRotation::DOWN) {
+    *p = *p - 1;
+  } else {  // GRRotation::NONE
+    *p = *p + 1;
   }
 }
 
-// increments pixel pointer down, with current rotation.
+// Increments pixel pointer down, with current rotation.
 static void incr_y(uint32_t** p, int row_pixels) {
-  if (rotation % 2) {
-    *p = *p + (rotation == 1 ? -1 : 1);
-  } else {
-    *p = *p + (rotation ? -1 : 1) * row_pixels;
+  if (rotation == GRRotation::LEFT) {
+    *p = *p + 1;
+  } else if (rotation == GRRotation::RIGHT) {
+    *p = *p - 1;
+  } else if (rotation == GRRotation::DOWN) {
+    *p = *p - row_pixels;
+  } else {  // GRRotation::NONE
+    *p = *p + row_pixels;
   }
 }
 
-// returns pixel pointer at given coordinates with rotation adjustment.
-static uint32_t* pixel_at(GRSurface* surf, int x, int y, int row_pixels) {
+// Returns pixel pointer at given coordinates with rotation adjustment.
+static uint32_t* PixelAt(GRSurface* surface, int x, int y, int row_pixels) {
   switch (rotation) {
-    case ROTATION_NONE:
-      return reinterpret_cast<uint32_t*>(surf->data) + y * row_pixels + x;
-    case ROTATION_RIGHT:
-      return reinterpret_cast<uint32_t*>(surf->data) + x * row_pixels + (surf->width - y);
-    case ROTATION_DOWN:
-      return reinterpret_cast<uint32_t*>(surf->data) + (surf->height - 1 - y) * row_pixels +
-             (surf->width - 1 - x);
-    case ROTATION_LEFT:
-      return reinterpret_cast<uint32_t*>(surf->data) + (surf->height - 1 - x) * row_pixels + y;
+    case GRRotation::NONE:
+      return reinterpret_cast<uint32_t*>(surface->data()) + y * row_pixels + x;
+    case GRRotation::RIGHT:
+      return reinterpret_cast<uint32_t*>(surface->data()) + x * row_pixels + (surface->width - y);
+    case GRRotation::DOWN:
+      return reinterpret_cast<uint32_t*>(surface->data()) + (surface->height - 1 - y) * row_pixels +
+             (surface->width - 1 - x);
+    case GRRotation::LEFT:
+      return reinterpret_cast<uint32_t*>(surface->data()) + (surface->height - 1 - x) * row_pixels +
+             y;
     default:
-      printf("invalid rotation %d", rotation);
+      printf("invalid rotation %d", static_cast<int>(rotation));
   }
   return nullptr;
 }
 
-static void text_blend(uint8_t* src_p, int src_row_bytes, uint32_t* dst_p, int dst_row_pixels,
-                       int width, int height) {
+static void TextBlend(const uint8_t* src_p, int src_row_bytes, uint32_t* dst_p, int dst_row_pixels,
+                      int width, int height) {
   uint8_t alpha_current = static_cast<uint8_t>((alpha_mask & gr_current) >> 24);
   for (int j = 0; j < height; ++j) {
-    uint8_t* sx = src_p;
+    const uint8_t* sx = src_p;
     uint32_t* px = dst_p;
     for (int i = 0; i < width; ++i, incr_x(&px, dst_row_pixels)) {
       uint8_t a = *sx++;
@@ -152,19 +177,19 @@
     }
 
     int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
-    uint8_t* src_p = font->texture->data + ((ch - ' ') * font->char_width) +
-                     (bold ? font->char_height * font->texture->row_bytes : 0);
-    uint32_t* dst_p = pixel_at(gr_draw, x, y, row_pixels);
+    const uint8_t* src_p = font->texture->data() + ((ch - ' ') * font->char_width) +
+                           (bold ? font->char_height * font->texture->row_bytes : 0);
+    uint32_t* dst_p = PixelAt(gr_draw, x, y, row_pixels);
 
-    text_blend(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width,
-               font->char_height);
+    TextBlend(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width,
+              font->char_height);
 
     x += font->char_width;
   }
 }
 
-void gr_texticon(int x, int y, GRSurface* icon) {
-  if (icon == NULL) return;
+void gr_texticon(int x, int y, const GRSurface* icon) {
+  if (icon == nullptr) return;
 
   if (icon->pixel_bytes != 1) {
     printf("gr_texticon: source has wrong format\n");
@@ -177,19 +202,18 @@
   if (outside(x, y) || outside(x + icon->width - 1, y + icon->height - 1)) return;
 
   int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
-  uint8_t* src_p = icon->data;
-  uint32_t* dst_p = pixel_at(gr_draw, x, y, row_pixels);
-
-  text_blend(src_p, icon->row_bytes, dst_p, row_pixels, icon->width, icon->height);
+  const uint8_t* src_p = icon->data();
+  uint32_t* dst_p = PixelAt(gr_draw, x, y, row_pixels);
+  TextBlend(src_p, icon->row_bytes, dst_p, row_pixels, icon->width, icon->height);
 }
 
 void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
   uint32_t r32 = r, g32 = g, b32 = b, a32 = a;
-#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
-  gr_current = (a32 << 24) | (r32 << 16) | (g32 << 8) | b32;
-#else
-  gr_current = (a32 << 24) | (b32 << 16) | (g32 << 8) | r32;
-#endif
+  if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
+    gr_current = (a32 << 24) | (r32 << 16) | (g32 << 8) | b32;
+  } else {
+    gr_current = (a32 << 24) | (b32 << 16) | (g32 << 8) | r32;
+  }
 }
 
 void gr_clear() {
@@ -197,9 +221,9 @@
       (gr_current & 0xff) == ((gr_current >> 16) & 0xff) &&
       (gr_current & 0xff) == ((gr_current >> 24) & 0xff) &&
       gr_draw->row_bytes == gr_draw->width * gr_draw->pixel_bytes) {
-    memset(gr_draw->data, gr_current & 0xff, gr_draw->height * gr_draw->row_bytes);
+    memset(gr_draw->data(), gr_current & 0xff, gr_draw->height * gr_draw->row_bytes);
   } else {
-    uint32_t* px = reinterpret_cast<uint32_t*>(gr_draw->data);
+    uint32_t* px = reinterpret_cast<uint32_t*>(gr_draw->data());
     int row_diff = gr_draw->row_bytes / gr_draw->pixel_bytes - gr_draw->width;
     for (int y = 0; y < gr_draw->height; ++y) {
       for (int x = 0; x < gr_draw->width; ++x) {
@@ -220,7 +244,7 @@
   if (outside(x1, y1) || outside(x2 - 1, y2 - 1)) return;
 
   int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
-  uint32_t* p = pixel_at(gr_draw, x1, y1, row_pixels);
+  uint32_t* p = PixelAt(gr_draw, x1, y1, row_pixels);
   uint8_t alpha = static_cast<uint8_t>(((gr_current & alpha_mask) >> 24));
   if (alpha > 0) {
     for (int y = y1; y < y2; ++y) {
@@ -234,8 +258,8 @@
   }
 }
 
-void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
-  if (source == NULL) return;
+void gr_blit(const GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
+  if (source == nullptr) return;
 
   if (gr_draw->pixel_bytes != source->pixel_bytes) {
     printf("gr_blit: source has wrong format\n");
@@ -247,14 +271,15 @@
 
   if (outside(dx, dy) || outside(dx + w - 1, dy + h - 1)) return;
 
-  if (rotation) {
+  if (rotation != GRRotation::NONE) {
     int src_row_pixels = source->row_bytes / source->pixel_bytes;
     int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
-    uint32_t* src_py = reinterpret_cast<uint32_t*>(source->data) + sy * source->row_bytes / 4 + sx;
-    uint32_t* dst_py = pixel_at(gr_draw, dx, dy, row_pixels);
+    const uint32_t* src_py =
+        reinterpret_cast<const uint32_t*>(source->data()) + sy * source->row_bytes / 4 + sx;
+    uint32_t* dst_py = PixelAt(gr_draw, dx, dy, row_pixels);
 
     for (int y = 0; y < h; y += 1) {
-      uint32_t* src_px = src_py;
+      const uint32_t* src_px = src_py;
       uint32_t* dst_px = dst_py;
       for (int x = 0; x < w; x += 1) {
         *dst_px = *src_px++;
@@ -264,11 +289,10 @@
       incr_y(&dst_py, row_pixels);
     }
   } else {
-    unsigned char* src_p = source->data + sy * source->row_bytes + sx * source->pixel_bytes;
-    unsigned char* dst_p = gr_draw->data + dy * gr_draw->row_bytes + dx * gr_draw->pixel_bytes;
+    const uint8_t* src_p = source->data() + sy * source->row_bytes + sx * source->pixel_bytes;
+    uint8_t* dst_p = gr_draw->data() + dy * gr_draw->row_bytes + dx * gr_draw->pixel_bytes;
 
-    int i;
-    for (i = 0; i < h; ++i) {
+    for (int i = 0; i < h; ++i) {
       memcpy(dst_p, src_p, w * source->pixel_bytes);
       src_p += source->row_bytes;
       dst_p += gr_draw->row_bytes;
@@ -276,15 +300,15 @@
   }
 }
 
-unsigned int gr_get_width(GRSurface* surface) {
-  if (surface == NULL) {
+unsigned int gr_get_width(const GRSurface* surface) {
+  if (surface == nullptr) {
     return 0;
   }
   return surface->width;
 }
 
-unsigned int gr_get_height(GRSurface* surface) {
-  if (surface == NULL) {
+unsigned int gr_get_height(const GRSurface* surface) {
+  if (surface == nullptr) {
     return 0;
   }
   return surface->height;
@@ -313,42 +337,28 @@
   return 0;
 }
 
-static void gr_init_font(void) {
-  int res = gr_init_font("font", &gr_font);
-  if (res == 0) {
-    return;
-  }
-
-  printf("failed to read font: res=%d\n", res);
-
-  // fall back to the compiled-in font.
-  gr_font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
-  gr_font->texture = static_cast<GRSurface*>(malloc(sizeof(*gr_font->texture)));
-  gr_font->texture->width = font.width;
-  gr_font->texture->height = font.height;
-  gr_font->texture->row_bytes = font.width;
-  gr_font->texture->pixel_bytes = 1;
-
-  unsigned char* bits = static_cast<unsigned char*>(malloc(font.width * font.height));
-  gr_font->texture->data = bits;
-
-  unsigned char data;
-  unsigned char* in = font.rundata;
-  while ((data = *in++)) {
-    memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f);
-    bits += (data & 0x7f);
-  }
-
-  gr_font->char_width = font.char_width;
-  gr_font->char_height = font.char_height;
-}
-
 void gr_flip() {
   gr_draw = gr_backend->Flip();
 }
 
 int gr_init() {
-  gr_init_font();
+  // pixel_format needs to be set before loading any resources or initializing backends.
+  std::string format = android::base::GetProperty("ro.minui.pixel_format", "");
+  if (format == "ABGR_8888") {
+    pixel_format = PixelFormat::ABGR;
+  } else if (format == "RGBX_8888") {
+    pixel_format = PixelFormat::RGBX;
+  } else if (format == "BGRA_8888") {
+    pixel_format = PixelFormat::BGRA;
+  } else {
+    pixel_format = PixelFormat::UNKNOWN;
+  }
+
+  int ret = gr_init_font("font", &gr_font);
+  if (ret != 0) {
+    printf("Failed to init font: %d, continuing graphic backend initialization without font file\n",
+           ret);
+  }
 
   auto backend = std::unique_ptr<MinuiBackend>{ std::make_unique<MinuiBackendAdf>() };
   gr_draw = backend->Init();
@@ -369,13 +379,28 @@
 
   gr_backend = backend.release();
 
+  int overscan_percent = android::base::GetIntProperty("ro.minui.overscan_percent", 0);
   overscan_offset_x = gr_draw->width * overscan_percent / 100;
   overscan_offset_y = gr_draw->height * overscan_percent / 100;
 
   gr_flip();
   gr_flip();
+  if (!gr_draw) {
+    printf("gr_init: gr_draw becomes nullptr after gr_flip\n");
+    return -1;
+  }
 
-  gr_rotate(DEFAULT_ROTATION);
+  std::string rotation_str =
+      android::base::GetProperty("ro.minui.default_rotation", "ROTATION_NONE");
+  if (rotation_str == "ROTATION_RIGHT") {
+    gr_rotate(GRRotation::RIGHT);
+  } else if (rotation_str == "ROTATION_DOWN") {
+    gr_rotate(GRRotation::DOWN);
+  } else if (rotation_str == "ROTATION_LEFT") {
+    gr_rotate(GRRotation::LEFT);
+  } else {  // "ROTATION_NONE" or unknown string
+    gr_rotate(GRRotation::NONE);
+  }
 
   if (gr_draw->pixel_bytes != 4) {
     printf("gr_init: Only 4-byte pixel formats supported\n");
@@ -386,16 +411,22 @@
 
 void gr_exit() {
   delete gr_backend;
+  gr_backend = nullptr;
+
+  delete gr_font;
+  gr_font = nullptr;
 }
 
 int gr_fb_width() {
-  return rotation % 2 ? gr_draw->height - 2 * overscan_offset_y
-                      : gr_draw->width - 2 * overscan_offset_x;
+  return (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT)
+             ? gr_draw->height - 2 * overscan_offset_y
+             : gr_draw->width - 2 * overscan_offset_x;
 }
 
 int gr_fb_height() {
-  return rotation % 2 ? gr_draw->width - 2 * overscan_offset_x
-                      : gr_draw->height - 2 * overscan_offset_y;
+  return (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT)
+             ? gr_draw->width - 2 * overscan_offset_x
+             : gr_draw->height - 2 * overscan_offset_y;
 }
 
 void gr_fb_blank(bool blank) {
diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp
index a59df00..10cd607 100644
--- a/minui/graphics_adf.cpp
+++ b/minui/graphics_adf.cpp
@@ -20,6 +20,7 @@
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/mman.h>
 #include <unistd.h>
 
@@ -28,51 +29,60 @@
 
 #include "minui/minui.h"
 
-MinuiBackendAdf::MinuiBackendAdf()
-    : intf_fd(-1), dev(), current_surface(0), n_surfaces(0), surfaces() {}
-
-int MinuiBackendAdf::SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf) {
-  *surf = {};
-  surf->fence_fd = -1;
-  surf->fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format,
-                                               &surf->offset, &surf->pitch);
-  if (surf->fd < 0) {
-    return surf->fd;
+GRSurfaceAdf::~GRSurfaceAdf() {
+  if (mmapped_buffer_) {
+    munmap(mmapped_buffer_, pitch * height);
   }
-
-  surf->width = mode->hdisplay;
-  surf->height = mode->vdisplay;
-  surf->row_bytes = surf->pitch;
-  surf->pixel_bytes = (format == DRM_FORMAT_RGB565) ? 2 : 4;
-
-  surf->data = static_cast<uint8_t*>(
-      mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset));
-  if (surf->data == MAP_FAILED) {
-    int saved_errno = errno;
-    close(surf->fd);
-    return -saved_errno;
+  if (fence_fd != -1) {
+    close(fence_fd);
   }
-
-  return 0;
+  if (fd != -1) {
+    close(fd);
+  }
 }
 
+std::unique_ptr<GRSurfaceAdf> GRSurfaceAdf::Create(int intf_fd, const drm_mode_modeinfo* mode,
+                                                   __u32 format, int* err) {
+  __u32 offset;
+  __u32 pitch;
+  auto fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format,
+                                              &offset, &pitch);
+
+  if (fd < 0) {
+    *err = fd;
+    return nullptr;
+  }
+
+  std::unique_ptr<GRSurfaceAdf> surf = std::unique_ptr<GRSurfaceAdf>(
+      new GRSurfaceAdf(mode->hdisplay, mode->vdisplay, pitch, (format == DRM_FORMAT_RGB565 ? 2 : 4),
+                       offset, pitch, fd));
+
+  auto mmapped =
+      mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset);
+  if (mmapped == MAP_FAILED) {
+    *err = -errno;
+    return nullptr;
+  }
+  surf->mmapped_buffer_ = static_cast<uint8_t*>(mmapped);
+  return surf;
+}
+
+MinuiBackendAdf::MinuiBackendAdf() : intf_fd(-1), dev(), current_surface(0), n_surfaces(0) {}
+
 int MinuiBackendAdf::InterfaceInit() {
   adf_interface_data intf_data;
-  int err = adf_get_interface_data(intf_fd, &intf_data);
-  if (err < 0) return err;
+  if (int err = adf_get_interface_data(intf_fd, &intf_data); err < 0) return err;
 
-  int ret = 0;
-  err = SurfaceInit(&intf_data.current_mode, &surfaces[0]);
-  if (err < 0) {
-    fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err));
-    ret = err;
+  int result = 0;
+  surfaces[0] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result);
+  if (!surfaces[0]) {
+    fprintf(stderr, "Failed to allocate surface 0: %s\n", strerror(-result));
     goto done;
   }
 
-  err = SurfaceInit(&intf_data.current_mode, &surfaces[1]);
-  if (err < 0) {
-    fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err));
-    surfaces[1] = {};
+  surfaces[1] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result);
+  if (!surfaces[1]) {
+    fprintf(stderr, "Failed to allocate surface 1: %s\n", strerror(-result));
     n_surfaces = 1;
   } else {
     n_surfaces = 2;
@@ -80,7 +90,7 @@
 
 done:
   adf_free_interface_data(&intf_data);
-  return ret;
+  return result;
 }
 
 int MinuiBackendAdf::DeviceInit(adf_device* dev) {
@@ -91,7 +101,7 @@
   err = adf_device_attach(dev, eng_id, intf_id);
   if (err < 0 && err != -EALREADY) return err;
 
-  intf_fd = adf_interface_open(dev, intf_id, O_RDWR);
+  intf_fd = adf_interface_open(dev, intf_id, O_RDWR | O_CLOEXEC);
   if (intf_fd < 0) return intf_fd;
 
   err = InterfaceInit();
@@ -104,15 +114,16 @@
 }
 
 GRSurface* MinuiBackendAdf::Init() {
-#if defined(RECOVERY_ABGR)
-  format = DRM_FORMAT_ABGR8888;
-#elif defined(RECOVERY_BGRA)
-  format = DRM_FORMAT_BGRA8888;
-#elif defined(RECOVERY_RGBX)
-  format = DRM_FORMAT_RGBX8888;
-#else
-  format = DRM_FORMAT_RGB565;
-#endif
+  PixelFormat pixel_format = gr_pixel_format();
+  if (pixel_format == PixelFormat::ABGR) {
+    format = DRM_FORMAT_ABGR8888;
+  } else if (pixel_format == PixelFormat::BGRA) {
+    format = DRM_FORMAT_BGRA8888;
+  } else if (pixel_format == PixelFormat::RGBX) {
+    format = DRM_FORMAT_RGBX8888;
+  } else {
+    format = DRM_FORMAT_RGB565;
+  }
 
   adf_id_t* dev_ids = nullptr;
   ssize_t n_dev_ids = adf_devices(&dev_ids);
@@ -152,12 +163,12 @@
 }
 
 void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) {
-  static constexpr unsigned int warningTimeout = 3000;
+  static constexpr unsigned int kWarningTimeout = 3000;
 
   if (surf == nullptr) return;
 
   if (surf->fence_fd >= 0) {
-    int err = sync_wait(surf->fence_fd, warningTimeout);
+    int err = sync_wait(surf->fence_fd, kWarningTimeout);
     if (err < 0) {
       perror("adf sync fence wait error\n");
     }
@@ -168,31 +179,22 @@
 }
 
 GRSurface* MinuiBackendAdf::Flip() {
-  GRSurfaceAdf* surf = &surfaces[current_surface];
+  const auto& surf = surfaces[current_surface];
 
   int fence_fd = adf_interface_simple_post(intf_fd, eng_id, surf->width, surf->height, format,
                                            surf->fd, surf->offset, surf->pitch, -1);
   if (fence_fd >= 0) surf->fence_fd = fence_fd;
 
   current_surface = (current_surface + 1) % n_surfaces;
-  Sync(&surfaces[current_surface]);
-  return &surfaces[current_surface];
+  Sync(surfaces[current_surface].get());
+  return surfaces[current_surface].get();
 }
 
 void MinuiBackendAdf::Blank(bool blank) {
   adf_interface_blank(intf_fd, blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON);
 }
 
-void MinuiBackendAdf::SurfaceDestroy(GRSurfaceAdf* surf) {
-  munmap(surf->data, surf->pitch * surf->height);
-  close(surf->fence_fd);
-  close(surf->fd);
-}
-
 MinuiBackendAdf::~MinuiBackendAdf() {
   adf_device_close(&dev);
-  for (unsigned int i = 0; i < n_surfaces; i++) {
-    SurfaceDestroy(&surfaces[i]);
-  }
   if (intf_fd >= 0) close(intf_fd);
 }
diff --git a/minui/graphics_adf.h b/minui/graphics_adf.h
index 2f019ed..79d8d2a 100644
--- a/minui/graphics_adf.h
+++ b/minui/graphics_adf.h
@@ -14,45 +14,63 @@
  * limitations under the License.
  */
 
-#ifndef _GRAPHICS_ADF_H_
-#define _GRAPHICS_ADF_H_
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <memory>
 
 #include <adf/adf.h>
 
 #include "graphics.h"
+#include "minui/minui.h"
 
 class GRSurfaceAdf : public GRSurface {
- private:
-  int fence_fd;
-  int fd;
-  __u32 offset;
-  __u32 pitch;
+ public:
+  ~GRSurfaceAdf() override;
 
+  static std::unique_ptr<GRSurfaceAdf> Create(int intf_fd, const drm_mode_modeinfo* mode,
+                                              __u32 format, int* err);
+
+  uint8_t* data() override {
+    return mmapped_buffer_;
+  }
+
+ private:
   friend class MinuiBackendAdf;
+
+  GRSurfaceAdf(size_t width, size_t height, size_t row_bytes, size_t pixel_bytes, __u32 offset,
+               __u32 pitch, int fd)
+      : GRSurface(width, height, row_bytes, pixel_bytes), offset(offset), pitch(pitch), fd(fd) {}
+
+  const __u32 offset;
+  const __u32 pitch;
+
+  int fd;
+  int fence_fd{ -1 };
+  uint8_t* mmapped_buffer_{ nullptr };
 };
 
 class MinuiBackendAdf : public MinuiBackend {
  public:
+  MinuiBackendAdf();
+  ~MinuiBackendAdf() override;
   GRSurface* Init() override;
   GRSurface* Flip() override;
   void Blank(bool) override;
-  ~MinuiBackendAdf() override;
-  MinuiBackendAdf();
 
  private:
-  int SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf);
   int InterfaceInit();
   int DeviceInit(adf_device* dev);
-  void SurfaceDestroy(GRSurfaceAdf* surf);
   void Sync(GRSurfaceAdf* surf);
 
   int intf_fd;
   adf_id_t eng_id;
   __u32 format;
   adf_device dev;
-  unsigned int current_surface;
-  unsigned int n_surfaces;
-  GRSurfaceAdf surfaces[2];
+  size_t current_surface;
+  size_t n_surfaces;
+  std::unique_ptr<GRSurfaceAdf> surfaces[2];
 };
-
-#endif  // _GRAPHICS_ADF_H_
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
index e7d4b38..7b2eed1 100644
--- a/minui/graphics_drm.cpp
+++ b/minui/graphics_drm.cpp
@@ -17,78 +17,44 @@
 #include "graphics_drm.h"
 
 #include <fcntl.h>
+#include <poll.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/mman.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <memory>
+
+#include <android-base/macros.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
 #include <drm_fourcc.h>
 #include <xf86drm.h>
 #include <xf86drmMode.h>
 
 #include "minui/minui.h"
 
-#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
-
-MinuiBackendDrm::MinuiBackendDrm()
-    : GRSurfaceDrms(), main_monitor_crtc(nullptr), main_monitor_connector(nullptr), drm_fd(-1) {}
-
-void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) {
-  if (crtc) {
-    drmModeSetCrtc(drm_fd, crtc->crtc_id,
-                   0,         // fb_id
-                   0, 0,      // x,y
-                   nullptr,   // connectors
-                   0,         // connector_count
-                   nullptr);  // mode
-  }
-}
-
-void MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface) {
-  int32_t ret = drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0,  // x,y
-                               &main_monitor_connector->connector_id,
-                               1,  // connector_count
-                               &main_monitor_crtc->mode);
-
-  if (ret) {
-    printf("drmModeSetCrtc failed ret=%d\n", ret);
-  }
-}
-
-void MinuiBackendDrm::Blank(bool blank) {
-  if (blank) {
-    DrmDisableCrtc(drm_fd, main_monitor_crtc);
-  } else {
-    DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]);
-  }
-}
-
-void MinuiBackendDrm::DrmDestroySurface(GRSurfaceDrm* surface) {
-  if (!surface) return;
-
-  if (surface->data) {
-    munmap(surface->data, surface->row_bytes * surface->height);
+GRSurfaceDrm::~GRSurfaceDrm() {
+  if (mmapped_buffer_) {
+    munmap(mmapped_buffer_, row_bytes * height);
   }
 
-  if (surface->fb_id) {
-    int ret = drmModeRmFB(drm_fd, surface->fb_id);
-    if (ret) {
-      printf("drmModeRmFB failed ret=%d\n", ret);
+  if (fb_id) {
+    if (drmModeRmFB(drm_fd_, fb_id) != 0) {
+      perror("Failed to drmModeRmFB");
+      // Falling through to free other resources.
     }
   }
 
-  if (surface->handle) {
+  if (handle) {
     drm_gem_close gem_close = {};
-    gem_close.handle = surface->handle;
+    gem_close.handle = handle;
 
-    int ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
-    if (ret) {
-      printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret);
+    if (drmIoctl(drm_fd_, DRM_IOCTL_GEM_CLOSE, &gem_close) != 0) {
+      perror("Failed to DRM_IOCTL_GEM_CLOSE");
     }
   }
-
-  delete surface;
 }
 
 static int drm_format_to_bpp(uint32_t format) {
@@ -108,20 +74,22 @@
   }
 }
 
-GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) {
-  GRSurfaceDrm* surface = new GRSurfaceDrm;
-  *surface = {};
-
+std::unique_ptr<GRSurfaceDrm> GRSurfaceDrm::Create(int drm_fd, int width, int height) {
   uint32_t format;
-#if defined(RECOVERY_ABGR)
-  format = DRM_FORMAT_RGBA8888;
-#elif defined(RECOVERY_BGRA)
-  format = DRM_FORMAT_ARGB8888;
-#elif defined(RECOVERY_RGBX)
-  format = DRM_FORMAT_XBGR8888;
-#else
-  format = DRM_FORMAT_RGB565;
-#endif
+  PixelFormat pixel_format = gr_pixel_format();
+  // PixelFormat comes in byte order, whereas DRM_FORMAT_* uses little-endian
+  // (external/libdrm/include/drm/drm_fourcc.h). Note that although drm_fourcc.h also defines a
+  // macro of DRM_FORMAT_BIG_ENDIAN, it doesn't seem to be actually supported (see the discussion
+  // in https://lists.freedesktop.org/archives/amd-gfx/2017-May/008560.html).
+  if (pixel_format == PixelFormat::ABGR) {
+    format = DRM_FORMAT_RGBA8888;
+  } else if (pixel_format == PixelFormat::BGRA) {
+    format = DRM_FORMAT_ARGB8888;
+  } else if (pixel_format == PixelFormat::RGBX) {
+    format = DRM_FORMAT_XBGR8888;
+  } else {
+    format = DRM_FORMAT_RGB565;
+  }
 
   drm_mode_create_dumb create_dumb = {};
   create_dumb.height = height;
@@ -129,53 +97,74 @@
   create_dumb.bpp = drm_format_to_bpp(format);
   create_dumb.flags = 0;
 
-  int ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
-  if (ret) {
-    printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n", ret);
-    DrmDestroySurface(surface);
+  if (drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb) != 0) {
+    perror("Failed to DRM_IOCTL_MODE_CREATE_DUMB");
     return nullptr;
   }
-  surface->handle = create_dumb.handle;
+
+  // Cannot use std::make_unique to access non-public ctor.
+  auto surface = std::unique_ptr<GRSurfaceDrm>(new GRSurfaceDrm(
+      width, height, create_dumb.pitch, create_dumb.bpp / 8, drm_fd, create_dumb.handle));
 
   uint32_t handles[4], pitches[4], offsets[4];
 
   handles[0] = surface->handle;
   pitches[0] = create_dumb.pitch;
   offsets[0] = 0;
-
-  ret =
-      drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &(surface->fb_id), 0);
-  if (ret) {
-    printf("drmModeAddFB2 failed ret=%d\n", ret);
-    DrmDestroySurface(surface);
+  if (drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &surface->fb_id, 0) !=
+      0) {
+    perror("Failed to drmModeAddFB2");
     return nullptr;
   }
 
   drm_mode_map_dumb map_dumb = {};
   map_dumb.handle = create_dumb.handle;
-  ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
-  if (ret) {
-    printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n", ret);
-    DrmDestroySurface(surface);
+  if (drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb) != 0) {
+    perror("Failed to DRM_IOCTL_MODE_MAP_DUMB");
     return nullptr;
   }
 
-  surface->height = height;
-  surface->width = width;
-  surface->row_bytes = create_dumb.pitch;
-  surface->pixel_bytes = create_dumb.bpp / 8;
-  surface->data = static_cast<unsigned char*>(mmap(nullptr, surface->height * surface->row_bytes,
-                                                   PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd,
-                                                   map_dumb.offset));
-  if (surface->data == MAP_FAILED) {
-    perror("mmap() failed");
-    DrmDestroySurface(surface);
+  auto mmapped = mmap(nullptr, surface->height * surface->row_bytes, PROT_READ | PROT_WRITE,
+                      MAP_SHARED, drm_fd, map_dumb.offset);
+  if (mmapped == MAP_FAILED) {
+    perror("Failed to mmap()");
     return nullptr;
   }
-
+  surface->mmapped_buffer_ = static_cast<uint8_t*>(mmapped);
   return surface;
 }
 
+void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) {
+  if (crtc) {
+    drmModeSetCrtc(drm_fd, crtc->crtc_id,
+                   0,         // fb_id
+                   0, 0,      // x,y
+                   nullptr,   // connectors
+                   0,         // connector_count
+                   nullptr);  // mode
+  }
+}
+
+bool MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc,
+                                    const std::unique_ptr<GRSurfaceDrm>& surface) {
+  if (drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0,  // x,y
+                     &main_monitor_connector->connector_id,
+                     1,  // connector_count
+                     &main_monitor_crtc->mode) != 0) {
+    perror("Failed to drmModeSetCrtc");
+    return false;
+  }
+  return true;
+}
+
+void MinuiBackendDrm::Blank(bool blank) {
+  if (blank) {
+    DrmDisableCrtc(drm_fd, main_monitor_crtc);
+  } else {
+    DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]);
+  }
+}
+
 static drmModeCrtc* find_crtc_for_connector(int fd, drmModeRes* resources,
                                             drmModeConnector* connector) {
   // Find the encoder. If we already have one, just use it.
@@ -257,7 +246,7 @@
   do {
     main_monitor_connector = find_used_connector_by_type(fd, resources, kConnectorPriority[i]);
     i++;
-  } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
+  } while (!main_monitor_connector && i < arraysize(kConnectorPriority));
 
   /* If we didn't find a connector, grab the first one that is connected. */
   if (!main_monitor_connector) {
@@ -291,60 +280,53 @@
 
 GRSurface* MinuiBackendDrm::Init() {
   drmModeRes* res = nullptr;
+  drm_fd = -1;
 
   /* Consider DRM devices in order. */
   for (int i = 0; i < DRM_MAX_MINOR; i++) {
-    char* dev_name;
-    int ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
-    if (ret < 0) continue;
+    auto dev_name = android::base::StringPrintf(DRM_DEV_NAME, DRM_DIR_NAME, i);
+    android::base::unique_fd fd(open(dev_name.c_str(), O_RDWR | O_CLOEXEC));
+    if (fd == -1) continue;
 
-    drm_fd = open(dev_name, O_RDWR, 0);
-    free(dev_name);
-    if (drm_fd < 0) continue;
-
-    uint64_t cap = 0;
     /* We need dumb buffers. */
-    ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
-    if (ret || cap == 0) {
-      close(drm_fd);
+    if (uint64_t cap = 0; drmGetCap(fd.get(), DRM_CAP_DUMB_BUFFER, &cap) != 0 || cap == 0) {
       continue;
     }
 
-    res = drmModeGetResources(drm_fd);
+    res = drmModeGetResources(fd.get());
     if (!res) {
-      close(drm_fd);
       continue;
     }
 
     /* Use this device if it has at least one connected monitor. */
     if (res->count_crtcs > 0 && res->count_connectors > 0) {
-      if (find_first_connected_connector(drm_fd, res)) break;
+      if (find_first_connected_connector(fd.get(), res)) {
+        drm_fd = fd.release();
+        break;
+      }
     }
 
     drmModeFreeResources(res);
-    close(drm_fd);
     res = nullptr;
   }
 
-  if (drm_fd < 0 || res == nullptr) {
-    perror("cannot find/open a drm device");
+  if (drm_fd == -1 || res == nullptr) {
+    perror("Failed to find/open a drm device");
     return nullptr;
   }
 
   uint32_t selected_mode;
   main_monitor_connector = FindMainMonitor(drm_fd, res, &selected_mode);
-
   if (!main_monitor_connector) {
-    printf("main_monitor_connector not found\n");
+    fprintf(stderr, "Failed to find main_monitor_connector\n");
     drmModeFreeResources(res);
     close(drm_fd);
     return nullptr;
   }
 
   main_monitor_crtc = find_crtc_for_connector(drm_fd, res, main_monitor_connector);
-
   if (!main_monitor_crtc) {
-    printf("main_monitor_crtc not found\n");
+    fprintf(stderr, "Failed to find main_monitor_crtc\n");
     drmModeFreeResources(res);
     close(drm_fd);
     return nullptr;
@@ -359,35 +341,66 @@
 
   drmModeFreeResources(res);
 
-  GRSurfaceDrms[0] = DrmCreateSurface(width, height);
-  GRSurfaceDrms[1] = DrmCreateSurface(width, height);
+  GRSurfaceDrms[0] = GRSurfaceDrm::Create(drm_fd, width, height);
+  GRSurfaceDrms[1] = GRSurfaceDrm::Create(drm_fd, width, height);
   if (!GRSurfaceDrms[0] || !GRSurfaceDrms[1]) {
-    // GRSurfaceDrms and drm_fd should be freed in d'tor.
     return nullptr;
   }
 
   current_buffer = 0;
 
-  DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1]);
+  // We will likely encounter errors in the backend functions (i.e. Flip) if EnableCrtc fails.
+  if (!DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1])) {
+    return nullptr;
+  }
 
-  return GRSurfaceDrms[0];
+  return GRSurfaceDrms[0].get();
+}
+
+static void page_flip_complete(__unused int fd,
+                               __unused unsigned int sequence,
+                               __unused unsigned int tv_sec,
+                               __unused unsigned int tv_usec,
+                               void *user_data) {
+  *static_cast<bool*>(user_data) = false;
 }
 
 GRSurface* MinuiBackendDrm::Flip() {
-  int ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
-                            GRSurfaceDrms[current_buffer]->fb_id, 0, nullptr);
-  if (ret < 0) {
-    printf("drmModePageFlip failed ret=%d\n", ret);
+  bool ongoing_flip = true;
+  if (drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, GRSurfaceDrms[current_buffer]->fb_id,
+                      DRM_MODE_PAGE_FLIP_EVENT, &ongoing_flip) != 0) {
+    perror("Failed to drmModePageFlip");
     return nullptr;
   }
+
+  while (ongoing_flip) {
+    struct pollfd fds = {
+      .fd = drm_fd,
+      .events = POLLIN
+    };
+
+    if (poll(&fds, 1, -1) == -1 || !(fds.revents & POLLIN)) {
+      perror("Failed to poll() on drm fd");
+      break;
+    }
+
+    drmEventContext evctx = {
+      .version = DRM_EVENT_CONTEXT_VERSION,
+      .page_flip_handler = page_flip_complete
+    };
+
+    if (drmHandleEvent(drm_fd, &evctx) != 0) {
+      perror("Failed to drmHandleEvent");
+      break;
+    }
+  }
+
   current_buffer = 1 - current_buffer;
-  return GRSurfaceDrms[current_buffer];
+  return GRSurfaceDrms[current_buffer].get();
 }
 
 MinuiBackendDrm::~MinuiBackendDrm() {
   DrmDisableCrtc(drm_fd, main_monitor_crtc);
-  DrmDestroySurface(GRSurfaceDrms[0]);
-  DrmDestroySurface(GRSurfaceDrms[1]);
   drmModeFreeCrtc(main_monitor_crtc);
   drmModeFreeConnector(main_monitor_connector);
   close(drm_fd);
diff --git a/minui/graphics_drm.h b/minui/graphics_drm.h
index de96212..57ba39b 100644
--- a/minui/graphics_drm.h
+++ b/minui/graphics_drm.h
@@ -14,45 +14,61 @@
  * limitations under the License.
  */
 
-#ifndef _GRAPHICS_DRM_H_
-#define _GRAPHICS_DRM_H_
+#pragma once
 
+#include <stddef.h>
 #include <stdint.h>
 
+#include <memory>
+
 #include <xf86drmMode.h>
 
 #include "graphics.h"
 #include "minui/minui.h"
 
 class GRSurfaceDrm : public GRSurface {
- private:
-  uint32_t fb_id;
-  uint32_t handle;
+ public:
+  ~GRSurfaceDrm() override;
 
+  // Creates a GRSurfaceDrm instance.
+  static std::unique_ptr<GRSurfaceDrm> Create(int drm_fd, int width, int height);
+
+  uint8_t* data() override {
+    return mmapped_buffer_;
+  }
+
+ private:
   friend class MinuiBackendDrm;
+
+  GRSurfaceDrm(size_t width, size_t height, size_t row_bytes, size_t pixel_bytes, int drm_fd,
+               uint32_t handle)
+      : GRSurface(width, height, row_bytes, pixel_bytes), drm_fd_(drm_fd), handle(handle) {}
+
+  const int drm_fd_;
+
+  uint32_t fb_id{ 0 };
+  uint32_t handle{ 0 };
+  uint8_t* mmapped_buffer_{ nullptr };
 };
 
 class MinuiBackendDrm : public MinuiBackend {
  public:
+  MinuiBackendDrm() = default;
+  ~MinuiBackendDrm() override;
+
   GRSurface* Init() override;
   GRSurface* Flip() override;
   void Blank(bool) override;
-  ~MinuiBackendDrm() override;
-  MinuiBackendDrm();
 
  private:
   void DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc);
-  void DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface);
-  GRSurfaceDrm* DrmCreateSurface(int width, int height);
-  void DrmDestroySurface(GRSurfaceDrm* surface);
+  bool DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, const std::unique_ptr<GRSurfaceDrm>& surface);
   void DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc);
   drmModeConnector* FindMainMonitor(int fd, drmModeRes* resources, uint32_t* mode_index);
 
-  GRSurfaceDrm* GRSurfaceDrms[2];
-  int current_buffer;
-  drmModeCrtc* main_monitor_crtc;
-  drmModeConnector* main_monitor_connector;
-  int drm_fd;
+  std::unique_ptr<GRSurfaceDrm> GRSurfaceDrms[2];
+  int current_buffer{ 0 };
+  drmModeCrtc* main_monitor_crtc{ nullptr };
+  drmModeConnector* main_monitor_connector{ nullptr };
+  int drm_fd{ -1 };
 };
-
-#endif  // _GRAPHICS_DRM_H_
diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp
index 746f42a..2584017 100644
--- a/minui/graphics_fbdev.cpp
+++ b/minui/graphics_fbdev.cpp
@@ -26,21 +26,29 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <memory>
+
+#include <android-base/unique_fd.h>
+
 #include "minui/minui.h"
 
-MinuiBackendFbdev::MinuiBackendFbdev() : gr_draw(nullptr), fb_fd(-1) {}
+std::unique_ptr<GRSurfaceFbdev> GRSurfaceFbdev::Create(size_t width, size_t height,
+                                                       size_t row_bytes, size_t pixel_bytes) {
+  // Cannot use std::make_unique to access non-public ctor.
+  return std::unique_ptr<GRSurfaceFbdev>(new GRSurfaceFbdev(width, height, row_bytes, pixel_bytes));
+}
 
 void MinuiBackendFbdev::Blank(bool blank) {
   int ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
   if (ret < 0) perror("ioctl(): blank");
 }
 
-void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) {
+void MinuiBackendFbdev::SetDisplayedFramebuffer(size_t n) {
   if (n > 1 || !double_buffered) return;
 
-  vi.yres_virtual = gr_framebuffer[0].height * 2;
-  vi.yoffset = n * gr_framebuffer[0].height;
-  vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
+  vi.yres_virtual = gr_framebuffer[0]->height * 2;
+  vi.yoffset = n * gr_framebuffer[0]->height;
+  vi.bits_per_pixel = gr_framebuffer[0]->pixel_bytes * 8;
   if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
     perror("active fb swap failed");
   }
@@ -48,7 +56,7 @@
 }
 
 GRSurface* MinuiBackendFbdev::Init() {
-  int fd = open("/dev/graphics/fb0", O_RDWR);
+  android::base::unique_fd fd(open("/dev/graphics/fb0", O_RDWR | O_CLOEXEC));
   if (fd == -1) {
     perror("cannot open fb0");
     return nullptr;
@@ -57,13 +65,11 @@
   fb_fix_screeninfo fi;
   if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
     perror("failed to get fb0 info");
-    close(fd);
     return nullptr;
   }
 
   if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
     perror("failed to get fb0 info");
-    close(fd);
     return nullptr;
   }
 
@@ -90,50 +96,41 @@
   void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
   if (bits == MAP_FAILED) {
     perror("failed to mmap framebuffer");
-    close(fd);
     return nullptr;
   }
 
   memset(bits, 0, fi.smem_len);
 
-  gr_framebuffer[0].width = vi.xres;
-  gr_framebuffer[0].height = vi.yres;
-  gr_framebuffer[0].row_bytes = fi.line_length;
-  gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
-  gr_framebuffer[0].data = static_cast<uint8_t*>(bits);
-  memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
+  gr_framebuffer[0] =
+      GRSurfaceFbdev::Create(vi.xres, vi.yres, fi.line_length, vi.bits_per_pixel / 8);
+  gr_framebuffer[0]->buffer_ = static_cast<uint8_t*>(bits);
+  memset(gr_framebuffer[0]->buffer_, 0, gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes);
+
+  gr_framebuffer[1] =
+      GRSurfaceFbdev::Create(gr_framebuffer[0]->width, gr_framebuffer[0]->height,
+                             gr_framebuffer[0]->row_bytes, gr_framebuffer[0]->pixel_bytes);
 
   /* check if we can use double buffering */
   if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
     double_buffered = true;
 
-    memcpy(gr_framebuffer + 1, gr_framebuffer, sizeof(GRSurface));
-    gr_framebuffer[1].data =
-        gr_framebuffer[0].data + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
-
-    gr_draw = gr_framebuffer + 1;
-
+    gr_framebuffer[1]->buffer_ =
+        gr_framebuffer[0]->buffer_ + gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes;
   } else {
     double_buffered = false;
 
-    // Without double-buffering, we allocate RAM for a buffer to
-    // draw in, and then "flipping" the buffer consists of a
-    // memcpy from the buffer we allocated to the framebuffer.
-
-    gr_draw = static_cast<GRSurface*>(malloc(sizeof(GRSurface)));
-    memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
-    gr_draw->data = static_cast<unsigned char*>(malloc(gr_draw->height * gr_draw->row_bytes));
-    if (!gr_draw->data) {
-      perror("failed to allocate in-memory surface");
-      return nullptr;
-    }
+    // Without double-buffering, we allocate RAM for a buffer to draw in, and then "flipping" the
+    // buffer consists of a memcpy from the buffer we allocated to the framebuffer.
+    memory_buffer.resize(gr_framebuffer[1]->height * gr_framebuffer[1]->row_bytes);
+    gr_framebuffer[1]->buffer_ = memory_buffer.data();
   }
 
-  memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes);
-  fb_fd = fd;
+  gr_draw = gr_framebuffer[1].get();
+  memset(gr_draw->buffer_, 0, gr_draw->height * gr_draw->row_bytes);
+  fb_fd = std::move(fd);
   SetDisplayedFramebuffer(0);
 
-  printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
+  printf("framebuffer: %d (%zu x %zu)\n", fb_fd.get(), gr_draw->width, gr_draw->height);
 
   Blank(true);
   Blank(false);
@@ -143,25 +140,13 @@
 
 GRSurface* MinuiBackendFbdev::Flip() {
   if (double_buffered) {
-    // Change gr_draw to point to the buffer currently displayed,
-    // then flip the driver so we're displaying the other buffer
-    // instead.
-    gr_draw = gr_framebuffer + displayed_buffer;
+    // Change gr_draw to point to the buffer currently displayed, then flip the driver so we're
+    // displaying the other buffer instead.
+    gr_draw = gr_framebuffer[displayed_buffer].get();
     SetDisplayedFramebuffer(1 - displayed_buffer);
   } else {
     // Copy from the in-memory surface to the framebuffer.
-    memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes);
+    memcpy(gr_framebuffer[0]->buffer_, gr_draw->buffer_, gr_draw->height * gr_draw->row_bytes);
   }
   return gr_draw;
 }
-
-MinuiBackendFbdev::~MinuiBackendFbdev() {
-  close(fb_fd);
-  fb_fd = -1;
-
-  if (!double_buffered && gr_draw) {
-    free(gr_draw->data);
-    free(gr_draw);
-  }
-  gr_draw = nullptr;
-}
diff --git a/minui/graphics_fbdev.h b/minui/graphics_fbdev.h
index 107e195..596ba74 100644
--- a/minui/graphics_fbdev.h
+++ b/minui/graphics_fbdev.h
@@ -14,31 +14,58 @@
  * limitations under the License.
  */
 
-#ifndef _GRAPHICS_FBDEV_H_
-#define _GRAPHICS_FBDEV_H_
+#pragma once
 
 #include <linux/fb.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include <android-base/unique_fd.h>
 
 #include "graphics.h"
 #include "minui/minui.h"
 
+class GRSurfaceFbdev : public GRSurface {
+ public:
+  // Creates and returns a GRSurfaceFbdev instance, or nullptr on error.
+  static std::unique_ptr<GRSurfaceFbdev> Create(size_t width, size_t height, size_t row_bytes,
+                                                size_t pixel_bytes);
+
+  uint8_t* data() override {
+    return buffer_;
+  }
+
+ protected:
+  using GRSurface::GRSurface;
+
+ private:
+  friend class MinuiBackendFbdev;
+
+  // Points to the start of the buffer: either the mmap'd framebuffer or one allocated in-memory.
+  uint8_t* buffer_{ nullptr };
+};
+
 class MinuiBackendFbdev : public MinuiBackend {
  public:
+  MinuiBackendFbdev() = default;
+  ~MinuiBackendFbdev() override = default;
+
   GRSurface* Init() override;
   GRSurface* Flip() override;
   void Blank(bool) override;
-  ~MinuiBackendFbdev() override;
-  MinuiBackendFbdev();
 
  private:
-  void SetDisplayedFramebuffer(unsigned n);
+  void SetDisplayedFramebuffer(size_t n);
 
-  GRSurface gr_framebuffer[2];
+  std::unique_ptr<GRSurfaceFbdev> gr_framebuffer[2];
+  // Points to the current surface (i.e. one of the two gr_framebuffer's).
+  GRSurfaceFbdev* gr_draw{ nullptr };
   bool double_buffered;
-  GRSurface* gr_draw;
-  int displayed_buffer;
+  std::vector<uint8_t> memory_buffer;
+  size_t displayed_buffer{ 0 };
   fb_var_screeninfo vi;
-  int fb_fd;
+  android::base::unique_fd fb_fd;
 };
-
-#endif  // _GRAPHICS_FBDEV_H_
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index f9da199..36bdcf1 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -14,25 +14,73 @@
  * limitations under the License.
  */
 
-#ifndef _MINUI_H_
-#define _MINUI_H_
+#pragma once
 
+#include <stdint.h>
+#include <stdlib.h>
 #include <sys/types.h>
 
 #include <functional>
+#include <memory>
 #include <string>
 #include <vector>
 
+#include <android-base/macros.h>
+#include <android-base/unique_fd.h>
+
 //
 // Graphics.
 //
 
-struct GRSurface {
-  int width;
-  int height;
-  int row_bytes;
-  int pixel_bytes;
-  unsigned char* data;
+class GRSurface {
+ public:
+  static constexpr size_t kSurfaceDataAlignment = 8;
+
+  virtual ~GRSurface() = default;
+
+  // Creates and returns a GRSurface instance that's sufficient for storing an image of the given
+  // size (i.e. row_bytes * height). The starting address of the surface data is aligned to
+  // kSurfaceDataAlignment. Returns the created GRSurface instance (in std::unique_ptr), or nullptr
+  // on error.
+  static std::unique_ptr<GRSurface> Create(size_t width, size_t height, size_t row_bytes,
+                                           size_t pixel_bytes);
+
+  // Clones the current GRSurface instance (i.e. an image).
+  std::unique_ptr<GRSurface> Clone() const;
+
+  virtual uint8_t* data() {
+    return data_.get();
+  }
+
+  const uint8_t* data() const {
+    return const_cast<const uint8_t*>(const_cast<GRSurface*>(this)->data());
+  }
+
+  size_t data_size() const {
+    return data_size_;
+  }
+
+  size_t width;
+  size_t height;
+  size_t row_bytes;
+  size_t pixel_bytes;
+
+ protected:
+  GRSurface(size_t width, size_t height, size_t row_bytes, size_t pixel_bytes)
+      : width(width), height(height), row_bytes(row_bytes), pixel_bytes(pixel_bytes) {}
+
+ private:
+  // The deleter for data_, whose data is allocated via aligned_alloc(3).
+  struct DataDeleter {
+    void operator()(uint8_t* data) {
+      free(data);
+    }
+  };
+
+  std::unique_ptr<uint8_t, DataDeleter> data_;
+  size_t data_size_;
+
+  DISALLOW_COPY_AND_ASSIGN(GRSurface);
 };
 
 struct GRFont {
@@ -41,14 +89,27 @@
   int char_height;
 };
 
-enum GRRotation {
-  ROTATION_NONE = 0,
-  ROTATION_RIGHT = 1,
-  ROTATION_DOWN = 2,
-  ROTATION_LEFT = 3,
+enum class GRRotation : int {
+  NONE = 0,
+  RIGHT = 1,
+  DOWN = 2,
+  LEFT = 3,
 };
 
+enum class PixelFormat : int {
+  UNKNOWN = 0,
+  ABGR = 1,
+  RGBX = 2,
+  BGRA = 3,
+};
+
+// Initializes the graphics backend and loads font file. Returns 0 on success, or -1 on error. Note
+// that the font initialization failure would be non-fatal, as caller may not need to draw any text
+// at all. Caller can check the font initialization result via gr_sys_font() as needed.
 int gr_init();
+
+// Frees the allocated resources. The function is idempotent, and safe to be called if gr_init()
+// didn't finish successfully.
 void gr_exit();
 
 int gr_fb_width();
@@ -57,25 +118,31 @@
 void gr_flip();
 void gr_fb_blank(bool blank);
 
-void gr_clear();  // clear entire surface to current color
+// Clears entire surface to current color.
+void gr_clear();
 void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
 void gr_fill(int x1, int y1, int x2, int y2);
 
-void gr_texticon(int x, int y, GRSurface* icon);
+void gr_texticon(int x, int y, const GRSurface* icon);
 
 const GRFont* gr_sys_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.
 int gr_measure(const GRFont* font, const char* s);
-void gr_font_size(const GRFont* font, int* x, int* y);
+// Returns -1 if font is nullptr.
+int gr_font_size(const GRFont* font, int* x, int* y);
 
-void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy);
-unsigned int gr_get_width(GRSurface* surface);
-unsigned int gr_get_height(GRSurface* surface);
+void gr_blit(const GRSurface* source, int sx, int sy, int w, int h, int dx, int dy);
+unsigned int gr_get_width(const GRSurface* surface);
+unsigned int gr_get_height(const GRSurface* surface);
 
-// Set rotation, flips gr_fb_width/height if 90 degree rotation difference
+// Sets rotation, flips gr_fb_width/height if 90 degree rotation difference
 void gr_rotate(GRRotation rotation);
 
+// Returns the current PixelFormat being used.
+PixelFormat gr_pixel_format();
+
 //
 // Input events.
 //
@@ -87,7 +154,7 @@
 
 int ev_init(ev_callback input_cb, bool allow_touch_inputs = false);
 void ev_exit();
-int ev_add_fd(int fd, ev_callback cb);
+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);
 int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
@@ -146,5 +213,3 @@
 // Free a surface allocated by any of the res_create_*_surface()
 // functions.
 void res_free_surface(GRSurface* surface);
-
-#endif
diff --git a/minui/include/private/resources.h b/minui/include/private/resources.h
new file mode 100644
index 0000000..047ebe2
--- /dev/null
+++ b/minui/include/private/resources.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include <png.h>
+
+// This class handles the PNG file parsing. It also holds the ownership of the PNG pointer and the
+// opened file pointer. Both will be destroyed / closed when this object goes out of scope.
+class PngHandler {
+ public:
+  // Constructs an instance by loading the PNG file from '/res/images/<name>.png', or '<name>'.
+  PngHandler(const std::string& name);
+
+  ~PngHandler();
+
+  png_uint_32 width() const {
+    return width_;
+  }
+
+  png_uint_32 height() const {
+    return height_;
+  }
+
+  png_byte channels() const {
+    return channels_;
+  }
+
+  int bit_depth() const {
+    return bit_depth_;
+  }
+
+  int color_type() const {
+    return color_type_;
+  }
+
+  png_structp png_ptr() const {
+    return png_ptr_;
+  }
+
+  png_infop info_ptr() const {
+    return info_ptr_;
+  }
+
+  int error_code() const {
+    return error_code_;
+  };
+
+  operator bool() const {
+    return error_code_ == 0;
+  }
+
+ private:
+  png_structp png_ptr_{ nullptr };
+  png_infop info_ptr_{ nullptr };
+  png_uint_32 width_;
+  png_uint_32 height_;
+  png_byte channels_;
+  int bit_depth_;
+  int color_type_;
+
+  // The |error_code_| is set to a negative value if an error occurs when opening the png file.
+  int error_code_{ 0 };
+  // After initialization, we'll keep the file pointer open before destruction of PngHandler.
+  std::unique_ptr<FILE, decltype(&fclose)> png_fp_{ nullptr, fclose };
+};
+
+// Overrides the default resource dir, for testing purpose.
+void res_set_resource_dir(const std::string&);
diff --git a/minui/mkfont.c b/minui/mkfont.c
deleted file mode 100644
index 61a5ede..0000000
--- a/minui/mkfont.c
+++ /dev/null
@@ -1,54 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-
-int main(int argc, char *argv)
-{
-    unsigned n;
-    unsigned char *x;
-    unsigned m;
-    unsigned run_val;
-    unsigned run_count;
- 
-    n = gimp_image.width * gimp_image.height;
-    m = 0;
-    x = gimp_image.pixel_data;
-
-    printf("struct {\n");
-    printf("  unsigned width;\n");
-    printf("  unsigned height;\n");
-    printf("  unsigned cwidth;\n");
-    printf("  unsigned cheight;\n");
-    printf("  unsigned char rundata[];\n");
-    printf("} font = {\n");
-    printf("  .width = %d,\n  .height = %d,\n  .cwidth = %d,\n  .cheight = %d,\n", gimp_image.width, gimp_image.height,
-           gimp_image.width / 96, gimp_image.height);
-    printf("  .rundata = {\n");
-   
-    run_val = (*x ? 0 : 255);
-    run_count = 1;
-    n--;
-    x+=3;
-
-    while(n-- > 0) {
-        unsigned val = (*x ? 0 : 255);
-        x+=3;
-        if((val == run_val) && (run_count < 127)) {
-            run_count++;
-        } else {
-eject:
-            printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00));
-            run_val = val;
-            run_count = 1;
-            m += 5;
-            if(m >= 75) {
-                printf("\n");
-                m = 0;
-            }
-        }
-    }
-    printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00));
-    printf("\n0x00,");
-    printf("\n");
-    printf("  }\n};\n");
-    return 0;
-}
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 52ab60b..00d36d5 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "private/resources.h"
+
 #include <fcntl.h>
 #include <linux/fb.h>
 #include <linux/kd.h>
@@ -25,87 +27,55 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <limits>
 #include <memory>
 #include <regex>
 #include <string>
 #include <vector>
 
-#include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <png.h>
 
 #include "minui/minui.h"
 
-#define SURFACE_DATA_ALIGNMENT 8
+static std::string g_resource_dir{ "/res/images" };
 
-static GRSurface* malloc_surface(size_t data_size) {
-    size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT;
-    unsigned char* temp = static_cast<unsigned char*>(malloc(size));
-    if (temp == NULL) return NULL;
-    GRSurface* surface = reinterpret_cast<GRSurface*>(temp);
-    surface->data = temp + sizeof(GRSurface) +
-        (SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT));
-    return surface;
+std::unique_ptr<GRSurface> GRSurface::Create(size_t width, size_t height, size_t row_bytes,
+                                             size_t pixel_bytes) {
+  if (width == 0 || row_bytes == 0 || height == 0 || pixel_bytes == 0) return nullptr;
+  if (std::numeric_limits<size_t>::max() / row_bytes < height) return nullptr;
+
+  // Cannot use std::make_unique to access non-public ctor.
+  auto result = std::unique_ptr<GRSurface>(new GRSurface(width, height, row_bytes, pixel_bytes));
+  size_t data_size = row_bytes * height;
+  result->data_size_ =
+      (data_size + kSurfaceDataAlignment - 1) / kSurfaceDataAlignment * kSurfaceDataAlignment;
+  result->data_.reset(
+      static_cast<uint8_t*>(aligned_alloc(kSurfaceDataAlignment, result->data_size_)));
+  if (!result->data_) return nullptr;
+  return result;
 }
 
-// This class handles the png file parsing. It also holds the ownership of the png pointer and the
-// opened file pointer. Both will be destroyed/closed when this object goes out of scope.
-class PngHandler {
- public:
-  PngHandler(const std::string& name);
+std::unique_ptr<GRSurface> GRSurface::Clone() const {
+  auto result = GRSurface::Create(width, height, row_bytes, pixel_bytes);
+  if (!result) return nullptr;
+  memcpy(result->data(), data(), data_size_);
+  return result;
+}
 
-  ~PngHandler();
-
-  png_uint_32 width() const {
-    return width_;
-  }
-
-  png_uint_32 height() const {
-    return height_;
-  }
-
-  png_byte channels() const {
-    return channels_;
-  }
-
-  png_structp png_ptr() const {
-    return png_ptr_;
-  }
-
-  png_infop info_ptr() const {
-    return info_ptr_;
-  }
-
-  int error_code() const {
-    return error_code_;
-  };
-
-  operator bool() const {
-    return error_code_ == 0;
-  }
-
- private:
-  png_structp png_ptr_{ nullptr };
-  png_infop info_ptr_{ nullptr };
-  png_uint_32 width_;
-  png_uint_32 height_;
-  png_byte channels_;
-
-  // The |error_code_| is set to a negative value if an error occurs when opening the png file.
-  int error_code_;
-  // After initialization, we'll keep the file pointer open before destruction of PngHandler.
-  std::unique_ptr<FILE, decltype(&fclose)> png_fp_;
-};
-
-PngHandler::PngHandler(const std::string& name) : error_code_(0), png_fp_(nullptr, fclose) {
-  std::string res_path = android::base::StringPrintf("/res/images/%s.png", name.c_str());
+PngHandler::PngHandler(const std::string& name) {
+  std::string res_path = g_resource_dir + "/" + name + ".png";
   png_fp_.reset(fopen(res_path.c_str(), "rbe"));
+  // Try to read from |name| if the resource path does not work.
+  if (!png_fp_) {
+    png_fp_.reset(fopen(name.c_str(), "rbe"));
+  }
   if (!png_fp_) {
     error_code_ = -1;
     return;
   }
 
-  unsigned char header[8];
+  uint8_t header[8];
   size_t bytesRead = fread(header, 1, sizeof(header), png_fp_.get());
   if (bytesRead != sizeof(header)) {
     error_code_ = -2;
@@ -138,19 +108,17 @@
   png_set_sig_bytes(png_ptr_, sizeof(header));
   png_read_info(png_ptr_, info_ptr_);
 
-  int color_type;
-  int bit_depth;
-  png_get_IHDR(png_ptr_, info_ptr_, &width_, &height_, &bit_depth, &color_type, nullptr, nullptr,
+  png_get_IHDR(png_ptr_, info_ptr_, &width_, &height_, &bit_depth_, &color_type_, nullptr, nullptr,
                nullptr);
 
   channels_ = png_get_channels(png_ptr_, info_ptr_);
 
-  if (bit_depth == 8 && channels_ == 3 && color_type == PNG_COLOR_TYPE_RGB) {
+  if (bit_depth_ == 8 && channels_ == 3 && color_type_ == PNG_COLOR_TYPE_RGB) {
     // 8-bit RGB images: great, nothing to do.
-  } else if (bit_depth <= 8 && channels_ == 1 && color_type == PNG_COLOR_TYPE_GRAY) {
+  } else if (bit_depth_ <= 8 && channels_ == 1 && color_type_ == PNG_COLOR_TYPE_GRAY) {
     // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
     png_set_expand_gray_1_2_4_to_8(png_ptr_);
-  } else if (bit_depth <= 8 && channels_ == 1 && color_type == PNG_COLOR_TYPE_PALETTE) {
+  } else if (bit_depth_ <= 8 && channels_ == 1 && color_type_ == PNG_COLOR_TYPE_PALETTE) {
     // paletted images: expand to 8-bit RGB.  Note that we DON'T
     // currently expand the tRNS chunk (if any) to an alpha
     // channel, because minui doesn't support alpha channels in
@@ -158,8 +126,8 @@
     png_set_palette_to_rgb(png_ptr_);
     channels_ = 3;
   } else {
-    fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth,
-            channels_, color_type);
+    fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth_,
+            channels_, color_type_);
     error_code_ = -7;
   }
 }
@@ -170,70 +138,49 @@
   }
 }
 
-// "display" surfaces are transformed into the framebuffer's required
-// pixel format (currently only RGBX is supported) at load time, so
-// gr_blit() can be nothing more than a memcpy() for each row.  The
-// next two functions are the only ones that know anything about the
-// framebuffer pixel format; they need to be modified if the
-// framebuffer format changes (but nothing else should).
+// "display" surfaces are transformed into the framebuffer's required pixel format (currently only
+// RGBX is supported) at load time, so gr_blit() can be nothing more than a memcpy() for each row.
 
-// Allocate and return a GRSurface* sufficient for storing an image of
-// the indicated size in the framebuffer pixel format.
-static GRSurface* init_display_surface(png_uint_32 width, png_uint_32 height) {
-    GRSurface* surface = malloc_surface(width * height * 4);
-    if (surface == NULL) return NULL;
-
-    surface->width = width;
-    surface->height = height;
-    surface->row_bytes = width * 4;
-    surface->pixel_bytes = 4;
-
-    return surface;
-}
-
-// Copy 'input_row' to 'output_row', transforming it to the
-// framebuffer pixel format.  The input format depends on the value of
-// 'channels':
+// Copies 'input_row' to 'output_row', transforming it to the framebuffer pixel format. The input
+// format depends on the value of 'channels':
 //
 //   1 - input is 8-bit grayscale
 //   3 - input is 24-bit RGB
 //   4 - input is 32-bit RGBA/RGBX
 //
 // 'width' is the number of pixels in the row.
-static void transform_rgb_to_draw(unsigned char* input_row,
-                                  unsigned char* output_row,
-                                  int channels, int width) {
-    int x;
-    unsigned char* ip = input_row;
-    unsigned char* op = output_row;
+static void TransformRgbToDraw(const uint8_t* input_row, uint8_t* output_row, int channels,
+                               int width) {
+  const uint8_t* ip = input_row;
+  uint8_t* op = output_row;
 
-    switch (channels) {
-        case 1:
-            // expand gray level to RGBX
-            for (x = 0; x < width; ++x) {
-                *op++ = *ip;
-                *op++ = *ip;
-                *op++ = *ip;
-                *op++ = 0xff;
-                ip++;
-            }
-            break;
+  switch (channels) {
+    case 1:
+      // expand gray level to RGBX
+      for (int x = 0; x < width; ++x) {
+        *op++ = *ip;
+        *op++ = *ip;
+        *op++ = *ip;
+        *op++ = 0xff;
+        ip++;
+      }
+      break;
 
-        case 3:
-            // expand RGBA to RGBX
-            for (x = 0; x < width; ++x) {
-                *op++ = *ip++;
-                *op++ = *ip++;
-                *op++ = *ip++;
-                *op++ = 0xff;
-            }
-            break;
+    case 3:
+      // expand RGBA to RGBX
+      for (int x = 0; x < width; ++x) {
+        *op++ = *ip++;
+        *op++ = *ip++;
+        *op++ = *ip++;
+        *op++ = 0xff;
+      }
+      break;
 
-        case 4:
-            // copy RGBA to RGBX
-            memcpy(output_row, input_row, width*4);
-            break;
-    }
+    case 4:
+      // copy RGBA to RGBX
+      memcpy(output_row, input_row, width * 4);
+      break;
+  }
 }
 
 int res_create_display_surface(const char* name, GRSurface** pSurface) {
@@ -246,23 +193,24 @@
   png_uint_32 width = png_handler.width();
   png_uint_32 height = png_handler.height();
 
-  GRSurface* surface = init_display_surface(width, height);
+  auto surface = GRSurface::Create(width, height, width * 4, 4);
   if (!surface) {
     return -8;
   }
 
-#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
-  png_set_bgr(png_ptr);
-#endif
-
-  for (png_uint_32 y = 0; y < height; ++y) {
-    std::vector<unsigned char> p_row(width * 4);
-    png_read_row(png_ptr, p_row.data(), nullptr);
-    transform_rgb_to_draw(p_row.data(), surface->data + y * surface->row_bytes,
-                          png_handler.channels(), width);
+  PixelFormat pixel_format = gr_pixel_format();
+  if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
+    png_set_bgr(png_ptr);
   }
 
-  *pSurface = surface;
+  for (png_uint_32 y = 0; y < height; ++y) {
+    std::vector<uint8_t> p_row(width * 4);
+    png_read_row(png_ptr, p_row.data(), nullptr);
+    TransformRgbToDraw(p_row.data(), surface->data() + y * surface->row_bytes,
+                       png_handler.channels(), width);
+  }
+
+  *pSurface = surface.release();
 
   return 0;
 }
@@ -315,23 +263,24 @@
     goto exit;
   }
   for (int i = 0; i < *frames; ++i) {
-    surface[i] = init_display_surface(width, height / *frames);
-    if (!surface[i]) {
+    auto created_surface = GRSurface::Create(width, height / *frames, width * 4, 4);
+    if (!created_surface) {
       result = -8;
       goto exit;
     }
+    surface[i] = created_surface.release();
   }
 
-#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
-  png_set_bgr(png_ptr);
-#endif
+  if (gr_pixel_format() == PixelFormat::ABGR || gr_pixel_format() == PixelFormat::BGRA) {
+    png_set_bgr(png_ptr);
+  }
 
   for (png_uint_32 y = 0; y < height; ++y) {
-    std::vector<unsigned char> p_row(width * 4);
+    std::vector<uint8_t> p_row(width * 4);
     png_read_row(png_ptr, p_row.data(), nullptr);
     int frame = y % *frames;
-    unsigned char* out_row = surface[frame]->data + (y / *frames) * surface[frame]->row_bytes;
-    transform_rgb_to_draw(p_row.data(), out_row, png_handler.channels(), width);
+    uint8_t* out_row = surface[frame]->data() + (y / *frames) * surface[frame]->row_bytes;
+    TransformRgbToDraw(p_row.data(), out_row, png_handler.channels(), width);
   }
 
   *pSurface = surface;
@@ -362,29 +311,30 @@
   png_uint_32 width = png_handler.width();
   png_uint_32 height = png_handler.height();
 
-  GRSurface* surface = malloc_surface(width * height);
+  auto surface = GRSurface::Create(width, height, width, 1);
   if (!surface) {
     return -8;
   }
-  surface->width = width;
-  surface->height = height;
-  surface->row_bytes = width;
-  surface->pixel_bytes = 1;
 
-#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
-  png_set_bgr(png_ptr);
-#endif
+  PixelFormat pixel_format = gr_pixel_format();
+  if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
+    png_set_bgr(png_ptr);
+  }
 
   for (png_uint_32 y = 0; y < height; ++y) {
-    unsigned char* p_row = surface->data + y * surface->row_bytes;
+    uint8_t* p_row = surface->data() + y * surface->row_bytes;
     png_read_row(png_ptr, p_row, nullptr);
   }
 
-  *pSurface = surface;
+  *pSurface = surface.release();
 
   return 0;
 }
 
+void res_set_resource_dir(const std::string& dirname) {
+  g_resource_dir = dirname;
+}
+
 // This function tests if a locale string stored in PNG (prefix) matches
 // the locale string provided by the system (locale).
 bool matches_locale(const std::string& prefix, const std::string& locale) {
@@ -397,6 +347,10 @@
   // match the locale string without the {script} section.
   // For instance, prefix == "en" matches locale == "en-US", prefix == "sr-Latn" matches locale
   // == "sr-Latn-BA", and prefix == "zh-CN" matches locale == "zh-Hans-CN".
+  if (prefix.empty()) {
+    return false;
+  }
+
   if (android::base::StartsWith(locale, prefix)) {
     return true;
   }
@@ -421,7 +375,7 @@
   }
 
   std::vector<std::string> result;
-  std::vector<unsigned char> row(png_handler.width());
+  std::vector<uint8_t> row(png_handler.width());
   for (png_uint_32 y = 0; y < png_handler.height(); ++y) {
     png_read_row(png_handler.png_ptr(), row.data(), nullptr);
     int h = (row[3] << 8) | row[2];
@@ -457,32 +411,34 @@
   png_uint_32 height = png_handler.height();
 
   for (png_uint_32 y = 0; y < height; ++y) {
-    std::vector<unsigned char> row(width);
+    std::vector<uint8_t> row(width);
     png_read_row(png_ptr, row.data(), nullptr);
     int w = (row[1] << 8) | row[0];
     int h = (row[3] << 8) | row[2];
     __unused int len = row[4];
     char* loc = reinterpret_cast<char*>(&row[5]);
 
-    if (y + 1 + h >= height || matches_locale(loc, locale)) {
+    // We need to include one additional line for the metadata of the localized image.
+    if (y + 1 + h > height) {
+      printf("Read exceeds the image boundary, y %u, h %d, height %u\n", y, h, height);
+      return -8;
+    }
+
+    if (matches_locale(loc, locale)) {
       printf("  %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
 
-      GRSurface* surface = malloc_surface(w * h);
+      auto surface = GRSurface::Create(w, h, w, 1);
       if (!surface) {
-        return -8;
+        return -9;
       }
-      surface->width = w;
-      surface->height = h;
-      surface->row_bytes = w;
-      surface->pixel_bytes = 1;
 
       for (int i = 0; i < h; ++i, ++y) {
         png_read_row(png_ptr, row.data(), nullptr);
-        memcpy(surface->data + i * w, row.data(), w);
+        memcpy(surface->data() + i * w, row.data(), w);
       }
 
-      *pSurface = surface;
-      break;
+      *pSurface = surface.release();
+      return 0;
     }
 
     for (int i = 0; i < h; ++i, ++y) {
@@ -490,7 +446,7 @@
     }
   }
 
-  return 0;
+  return -10;
 }
 
 void res_free_surface(GRSurface* surface) {
diff --git a/otafault/Android.bp b/otafault/Android.bp
deleted file mode 100644
index b39d5be..0000000
--- a/otafault/Android.bp
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-cc_library_static {
-    name: "libotafault",
-
-    host_supported: true,
-
-    srcs: [
-        "config.cpp",
-        "ota_io.cpp",
-    ],
-
-    static_libs: [
-        "libbase",
-        "liblog",
-        "libziparchive",
-    ],
-
-    export_include_dirs: [
-        "include",
-    ],
-
-    cflags: [
-        "-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS",
-        "-Wall",
-        "-Werror",
-        "-Wthread-safety",
-        "-Wthread-safety-negative",
-    ],
-
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-}
-
-cc_test {
-    name: "otafault_test",
-
-    srcs: ["test.cpp"],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-
-    static_executable: true,
-
-    static_libs: [
-        "libotafault",
-        "libziparchive",
-        "libbase",
-        "liblog",
-    ],
-}
diff --git a/otafault/config.cpp b/otafault/config.cpp
deleted file mode 100644
index 3993948..0000000
--- a/otafault/config.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "otafault/config.h"
-
-#include <map>
-#include <string>
-
-#include <android-base/stringprintf.h>
-#include <ziparchive/zip_archive.h>
-
-#include "otafault/ota_io.h"
-
-#define OTAIO_MAX_FNAME_SIZE 128
-
-static ZipArchiveHandle archive;
-static bool is_retry = false;
-static std::map<std::string, bool> should_inject_cache;
-
-static std::string get_type_path(const char* io_type) {
-    return android::base::StringPrintf("%s/%s", OTAIO_BASE_DIR, io_type);
-}
-
-void ota_io_init(ZipArchiveHandle za, bool retry) {
-    archive = za;
-    is_retry = retry;
-    ota_set_fault_files();
-}
-
-bool should_fault_inject(const char* io_type) {
-    // archive will be NULL if we used an entry point other
-    // than updater/updater.cpp:main
-    if (archive == nullptr || is_retry) {
-        return false;
-    }
-    const std::string type_path = get_type_path(io_type);
-    if (should_inject_cache.find(type_path) != should_inject_cache.end()) {
-        return should_inject_cache[type_path];
-    }
-    ZipString zip_type_path(type_path.c_str());
-    ZipEntry entry;
-    int status = FindEntry(archive, zip_type_path, &entry);
-    should_inject_cache[type_path] = (status == 0);
-    return (status == 0);
-}
-
-bool should_hit_cache() {
-    return should_fault_inject(OTAIO_CACHE);
-}
-
-std::string fault_fname(const char* io_type) {
-    std::string type_path = get_type_path(io_type);
-    std::string fname;
-    fname.resize(OTAIO_MAX_FNAME_SIZE);
-    ZipString zip_type_path(type_path.c_str());
-    ZipEntry entry;
-    if (FindEntry(archive, zip_type_path, &entry) != 0) {
-        return {};
-    }
-    ExtractToMemory(archive, &entry, reinterpret_cast<uint8_t*>(&fname[0]), OTAIO_MAX_FNAME_SIZE);
-    return fname;
-}
diff --git a/otafault/include/otafault/config.h b/otafault/include/otafault/config.h
deleted file mode 100644
index cc4bfd2..0000000
--- a/otafault/include/otafault/config.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/*
- * Read configuration files in the OTA package to determine which files, if any, will trigger
- * errors.
- *
- * OTA packages can be modified to trigger errors by adding a top-level directory called
- * .libotafault, which may optionally contain up to three files called READ, WRITE, and FSYNC.
- * Each one of these optional files contains the name of a single file on the device disk which
- * will cause an IO error on the first call of the appropriate I/O action to that file.
- *
- * Example:
- * ota.zip
- *   <normal package contents>
- *   .libotafault
- *     WRITE
- *
- * If the contents of the file WRITE were /system/build.prop, the first write action to
- * /system/build.prop would fail with EIO. Note that READ and FSYNC files are absent, so these
- * actions will not cause an error.
- */
-
-#ifndef _UPDATER_OTA_IO_CFG_H_
-#define _UPDATER_OTA_IO_CFG_H_
-
-#include <string>
-
-#include <ziparchive/zip_archive.h>
-
-#define OTAIO_BASE_DIR ".libotafault"
-#define OTAIO_READ "READ"
-#define OTAIO_WRITE "WRITE"
-#define OTAIO_FSYNC "FSYNC"
-#define OTAIO_CACHE "CACHE"
-
-/*
- * Initialize libotafault by providing a reference to the OTA package.
- */
-void ota_io_init(ZipArchiveHandle zip, bool retry);
-
-/*
- * Return true if a config file is present for the given IO type.
- */
-bool should_fault_inject(const char* io_type);
-
-/*
- * Return true if an EIO should occur on the next hit to /cache/saved.file
- * instead of the next hit to the specified file.
- */
-bool should_hit_cache();
-
-/*
- * Return the name of the file that should cause an error for the
- * given IO type.
- */
-std::string fault_fname(const char* io_type);
-
-#endif
diff --git a/otafault/include/otafault/ota_io.h b/otafault/include/otafault/ota_io.h
deleted file mode 100644
index 45e481a..0000000
--- a/otafault/include/otafault/ota_io.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/*
- * Provide a series of proxy functions for basic file accessors.
- * The behavior of these functions can be changed to return different
- * errors under a variety of conditions.
- */
-
-#ifndef _UPDATER_OTA_IO_H_
-#define _UPDATER_OTA_IO_H_
-
-#include <stddef.h>
-#include <stdio.h>
-#include <sys/stat.h>  // mode_t
-
-#include <memory>
-
-#include <android-base/unique_fd.h>
-
-#define OTAIO_CACHE_FNAME "/cache/saved.file"
-
-void ota_set_fault_files();
-
-int ota_open(const char* path, int oflags);
-
-int ota_open(const char* path, int oflags, mode_t mode);
-
-FILE* ota_fopen(const char* filename, const char* mode);
-
-size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream);
-
-ssize_t ota_read(int fd, void* buf, size_t nbyte);
-
-size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
-
-ssize_t ota_write(int fd, const void* buf, size_t nbyte);
-
-int ota_fsync(int fd);
-
-struct OtaCloser {
-  static void Close(int);
-};
-
-using unique_fd = android::base::unique_fd_impl<OtaCloser>;
-
-int ota_close(unique_fd& fd);
-
-struct OtaFcloser {
-  void operator()(FILE*) const;
-};
-
-using unique_file = std::unique_ptr<FILE, OtaFcloser>;
-
-int ota_fclose(unique_file& fh);
-
-#endif
diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp
deleted file mode 100644
index 63ef18e..0000000
--- a/otafault/ota_io.cpp
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "otafault/ota_io.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <map>
-#include <mutex>
-#include <string>
-
-#include <android-base/thread_annotations.h>
-
-#include "otafault/config.h"
-
-static std::mutex filename_mutex;
-static std::map<intptr_t, const char*> filename_cache GUARDED_BY(filename_mutex);
-static std::string read_fault_file_name = "";
-static std::string write_fault_file_name = "";
-static std::string fsync_fault_file_name = "";
-
-static bool get_hit_file(const char* cached_path, const std::string& ffn) {
-    return should_hit_cache()
-        ? !strncmp(cached_path, OTAIO_CACHE_FNAME, strlen(cached_path))
-        : !strncmp(cached_path, ffn.c_str(), strlen(cached_path));
-}
-
-void ota_set_fault_files() {
-    if (should_fault_inject(OTAIO_READ)) {
-        read_fault_file_name = fault_fname(OTAIO_READ);
-    }
-    if (should_fault_inject(OTAIO_WRITE)) {
-        write_fault_file_name = fault_fname(OTAIO_WRITE);
-    }
-    if (should_fault_inject(OTAIO_FSYNC)) {
-        fsync_fault_file_name = fault_fname(OTAIO_FSYNC);
-    }
-}
-
-bool have_eio_error = false;
-
-int ota_open(const char* path, int oflags) {
-    // Let the caller handle errors; we do not care if open succeeds or fails
-    int fd = open(path, oflags);
-    std::lock_guard<std::mutex> lock(filename_mutex);
-    filename_cache[fd] = path;
-    return fd;
-}
-
-int ota_open(const char* path, int oflags, mode_t mode) {
-    int fd = open(path, oflags, mode);
-    std::lock_guard<std::mutex> lock(filename_mutex);
-    filename_cache[fd] = path;
-    return fd;
-}
-
-FILE* ota_fopen(const char* path, const char* mode) {
-    FILE* fh = fopen(path, mode);
-    std::lock_guard<std::mutex> lock(filename_mutex);
-    filename_cache[(intptr_t)fh] = path;
-    return fh;
-}
-
-static int __ota_close(int fd) {
-    // descriptors can be reused, so make sure not to leave them in the cache
-    std::lock_guard<std::mutex> lock(filename_mutex);
-    filename_cache.erase(fd);
-    return close(fd);
-}
-
-void OtaCloser::Close(int fd) {
-    __ota_close(fd);
-}
-
-int ota_close(unique_fd& fd) {
-    return __ota_close(fd.release());
-}
-
-static int __ota_fclose(FILE* fh) {
-    std::lock_guard<std::mutex> lock(filename_mutex);
-    filename_cache.erase(reinterpret_cast<intptr_t>(fh));
-    return fclose(fh);
-}
-
-void OtaFcloser::operator()(FILE* f) const {
-    __ota_fclose(f);
-};
-
-int ota_fclose(unique_file& fh) {
-  return __ota_fclose(fh.release());
-}
-
-size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) {
-    if (should_fault_inject(OTAIO_READ)) {
-        std::lock_guard<std::mutex> lock(filename_mutex);
-        auto cached = filename_cache.find((intptr_t)stream);
-        const char* cached_path = cached->second;
-        if (cached != filename_cache.end() &&
-                get_hit_file(cached_path, read_fault_file_name)) {
-            read_fault_file_name = "";
-            errno = EIO;
-            have_eio_error = true;
-            return 0;
-        }
-    }
-    size_t status = fread(ptr, size, nitems, stream);
-    // If I/O error occurs, set the retry-update flag.
-    if (status != nitems && errno == EIO) {
-        have_eio_error = true;
-    }
-    return status;
-}
-
-ssize_t ota_read(int fd, void* buf, size_t nbyte) {
-    if (should_fault_inject(OTAIO_READ)) {
-        std::lock_guard<std::mutex> lock(filename_mutex);
-        auto cached = filename_cache.find(fd);
-        const char* cached_path = cached->second;
-        if (cached != filename_cache.end()
-                && get_hit_file(cached_path, read_fault_file_name)) {
-            read_fault_file_name = "";
-            errno = EIO;
-            have_eio_error = true;
-            return -1;
-        }
-    }
-    ssize_t status = read(fd, buf, nbyte);
-    if (status == -1 && errno == EIO) {
-        have_eio_error = true;
-    }
-    return status;
-}
-
-size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) {
-    if (should_fault_inject(OTAIO_WRITE)) {
-        std::lock_guard<std::mutex> lock(filename_mutex);
-        auto cached = filename_cache.find((intptr_t)stream);
-        const char* cached_path = cached->second;
-        if (cached != filename_cache.end() &&
-                get_hit_file(cached_path, write_fault_file_name)) {
-            write_fault_file_name = "";
-            errno = EIO;
-            have_eio_error = true;
-            return 0;
-        }
-    }
-    size_t status = fwrite(ptr, size, count, stream);
-    if (status != count && errno == EIO) {
-        have_eio_error = true;
-    }
-    return status;
-}
-
-ssize_t ota_write(int fd, const void* buf, size_t nbyte) {
-    if (should_fault_inject(OTAIO_WRITE)) {
-        std::lock_guard<std::mutex> lock(filename_mutex);
-        auto cached = filename_cache.find(fd);
-        const char* cached_path = cached->second;
-        if (cached != filename_cache.end() &&
-                get_hit_file(cached_path, write_fault_file_name)) {
-            write_fault_file_name = "";
-            errno = EIO;
-            have_eio_error = true;
-            return -1;
-        }
-    }
-    ssize_t status = write(fd, buf, nbyte);
-    if (status == -1 && errno == EIO) {
-        have_eio_error = true;
-    }
-    return status;
-}
-
-int ota_fsync(int fd) {
-    if (should_fault_inject(OTAIO_FSYNC)) {
-        std::lock_guard<std::mutex> lock(filename_mutex);
-        auto cached = filename_cache.find(fd);
-        const char* cached_path = cached->second;
-        if (cached != filename_cache.end() &&
-                get_hit_file(cached_path, fsync_fault_file_name)) {
-            fsync_fault_file_name = "";
-            errno = EIO;
-            have_eio_error = true;
-            return -1;
-        }
-    }
-    int status = fsync(fd);
-    if (status == -1 && errno == EIO) {
-        have_eio_error = true;
-    }
-    return status;
-}
-
diff --git a/otafault/test.cpp b/otafault/test.cpp
deleted file mode 100644
index 63e2445..0000000
--- a/otafault/test.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <fcntl.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "otafault/ota_io.h"
-
-int main(int /* argc */, char** /* argv */) {
-    int fd = open("testdata/test.file", O_RDWR);
-    char buf[8];
-    const char* out = "321";
-    int readv = ota_read(fd, buf, 4);
-    printf("Read returned %d\n", readv);
-    int writev = ota_write(fd, out, 4);
-    printf("Write returned %d\n", writev);
-    close(fd);
-    return 0;
-}
diff --git a/otautil/Android.bp b/otautil/Android.bp
index 75cf691..73398c3 100644
--- a/otautil/Android.bp
+++ b/otautil/Android.bp
@@ -16,27 +16,56 @@
     name: "libotautil",
 
     host_supported: true,
+    recovery_available: true,
 
+    defaults: [
+        "recovery_defaults",
+    ],
+
+    // Minimal set of files to support host build.
     srcs: [
-        "SysUtil.cpp",
-        "DirUtil.cpp",
-        "ThermalUtil.cpp",
-        "cache_location.cpp",
+        "paths.cpp",
         "rangeset.cpp",
     ],
 
-    static_libs: [
-        "libselinux",
+    shared_libs: [
         "libbase",
     ],
 
-    cflags: [
-        "-D_FILE_OFFSET_BITS=64",
-        "-Werror",
-        "-Wall",
-    ],
-
     export_include_dirs: [
         "include",
     ],
+
+    target: {
+        android: {
+            srcs: [
+                "dirutil.cpp",
+                "logging.cpp",
+                "mounts.cpp",
+                "parse_install_logs.cpp",
+                "roots.cpp",
+                "sysutil.cpp",
+                "thermalutil.cpp",
+            ],
+
+            include_dirs: [
+                "system/vold",
+            ],
+
+            static_libs: [
+                "libfstab",
+            ],
+
+            shared_libs: [
+                "libcutils",
+                "libext4_utils",
+                "libfs_mgr",
+                "libselinux",
+            ],
+
+            export_static_lib_headers: [
+                "libfstab",
+            ],
+        },
+    },
 }
diff --git a/otautil/SysUtil.cpp b/otautil/SysUtil.cpp
deleted file mode 100644
index 48336ad..0000000
--- a/otautil/SysUtil.cpp
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright 2006 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/SysUtil.h"
-
-#include <errno.h>  // TEMP_FAILURE_RETRY
-#include <fcntl.h>
-#include <stdint.h>  // SIZE_MAX
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <string>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-
-bool MemMapping::MapFD(int fd) {
-  struct stat sb;
-  if (fstat(fd, &sb) == -1) {
-    PLOG(ERROR) << "fstat(" << fd << ") failed";
-    return false;
-  }
-
-  void* memPtr = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
-  if (memPtr == MAP_FAILED) {
-    PLOG(ERROR) << "mmap(" << sb.st_size << ", R, PRIVATE, " << fd << ", 0) failed";
-    return false;
-  }
-
-  addr = static_cast<unsigned char*>(memPtr);
-  length = sb.st_size;
-  ranges_.clear();
-  ranges_.emplace_back(MappedRange{ memPtr, static_cast<size_t>(sb.st_size) });
-
-  return true;
-}
-
-// A "block map" which looks like this (from uncrypt/uncrypt.cpp):
-//
-//   /dev/block/platform/msm_sdcc.1/by-name/userdata     # block device
-//   49652 4096                                          # file size in bytes, block size
-//   3                                                   # count of block ranges
-//   1000 1008                                           # block range 0
-//   2100 2102                                           # ... block range 1
-//   30 33                                               # ... block range 2
-//
-// Each block range represents a half-open interval; the line "30 33" reprents the blocks
-// [30, 31, 32].
-bool MemMapping::MapBlockFile(const std::string& filename) {
-  std::string content;
-  if (!android::base::ReadFileToString(filename, &content)) {
-    PLOG(ERROR) << "Failed to read " << filename;
-    return false;
-  }
-
-  std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n");
-  if (lines.size() < 4) {
-    LOG(ERROR) << "Block map file is too short: " << lines.size();
-    return false;
-  }
-
-  size_t size;
-  size_t blksize;
-  if (sscanf(lines[1].c_str(), "%zu %zu", &size, &blksize) != 2) {
-    LOG(ERROR) << "Failed to parse file size and block size: " << lines[1];
-    return false;
-  }
-
-  size_t range_count;
-  if (sscanf(lines[2].c_str(), "%zu", &range_count) != 1) {
-    LOG(ERROR) << "Failed to parse block map header: " << lines[2];
-    return false;
-  }
-
-  size_t blocks;
-  if (blksize != 0) {
-    blocks = ((size - 1) / blksize) + 1;
-  }
-  if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0 ||
-      lines.size() != 3 + range_count) {
-    LOG(ERROR) << "Invalid data in block map file: size " << size << ", blksize " << blksize
-               << ", range_count " << range_count << ", lines " << lines.size();
-    return false;
-  }
-
-  // Reserve enough contiguous address space for the whole file.
-  void* reserve = mmap(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
-  if (reserve == MAP_FAILED) {
-    PLOG(ERROR) << "failed to reserve address space";
-    return false;
-  }
-
-  const std::string& block_dev = lines[0];
-  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_dev.c_str(), O_RDONLY)));
-  if (fd == -1) {
-    PLOG(ERROR) << "failed to open block device " << block_dev;
-    munmap(reserve, blocks * blksize);
-    return false;
-  }
-
-  ranges_.clear();
-
-  unsigned char* next = static_cast<unsigned char*>(reserve);
-  size_t remaining_size = blocks * blksize;
-  bool success = true;
-  for (size_t i = 0; i < range_count; ++i) {
-    const std::string& line = lines[i + 3];
-
-    size_t start, end;
-    if (sscanf(line.c_str(), "%zu %zu\n", &start, &end) != 2) {
-      LOG(ERROR) << "failed to parse range " << i << ": " << line;
-      success = false;
-      break;
-    }
-    size_t range_size = (end - start) * blksize;
-    if (end <= start || (end - start) > SIZE_MAX / blksize || range_size > remaining_size) {
-      LOG(ERROR) << "Invalid range: " << start << " " << end;
-      success = false;
-      break;
-    }
-
-    void* range_start = mmap(next, range_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd,
-                             static_cast<off_t>(start) * blksize);
-    if (range_start == MAP_FAILED) {
-      PLOG(ERROR) << "failed to map range " << i << ": " << line;
-      success = false;
-      break;
-    }
-    ranges_.emplace_back(MappedRange{ range_start, range_size });
-
-    next += range_size;
-    remaining_size -= range_size;
-  }
-  if (success && remaining_size != 0) {
-    LOG(ERROR) << "Invalid ranges: remaining_size " << remaining_size;
-    success = false;
-  }
-  if (!success) {
-    munmap(reserve, blocks * blksize);
-    return false;
-  }
-
-  addr = static_cast<unsigned char*>(reserve);
-  length = size;
-
-  LOG(INFO) << "mmapped " << range_count << " ranges";
-
-  return true;
-}
-
-bool MemMapping::MapFile(const std::string& fn) {
-  if (fn.empty()) {
-    LOG(ERROR) << "Empty filename";
-    return false;
-  }
-
-  if (fn[0] == '@') {
-    // Block map file "@/cache/recovery/block.map".
-    if (!MapBlockFile(fn.substr(1))) {
-      LOG(ERROR) << "Map of '" << fn << "' failed";
-      return false;
-    }
-  } else {
-    // This is a regular file.
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY)));
-    if (fd == -1) {
-      PLOG(ERROR) << "Unable to open '" << fn << "'";
-      return false;
-    }
-
-    if (!MapFD(fd)) {
-      LOG(ERROR) << "Map of '" << fn << "' failed";
-      return false;
-    }
-  }
-  return true;
-}
-
-MemMapping::~MemMapping() {
-  for (const auto& range : ranges_) {
-    if (munmap(range.addr, range.length) == -1) {
-      PLOG(ERROR) << "Failed to munmap(" << range.addr << ", " << range.length << ")";
-    }
-  };
-  ranges_.clear();
-}
diff --git a/otautil/cache_location.cpp b/otautil/cache_location.cpp
deleted file mode 100644
index 8ddefec..0000000
--- a/otautil/cache_location.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "otautil/cache_location.h"
-
-constexpr const char kDefaultCacheTempSource[] = "/cache/saved.file";
-constexpr const char kDefaultLastCommandFile[] = "/cache/recovery/last_command";
-constexpr const char kDefaultStashDirectoryBase[] = "/cache/recovery";
-
-CacheLocation& CacheLocation::location() {
-  static CacheLocation cache_location;
-  return cache_location;
-}
-
-CacheLocation::CacheLocation()
-    : cache_temp_source_(kDefaultCacheTempSource),
-      last_command_file_(kDefaultLastCommandFile),
-      stash_directory_base_(kDefaultStashDirectoryBase) {}
diff --git a/otautil/DirUtil.cpp b/otautil/dirutil.cpp
similarity index 98%
rename from otautil/DirUtil.cpp
rename to otautil/dirutil.cpp
index 61c8328..ae1cd5c 100644
--- a/otautil/DirUtil.cpp
+++ b/otautil/dirutil.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "otautil/DirUtil.h"
+#include "otautil/dirutil.h"
 
 #include <dirent.h>
 #include <errno.h>
diff --git a/otautil/include/otautil/SysUtil.h b/otautil/include/otautil/SysUtil.h
deleted file mode 100644
index 52f6d20..0000000
--- a/otautil/include/otautil/SysUtil.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2006 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_SYSUTIL
-#define _OTAUTIL_SYSUTIL
-
-#include <sys/types.h>
-
-#include <string>
-#include <vector>
-
-/*
- * Use this to keep track of mapped segments.
- */
-class MemMapping {
- public:
-  ~MemMapping();
-  // Map a file into a private, read-only memory segment. If 'filename' begins with an '@'
-  // character, it is a map of blocks to be mapped, otherwise it is treated as an ordinary file.
-  bool MapFile(const std::string& filename);
-  size_t ranges() const {
-    return ranges_.size();
-  };
-
-  unsigned char* addr;  // start of data
-  size_t length;        // length of data
-
- private:
-  struct MappedRange {
-    void* addr;
-    size_t length;
-  };
-
-  bool MapBlockFile(const std::string& filename);
-  bool MapFD(int fd);
-
-  std::vector<MappedRange> ranges_;
-};
-
-#endif  // _OTAUTIL_SYSUTIL
diff --git a/otautil/include/otautil/cache_location.h b/otautil/include/otautil/cache_location.h
deleted file mode 100644
index f2f6638..0000000
--- a/otautil/include/otautil/cache_location.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _OTAUTIL_OTAUTIL_CACHE_LOCATION_H_
-#define _OTAUTIL_OTAUTIL_CACHE_LOCATION_H_
-
-#include <string>
-
-#include "android-base/macros.h"
-
-// A singleton class to maintain the update related locations. The locations should be only set
-// once at the start of the program.
-class CacheLocation {
- public:
-  static CacheLocation& location();
-
-  // getter and setter functions.
-  std::string cache_temp_source() const {
-    return cache_temp_source_;
-  }
-  void set_cache_temp_source(const std::string& temp_source) {
-    cache_temp_source_ = temp_source;
-  }
-
-  std::string last_command_file() const {
-    return last_command_file_;
-  }
-  void set_last_command_file(const std::string& last_command) {
-    last_command_file_ = last_command;
-  }
-
-  std::string stash_directory_base() const {
-    return stash_directory_base_;
-  }
-  void set_stash_directory_base(const std::string& base) {
-    stash_directory_base_ = base;
-  }
-
- private:
-  CacheLocation();
-  DISALLOW_COPY_AND_ASSIGN(CacheLocation);
-
-  // When there isn't enough room on the target filesystem to hold the patched version of the file,
-  // we copy the original here and delete it to free up space.  If the expected source file doesn't
-  // exist, or is corrupted, we look to see if the cached file contains the bits we want and use it
-  // as the source instead.  The default location for the cached source is "/cache/saved.file".
-  std::string cache_temp_source_;
-
-  // Location to save the last command that stashes blocks.
-  std::string last_command_file_;
-
-  // The base directory to write stashes during update.
-  std::string stash_directory_base_;
-};
-
-#endif  // _OTAUTIL_OTAUTIL_CACHE_LOCATION_H_
diff --git a/otautil/include/otautil/DirUtil.h b/otautil/include/otautil/dirutil.h
similarity index 100%
rename from otautil/include/otautil/DirUtil.h
rename to otautil/include/otautil/dirutil.h
diff --git a/otautil/include/otautil/error_code.h b/otautil/include/otautil/error_code.h
index b0ff42d..2b73c13 100644
--- a/otautil/include/otautil/error_code.h
+++ b/otautil/include/otautil/error_code.h
@@ -48,6 +48,8 @@
   kRebootFailure,
   kPackageExtractFileFailure,
   kPatchApplicationFailure,
+  kHashTreeComputationFailure,
+  kEioFailure,
   kVendorFailure = 200
 };
 
diff --git a/rotate_logs.h b/otautil/include/otautil/logging.h
similarity index 60%
rename from rotate_logs.h
rename to otautil/include/otautil/logging.h
index 007c33d..6083497 100644
--- a/rotate_logs.h
+++ b/otautil/include/otautil/logging.h
@@ -14,16 +14,30 @@
  * limitations under the License.
  */
 
-#ifndef _ROTATE_LOGS_H
-#define _ROTATE_LOGS_H
+#ifndef _LOGGING_H
+#define _LOGGING_H
 
 #include <stddef.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 
+#include <string>
+#include <vector>
+
 #include <log/log_id.h>
 
 static constexpr int KEEP_LOG_COUNT = 10;
 
+struct selabel_handle;
+
+struct saved_log_file {
+  std::string name;
+  struct stat sb;
+  std::string data;
+};
+
+void SetLoggingSehandle(selabel_handle* handle);
+
 ssize_t logbasename(log_id_t id, char prio, const char* filename, const char* buf, size_t len,
                     void* arg);
 
@@ -35,4 +49,17 @@
 // Overwrite any existing last_log.$max and last_kmsg.$max.
 void rotate_logs(const char* last_log_file, const char* last_kmsg_file);
 
-#endif //_ROTATE_LOG_H
+// In turn fflush(3)'s, fsync(3)'s and fclose(3)'s the given stream.
+void check_and_fclose(FILE* fp, const std::string& name);
+
+void copy_log_file_to_pmsg(const std::string& source, const std::string& destination);
+void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle);
+void reset_tmplog_offset();
+
+void save_kernel_log(const char* destination);
+
+std::vector<saved_log_file> ReadLogFilesToMemory();
+
+bool RestoreLogFilesAfterFormat(const std::vector<saved_log_file>& log_files);
+
+#endif  //_LOGGING_H
diff --git a/mounts.h b/otautil/include/otautil/mounts.h
similarity index 94%
rename from mounts.h
rename to otautil/include/otautil/mounts.h
index 0de1ebd..6786c8d 100644
--- a/mounts.h
+++ b/otautil/include/otautil/mounts.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef MOUNTS_H_
-#define MOUNTS_H_
+#pragma once
 
 struct MountedVolume;
 
@@ -24,5 +23,3 @@
 MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point);
 
 int unmount_mounted_volume(MountedVolume* volume);
-
-#endif
diff --git a/otautil/include/otautil/parse_install_logs.h b/otautil/include/otautil/parse_install_logs.h
new file mode 100644
index 0000000..135d29c
--- /dev/null
+++ b/otautil/include/otautil/parse_install_logs.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+constexpr const char* LAST_INSTALL_FILE = "/data/misc/recovery/last_install";
+constexpr const char* LAST_INSTALL_FILE_IN_CACHE = "/cache/recovery/last_install";
+
+// Parses the metrics of update applied under recovery mode in |lines|, and returns a map with
+// "name: value".
+std::map<std::string, int64_t> ParseRecoveryUpdateMetrics(const std::vector<std::string>& lines);
+// Parses the sideload history and update metrics in the last_install file. Returns a map with
+// entries as "metrics_name: value". If no such file exists, returns an empty map.
+std::map<std::string, int64_t> ParseLastInstall(const std::string& file_name);
diff --git a/otautil/include/otautil/paths.h b/otautil/include/otautil/paths.h
new file mode 100644
index 0000000..f95741a
--- /dev/null
+++ b/otautil/include/otautil/paths.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _OTAUTIL_PATHS_H_
+#define _OTAUTIL_PATHS_H_
+
+#include <string>
+
+#include <android-base/macros.h>
+
+// A singleton class to maintain the update related paths. The paths should be only set once at the
+// start of the program.
+class Paths {
+ public:
+  static Paths& Get();
+
+  std::string cache_log_directory() const {
+    return cache_log_directory_;
+  }
+  void set_cache_log_directory(const std::string& log_dir) {
+    cache_log_directory_ = log_dir;
+  }
+
+  std::string cache_temp_source() const {
+    return cache_temp_source_;
+  }
+  void set_cache_temp_source(const std::string& temp_source) {
+    cache_temp_source_ = temp_source;
+  }
+
+  std::string last_command_file() const {
+    return last_command_file_;
+  }
+  void set_last_command_file(const std::string& last_command_file) {
+    last_command_file_ = last_command_file;
+  }
+
+  std::string resource_dir() const {
+    return resource_dir_;
+  }
+  void set_resource_dir(const std::string& resource_dir) {
+    resource_dir_ = resource_dir;
+  }
+
+  std::string stash_directory_base() const {
+    return stash_directory_base_;
+  }
+  void set_stash_directory_base(const std::string& base) {
+    stash_directory_base_ = base;
+  }
+
+  std::string temporary_install_file() const {
+    return temporary_install_file_;
+  }
+  void set_temporary_install_file(const std::string& install_file) {
+    temporary_install_file_ = install_file;
+  }
+
+  std::string temporary_log_file() const {
+    return temporary_log_file_;
+  }
+  void set_temporary_log_file(const std::string& log_file) {
+    temporary_log_file_ = log_file;
+  }
+
+  std::string temporary_update_binary() const {
+    return temporary_update_binary_;
+  }
+  void set_temporary_update_binary(const std::string& update_binary) {
+    temporary_update_binary_ = update_binary;
+  }
+
+ private:
+  Paths();
+  DISALLOW_COPY_AND_ASSIGN(Paths);
+
+  // Path to the directory that contains last_log and last_kmsg log files.
+  std::string cache_log_directory_;
+
+  // Path to the temporary source file on /cache. When there isn't enough room on the target
+  // filesystem to hold the patched version of the file, we copy the original here and delete it to
+  // free up space. If the expected source file doesn't exist, or is corrupted, we look to see if
+  // the cached file contains the bits we want and use it as the source instead.
+  std::string cache_temp_source_;
+
+  // Path to the last command file.
+  std::string last_command_file_;
+
+  // Path to the resource dir;
+  std::string resource_dir_;
+
+  // Path to the base directory to write stashes during update.
+  std::string stash_directory_base_;
+
+  // Path to the temporary file that contains the install result.
+  std::string temporary_install_file_;
+
+  // Path to the temporary log file while under recovery.
+  std::string temporary_log_file_;
+
+  // Path to the temporary update binary while installing a non-A/B package.
+  std::string temporary_update_binary_;
+};
+
+#endif  // _OTAUTIL_PATHS_H_
diff --git a/otautil/include/otautil/rangeset.h b/otautil/include/otautil/rangeset.h
index e91d02c..a18c30e 100644
--- a/otautil/include/otautil/rangeset.h
+++ b/otautil/include/otautil/rangeset.h
@@ -18,6 +18,7 @@
 
 #include <stddef.h>
 
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -49,6 +50,12 @@
   // bounds. For example, "3,5" contains blocks 3 and 4. So "3,5" and "5,7" are not overlapped.
   bool Overlaps(const RangeSet& other) const;
 
+  // Returns a subset of ranges starting from |start_index| with respect to the original range. The
+  // output range will have |num_of_blocks| blocks in size. Returns std::nullopt if the input is
+  // invalid. e.g. RangeSet({{0, 5}, {10, 15}}).GetSubRanges(1, 5) returns
+  // RangeSet({{1, 5}, {10, 11}}).
+  std::optional<RangeSet> GetSubRanges(size_t start_index, size_t num_of_blocks) const;
+
   // Returns a vector of RangeSets that contain the same set of blocks represented by the current
   // RangeSet. The RangeSets in the vector contain similar number of blocks, with a maximum delta
   // of 1-block between any two of them. For example, 14 blocks would be split into 4 + 4 + 3 + 3,
diff --git a/roots.h b/otautil/include/otautil/roots.h
similarity index 80%
rename from roots.h
rename to otautil/include/otautil/roots.h
index 46bb77e..2ab3f45 100644
--- a/roots.h
+++ b/otautil/include/otautil/roots.h
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef RECOVERY_ROOTS_H_
-#define RECOVERY_ROOTS_H_
+#pragma once
 
 #include <string>
 
-typedef struct fstab_rec Volume;
+#include <fstab/fstab.h>
+
+using Volume = android::fs_mgr::FstabEntry;
 
 // Load and parse volume data from /etc/recovery.fstab.
 void load_volume_table();
@@ -29,28 +30,26 @@
 
 // Make sure that the volume 'path' is on is mounted.  Returns 0 on
 // success (volume is mounted).
-int ensure_path_mounted(const char* path);
+int ensure_path_mounted(const std::string& path);
 
 // Similar to ensure_path_mounted, but allows one to specify the mount_point.
-int ensure_path_mounted_at(const char* path, const char* mount_point);
+int ensure_path_mounted_at(const std::string& path, const std::string& mount_point);
 
 // Make sure that the volume 'path' is on is unmounted.  Returns 0 on
 // success (volume is unmounted);
-int ensure_path_unmounted(const char* path);
+int ensure_path_unmounted(const std::string& path);
 
 // Reformat the given volume (must be the mount point only, eg
 // "/cache"), no paths permitted.  Attempts to unmount the volume if
 // it is mounted.
-int format_volume(const char* volume);
+int format_volume(const std::string& volume);
 
 // Reformat the given volume (must be the mount point only, eg
 // "/cache"), no paths permitted.  Attempts to unmount the volume if
 // it is mounted.
 // Copies 'directory' to root of the newly formatted volume
-int format_volume(const char* volume, const char* directory);
+int format_volume(const std::string& volume, const std::string& directory);
 
 // Ensure that all and only the volumes that packages expect to find
 // mounted (/tmp and /cache) are mounted.  Returns 0 on success.
 int setup_install_mounts();
-
-#endif  // RECOVERY_ROOTS_H_
diff --git a/otautil/include/otautil/sysutil.h b/otautil/include/otautil/sysutil.h
new file mode 100644
index 0000000..48e9011
--- /dev/null
+++ b/otautil/include/otautil/sysutil.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "rangeset.h"
+
+// This class holds the content of a block map file.
+class BlockMapData {
+ public:
+  // A "block map" which looks like this (from uncrypt/uncrypt.cpp):
+  //
+  //   /dev/block/platform/msm_sdcc.1/by-name/userdata     # block device
+  //   49652 4096                                          # file size in bytes, block size
+  //   3                                                   # count of block ranges
+  //   1000 1008                                           # block range 0
+  //   2100 2102                                           # ... block range 1
+  //   30 33                                               # ... block range 2
+  //
+  // Each block range represents a half-open interval; the line "30 33" reprents the blocks
+  // [30, 31, 32].
+  static BlockMapData ParseBlockMapFile(const std::string& block_map_path);
+
+  explicit operator bool() const {
+    return !path_.empty();
+  }
+
+  std::string path() const {
+    return path_;
+  }
+  uint64_t file_size() const {
+    return file_size_;
+  }
+  uint32_t block_size() const {
+    return block_size_;
+  }
+  RangeSet block_ranges() const {
+    return block_ranges_;
+  }
+
+ private:
+  BlockMapData() = default;
+
+  BlockMapData(const std::string& path, uint64_t file_size, uint32_t block_size,
+               RangeSet block_ranges)
+      : path_(path),
+        file_size_(file_size),
+        block_size_(block_size),
+        block_ranges_(std::move(block_ranges)) {}
+
+  std::string path_;
+  uint64_t file_size_ = 0;
+  uint32_t block_size_ = 0;
+  RangeSet block_ranges_;
+};
+
+/*
+ * Use this to keep track of mapped segments.
+ */
+class MemMapping {
+ public:
+  ~MemMapping();
+  // Map a file into a private, read-only memory segment. If 'filename' begins with an '@'
+  // character, it is a map of blocks to be mapped, otherwise it is treated as an ordinary file.
+  bool MapFile(const std::string& filename);
+  size_t ranges() const {
+    return ranges_.size();
+  };
+
+  unsigned char* addr;  // start of data
+  size_t length;        // length of data
+
+ private:
+  struct MappedRange {
+    void* addr;
+    size_t length;
+  };
+
+  bool MapBlockFile(const std::string& filename);
+  bool MapFD(int fd);
+
+  std::vector<MappedRange> ranges_;
+};
+
+// Reboots the device into the specified target, by additionally handling quiescent reboot mode.
+// 'target' can be an empty string, which indicates booting into Android.
+bool Reboot(std::string_view target);
+
+// Triggers a shutdown.
+bool Shutdown();
+
+// Returns a null-terminated char* array, where the elements point to the C-strings in the given
+// vector, plus an additional nullptr at the end. This is a helper function that facilitates
+// calling C functions (such as getopt(3)) that expect an array of C-strings.
+std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args);
diff --git a/otautil/include/otautil/ThermalUtil.h b/otautil/include/otautil/thermalutil.h
similarity index 100%
rename from otautil/include/otautil/ThermalUtil.h
rename to otautil/include/otautil/thermalutil.h
diff --git a/otautil/logging.cpp b/otautil/logging.cpp
new file mode 100644
index 0000000..484f115
--- /dev/null
+++ b/otautil/logging.cpp
@@ -0,0 +1,325 @@
+/*
+ * 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/logging.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/klog.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <private/android_filesystem_config.h> /* for AID_SYSTEM */
+#include <private/android_logger.h>            /* private pmsg functions */
+#include <selinux/label.h>
+
+#include "otautil/dirutil.h"
+#include "otautil/paths.h"
+#include "otautil/roots.h"
+
+constexpr const char* LOG_FILE = "/cache/recovery/log";
+constexpr const char* LAST_INSTALL_FILE = "/cache/recovery/last_install";
+constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
+constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log";
+
+constexpr const char* LAST_KMSG_FILTER = "recovery/last_kmsg";
+constexpr const char* LAST_LOG_FILTER = "recovery/last_log";
+
+constexpr const char* CACHE_LOG_DIR = "/cache/recovery";
+
+static struct selabel_handle* logging_sehandle;
+
+void SetLoggingSehandle(selabel_handle* handle) {
+  logging_sehandle = handle;
+}
+
+// fopen(3)'s the given file, by mounting volumes and making parent dirs as necessary. Returns the
+// file pointer, or nullptr on error.
+static FILE* fopen_path(const std::string& path, const char* mode, const selabel_handle* sehandle) {
+  if (ensure_path_mounted(path) != 0) {
+    LOG(ERROR) << "Can't mount " << path;
+    return nullptr;
+  }
+
+  // When writing, try to create the containing directory, if necessary. Use generous permissions,
+  // the system (init.rc) will reset them.
+  if (strchr("wa", mode[0])) {
+    mkdir_recursively(path, 0777, true, sehandle);
+  }
+  return fopen(path.c_str(), mode);
+}
+
+void check_and_fclose(FILE* fp, const std::string& name) {
+  fflush(fp);
+  if (fsync(fileno(fp)) == -1) {
+    PLOG(ERROR) << "Failed to fsync " << name;
+  }
+  if (ferror(fp)) {
+    PLOG(ERROR) << "Error in " << name;
+  }
+  fclose(fp);
+}
+
+// close a file, log an error if the error indicator is set
+ssize_t logbasename(log_id_t /* id */, char /* prio */, const char* filename, const char* /* buf */,
+                    size_t len, void* arg) {
+  bool* do_rotate = static_cast<bool*>(arg);
+  if (std::string(LAST_KMSG_FILTER).find(filename) != std::string::npos ||
+      std::string(LAST_LOG_FILTER).find(filename) != std::string::npos) {
+    *do_rotate = true;
+  }
+  return len;
+}
+
+ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len,
+                  void* arg) {
+  bool* do_rotate = static_cast<bool*>(arg);
+  if (!*do_rotate) {
+    return __android_log_pmsg_file_write(id, prio, filename, buf, len);
+  }
+
+  std::string name(filename);
+  size_t dot = name.find_last_of('.');
+  std::string sub = name.substr(0, dot);
+
+  if (std::string(LAST_KMSG_FILTER).find(sub) == std::string::npos &&
+      std::string(LAST_LOG_FILTER).find(sub) == std::string::npos) {
+    return __android_log_pmsg_file_write(id, prio, filename, buf, len);
+  }
+
+  // filename rotation
+  if (dot == std::string::npos) {
+    name += ".1";
+  } else {
+    std::string number = name.substr(dot + 1);
+    if (!isdigit(number[0])) {
+      name += ".1";
+    } else {
+      size_t i;
+      if (!android::base::ParseUint(number, &i)) {
+        LOG(ERROR) << "failed to parse uint in " << number;
+        return -1;
+      }
+      name = sub + "." + std::to_string(i + 1);
+    }
+  }
+
+  return __android_log_pmsg_file_write(id, prio, name.c_str(), buf, len);
+}
+
+// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max.
+// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max.
+// Overwrite any existing last_log.$max and last_kmsg.$max.
+void rotate_logs(const char* last_log_file, const char* last_kmsg_file) {
+  // Logs should only be rotated once.
+  static bool rotated = false;
+  if (rotated) {
+    return;
+  }
+  rotated = true;
+
+  for (int i = KEEP_LOG_COUNT - 1; i >= 0; --i) {
+    std::string old_log = android::base::StringPrintf("%s", last_log_file);
+    if (i > 0) {
+      old_log += "." + std::to_string(i);
+    }
+    std::string new_log = android::base::StringPrintf("%s.%d", last_log_file, i + 1);
+    // Ignore errors if old_log doesn't exist.
+    rename(old_log.c_str(), new_log.c_str());
+
+    std::string old_kmsg = android::base::StringPrintf("%s", last_kmsg_file);
+    if (i > 0) {
+      old_kmsg += "." + std::to_string(i);
+    }
+    std::string new_kmsg = android::base::StringPrintf("%s.%d", last_kmsg_file, i + 1);
+    rename(old_kmsg.c_str(), new_kmsg.c_str());
+  }
+}
+
+// Writes content to the current pmsg session.
+static ssize_t __pmsg_write(const std::string& filename, const std::string& buf) {
+  return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filename.c_str(),
+                                       buf.data(), buf.size());
+}
+
+void copy_log_file_to_pmsg(const std::string& source, const std::string& destination) {
+  std::string content;
+  android::base::ReadFileToString(source, &content);
+  __pmsg_write(destination, content);
+}
+
+// How much of the temp log we have copied to the copy in cache.
+static off_t tmplog_offset = 0;
+
+void reset_tmplog_offset() {
+  tmplog_offset = 0;
+}
+
+static void copy_log_file(const std::string& source, const std::string& destination, bool append,
+                          const selabel_handle* sehandle) {
+  FILE* dest_fp = fopen_path(destination, append ? "ae" : "we", sehandle);
+  if (dest_fp == nullptr) {
+    PLOG(ERROR) << "Can't open " << destination;
+  } else {
+    FILE* source_fp = fopen(source.c_str(), "re");
+    if (source_fp != nullptr) {
+      if (append) {
+        fseeko(source_fp, tmplog_offset, SEEK_SET);  // Since last write
+      }
+      char buf[4096];
+      size_t bytes;
+      while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
+        fwrite(buf, 1, bytes, dest_fp);
+      }
+      if (append) {
+        tmplog_offset = ftello(source_fp);
+      }
+      check_and_fclose(source_fp, source);
+    }
+    check_and_fclose(dest_fp, destination);
+  }
+}
+
+void copy_logs(bool save_current_log, bool has_cache, const selabel_handle* sehandle) {
+  // We only rotate and record the log of the current session if explicitly requested. This usually
+  // happens after wipes, installation from BCB or menu selections. This is to avoid unnecessary
+  // rotation (and possible deletion) of log files, if it does not do anything loggable.
+  if (!save_current_log) {
+    return;
+  }
+
+  // Always write to pmsg, this allows the OTA logs to be caught in `logcat -L`.
+  copy_log_file_to_pmsg(Paths::Get().temporary_log_file(), LAST_LOG_FILE);
+  copy_log_file_to_pmsg(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE);
+
+  // We can do nothing for now if there's no /cache partition.
+  if (!has_cache) {
+    return;
+  }
+
+  ensure_path_mounted(LAST_LOG_FILE);
+  ensure_path_mounted(LAST_KMSG_FILE);
+  rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);
+
+  // Copy logs to cache so the system can find out what happened.
+  copy_log_file(Paths::Get().temporary_log_file(), LOG_FILE, true, sehandle);
+  copy_log_file(Paths::Get().temporary_log_file(), LAST_LOG_FILE, false, sehandle);
+  copy_log_file(Paths::Get().temporary_install_file(), LAST_INSTALL_FILE, false, sehandle);
+  save_kernel_log(LAST_KMSG_FILE);
+  chmod(LOG_FILE, 0600);
+  chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM);
+  chmod(LAST_KMSG_FILE, 0600);
+  chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM);
+  chmod(LAST_LOG_FILE, 0640);
+  chmod(LAST_INSTALL_FILE, 0644);
+  chown(LAST_INSTALL_FILE, AID_SYSTEM, AID_SYSTEM);
+  sync();
+}
+
+// Read from kernel log into buffer and write out to file.
+void save_kernel_log(const char* destination) {
+  int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0);
+  if (klog_buf_len <= 0) {
+    PLOG(ERROR) << "Error getting klog size";
+    return;
+  }
+
+  std::string buffer(klog_buf_len, 0);
+  int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len);
+  if (n == -1) {
+    PLOG(ERROR) << "Error in reading klog";
+    return;
+  }
+  buffer.resize(n);
+  android::base::WriteStringToFile(buffer, destination);
+}
+
+std::vector<saved_log_file> ReadLogFilesToMemory() {
+  ensure_path_mounted("/cache");
+
+  struct dirent* de;
+  std::unique_ptr<DIR, decltype(&closedir)> d(opendir(CACHE_LOG_DIR), closedir);
+  if (!d) {
+    if (errno != ENOENT) {
+      PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR;
+    }
+    return {};
+  }
+
+  std::vector<saved_log_file> log_files;
+  while ((de = readdir(d.get())) != nullptr) {
+    if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) {
+      std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name);
+
+      struct stat sb;
+      if (stat(path.c_str(), &sb) != 0) {
+        PLOG(ERROR) << "Failed to stat " << path;
+        continue;
+      }
+      // Truncate files to 512kb
+      size_t read_size = std::min<size_t>(sb.st_size, 1 << 19);
+      std::string data(read_size, '\0');
+
+      android::base::unique_fd log_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY)));
+      if (log_fd == -1 || !android::base::ReadFully(log_fd, data.data(), read_size)) {
+        PLOG(ERROR) << "Failed to read log file " << path;
+        continue;
+      }
+
+      log_files.emplace_back(saved_log_file{ path, sb, data });
+    }
+  }
+
+  return log_files;
+}
+
+bool RestoreLogFilesAfterFormat(const std::vector<saved_log_file>& log_files) {
+  // Re-create the log dir and write back the log entries.
+  if (ensure_path_mounted(CACHE_LOG_DIR) != 0) {
+    PLOG(ERROR) << "Failed to mount " << CACHE_LOG_DIR;
+    return false;
+  }
+
+  if (mkdir_recursively(CACHE_LOG_DIR, 0777, false, logging_sehandle) != 0) {
+    PLOG(ERROR) << "Failed to create " << CACHE_LOG_DIR;
+    return false;
+  }
+
+  for (const auto& log : log_files) {
+    if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid,
+                                          log.sb.st_gid)) {
+      PLOG(ERROR) << "Failed to write to " << log.name;
+    }
+  }
+
+  // Any part of the log we'd copied to cache is now gone.
+  // Reset the pointer so we copy from the beginning of the temp
+  // log.
+  reset_tmplog_offset();
+  copy_logs(true /* save_current_log */, true /* has_cache */, logging_sehandle);
+
+  return true;
+}
diff --git a/mounts.cpp b/otautil/mounts.cpp
similarity index 60%
rename from mounts.cpp
rename to otautil/mounts.cpp
index 76fa657..951311b 100644
--- a/mounts.cpp
+++ b/otautil/mounts.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "mounts.h"
+#include "otautil/mounts.h"
 
 #include <errno.h>
 #include <fcntl.h>
@@ -30,43 +30,43 @@
 #include <android-base/logging.h>
 
 struct MountedVolume {
-    std::string device;
-    std::string mount_point;
-    std::string filesystem;
-    std::string flags;
+  std::string device;
+  std::string mount_point;
+  std::string filesystem;
+  std::string flags;
 };
 
-std::vector<MountedVolume*> g_mounts_state;
+static std::vector<MountedVolume*> g_mounts_state;
 
 bool scan_mounted_volumes() {
-    for (size_t i = 0; i < g_mounts_state.size(); ++i) {
-        delete g_mounts_state[i];
-    }
-    g_mounts_state.clear();
+  for (size_t i = 0; i < g_mounts_state.size(); ++i) {
+    delete g_mounts_state[i];
+  }
+  g_mounts_state.clear();
 
-    // Open and read mount table entries.
-    FILE* fp = setmntent("/proc/mounts", "re");
-    if (fp == NULL) {
-        return false;
-    }
-    mntent* e;
-    while ((e = getmntent(fp)) != NULL) {
-        MountedVolume* v = new MountedVolume;
-        v->device = e->mnt_fsname;
-        v->mount_point = e->mnt_dir;
-        v->filesystem = e->mnt_type;
-        v->flags = e->mnt_opts;
-        g_mounts_state.push_back(v);
-    }
-    endmntent(fp);
-    return true;
+  // Open and read mount table entries.
+  FILE* fp = setmntent("/proc/mounts", "re");
+  if (fp == NULL) {
+    return false;
+  }
+  mntent* e;
+  while ((e = getmntent(fp)) != NULL) {
+    MountedVolume* v = new MountedVolume;
+    v->device = e->mnt_fsname;
+    v->mount_point = e->mnt_dir;
+    v->filesystem = e->mnt_type;
+    v->flags = e->mnt_opts;
+    g_mounts_state.push_back(v);
+  }
+  endmntent(fp);
+  return true;
 }
 
 MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) {
-    for (size_t i = 0; i < g_mounts_state.size(); ++i) {
-        if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i];
-    }
-    return nullptr;
+  for (size_t i = 0; i < g_mounts_state.size(); ++i) {
+    if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i];
+  }
+  return nullptr;
 }
 
 int unmount_mounted_volume(MountedVolume* volume) {
diff --git a/otautil/parse_install_logs.cpp b/otautil/parse_install_logs.cpp
new file mode 100644
index 0000000..13a7299
--- /dev/null
+++ b/otautil/parse_install_logs.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "otautil/parse_install_logs.h"
+
+#include <unistd.h>
+
+#include <optional>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+
+constexpr const char* OTA_SIDELOAD_METRICS = "ota_sideload";
+
+// Here is an example of lines in last_install:
+// ...
+// time_total: 101
+// bytes_written_vendor: 51074
+// bytes_stashed_vendor: 200
+std::map<std::string, int64_t> ParseRecoveryUpdateMetrics(const std::vector<std::string>& lines) {
+  constexpr unsigned int kMiB = 1024 * 1024;
+  std::optional<int64_t> bytes_written_in_mib;
+  std::optional<int64_t> bytes_stashed_in_mib;
+  std::map<std::string, int64_t> metrics;
+  for (const auto& line : lines) {
+    size_t num_index = line.find(':');
+    if (num_index == std::string::npos) {
+      LOG(WARNING) << "Skip parsing " << line;
+      continue;
+    }
+
+    std::string num_string = android::base::Trim(line.substr(num_index + 1));
+    int64_t parsed_num;
+    if (!android::base::ParseInt(num_string, &parsed_num)) {
+      LOG(ERROR) << "Failed to parse numbers in " << line;
+      continue;
+    }
+
+    if (android::base::StartsWith(line, "bytes_written")) {
+      bytes_written_in_mib = bytes_written_in_mib.value_or(0) + parsed_num / kMiB;
+    } else if (android::base::StartsWith(line, "bytes_stashed")) {
+      bytes_stashed_in_mib = bytes_stashed_in_mib.value_or(0) + parsed_num / kMiB;
+    } else if (android::base::StartsWith(line, "time")) {
+      metrics.emplace("ota_time_total", parsed_num);
+    } else if (android::base::StartsWith(line, "uncrypt_time")) {
+      metrics.emplace("ota_uncrypt_time", parsed_num);
+    } else if (android::base::StartsWith(line, "source_build")) {
+      metrics.emplace("ota_source_version", parsed_num);
+    } else if (android::base::StartsWith(line, "temperature_start")) {
+      metrics.emplace("ota_temperature_start", parsed_num);
+    } else if (android::base::StartsWith(line, "temperature_end")) {
+      metrics.emplace("ota_temperature_end", parsed_num);
+    } else if (android::base::StartsWith(line, "temperature_max")) {
+      metrics.emplace("ota_temperature_max", parsed_num);
+    } else if (android::base::StartsWith(line, "error")) {
+      metrics.emplace("ota_non_ab_error_code", parsed_num);
+    } else if (android::base::StartsWith(line, "cause")) {
+      metrics.emplace("ota_non_ab_cause_code", parsed_num);
+    }
+  }
+
+  if (bytes_written_in_mib) {
+    metrics.emplace("ota_written_in_MiBs", bytes_written_in_mib.value());
+  }
+  if (bytes_stashed_in_mib) {
+    metrics.emplace("ota_stashed_in_MiBs", bytes_stashed_in_mib.value());
+  }
+
+  return metrics;
+}
+
+std::map<std::string, int64_t> ParseLastInstall(const std::string& file_name) {
+  if (access(file_name.c_str(), F_OK) != 0) {
+    return {};
+  }
+
+  std::string content;
+  if (!android::base::ReadFileToString(file_name, &content)) {
+    PLOG(ERROR) << "Failed to read " << file_name;
+    return {};
+  }
+
+  if (content.empty()) {
+    LOG(INFO) << "Empty last_install file";
+    return {};
+  }
+
+  std::vector<std::string> lines = android::base::Split(content, "\n");
+  auto metrics = ParseRecoveryUpdateMetrics(lines);
+
+  // LAST_INSTALL starts with "/sideload/package.zip" after a sideload.
+  if (android::base::Trim(lines[0]) == "/sideload/package.zip") {
+    int type = (android::base::GetProperty("ro.build.type", "") == "user") ? 1 : 0;
+    metrics.emplace(OTA_SIDELOAD_METRICS, type);
+  }
+
+  return metrics;
+}
diff --git a/otautil/paths.cpp b/otautil/paths.cpp
new file mode 100644
index 0000000..33ab4a5
--- /dev/null
+++ b/otautil/paths.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "otautil/paths.h"
+
+constexpr const char kDefaultCacheLogDirectory[] = "/cache/recovery";
+constexpr const char kDefaultCacheTempSource[] = "/cache/saved.file";
+constexpr const char kDefaultLastCommandFile[] = "/cache/recovery/last_command";
+constexpr const char kDefaultResourceDirectory[] = "/res/images";
+constexpr const char kDefaultStashDirectoryBase[] = "/cache/recovery";
+constexpr const char kDefaultTemporaryInstallFile[] = "/tmp/last_install";
+constexpr const char kDefaultTemporaryLogFile[] = "/tmp/recovery.log";
+constexpr const char kDefaultTemporaryUpdateBinary[] = "/tmp/update-binary";
+
+Paths& Paths::Get() {
+  static Paths paths;
+  return paths;
+}
+
+Paths::Paths()
+    : cache_log_directory_(kDefaultCacheLogDirectory),
+      cache_temp_source_(kDefaultCacheTempSource),
+      last_command_file_(kDefaultLastCommandFile),
+      resource_dir_(kDefaultResourceDirectory),
+      stash_directory_base_(kDefaultStashDirectoryBase),
+      temporary_install_file_(kDefaultTemporaryInstallFile),
+      temporary_log_file_(kDefaultTemporaryLogFile),
+      temporary_update_binary_(kDefaultTemporaryUpdateBinary) {}
diff --git a/otautil/rangeset.cpp b/otautil/rangeset.cpp
index 96955b9..8ee99dd 100644
--- a/otautil/rangeset.cpp
+++ b/otautil/rangeset.cpp
@@ -148,8 +148,8 @@
     return "";
   }
   std::string result = std::to_string(ranges_.size() * 2);
-  for (const auto& r : ranges_) {
-    result += android::base::StringPrintf(",%zu,%zu", r.first, r.second);
+  for (const auto& [begin, end] : ranges_) {
+    result += android::base::StringPrintf(",%zu,%zu", begin, end);
   }
 
   return result;
@@ -159,11 +159,11 @@
 size_t RangeSet::GetBlockNumber(size_t idx) const {
   CHECK_LT(idx, blocks_) << "Out of bound index " << idx << " (total blocks: " << blocks_ << ")";
 
-  for (const auto& range : ranges_) {
-    if (idx < range.second - range.first) {
-      return range.first + idx;
+  for (const auto& [begin, end] : ranges_) {
+    if (idx < end - begin) {
+      return begin + idx;
     }
-    idx -= (range.second - range.first);
+    idx -= (end - begin);
   }
 
   CHECK(false) << "Failed to find block number for index " << idx;
@@ -173,14 +173,10 @@
 // RangeSet has half-closed half-open bounds. For example, "3,5" contains blocks 3 and 4. So "3,5"
 // and "5,7" are not overlapped.
 bool RangeSet::Overlaps(const RangeSet& other) const {
-  for (const auto& range : ranges_) {
-    size_t start = range.first;
-    size_t end = range.second;
-    for (const auto& other_range : other.ranges_) {
-      size_t other_start = other_range.first;
-      size_t other_end = other_range.second;
-      // [start, end) vs [other_start, other_end)
-      if (!(other_start >= end || start >= other_end)) {
+  for (const auto& [begin, end] : ranges_) {
+    for (const auto& [other_begin, other_end] : other.ranges_) {
+      // [begin, end) vs [other_begin, other_end)
+      if (!(other_begin >= end || begin >= other_end)) {
         return true;
       }
     }
@@ -188,6 +184,58 @@
   return false;
 }
 
+std::optional<RangeSet> RangeSet::GetSubRanges(size_t start_index, size_t num_of_blocks) const {
+  size_t end_index = start_index + num_of_blocks;  // The index of final block to read plus one
+  if (start_index > end_index || end_index > blocks_) {
+    LOG(ERROR) << "Failed to get the sub ranges for start_index " << start_index
+               << " num_of_blocks " << num_of_blocks
+               << " total number of blocks the range contains is " << blocks_;
+    return std::nullopt;
+  }
+
+  if (num_of_blocks == 0) {
+    LOG(WARNING) << "num_of_blocks is zero when calling GetSubRanges()";
+    return RangeSet();
+  }
+
+  RangeSet result;
+  size_t current_index = 0;
+  for (const auto& [range_start, range_end] : ranges_) {
+    CHECK_LT(range_start, range_end);
+    size_t blocks_in_range = range_end - range_start;
+    // Linear search to skip the ranges until we reach start_block.
+    if (current_index + blocks_in_range <= start_index) {
+      current_index += blocks_in_range;
+      continue;
+    }
+
+    size_t trimmed_range_start = range_start;
+    // We have found the first block range to read, trim the heading blocks.
+    if (current_index < start_index) {
+      trimmed_range_start += start_index - current_index;
+    }
+    // Trim the trailing blocks if the last range has more blocks than desired; also return the
+    // result.
+    if (current_index + blocks_in_range >= end_index) {
+      size_t trimmed_range_end = range_end - (current_index + blocks_in_range - end_index);
+      if (!result.PushBack({ trimmed_range_start, trimmed_range_end })) {
+        return std::nullopt;
+      }
+
+      return result;
+    }
+
+    if (!result.PushBack({ trimmed_range_start, range_end })) {
+      return std::nullopt;
+    }
+    current_index += blocks_in_range;
+  }
+
+  LOG(ERROR) << "Failed to construct byte ranges to read, start_block: " << start_index
+             << ", num_of_blocks: " << num_of_blocks << " total number of blocks: " << blocks_;
+  return std::nullopt;
+}
+
 // Ranges in the the set should be mutually exclusive; and they're sorted by the start block.
 SortedRangeSet::SortedRangeSet(std::vector<Range>&& pairs) : RangeSet(std::move(pairs)) {
   std::sort(ranges_.begin(), ranges_.end());
@@ -248,20 +296,20 @@
 size_t SortedRangeSet::GetOffsetInRangeSet(size_t old_offset) const {
   size_t old_block_start = old_offset / kBlockSize;
   size_t new_block_start = 0;
-  for (const auto& range : ranges_) {
+  for (const auto& [start, end] : ranges_) {
     // Find the index of old_block_start.
-    if (old_block_start >= range.second) {
-      new_block_start += (range.second - range.first);
-    } else if (old_block_start >= range.first) {
-      new_block_start += (old_block_start - range.first);
+    if (old_block_start >= end) {
+      new_block_start += (end - start);
+    } else if (old_block_start >= start) {
+      new_block_start += (old_block_start - start);
       return (new_block_start * kBlockSize + old_offset % kBlockSize);
     } else {
       CHECK(false) << "block_start " << old_block_start
-                   << " is missing between two ranges: " << this->ToString();
+                   << " is missing between two ranges: " << ToString();
       return 0;
     }
   }
   CHECK(false) << "block_start " << old_block_start
-               << " exceeds the limit of current RangeSet: " << this->ToString();
+               << " exceeds the limit of current RangeSet: " << ToString();
   return 0;
 }
diff --git a/otautil/roots.cpp b/otautil/roots.cpp
new file mode 100644
index 0000000..a778e05
--- /dev/null
+++ b/otautil/roots.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "otautil/roots.h"
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <cryptfs.h>
+#include <ext4_utils/wipe.h>
+#include <fs_mgr.h>
+#include <fs_mgr/roots.h>
+#include <fs_mgr_dm_linear.h>
+
+#include "otautil/mounts.h"
+#include "otautil/sysutil.h"
+
+using android::fs_mgr::Fstab;
+using android::fs_mgr::FstabEntry;
+using android::fs_mgr::ReadDefaultFstab;
+
+static Fstab fstab;
+
+void load_volume_table() {
+  if (!ReadDefaultFstab(&fstab)) {
+    LOG(ERROR) << "Failed to read default fstab";
+    return;
+  }
+
+  fstab.emplace_back(FstabEntry{
+      .mount_point = "/tmp", .fs_type = "ramdisk", .blk_device = "ramdisk", .length = 0 });
+
+  std::cout << "recovery filesystem table" << std::endl << "=========================" << std::endl;
+  for (size_t i = 0; i < fstab.size(); ++i) {
+    const auto& entry = fstab[i];
+    std::cout << "  " << i << " " << entry.mount_point << " "
+              << " " << entry.fs_type << " " << entry.blk_device << " " << entry.length
+              << std::endl;
+  }
+  std::cout << std::endl;
+}
+
+Volume* volume_for_mount_point(const std::string& mount_point) {
+  return android::fs_mgr::GetEntryForMountPoint(&fstab, mount_point);
+}
+
+// Mount the volume specified by path at the given mount_point.
+int ensure_path_mounted_at(const std::string& path, const std::string& mount_point) {
+  return android::fs_mgr::EnsurePathMounted(&fstab, path, mount_point) ? 0 : -1;
+}
+
+int ensure_path_mounted(const std::string& path) {
+  // Mount at the default mount point.
+  return android::fs_mgr::EnsurePathMounted(&fstab, path) ? 0 : -1;
+}
+
+int ensure_path_unmounted(const std::string& path) {
+  return android::fs_mgr::EnsurePathUnmounted(&fstab, path) ? 0 : -1;
+}
+
+static int exec_cmd(const std::vector<std::string>& args) {
+  CHECK(!args.empty());
+  auto argv = StringVectorToNullTerminatedArray(args);
+
+  pid_t child;
+  if ((child = fork()) == 0) {
+    execv(argv[0], argv.data());
+    _exit(EXIT_FAILURE);
+  }
+
+  int status;
+  waitpid(child, &status, 0);
+  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+    LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status);
+  }
+  return WEXITSTATUS(status);
+}
+
+static int64_t get_file_size(int fd, uint64_t reserve_len) {
+  struct stat buf;
+  int ret = fstat(fd, &buf);
+  if (ret) return 0;
+
+  int64_t computed_size;
+  if (S_ISREG(buf.st_mode)) {
+    computed_size = buf.st_size - reserve_len;
+  } else if (S_ISBLK(buf.st_mode)) {
+    uint64_t block_device_size = get_block_device_size(fd);
+    if (block_device_size < reserve_len ||
+        block_device_size > std::numeric_limits<int64_t>::max()) {
+      computed_size = 0;
+    } else {
+      computed_size = block_device_size - reserve_len;
+    }
+  } else {
+    computed_size = 0;
+  }
+
+  return computed_size;
+}
+
+int format_volume(const std::string& volume, const std::string& directory) {
+  const FstabEntry* v = android::fs_mgr::GetEntryForPath(&fstab, volume);
+  if (v == nullptr) {
+    LOG(ERROR) << "unknown volume \"" << volume << "\"";
+    return -1;
+  }
+  if (v->fs_type == "ramdisk") {
+    LOG(ERROR) << "can't format_volume \"" << volume << "\"";
+    return -1;
+  }
+  if (v->mount_point != volume) {
+    LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume";
+    return -1;
+  }
+  if (ensure_path_unmounted(volume) != 0) {
+    LOG(ERROR) << "format_volume: Failed to unmount \"" << v->mount_point << "\"";
+    return -1;
+  }
+  if (v->fs_type != "ext4" && v->fs_type != "f2fs") {
+    LOG(ERROR) << "format_volume: fs_type \"" << v->fs_type << "\" unsupported";
+    return -1;
+  }
+
+  // If there's a key_loc that looks like a path, it should be a block device for storing encryption
+  // metadata. Wipe it too.
+  if (!v->key_loc.empty() && v->key_loc[0] == '/') {
+    LOG(INFO) << "Wiping " << v->key_loc;
+    int fd = open(v->key_loc.c_str(), O_WRONLY | O_CREAT, 0644);
+    if (fd == -1) {
+      PLOG(ERROR) << "format_volume: Failed to open " << v->key_loc;
+      return -1;
+    }
+    wipe_block_device(fd, get_file_size(fd));
+    close(fd);
+  }
+
+  int64_t length = 0;
+  if (v->length > 0) {
+    length = v->length;
+  } else if (v->length < 0 || v->key_loc == "footer") {
+    android::base::unique_fd fd(open(v->blk_device.c_str(), O_RDONLY));
+    if (fd == -1) {
+      PLOG(ERROR) << "format_volume: failed to open " << v->blk_device;
+      return -1;
+    }
+    length = get_file_size(fd.get(), v->length ? -v->length : CRYPT_FOOTER_OFFSET);
+    if (length <= 0) {
+      LOG(ERROR) << "get_file_size: invalid size " << length << " for " << v->blk_device;
+      return -1;
+    }
+  }
+
+  if (v->fs_type == "ext4") {
+    static constexpr int kBlockSize = 4096;
+    std::vector<std::string> mke2fs_args = {
+      "/system/bin/mke2fs", "-F", "-t", "ext4", "-b", std::to_string(kBlockSize),
+    };
+
+    int raid_stride = v->logical_blk_size / kBlockSize;
+    int raid_stripe_width = v->erase_blk_size / kBlockSize;
+    // stride should be the max of 8KB and logical block size
+    if (v->logical_blk_size != 0 && v->logical_blk_size < 8192) {
+      raid_stride = 8192 / kBlockSize;
+    }
+    if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
+      mke2fs_args.push_back("-E");
+      mke2fs_args.push_back(
+          android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride, raid_stripe_width));
+    }
+    mke2fs_args.push_back(v->blk_device);
+    if (length != 0) {
+      mke2fs_args.push_back(std::to_string(length / kBlockSize));
+    }
+
+    int result = exec_cmd(mke2fs_args);
+    if (result == 0 && !directory.empty()) {
+      std::vector<std::string> e2fsdroid_args = {
+        "/system/bin/e2fsdroid", "-e", "-f", directory, "-a", volume, v->blk_device,
+      };
+      result = exec_cmd(e2fsdroid_args);
+    }
+
+    if (result != 0) {
+      PLOG(ERROR) << "format_volume: Failed to make ext4 on " << v->blk_device;
+      return -1;
+    }
+    return 0;
+  }
+
+  // Has to be f2fs because we checked earlier.
+  static constexpr int kSectorSize = 4096;
+  std::vector<std::string> make_f2fs_cmd = {
+    "/system/bin/make_f2fs",
+    "-g",
+    "android",
+    v->blk_device,
+  };
+  if (length >= kSectorSize) {
+    make_f2fs_cmd.push_back(std::to_string(length / kSectorSize));
+  }
+
+  if (exec_cmd(make_f2fs_cmd) != 0) {
+    PLOG(ERROR) << "format_volume: Failed to make_f2fs on " << v->blk_device;
+    return -1;
+  }
+  if (!directory.empty()) {
+    std::vector<std::string> sload_f2fs_cmd = {
+      "/system/bin/sload_f2fs", "-f", directory, "-t", volume, v->blk_device,
+    };
+    if (exec_cmd(sload_f2fs_cmd) != 0) {
+      PLOG(ERROR) << "format_volume: Failed to sload_f2fs on " << v->blk_device;
+      return -1;
+    }
+  }
+  return 0;
+}
+
+int format_volume(const std::string& volume) {
+  return format_volume(volume, "");
+}
+
+int setup_install_mounts() {
+  if (fstab.empty()) {
+    LOG(ERROR) << "can't set up install mounts: no fstab loaded";
+    return -1;
+  }
+  for (const FstabEntry& entry : fstab) {
+    // We don't want to do anything with "/".
+    if (entry.mount_point == "/") {
+      continue;
+    }
+
+    if (entry.mount_point == "/tmp" || entry.mount_point == "/cache") {
+      if (ensure_path_mounted(entry.mount_point) != 0) {
+        LOG(ERROR) << "Failed to mount " << entry.mount_point;
+        return -1;
+      }
+    } else {
+      if (ensure_path_unmounted(entry.mount_point) != 0) {
+        LOG(ERROR) << "Failed to unmount " << entry.mount_point;
+        return -1;
+      }
+    }
+  }
+  return 0;
+}
diff --git a/otautil/sysutil.cpp b/otautil/sysutil.cpp
new file mode 100644
index 0000000..420db4c
--- /dev/null
+++ b/otautil/sysutil.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2006 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/sysutil.h"
+
+#include <errno.h>  // TEMP_FAILURE_RETRY
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <cutils/android_reboot.h>
+
+BlockMapData BlockMapData::ParseBlockMapFile(const std::string& block_map_path) {
+  std::string content;
+  if (!android::base::ReadFileToString(block_map_path, &content)) {
+    LOG(ERROR) << "Failed to read " << block_map_path;
+    return {};
+  }
+
+  std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n");
+  if (lines.size() < 4) {
+    LOG(ERROR) << "Block map file is too short: " << lines.size();
+    return {};
+  }
+
+  const std::string& block_dev = lines[0];
+
+  uint64_t file_size;
+  uint32_t blksize;
+  if (sscanf(lines[1].c_str(), "%" SCNu64 "%" SCNu32, &file_size, &blksize) != 2) {
+    LOG(ERROR) << "Failed to parse file size and block size: " << lines[1];
+    return {};
+  }
+
+  if (file_size == 0 || blksize == 0) {
+    LOG(ERROR) << "Invalid size in block map file: size " << file_size << ", blksize " << blksize;
+    return {};
+  }
+
+  size_t range_count;
+  if (sscanf(lines[2].c_str(), "%zu", &range_count) != 1) {
+    LOG(ERROR) << "Failed to parse block map header: " << lines[2];
+    return {};
+  }
+
+  uint64_t blocks = ((file_size - 1) / blksize) + 1;
+  if (blocks > std::numeric_limits<uint32_t>::max() || range_count == 0 ||
+      lines.size() != 3 + range_count) {
+    LOG(ERROR) << "Invalid data in block map file: size " << file_size << ", blksize " << blksize
+               << ", range_count " << range_count << ", lines " << lines.size();
+    return {};
+  }
+
+  RangeSet ranges;
+  uint64_t remaining_blocks = blocks;
+  for (size_t i = 0; i < range_count; ++i) {
+    const std::string& line = lines[i + 3];
+    uint64_t start, end;
+    if (sscanf(line.c_str(), "%" SCNu64 "%" SCNu64, &start, &end) != 2) {
+      LOG(ERROR) << "failed to parse range " << i << ": " << line;
+      return {};
+    }
+    uint64_t range_blocks = end - start;
+    if (end <= start || range_blocks > remaining_blocks) {
+      LOG(ERROR) << "Invalid range: " << start << " " << end;
+      return {};
+    }
+    ranges.PushBack({ start, end });
+    remaining_blocks -= range_blocks;
+  }
+
+  if (remaining_blocks != 0) {
+    LOG(ERROR) << "Invalid ranges: remaining blocks " << remaining_blocks;
+    return {};
+  }
+
+  return BlockMapData(block_dev, file_size, blksize, std::move(ranges));
+}
+
+bool MemMapping::MapFD(int fd) {
+  struct stat sb;
+  if (fstat(fd, &sb) == -1) {
+    PLOG(ERROR) << "fstat(" << fd << ") failed";
+    return false;
+  }
+
+  void* memPtr = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+  if (memPtr == MAP_FAILED) {
+    PLOG(ERROR) << "mmap(" << sb.st_size << ", R, PRIVATE, " << fd << ", 0) failed";
+    return false;
+  }
+
+  addr = static_cast<unsigned char*>(memPtr);
+  length = sb.st_size;
+  ranges_.clear();
+  ranges_.emplace_back(MappedRange{ memPtr, static_cast<size_t>(sb.st_size) });
+
+  return true;
+}
+
+bool MemMapping::MapBlockFile(const std::string& filename) {
+  auto block_map_data = BlockMapData::ParseBlockMapFile(filename);
+  if (!block_map_data) {
+    return false;
+  }
+
+  if (block_map_data.file_size() > std::numeric_limits<size_t>::max()) {
+    LOG(ERROR) << "File size is too large for mmap " << block_map_data.file_size();
+    return false;
+  }
+
+  // Reserve enough contiguous address space for the whole file.
+  uint32_t blksize = block_map_data.block_size();
+  uint64_t blocks = ((block_map_data.file_size() - 1) / blksize) + 1;
+  void* reserve = mmap(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
+  if (reserve == MAP_FAILED) {
+    PLOG(ERROR) << "failed to reserve address space";
+    return false;
+  }
+
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(block_map_data.path().c_str(), O_RDONLY)));
+  if (fd == -1) {
+    PLOG(ERROR) << "failed to open block device " << block_map_data.path();
+    munmap(reserve, blocks * blksize);
+    return false;
+  }
+
+  ranges_.clear();
+
+  auto next = static_cast<unsigned char*>(reserve);
+  size_t remaining_size = blocks * blksize;
+  for (const auto& [start, end] : block_map_data.block_ranges()) {
+    size_t range_size = (end - start) * blksize;
+    void* range_start = mmap(next, range_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd,
+                             static_cast<off_t>(start) * blksize);
+    if (range_start == MAP_FAILED) {
+      PLOG(ERROR) << "failed to map range " << start << ": " << end;
+      munmap(reserve, blocks * blksize);
+      return false;
+    }
+    ranges_.emplace_back(MappedRange{ range_start, range_size });
+
+    next += range_size;
+    remaining_size -= range_size;
+  }
+  if (remaining_size != 0) {
+    LOG(ERROR) << "Invalid ranges: remaining_size " << remaining_size;
+    munmap(reserve, blocks * blksize);
+    return false;
+  }
+
+  addr = static_cast<unsigned char*>(reserve);
+  length = block_map_data.file_size();
+
+  LOG(INFO) << "mmapped " << block_map_data.block_ranges().size() << " ranges";
+
+  return true;
+}
+
+bool MemMapping::MapFile(const std::string& fn) {
+  if (fn.empty()) {
+    LOG(ERROR) << "Empty filename";
+    return false;
+  }
+
+  if (fn[0] == '@') {
+    // Block map file "@/cache/recovery/block.map".
+    if (!MapBlockFile(fn.substr(1))) {
+      LOG(ERROR) << "Map of '" << fn << "' failed";
+      return false;
+    }
+  } else {
+    // This is a regular file.
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY)));
+    if (fd == -1) {
+      PLOG(ERROR) << "Unable to open '" << fn << "'";
+      return false;
+    }
+
+    if (!MapFD(fd)) {
+      LOG(ERROR) << "Map of '" << fn << "' failed";
+      return false;
+    }
+  }
+  return true;
+}
+
+MemMapping::~MemMapping() {
+  for (const auto& range : ranges_) {
+    if (munmap(range.addr, range.length) == -1) {
+      PLOG(ERROR) << "Failed to munmap(" << range.addr << ", " << range.length << ")";
+    }
+  };
+  ranges_.clear();
+}
+
+bool Reboot(std::string_view target) {
+  std::string cmd = "reboot," + std::string(target);
+  // Honor the quiescent mode if applicable.
+  if (target != "bootloader" && target != "fastboot" &&
+      android::base::GetBoolProperty("ro.boot.quiescent", false)) {
+    cmd += ",quiescent";
+  }
+  return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
+}
+
+bool Shutdown() {
+  // "shutdown" doesn't need a "reason" arg nor a comma.
+  return android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown");
+}
+
+std::vector<char*> StringVectorToNullTerminatedArray(const std::vector<std::string>& args) {
+  std::vector<char*> result(args.size());
+  std::transform(args.cbegin(), args.cend(), result.begin(),
+                 [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
+  result.push_back(nullptr);
+  return result;
+}
diff --git a/otautil/ThermalUtil.cpp b/otautil/thermalutil.cpp
similarity index 98%
rename from otautil/ThermalUtil.cpp
rename to otautil/thermalutil.cpp
index 5d9bd45..4660e05 100644
--- a/otautil/ThermalUtil.cpp
+++ b/otautil/thermalutil.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "otautil/ThermalUtil.h"
+#include "otautil/thermalutil.h"
 
 #include <dirent.h>
 #include <stdio.h>
diff --git a/private/install.h b/private/install.h
deleted file mode 100644
index ef64bd4..0000000
--- a/private/install.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// Private headers exposed for testing purpose only.
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-#include <ziparchive/zip_archive.h>
-
-// Extract the update binary from the open zip archive |zip| located at |package| to |binary_path|.
-// Store the command line that should be called into |cmd|. The |status_fd| is the file descriptor
-// the child process should use to report back the progress of the update.
-int update_binary_command(const std::string& package, ZipArchiveHandle zip,
-                          const std::string& binary_path, int retry_count, int status_fd,
-                          std::vector<std::string>* cmd);
diff --git a/recovery-persist.cpp b/recovery-persist.cpp
index dbce7ff..294017a 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -35,19 +35,22 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <limits>
 #include <string>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <metricslogger/metrics_logger.h>
 #include <private/android_logger.h> /* private pmsg functions */
 
-#include "rotate_logs.h"
+#include "otautil/logging.h"
+#include "otautil/parse_install_logs.h"
 
-static const char *LAST_LOG_FILE = "/data/misc/recovery/last_log";
-static const char *LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0";
-static const char *LAST_KMSG_FILE = "/data/misc/recovery/last_kmsg";
-static const char *LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops-0";
-static const char *ALT_LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops";
+constexpr const char* LAST_LOG_FILE = "/data/misc/recovery/last_log";
+constexpr const char* LAST_PMSG_FILE = "/sys/fs/pstore/pmsg-ramoops-0";
+constexpr const char* LAST_KMSG_FILE = "/data/misc/recovery/last_kmsg";
+constexpr const char* LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops-0";
+constexpr const char* ALT_LAST_CONSOLE_FILE = "/sys/fs/pstore/console-ramoops";
 
 // close a file, log an error if the error indicator is set
 static void check_and_fclose(FILE *fp, const char *name) {
@@ -109,6 +112,20 @@
     return android::base::WriteStringToFile(buffer, destination.c_str());
 }
 
+// Parses the LAST_INSTALL file and reports the update metrics saved under recovery mode.
+static void report_metrics_from_last_install(const std::string& file_name) {
+  auto metrics = ParseLastInstall(file_name);
+  // TODO(xunchang) report the installation result.
+  for (const auto& [event, value] : metrics) {
+    if (value > std::numeric_limits<int>::max()) {
+      LOG(WARNING) << event << " (" << value << ") exceeds integer max.";
+    } else {
+      LOG(INFO) << "Uploading " << value << " to " << event;
+      android::metricslogger::LogHistogram(event, value);
+    }
+  }
+}
+
 int main(int argc, char **argv) {
 
     /* Is /cache a mount?, we have been delivered where we are not wanted */
@@ -138,14 +155,18 @@
     }
 
     if (has_cache) {
-        /*
-         * TBD: Future location to move content from
-         * /cache/recovery to /data/misc/recovery/
-         */
-        /* if --force-persist flag, then transfer pmsg data anyways */
-        if ((argc <= 1) || !argv[1] || strcmp(argv[1], "--force-persist")) {
-            return 0;
-        }
+      // Collects and reports the non-a/b update metrics from last_install; and removes the file
+      // to avoid duplicate report.
+      report_metrics_from_last_install(LAST_INSTALL_FILE_IN_CACHE);
+      if (access(LAST_INSTALL_FILE_IN_CACHE, F_OK) && unlink(LAST_INSTALL_FILE_IN_CACHE) == -1) {
+        PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE_IN_CACHE;
+      }
+
+      // TBD: Future location to move content from /cache/recovery to /data/misc/recovery/
+      // if --force-persist flag, then transfer pmsg data anyways
+      if ((argc <= 1) || !argv[1] || strcmp(argv[1], "--force-persist")) {
+        return 0;
+      }
     }
 
     /* Is there something in pmsg? */
@@ -157,6 +178,15 @@
     __android_log_pmsg_file_read(
         LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", logsave, NULL);
 
+    // For those device without /cache, the last_install file has been copied to
+    // /data/misc/recovery from pmsg. Looks for the sideload history only.
+    if (!has_cache) {
+      report_metrics_from_last_install(LAST_INSTALL_FILE);
+      if (access(LAST_INSTALL_FILE, F_OK) && unlink(LAST_INSTALL_FILE) == -1) {
+        PLOG(ERROR) << "Failed to unlink " << LAST_INSTALL_FILE;
+      }
+    }
+
     /* Is there a last console log too? */
     if (rotated) {
         if (!access(LAST_CONSOLE_FILE, R_OK)) {
diff --git a/recovery-refresh.cpp b/recovery-refresh.cpp
index 14565d3..d41755d 100644
--- a/recovery-refresh.cpp
+++ b/recovery-refresh.cpp
@@ -42,7 +42,7 @@
 
 #include <private/android_logger.h> /* private pmsg functions */
 
-#include "rotate_logs.h"
+#include "otautil/logging.h"
 
 int main(int argc, char **argv) {
     static const char filter[] = "recovery/";
diff --git a/recovery.cpp b/recovery.cpp
index 07ec5cf..20e5a1b 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -14,28 +14,22 @@
  * limitations under the License.
  */
 
+#include "recovery.h"
+
 #include <ctype.h>
-#include <dirent.h>
 #include <errno.h>
-#include <fcntl.h>
 #include <getopt.h>
 #include <inttypes.h>
 #include <limits.h>
-#include <linux/fs.h>
 #include <linux/input.h>
-#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/klog.h>
-#include <sys/stat.h>
 #include <sys/types.h>
-#include <sys/wait.h>
-#include <time.h>
 #include <unistd.h>
 
-#include <algorithm>
-#include <chrono>
+#include <functional>
+#include <iterator>
 #include <memory>
 #include <string>
 #include <vector>
@@ -46,98 +40,42 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <bootloader_message/bootloader_message.h>
-#include <cutils/android_reboot.h>
 #include <cutils/properties.h> /* for property_list */
-#include <health2/Health.h>
-#include <private/android_filesystem_config.h> /* for AID_SYSTEM */
-#include <private/android_logger.h>            /* private pmsg functions */
-#include <selinux/android.h>
-#include <selinux/label.h>
-#include <selinux/selinux.h>
+#include <fs_mgr/roots.h>
+#include <healthhalutils/HealthHalUtils.h>
 #include <ziparchive/zip_archive.h>
 
-#include "adb_install.h"
+#include "bootloader_message/bootloader_message.h"
 #include "common.h"
-#include "device.h"
-#include "fuse_sdcard_provider.h"
-#include "fuse_sideload.h"
-#include "install.h"
-#include "minadbd/minadbd.h"
-#include "minui/minui.h"
-#include "otautil/DirUtil.h"
+#include "fsck_unshare_blocks.h"
+#include "install/adb_install.h"
+#include "install/fuse_sdcard_install.h"
+#include "install/install.h"
+#include "install/package.h"
+#include "install/wipe_data.h"
+#include "install/wipe_device.h"
 #include "otautil/error_code.h"
-#include "roots.h"
-#include "rotate_logs.h"
-#include "screen_ui.h"
-#include "stub_ui.h"
-#include "ui.h"
+#include "otautil/logging.h"
+#include "otautil/paths.h"
+#include "otautil/roots.h"
+#include "otautil/sysutil.h"
+#include "recovery_ui/screen_ui.h"
+#include "recovery_ui/ui.h"
 
-static const struct option OPTIONS[] = {
-  { "update_package", required_argument, NULL, 'u' },
-  { "retry_count", required_argument, NULL, 'n' },
-  { "wipe_data", no_argument, NULL, 'w' },
-  { "wipe_cache", no_argument, NULL, 'c' },
-  { "show_text", no_argument, NULL, 't' },
-  { "sideload", no_argument, NULL, 's' },
-  { "sideload_auto_reboot", no_argument, NULL, 'a' },
-  { "just_exit", no_argument, NULL, 'x' },
-  { "locale", required_argument, NULL, 'l' },
-  { "shutdown_after", no_argument, NULL, 'p' },
-  { "reason", required_argument, NULL, 'r' },
-  { "security", no_argument, NULL, 'e'},
-  { "wipe_ab", no_argument, NULL, 0 },
-  { "wipe_package_size", required_argument, NULL, 0 },
-  { "prompt_and_wipe_data", no_argument, NULL, 0 },
-  { NULL, 0, NULL, 0 },
-};
+static constexpr const char* COMMAND_FILE = "/cache/recovery/command";
+static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
+static constexpr const char* LAST_LOG_FILE = "/cache/recovery/last_log";
+static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale";
 
-// More bootreasons can be found in "system/core/bootstat/bootstat.cpp".
-static const std::vector<std::string> bootreason_blacklist {
-  "kernel_panic",
-  "Panic",
-};
-
-static const char *CACHE_LOG_DIR = "/cache/recovery";
-static const char *COMMAND_FILE = "/cache/recovery/command";
-static const char *LOG_FILE = "/cache/recovery/log";
-static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";
-static const char *LOCALE_FILE = "/cache/recovery/last_locale";
-static const char *CONVERT_FBE_DIR = "/tmp/convert_fbe";
-static const char *CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe";
-static const char *CACHE_ROOT = "/cache";
-static const char *DATA_ROOT = "/data";
-static const char* METADATA_ROOT = "/metadata";
-static const char *SDCARD_ROOT = "/sdcard";
-static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
-static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
-static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
-static const char *LAST_LOG_FILE = "/cache/recovery/last_log";
-// We will try to apply the update package 5 times at most in case of an I/O error or
-// bspatch | imgpatch error.
-static const int RETRY_LIMIT = 4;
-static const int BATTERY_READ_TIMEOUT_IN_SEC = 10;
-// GmsCore enters recovery mode to install package when having enough battery
-// percentage. Normally, the threshold is 40% without charger and 20% with charger.
-// So we should check battery with a slightly lower limitation.
-static const int BATTERY_OK_PERCENTAGE = 20;
-static const int BATTERY_WITH_CHARGER_OK_PERCENTAGE = 15;
-static constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe";
-static constexpr const char* DEFAULT_LOCALE = "en-US";
+static constexpr const char* CACHE_ROOT = "/cache";
 
 // We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed
 // into target_files.zip. Assert the version defined in code and in Android.mk are consistent.
 static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions.");
 
-static std::string locale;
-static bool has_cache = false;
-
-RecoveryUI* ui = nullptr;
-bool modified_flash = false;
+static bool save_current_log = false;
 std::string stage;
 const char* reason = nullptr;
-struct selabel_handle* sehandle;
 
 /*
  * The recovery tool communicates with the main system through /cache files.
@@ -147,9 +85,10 @@
  * The arguments which may be supplied in the recovery.command file:
  *   --update_package=path - verify install an OTA package file
  *   --wipe_data - erase user data (and cache), then reboot
- *   --prompt_and_wipe_data - prompt the user that data is corrupt,
- *       with their consent erase user data (and cache), then reboot
+ *   --prompt_and_wipe_data - prompt the user that data is corrupt, with their consent erase user
+ *       data (and cache), then reboot
  *   --wipe_cache - wipe cache (but not user data), then reboot
+ *   --show_text - show the recovery text menu, used by some bootloader (e.g. http://b/36872519).
  *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
  *   --just_exit - do nothing; exit and reboot
  *
@@ -175,318 +114,24 @@
  * 3. main system reboots into recovery
  * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
  *    -- after this, rebooting will attempt to reinstall the update --
- * 5. install_package() attempts to install the update
+ * 5. InstallPackage() attempts to install the update
  *    NOTE: the package install must itself be restartable from any point
  * 6. finish_recovery() erases BCB
  *    -- after this, rebooting will (try to) restart the main system --
  * 7. ** if install failed **
- *    7a. prompt_and_wait() shows an error icon and waits for the user
+ *    7a. PromptAndWait() shows an error icon and waits for the user
  *    7b. the user reboots (pulling the battery, etc) into the main system
  */
 
-// Open a given path, mounting partitions as necessary.
-FILE* fopen_path(const char* path, const char* mode) {
-  if (ensure_path_mounted(path) != 0) {
-    LOG(ERROR) << "Can't mount " << path;
-    return nullptr;
-  }
-
-  // When writing, try to create the containing directory, if necessary. Use generous permissions,
-  // the system (init.rc) will reset them.
-  if (strchr("wa", mode[0])) {
-    mkdir_recursively(path, 0777, true, sehandle);
-  }
-  return fopen(path, mode);
-}
-
-// close a file, log an error if the error indicator is set
-static void check_and_fclose(FILE *fp, const char *name) {
-    fflush(fp);
-    if (fsync(fileno(fp)) == -1) {
-        PLOG(ERROR) << "Failed to fsync " << name;
-    }
-    if (ferror(fp)) {
-        PLOG(ERROR) << "Error in " << name;
-    }
-    fclose(fp);
-}
-
 bool is_ro_debuggable() {
     return android::base::GetBoolProperty("ro.debuggable", false);
 }
 
-bool reboot(const std::string& command) {
-    std::string cmd = command;
-    if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
-        cmd += ",quiescent";
-    }
-    return android::base::SetProperty(ANDROID_RB_PROPERTY, cmd);
-}
-
-static void redirect_stdio(const char* filename) {
-    int pipefd[2];
-    if (pipe(pipefd) == -1) {
-        PLOG(ERROR) << "pipe failed";
-
-        // Fall back to traditional logging mode without timestamps.
-        // If these fail, there's not really anywhere to complain...
-        freopen(filename, "a", stdout); setbuf(stdout, NULL);
-        freopen(filename, "a", stderr); setbuf(stderr, NULL);
-
-        return;
-    }
-
-    pid_t pid = fork();
-    if (pid == -1) {
-        PLOG(ERROR) << "fork failed";
-
-        // Fall back to traditional logging mode without timestamps.
-        // If these fail, there's not really anywhere to complain...
-        freopen(filename, "a", stdout); setbuf(stdout, NULL);
-        freopen(filename, "a", stderr); setbuf(stderr, NULL);
-
-        return;
-    }
-
-    if (pid == 0) {
-        /// Close the unused write end.
-        close(pipefd[1]);
-
-        auto start = std::chrono::steady_clock::now();
-
-        // Child logger to actually write to the log file.
-        FILE* log_fp = fopen(filename, "ae");
-        if (log_fp == nullptr) {
-            PLOG(ERROR) << "fopen \"" << filename << "\" failed";
-            close(pipefd[0]);
-            _exit(EXIT_FAILURE);
-        }
-
-        FILE* pipe_fp = fdopen(pipefd[0], "r");
-        if (pipe_fp == nullptr) {
-            PLOG(ERROR) << "fdopen failed";
-            check_and_fclose(log_fp, filename);
-            close(pipefd[0]);
-            _exit(EXIT_FAILURE);
-        }
-
-        char* line = nullptr;
-        size_t len = 0;
-        while (getline(&line, &len, pipe_fp) != -1) {
-            auto now = std::chrono::steady_clock::now();
-            double duration = std::chrono::duration_cast<std::chrono::duration<double>>(
-                    now - start).count();
-            if (line[0] == '\n') {
-                fprintf(log_fp, "[%12.6lf]\n", duration);
-            } else {
-                fprintf(log_fp, "[%12.6lf] %s", duration, line);
-            }
-            fflush(log_fp);
-        }
-
-        PLOG(ERROR) << "getline failed";
-
-        free(line);
-        check_and_fclose(log_fp, filename);
-        close(pipefd[0]);
-        _exit(EXIT_FAILURE);
-    } else {
-        // Redirect stdout/stderr to the logger process.
-        // Close the unused read end.
-        close(pipefd[0]);
-
-        setbuf(stdout, nullptr);
-        setbuf(stderr, nullptr);
-
-        if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
-            PLOG(ERROR) << "dup2 stdout failed";
-        }
-        if (dup2(pipefd[1], STDERR_FILENO) == -1) {
-            PLOG(ERROR) << "dup2 stderr failed";
-        }
-
-        close(pipefd[1]);
-    }
-}
-
-// command line args come from, in decreasing precedence:
-//   - the actual command line
-//   - the bootloader control block (one per line, after "recovery")
-//   - the contents of COMMAND_FILE (one per line)
-static std::vector<std::string> get_args(const int argc, char** const argv) {
-  CHECK_GT(argc, 0);
-
-  bootloader_message boot = {};
-  std::string err;
-  if (!read_bootloader_message(&boot, &err)) {
-    LOG(ERROR) << err;
-    // If fails, leave a zeroed bootloader_message.
-    boot = {};
-  }
-  stage = std::string(boot.stage);
-
-  if (boot.command[0] != 0) {
-    std::string boot_command = std::string(boot.command, sizeof(boot.command));
-    LOG(INFO) << "Boot command: " << boot_command;
-  }
-
-  if (boot.status[0] != 0) {
-    std::string boot_status = std::string(boot.status, sizeof(boot.status));
-    LOG(INFO) << "Boot status: " << boot_status;
-  }
-
-  std::vector<std::string> args(argv, argv + argc);
-
-  // --- if arguments weren't supplied, look in the bootloader control block
-  if (args.size() == 1) {
-    boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
-    std::string boot_recovery(boot.recovery);
-    std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");
-    if (!tokens.empty() && tokens[0] == "recovery") {
-      for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {
-        // Skip empty and '\0'-filled tokens.
-        if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
-      }
-      LOG(INFO) << "Got " << args.size() << " arguments from boot message";
-    } else if (boot.recovery[0] != 0) {
-      LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\"";
-    }
-  }
-
-  // --- if that doesn't work, try the command file (if we have /cache).
-  if (args.size() == 1 && has_cache) {
-    std::string content;
-    if (ensure_path_mounted(COMMAND_FILE) == 0 &&
-        android::base::ReadFileToString(COMMAND_FILE, &content)) {
-      std::vector<std::string> tokens = android::base::Split(content, "\n");
-      // All the arguments in COMMAND_FILE are needed (unlike the BCB message,
-      // COMMAND_FILE doesn't use filename as the first argument).
-      for (auto it = tokens.begin(); it != tokens.end(); it++) {
-        // Skip empty and '\0'-filled tokens.
-        if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
-      }
-      LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE;
-    }
-  }
-
-  // Write the arguments (excluding the filename in args[0]) back into the
-  // bootloader control block. So the device will always boot into recovery to
-  // finish the pending work, until finish_recovery() is called.
-  std::vector<std::string> options(args.cbegin() + 1, args.cend());
-  if (!update_bootloader_message(options, &err)) {
-    LOG(ERROR) << "Failed to set BCB message: " << err;
-  }
-
-  return args;
-}
-
-// Set the BCB to reboot back into recovery (it won't resume the install from
-// sdcard though).
-static void set_sdcard_update_bootloader_message() {
-  std::vector<std::string> options;
-  std::string err;
-  if (!update_bootloader_message(options, &err)) {
-    LOG(ERROR) << "Failed to set BCB message: " << err;
-  }
-}
-
-// Read from kernel log into buffer and write out to file.
-static void save_kernel_log(const char* destination) {
-    int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0);
-    if (klog_buf_len <= 0) {
-        PLOG(ERROR) << "Error getting klog size";
-        return;
-    }
-
-    std::string buffer(klog_buf_len, 0);
-    int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len);
-    if (n == -1) {
-        PLOG(ERROR) << "Error in reading klog";
-        return;
-    }
-    buffer.resize(n);
-    android::base::WriteStringToFile(buffer, destination);
-}
-
-// write content to the current pmsg session.
-static ssize_t __pmsg_write(const char *filename, const char *buf, size_t len) {
-    return __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
-                                         filename, buf, len);
-}
-
-static void copy_log_file_to_pmsg(const char* source, const char* destination) {
-    std::string content;
-    android::base::ReadFileToString(source, &content);
-    __pmsg_write(destination, content.c_str(), content.length());
-}
-
-// How much of the temp log we have copied to the copy in cache.
-static off_t tmplog_offset = 0;
-
-static void copy_log_file(const char* source, const char* destination, bool append) {
-  FILE* dest_fp = fopen_path(destination, append ? "ae" : "we");
-  if (dest_fp == nullptr) {
-    PLOG(ERROR) << "Can't open " << destination;
-  } else {
-    FILE* source_fp = fopen(source, "re");
-    if (source_fp != nullptr) {
-      if (append) {
-        fseeko(source_fp, tmplog_offset, SEEK_SET);  // Since last write
-      }
-      char buf[4096];
-      size_t bytes;
-      while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
-        fwrite(buf, 1, bytes, dest_fp);
-      }
-      if (append) {
-        tmplog_offset = ftello(source_fp);
-      }
-      check_and_fclose(source_fp, source);
-    }
-    check_and_fclose(dest_fp, destination);
-  }