Merge AOSP android-9.0.0_r3

Fix conflicts and make it build in 5.1, 6.0, 7.1, 8.1, and 9.0

Change-Id: Ida0a64c29ff27d339b7f42a18d820930964ac6e4
diff --git a/.clang-format b/.clang-format
index 5322788..0e0f4d1 100644
--- a/.clang-format
+++ b/.clang-format
@@ -10,6 +10,5 @@
 PointerAlignment: Left
 TabWidth: 2
 UseTab: Never
-PenaltyExcessCharacter: 32
 
 Cpp11BracedListStyle: false
diff --git a/Android.bp b/Android.bp
index 07fc27d..8837b20 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,4 +1,8 @@
 subdirs = [
-//    "bootloader_message",
+//    "applypatch",
+    "bootloader_message",
+//    "edify",
+//    "otafault",
 //    "otautil",
+//    "uncrypt",
 ]
diff --git a/Android.mk b/Android.mk
index 6880ed2..cacd783 100644
--- a/Android.mk
+++ b/Android.mk
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 LOCAL_PATH := $(call my-dir)
+commands_TWRP_local_path := $(LOCAL_PATH)
 
 ifdef project-path-for
     ifeq ($(LOCAL_PATH),$(call project-path-for,recovery))
@@ -114,6 +115,7 @@
 LOCAL_C_INCLUDES += bionic
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
     LOCAL_C_INCLUDES += external/stlport/stlport external/openssl/include
+    LOCAL_CFLAGS += -DUSE_FUSE_SIDELOAD22
 else
     LOCAL_C_INCLUDES += external/boringssl/include external/libcxx/include
 endif
@@ -143,6 +145,7 @@
 
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0)
    LOCAL_SHARED_LIBRARIES += libziparchive
+   LOCAL_C_INCLUDES += $(LOCAL_PATH)/otautil/include
 else
     LOCAL_SHARED_LIBRARIES += libminzip
     LOCAL_CFLAGS += -DUSE_MINZIP
@@ -160,11 +163,13 @@
 endif
 
 ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
-    LOCAL_CFLAGS += -DUSE_EXT4
-    LOCAL_C_INCLUDES += system/extras/ext4_utils
-    LOCAL_SHARED_LIBRARIES += libext4_utils
-    ifneq ($(wildcard external/lz4/Android.mk),)
-        #LOCAL_STATIC_LIBRARIES += liblz4
+    ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 28; echo $$?),0)
+        LOCAL_CFLAGS += -DUSE_EXT4
+        LOCAL_C_INCLUDES += system/extras/ext4_utils
+        LOCAL_SHARED_LIBRARIES += libext4_utils
+        ifneq ($(wildcard external/lz4/Android.mk),)
+            #LOCAL_STATIC_LIBRARIES += liblz4
+        endif
     endif
 endif
 LOCAL_C_INCLUDES += external/libselinux/include
@@ -181,7 +186,7 @@
 ifeq ($(AB_OTA_UPDATER),true)
     LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
     LOCAL_SHARED_LIBRARIES += libhardware
-    LOCAL_ADDITIONAL_DEPENDENCIES += libhardware
+    LOCAL_REQUIRED_MODULES += libhardware
 endif
 
 LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
@@ -362,7 +367,7 @@
 ifneq ($(TW_CLOCK_OFFSET),)
 	LOCAL_CFLAGS += -DTW_CLOCK_OFFSET=$(TW_CLOCK_OFFSET)
 endif
-LOCAL_ADDITIONAL_DEPENDENCIES += \
+LOCAL_REQUIRED_MODULES += \
     dump_image \
     erase_image \
     flash_image \
@@ -390,7 +395,6 @@
     LOCAL_LDFLAGS += -Wl,-dynamic-linker,/sbin/linker64
 endif
 ifneq ($(TW_USE_TOOLBOX), true)
-    LOCAL_ADDITIONAL_DEPENDENCIES += busybox_symlinks
     ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0)
         LOCAL_POST_INSTALL_CMD := \
             $(hide) mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/sbin && \
@@ -398,72 +402,72 @@
     endif
 else
     ifneq ($(wildcard external/toybox/Android.mk),)
-        LOCAL_ADDITIONAL_DEPENDENCIES += toybox_symlinks
+        LOCAL_REQUIRED_MODULES += toybox_symlinks
     endif
     ifneq ($(wildcard external/zip/Android.mk),)
-        LOCAL_ADDITIONAL_DEPENDENCIES += zip
+        LOCAL_REQUIRED_MODULES += zip
     endif
     ifneq ($(wildcard external/unzip/Android.mk),)
-        LOCAL_ADDITIONAL_DEPENDENCIES += unzip
+        LOCAL_REQUIRED_MODULES += unzip
     endif
 endif
 
 ifneq ($(TW_NO_EXFAT), true)
-    LOCAL_ADDITIONAL_DEPENDENCIES += mkexfatfs fsckexfat
+    LOCAL_REQUIRED_MODULES += mkexfatfs fsckexfat
     ifneq ($(TW_NO_EXFAT_FUSE), true)
-        LOCAL_ADDITIONAL_DEPENDENCIES += exfat-fuse
+        LOCAL_REQUIRED_MODULES += exfat-fuse
     endif
 endif
 ifeq ($(BOARD_HAS_NO_REAL_SDCARD),)
     ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 22; echo $$?),0)
-        LOCAL_ADDITIONAL_DEPENDENCIES += sgdisk
+        LOCAL_REQUIRED_MODULES += sgdisk
     else
-        LOCAL_ADDITIONAL_DEPENDENCIES += sgdisk_static
+        LOCAL_REQUIRED_MODULES += sgdisk_static
     endif
 endif
 ifneq ($(TW_EXCLUDE_ENCRYPTED_BACKUPS), true)
-    LOCAL_ADDITIONAL_DEPENDENCIES += openaes openaes_license
+    LOCAL_REQUIRED_MODULES += openaes openaes_license
 endif
 ifeq ($(TW_INCLUDE_DUMLOCK), true)
-    LOCAL_ADDITIONAL_DEPENDENCIES += \
+    LOCAL_REQUIRED_MODULES += \
         htcdumlock htcdumlocksys flash_imagesys dump_imagesys libbmlutils.so \
         libflashutils.so libmmcutils.so libmtdutils.so HTCDumlock.apk
 endif
 ifeq ($(TW_INCLUDE_FB2PNG), true)
-    LOCAL_ADDITIONAL_DEPENDENCIES += fb2png
+    LOCAL_REQUIRED_MODULES += fb2png
 endif
 ifneq ($(TW_OEM_BUILD),true)
-    LOCAL_ADDITIONAL_DEPENDENCIES += orscmd
+    LOCAL_REQUIRED_MODULES += orscmd
 endif
 ifeq ($(BOARD_USES_BML_OVER_MTD),true)
-    LOCAL_ADDITIONAL_DEPENDENCIES += bml_over_mtd
+    LOCAL_REQUIRED_MODULES += bml_over_mtd
 endif
 ifeq ($(TW_INCLUDE_INJECTTWRP), true)
-    LOCAL_ADDITIONAL_DEPENDENCIES += injecttwrp
+    LOCAL_REQUIRED_MODULES += injecttwrp
 endif
 ifneq ($(TW_EXCLUDE_DEFAULT_USB_INIT), true)
-    LOCAL_ADDITIONAL_DEPENDENCIES += init.recovery.usb.rc
+    LOCAL_REQUIRED_MODULES += init.recovery.usb.rc
 endif
 ifeq ($(TWRP_INCLUDE_LOGCAT), true)
-    LOCAL_ADDITIONAL_DEPENDENCIES += logcat
+    LOCAL_REQUIRED_MODULES += logcat
     ifeq ($(TARGET_USES_LOGD), true)
-        LOCAL_ADDITIONAL_DEPENDENCIES += logd libsysutils libnl init.recovery.logd.rc
+        LOCAL_REQUIRED_MODULES += logd libsysutils libnl init.recovery.logd.rc
     endif
 endif
 # Allow devices to specify device-specific recovery dependencies
 ifneq ($(TARGET_RECOVERY_DEVICE_MODULES),)
-    LOCAL_ADDITIONAL_DEPENDENCIES += $(TARGET_RECOVERY_DEVICE_MODULES)
+    LOCAL_REQUIRED_MODULES += $(TARGET_RECOVERY_DEVICE_MODULES)
 endif
 LOCAL_CFLAGS += -DTWRES=\"$(TWRES_PATH)\"
 LOCAL_CFLAGS += -DTWHTCD_PATH=\"$(TWHTCD_PATH)\"
 ifeq ($(TW_INCLUDE_NTFS_3G),true)
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 22; echo $$?),0)
-    LOCAL_ADDITIONAL_DEPENDENCIES += \
+    LOCAL_REQUIRED_MODULES += \
         mount.ntfs \
         fsck.ntfs \
         mkfs.ntfs
 else
-    LOCAL_ADDITIONAL_DEPENDENCIES += \
+    LOCAL_REQUIRED_MODULES += \
         ntfs-3g \
         ntfsfix \
         mkntfs
@@ -471,14 +475,14 @@
 endif
 ifeq ($(TARGET_USERIMAGES_USE_F2FS), true)
 ifeq ($(shell test $(CM_PLATFORM_SDK_VERSION) -ge 3; echo $$?),0)
-    LOCAL_ADDITIONAL_DEPENDENCIES += \
+    LOCAL_REQUIRED_MODULES += \
         fsck.f2fs \
         mkfs.f2fs
 endif
 endif
 
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 25; echo $$?),0)
-    LOCAL_ADDITIONAL_DEPENDENCIES += file_contexts_text
+    LOCAL_REQUIRED_MODULES += file_contexts_text
 endif
 
 ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),)
@@ -494,7 +498,7 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_REQUIRED_MODULES := file_contexts.bin
 LOCAL_POST_INSTALL_CMD := \
-    $(hide) cp -f $(OUT)/obj/ETC/file_contexts.bin_intermediates/file_contexts.concat.tmp $(TARGET_RECOVERY_ROOT_OUT)/file_contexts
+    $(hide) cp -f $(PRODUCT_OUT)/obj/ETC/file_contexts.bin_intermediates/file_contexts.concat.tmp $(TARGET_RECOVERY_ROOT_OUT)/file_contexts
 
 include $(BUILD_PHONY_PACKAGE)
 
@@ -571,9 +575,8 @@
 # shared libfusesideload
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := fuse_sideload.cpp
 LOCAL_CLANG := true
-LOCAL_CFLAGS := -Wall -Werror
+LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter
 LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
 
 LOCAL_MODULE_TAGS := optional
@@ -586,14 +589,19 @@
 else
     LOCAL_SHARED_LIBRARIES += libcrypto
 endif
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
+    LOCAL_SRC_FILES := fuse_sideload22.cpp
+    LOCAL_CFLAGS += -DUSE_FUSE_SIDELOAD22
+else
+    LOCAL_SRC_FILES := fuse_sideload.cpp
+endif
 include $(BUILD_SHARED_LIBRARY)
 
 # static libfusesideload
 # =============================== (required to fix build errors in 8.1 due to use by tests)
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := fuse_sideload.cpp
 LOCAL_CLANG := true
-LOCAL_CFLAGS := -Wall -Werror
+LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter
 LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
 
 LOCAL_MODULE_TAGS := optional
@@ -606,6 +614,12 @@
 else
     LOCAL_STATIC_LIBRARIES += libcrypto_static
 endif
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
+    LOCAL_SRC_FILES := fuse_sideload22.cpp
+    LOCAL_CFLAGS += -DUSE_FUSE_SIDELOAD22
+else
+    LOCAL_SRC_FILES := fuse_sideload.cpp
+endif
 include $(BUILD_STATIC_LIBRARY)
 
 # libmounts (static library)
@@ -634,6 +648,7 @@
 LOCAL_MODULE := librecovery
 LOCAL_STATIC_LIBRARIES := \
     libminui \
+    libotautil \
     libvintf_recovery \
     libcrypto_utils \
     libcrypto \
@@ -646,6 +661,7 @@
 # ===============================
 include $(CLEAR_VARS)
 
+
 LOCAL_MODULE := libaosprecovery
 LOCAL_MODULE_TAGS := eng optional
 LOCAL_CFLAGS := -std=gnu++0x
@@ -655,6 +671,7 @@
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
     LOCAL_SHARED_LIBRARIES += libstdc++ libstlport
     LOCAL_C_INCLUDES += bionic external/stlport/stlport
+    LOCAL_CFLAGS += -DUSE_FUSE_SIDELOAD22
 else
     LOCAL_SHARED_LIBRARIES += libc++
 endif
@@ -666,37 +683,49 @@
 else
     LOCAL_SHARED_LIBRARIES += libcrypto libbase
     LOCAL_SRC_FILES += verifier.cpp asn1_decoder.cpp
+    LOCAL_C_INCLUDES += $(LOCAL_PATH)/otautil/include
 endif
 
 ifeq ($(AB_OTA_UPDATER),true)
     LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
 endif
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0)
-    LOCAL_SRC_FILES += otautil/ZipUtil.cpp otautil/SysUtil.cpp
+    LOCAL_SRC_FILES += otautil/ZipUtil.cpp otautil/SysUtil.cpp otautil/DirUtil.cpp
     LOCAL_SHARED_LIBRARIES += libziparchive libext4_utils libcrypto libcrypto_utils
     LOCAL_STATIC_LIBRARIES += libvintf_recovery libfs_mgr liblogwrap libavb libvintf libtinyxml2 libz
-    LOCAL_WHOLE_STATIC_LIBRARIES +=
+    LOCAL_C_INCLUDES += $(LOCAL_PATH)/otautil/include
+    ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 27; echo $$?),0)
+        # Android 9.0 needs c++17 for libvintf
+        LOCAL_CPPFLAGS += -std=c++17
+        # Android 9.0's libvintf also needs this library
+        LOCAL_STATIC_LIBRARIES += libhidl-gen-utils
+    endif
 else
     LOCAL_CFLAGS += -DUSE_MINZIP
 endif
 
 include $(BUILD_SHARED_LIBRARY)
-
-# All the APIs for testing
+# libverifier (static library)
+# ===============================
 include $(CLEAR_VARS)
 LOCAL_CLANG := true
 LOCAL_MODULE := libverifier
 LOCAL_SRC_FILES := \
     asn1_decoder.cpp \
-    verifier.cpp \
-    ui.cpp
-LOCAL_STATIC_LIBRARIES := libcrypto_static
+    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
@@ -708,6 +737,7 @@
 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
@@ -716,20 +746,23 @@
 
 commands_recovery_local_path := $(LOCAL_PATH)
 
+#    $(LOCAL_PATH)/edify/Android.mk
+#    $(LOCAL_PATH)/otafault/Android.mk
+#    $(LOCAL_PATH)/bootloader_message/Android.mk
 include \
-    $(LOCAL_PATH)/applypatch/Android.mk \
-    $(LOCAL_PATH)/boot_control/Android.mk \
-    $(LOCAL_PATH)/edify/Android.mk \
-    $(LOCAL_PATH)/otafault/Android.mk \
-    $(LOCAL_PATH)/tests/Android.mk \
-    $(LOCAL_PATH)/tools/Android.mk \
-    $(LOCAL_PATH)/updater/Android.mk \
-    $(LOCAL_PATH)/update_verifier/Android.mk \
-    $(LOCAL_PATH)/bootloader_message/Android.mk \
-    $(LOCAL_PATH)/bootloader_message_twrp/Android.mk
+    $(commands_TWRP_local_path)/boot_control/Android.mk \
+    $(commands_TWRP_local_path)/tests/Android.mk \
+    $(commands_TWRP_local_path)/tools/Android.mk \
+    $(commands_TWRP_local_path)/updater/Android.mk \
+    $(commands_TWRP_local_path)/update_verifier/Android.mk \
+    $(commands_TWRP_local_path)/bootloader_message_twrp/Android.mk
+
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -le 25; echo $$?),0)
+include $(commands_TWRP_local_path)/bootloader_message/Android.mk
+endif
 
 ifeq ($(wildcard system/core/uncrypt/Android.mk),)
-    include $(commands_recovery_local_path)/uncrypt/Android.mk
+    #include $(commands_TWRP_local_path)/uncrypt/Android.mk
 endif
 
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 22; echo $$?),0)
@@ -737,81 +770,81 @@
         TARGET_GLOBAL_CFLAGS += -DTW_USE_MINUI_WITH_DATA
         CLANG_TARGET_GLOBAL_CFLAGS += -DTW_USE_MINUI_WITH_DATA
     endif
-    include $(commands_recovery_local_path)/minadbd/Android.mk \
-        $(commands_recovery_local_path)/minui/Android.mk
+    include $(commands_TWRP_local_path)/minadbd/Android.mk \
+        $(commands_TWRP_local_path)/minui/Android.mk
 else
     TARGET_GLOBAL_CFLAGS += -DTW_USE_MINUI_21
-    include $(commands_recovery_local_path)/minadbd21/Android.mk \
-        $(commands_recovery_local_path)/minui21/Android.mk
+    include $(commands_TWRP_local_path)/minadbd21/Android.mk \
+        $(commands_TWRP_local_path)/minui21/Android.mk
 endif
 
+#$(commands_TWRP_local_path)/otautil/Android.mk
 #includes for TWRP
-include $(commands_recovery_local_path)/injecttwrp/Android.mk \
-    $(commands_recovery_local_path)/htcdumlock/Android.mk \
-    $(commands_recovery_local_path)/gui/Android.mk \
-    $(commands_recovery_local_path)/mmcutils/Android.mk \
-    $(commands_recovery_local_path)/bmlutils/Android.mk \
-    $(commands_recovery_local_path)/prebuilt/Android.mk \
-    $(commands_recovery_local_path)/mtdutils/Android.mk \
-    $(commands_recovery_local_path)/flashutils/Android.mk \
-    $(commands_recovery_local_path)/pigz/Android.mk \
-    $(commands_recovery_local_path)/libtar/Android.mk \
-    $(commands_recovery_local_path)/libcrecovery/Android.mk \
-    $(commands_recovery_local_path)/libblkid/Android.mk \
-    $(commands_recovery_local_path)/minuitwrp/Android.mk \
-    $(commands_recovery_local_path)/otautil/Android.mk \
-    $(commands_recovery_local_path)/openaes/Android.mk \
-    $(commands_recovery_local_path)/toolbox/Android.mk \
-    $(commands_recovery_local_path)/twrpTarMain/Android.mk \
-    $(commands_recovery_local_path)/mtp/Android.mk \
-    $(commands_recovery_local_path)/minzip/Android.mk \
-    $(commands_recovery_local_path)/dosfstools/Android.mk \
-    $(commands_recovery_local_path)/etc/Android.mk \
-    $(commands_recovery_local_path)/toybox/Android.mk \
-    $(commands_recovery_local_path)/simg2img/Android.mk \
-    $(commands_recovery_local_path)/adbbu/Android.mk \
-    $(commands_recovery_local_path)/libpixelflinger/Android.mk \
-    $(commands_recovery_local_path)/twrpDigest/Android.mk \
-    $(commands_recovery_local_path)/attr/Android.mk
+include $(commands_TWRP_local_path)/injecttwrp/Android.mk \
+    $(commands_TWRP_local_path)/htcdumlock/Android.mk \
+    $(commands_TWRP_local_path)/gui/Android.mk \
+    $(commands_TWRP_local_path)/mmcutils/Android.mk \
+    $(commands_TWRP_local_path)/bmlutils/Android.mk \
+    $(commands_TWRP_local_path)/prebuilt/Android.mk \
+    $(commands_TWRP_local_path)/mtdutils/Android.mk \
+    $(commands_TWRP_local_path)/flashutils/Android.mk \
+    $(commands_TWRP_local_path)/pigz/Android.mk \
+    $(commands_TWRP_local_path)/libtar/Android.mk \
+    $(commands_TWRP_local_path)/libcrecovery/Android.mk \
+    $(commands_TWRP_local_path)/libblkid/Android.mk \
+    $(commands_TWRP_local_path)/minuitwrp/Android.mk \
+    $(commands_TWRP_local_path)/openaes/Android.mk \
+    $(commands_TWRP_local_path)/toolbox/Android.mk \
+    $(commands_TWRP_local_path)/twrpTarMain/Android.mk \
+    $(commands_TWRP_local_path)/mtp/Android.mk \
+    $(commands_TWRP_local_path)/minzip/Android.mk \
+    $(commands_TWRP_local_path)/dosfstools/Android.mk \
+    $(commands_TWRP_local_path)/etc/Android.mk \
+    $(commands_TWRP_local_path)/toybox/Android.mk \
+    $(commands_TWRP_local_path)/simg2img/Android.mk \
+    $(commands_TWRP_local_path)/adbbu/Android.mk \
+    $(commands_TWRP_local_path)/libpixelflinger/Android.mk \
+    $(commands_TWRP_local_path)/twrpDigest/Android.mk \
+    $(commands_TWRP_local_path)/attr/Android.mk
 
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?),0)
-    include $(commands_recovery_local_path)/libmincrypt/Android.mk
+    include $(commands_TWRP_local_path)/libmincrypt/Android.mk
 endif
 
 ifeq ($(TW_INCLUDE_CRYPTO), true)
-    include $(commands_recovery_local_path)/crypto/lollipop/Android.mk
-    include $(commands_recovery_local_path)/crypto/scrypt/Android.mk
+    include $(commands_TWRP_local_path)/crypto/lollipop/Android.mk
+    include $(commands_TWRP_local_path)/crypto/scrypt/Android.mk
     ifeq ($(TW_INCLUDE_CRYPTO_FBE), true)
-        include $(commands_recovery_local_path)/crypto/ext4crypt/Android.mk
+        include $(commands_TWRP_local_path)/crypto/ext4crypt/Android.mk
     endif
     ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),)
     ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),false)
-        include $(commands_recovery_local_path)/crypto/vold_decrypt/Android.mk
+        include $(commands_TWRP_local_path)/crypto/vold_decrypt/Android.mk
     endif
     endif
-    include $(commands_recovery_local_path)/gpt/Android.mk
+    include $(commands_TWRP_local_path)/gpt/Android.mk
 endif
 ifeq ($(BUILD_ID), GINGERBREAD)
     TW_NO_EXFAT := true
 endif
 ifneq ($(TW_NO_EXFAT), true)
-    include $(commands_recovery_local_path)/exfat/mkfs/Android.mk \
-            $(commands_recovery_local_path)/exfat/fsck/Android.mk \
-            $(commands_recovery_local_path)/fuse/Android.mk \
-            $(commands_recovery_local_path)/exfat/libexfat/Android.mk
+    include $(commands_TWRP_local_path)/exfat/mkfs/Android.mk \
+            $(commands_TWRP_local_path)/exfat/fsck/Android.mk \
+            $(commands_TWRP_local_path)/fuse/Android.mk \
+            $(commands_TWRP_local_path)/exfat/libexfat/Android.mk
     ifneq ($(TW_NO_EXFAT_FUSE), true)
-        include $(commands_recovery_local_path)/exfat/fuse/Android.mk
+        include $(commands_TWRP_local_path)/exfat/fuse/Android.mk
     endif
 endif
 ifneq ($(TW_OEM_BUILD),true)
-    include $(commands_recovery_local_path)/orscmd/Android.mk
+    include $(commands_TWRP_local_path)/orscmd/Android.mk
 endif
 
 # FB2PNG
 ifeq ($(TW_INCLUDE_FB2PNG), true)
-    include $(commands_recovery_local_path)/fb2png/Android.mk
+    include $(commands_TWRP_local_path)/fb2png/Android.mk
 endif
 
-commands_recovery_local_path :=
-
 endif
+
+commands_TWRP_local_path :=
diff --git a/OWNERS b/OWNERS
index 09754c6..b3f11dc 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,3 @@
-enh+aosp-gerrit@google.com
+enh@google.com
 tbao@google.com
 xunchang@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..b5f5f03
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,6 @@
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+# Handle native codes only.
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
diff --git a/adb_install.h b/adb_install.h
index 121ae3c..97dc83d 100644
--- a/adb_install.h
+++ b/adb_install.h
@@ -21,8 +21,8 @@
 
 //class RecoveryUI;
 
-static void set_usb_driver(bool enabled);
-static void maybe_restart_adbd();
+//static void set_usb_driver(bool enabled);
+//static void maybe_restart_adbd();
 int apply_from_adb(const char* install_file, pid_t* child_pid);
 
 #endif
diff --git a/adbbu/adbbumain.cpp b/adbbu/adbbumain.cpp
index 2e66bce..bd96b20 100644
--- a/adbbu/adbbumain.cpp
+++ b/adbbu/adbbumain.cpp
@@ -32,9 +32,9 @@
 
 int main(int argc, char **argv) {
 	int index;
-	int pos = 0;
+	size_t pos = 0;
 	bool ret = false;
-	int maxpos = sizeof(TWRPARG + 2);
+	size_t maxpos = strlen(TWRPARG) + 2;
 	std::string command;
 	twrpback tw;
 
@@ -45,10 +45,10 @@
 	}
 
 	pos = command.find(TWRP_BACKUP_ARG);
-	if (pos < 0 || pos > (maxpos + sizeof(TWRP_BACKUP_ARG) + 1)) {
+	if (pos == std::string::npos || pos > (maxpos + strlen(TWRP_BACKUP_ARG) + 1)) {
 		pos = command.find(TWRP_RESTORE_ARG);
 	}
-	if (pos < 0 || pos > maxpos + sizeof(TWRP_STREAM_ARG + 1)) {
+	if (pos == std::string::npos || pos > maxpos + strlen(TWRP_STREAM_ARG) + 1) {
 		pos = command.find(TWRP_STREAM_ARG);
 	}
 
diff --git a/adbbu/twrpback.cpp b/adbbu/twrpback.cpp
index 56bc13d..b3de76d 100644
--- a/adbbu/twrpback.cpp
+++ b/adbbu/twrpback.cpp
@@ -144,7 +144,7 @@
 	uint64_t md5fnsize = 0;
 	struct AdbBackupControlType endadb;
 
-	ADBSTRUCT_STATIC_ASSERT(sizeof(endadb) == MAX_ADB_READ);
+	//ADBSTRUCT_STATIC_ASSERT(sizeof(endadb) == MAX_ADB_READ);
 
 	bool writedata = true;
 	bool compressed = false;
@@ -177,7 +177,7 @@
 	}
 
 	memset(operation, 0, sizeof(operation));
-	if (snprintf(operation, sizeof(operation), "adbbackup %s", command.c_str()) >= sizeof(operation)) {
+	if (snprintf(operation, sizeof(operation), "adbbackup %s", command.c_str()) >= (int)sizeof(operation)) {
 		adblogwrite("Operation too big to write to ORS_INPUT_FILE\n");
 		close_backup_fds();
 		return false;
@@ -278,7 +278,7 @@
 				adblogwrite("writing TWFN\n");
 				digest.init();
 
-				ADBSTRUCT_STATIC_ASSERT(sizeof(twfilehdr) == MAX_ADB_READ);
+				//ADBSTRUCT_STATIC_ASSERT(sizeof(twfilehdr) == MAX_ADB_READ);
 
 				memset(&twfilehdr, 0, sizeof(twfilehdr));
 				memcpy(&twfilehdr, cmd, sizeof(cmd));
@@ -420,7 +420,7 @@
 				fileBytes += bytes;
 				dataChunkBytes += bytes;
 
-				if (fwrite(writeAdbReadStream, 1, bytes, adbd_fp) != bytes) {
+				if (fwrite(writeAdbReadStream, 1, bytes, adbd_fp) != (unsigned long long)bytes) {
 					adblogwrite("Error writing backup data to adbd\n");
 					close_backup_fds();
 					return false;
@@ -508,8 +508,8 @@
 	int errctr = 0;
 	uint64_t totalbytes = 0, dataChunkBytes = 0;
 	uint64_t md5fnsize = 0, fileBytes = 0;
-	bool writedata, read_from_adb;
-	bool eofsent, md5trsent, md5sumdata;
+	bool read_from_adb;
+	bool md5sumdata;
 	bool compressed, tweofrcvd, extraData;
 
 	read_from_adb = true;
@@ -649,7 +649,7 @@
 					struct AdbBackupStreamHeader cnthdr;
 					uint32_t crc, cnthdrcrc;
 
-					ADBSTRUCT_STATIC_ASSERT(sizeof(cnthdr) == MAX_ADB_READ);
+					//ADBSTRUCT_STATIC_ASSERT(sizeof(cnthdr) == MAX_ADB_READ);
 
 					md5sumdata = false;
 					memset(&cnthdr, 0, sizeof(cnthdr));
@@ -884,7 +884,7 @@
 	struct AdbBackupFileTrailer md5tr;
 	uint32_t crc, md5trcrc, md5ident, md5identmatch;
 
-	ADBSTRUCT_STATIC_ASSERT(sizeof(md5tr) == MAX_ADB_READ);
+	//ADBSTRUCT_STATIC_ASSERT(sizeof(md5tr) == MAX_ADB_READ);
 	memcpy(&md5tr, readAdbStream, MAX_ADB_READ);
 	md5ident = md5tr.ident;
 
diff --git a/applypatch/Android.bp b/applypatch/Android.bp
new file mode 100644
index 0000000..cb0b367
--- /dev/null
+++ b/applypatch/Android.bp
@@ -0,0 +1,206 @@
+// 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_defaults {
+    name: "applypatch_defaults",
+
+    cflags: [
+        "-D_FILE_OFFSET_BITS=64",
+        "-DZLIB_CONST",
+        "-Wall",
+        "-Werror",
+    ],
+
+    local_include_dirs: [
+        "include",
+    ],
+}
+
+cc_library_static {
+    name: "libapplypatch",
+
+    host_supported: true,
+
+    defaults: [
+        "applypatch_defaults",
+    ],
+
+    srcs: [
+        "applypatch.cpp",
+        "bspatch.cpp",
+        "freecache.cpp",
+        "imgpatch.cpp",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libbspatch",
+        "libbz",
+        "libcrypto",
+        "libedify",
+        "libotafault",
+        "libotautil",
+        "libz",
+    ],
+
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+}
+
+cc_library_static {
+    name: "libapplypatch_modes",
+
+    defaults: [
+        "applypatch_defaults",
+    ],
+
+    srcs: [
+        "applypatch_modes.cpp",
+    ],
+
+    static_libs: [
+        "libapplypatch",
+        "libbase",
+        "libcrypto",
+        "libedify",
+        "libotautil",
+    ],
+}
+
+cc_binary {
+    name: "applypatch",
+
+    defaults: [
+        "applypatch_defaults",
+    ],
+
+    srcs: [
+        "applypatch_main.cpp",
+    ],
+
+    static_libs: [
+        "libapplypatch_modes",
+        "libapplypatch",
+        "libedify",
+        "libotafault",
+        "libotautil",
+        "libbspatch",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libbrotli",
+        "libbz",
+        "libcrypto",
+        "liblog",
+        "libz",
+        "libziparchive",
+    ],
+}
+
+cc_library_static {
+    name: "libimgdiff",
+
+    host_supported: true,
+
+    defaults: [
+        "applypatch_defaults",
+    ],
+
+    srcs: [
+        "imgdiff.cpp",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libbsdiff",
+        "libdivsufsort",
+        "libdivsufsort64",
+        "liblog",
+        "libotautil",
+        "libutils",
+        "libz",
+        "libziparchive",
+    ],
+}
+
+cc_binary_host {
+    name: "imgdiff",
+
+    srcs: [
+        "imgdiff_main.cpp",
+    ],
+
+    defaults: [
+        "applypatch_defaults",
+    ],
+
+    static_libs: [
+        "libimgdiff",
+        "libotautil",
+        "libbsdiff",
+        "libdivsufsort",
+        "libdivsufsort64",
+        "libziparchive",
+        "libbase",
+        "libutils",
+        "liblog",
+        "libbrotli",
+        "libbz",
+        "libz",
+    ],
+}
+
+cc_library_static {
+    name: "libimgpatch",
+
+    // The host module is for recovery_host_test (Linux only).
+    host_supported: true,
+
+    defaults: [
+        "applypatch_defaults",
+    ],
+
+    srcs: [
+        "bspatch.cpp",
+        "imgpatch.cpp",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libbspatch",
+        "libbz",
+        "libcrypto",
+        "libedify",
+        "libotautil",
+        "libz",
+    ],
+
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+}
diff --git a/applypatch/Makefile b/applypatch/Makefile
deleted file mode 100644
index fb49843..0000000
--- a/applypatch/Makefile
+++ /dev/null
@@ -1,33 +0,0 @@
-# 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.
-
-# This file is for building imgdiff in Chrome OS.
-
-CPPFLAGS += -iquote.. -Iinclude
-CXXFLAGS += -std=c++11 -O3 -Wall -Werror
-LDLIBS += -lbz2 -lz
-
-.PHONY: all clean
-
-all: imgdiff libimgpatch.a
-
-clean:
-	rm -f *.o imgdiff libimgpatch.a
-
-imgdiff: imgdiff.o bsdiff.o utils.o
-	$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDLIBS) -o $@ $^
-
-libimgpatch.a utils.o: CXXFLAGS += -fPIC
-libimgpatch.a: imgpatch.o bspatch.o utils.o
-	${AR} rcs $@ $^
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 43e9b80..5c6c83f 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -42,8 +42,9 @@
 #include "mtdutils/mtdutils.h"
 
 #include "edify/expr.h"
-#include "ota_io.h"
-#include "print_sha1.h"
+#include "otafault/ota_io.h"
+#include "otautil/cache_location.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);
@@ -64,12 +65,13 @@
     return LoadPartitionContents(filename, file);
   }
 
-  if (stat(filename, &file->st) == -1) {
+  struct stat sb;
+  if (stat(filename, &sb) == -1) {
     printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
     return -1;
   }
 
-  std::vector<unsigned char> data(file->st.st_size);
+  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));
@@ -208,10 +210,6 @@
 
   buffer.resize(buffer_size);
   file->data = std::move(buffer);
-  // Fake some stat() info.
-  file->st.st_mode = 0644;
-  file->st.st_uid = 0;
-  file->st.st_gid = 0;
 
   return 0;
 }
@@ -240,15 +238,6 @@
     return -1;
   }
 
-  if (chmod(filename, file->st.st_mode) != 0) {
-    printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno));
-    return -1;
-  }
-  if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) {
-    printf("chown of \"%s\" failed: %s\n", filename, strerror(errno));
-    return -1;
-  }
-
   return 0;
 }
 
@@ -509,12 +498,10 @@
       (!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(CACHE_TEMP_SOURCE, &file) != 0) {
+    // 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;
     }
@@ -560,9 +547,8 @@
     if (MakeFreeSpaceOnCache(bytes) < 0) {
         printf("unable to make %zu bytes available on /cache\n", bytes);
         return 1;
-    } else {
-        return 0;
     }
+    return 0;
 }
 
 // This function applies binary patches to EMMC target files in a way that is safe (the original
@@ -587,7 +573,7 @@
 // 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 __unused,
+               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);
@@ -637,7 +623,7 @@
   printf("source file is bad; trying copy\n");
 
   FileContents copy_file;
-  if (LoadFileContents(CACHE_TEMP_SOURCE, &copy_file) < 0) {
+  if (LoadFileContents(CacheLocation::location().cache_temp_source().c_str(), &copy_file) < 0) {
     printf("failed to read copy file\n");
     return 1;
   }
@@ -732,7 +718,7 @@
     printf("not enough free space on /cache\n");
     return 1;
   }
-  if (SaveFileContents(CACHE_TEMP_SOURCE, &source_file) < 0) {
+  if (SaveFileContents(CacheLocation::location().cache_temp_source().c_str(), &source_file) < 0) {
     printf("failed to back up source file\n");
     return 1;
   }
@@ -749,11 +735,11 @@
 
   int result;
   if (use_bsdiff) {
-    result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch.get(), 0,
-                              sink, &ctx);
+    result =
+        ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), *patch, 0, sink, &ctx);
   } else {
-    result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch.get(), sink,
-                             &ctx, bonus_data);
+    result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), *patch, sink, &ctx,
+                             bonus_data);
   }
 
   if (result != 0) {
@@ -778,7 +764,7 @@
   }
 
   // Delete the backup copy of the source.
-  unlink(CACHE_TEMP_SOURCE);
+  unlink(CacheLocation::location().cache_temp_source().c_str());
 
   // Success!
   return 0;
diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp
index 65ee614..912dbbd 100644
--- a/applypatch/bspatch.cpp
+++ b/applypatch/bspatch.cpp
@@ -26,11 +26,12 @@
 #include <string>
 
 #include <android-base/logging.h>
-#include <bspatch.h>
+#include <bsdiff/bspatch.h>
 #include <openssl/sha.h>
 
 #include "applypatch/applypatch.h"
-#include "print_sha1.h"
+#include "edify/expr.h"
+#include "otautil/print_sha1.h"
 
 void ShowBSDiffLicense() {
     puts("The bsdiff library used herein is:\n"
@@ -64,7 +65,7 @@
         );
 }
 
-int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch,
+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);
@@ -72,23 +73,21 @@
     return len;
   };
 
-  CHECK(patch != nullptr);
-  CHECK_LE(patch_offset, patch->data.size());
+  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);
+                               reinterpret_cast<const uint8_t*>(&patch.data[patch_offset]),
+                               patch.data.size() - patch_offset, sha_sink);
   if (result != 0) {
     LOG(ERROR) << "bspatch failed, result: " << result;
     // print SHA1 of the patch in the case of a data error.
     if (result == 2) {
       uint8_t digest[SHA_DIGEST_LENGTH];
-      SHA1(reinterpret_cast<const uint8_t*>(patch->data.data() + patch_offset),
-           patch->data.size() - patch_offset, digest);
+      SHA1(reinterpret_cast<const uint8_t*>(patch.data.data() + patch_offset),
+           patch.data.size() - patch_offset, digest);
       std::string patch_sha1 = print_sha1(digest);
-      LOG(ERROR) << "Patch may be corrupted, offset: " << patch_offset << ", SHA1: "
-                 << patch_sha1;
+      LOG(ERROR) << "Patch may be corrupted, offset: " << patch_offset << ", SHA1: " << patch_sha1;
     }
   }
   return result;
-}
\ No newline at end of file
+}
diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp
index 331cae2..ea364d8 100644
--- a/applypatch/freecache.cpp
+++ b/applypatch/freecache.cpp
@@ -33,6 +33,7 @@
 #include <android-base/stringprintf.h>
 
 #include "applypatch/applypatch.h"
+#include "otautil/cache_location.h"
 
 static int EliminateOpenFiles(std::set<std::string>* files) {
   std::unique_ptr<DIR, decltype(&closedir)> d(opendir("/proc"), closedir);
@@ -90,10 +91,9 @@
     while ((de = readdir(d.get())) != 0) {
       std::string path = std::string(dirs[i]) + "/" + 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 == CACHE_TEMP_SOURCE) {
+      // 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;
       }
 
@@ -112,6 +112,12 @@
 }
 
 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);
 
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index fc24064..674cc2b 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -15,53 +15,44 @@
  */
 
 /*
- * This program constructs binary patches for images -- such as boot.img
- * and recovery.img -- that consist primarily of large chunks of gzipped
- * data interspersed with uncompressed data.  Doing a naive bsdiff of
- * these files is not useful because small changes in the data lead to
- * large changes in the compressed bitstream; bsdiff patches of gzipped
- * data are typically as large as the data itself.
+ * This program constructs binary patches for images -- such as boot.img and recovery.img -- that
+ * consist primarily of large chunks of gzipped data interspersed with uncompressed data.  Doing a
+ * naive bsdiff of these files is not useful because small changes in the data lead to large
+ * changes in the compressed bitstream; bsdiff patches of gzipped data are typically as large as
+ * the data itself.
  *
- * To patch these usefully, we break the source and target images up into
- * chunks of two types: "normal" and "gzip".  Normal chunks are simply
- * patched using a plain bsdiff.  Gzip chunks are first expanded, then a
- * bsdiff is applied to the uncompressed data, then the patched data is
- * gzipped using the same encoder parameters.  Patched chunks are
- * concatenated together to create the output file; the output image
- * should be *exactly* the same series of bytes as the target image used
- * originally to generate the patch.
+ * To patch these usefully, we break the source and target images up into chunks of two types:
+ * "normal" and "gzip".  Normal chunks are simply patched using a plain bsdiff.  Gzip chunks are
+ * first expanded, then a bsdiff is applied to the uncompressed data, then the patched data is
+ * gzipped using the same encoder parameters.  Patched chunks are concatenated together to create
+ * the output file; the output image should be *exactly* the same series of bytes as the target
+ * image used originally to generate the patch.
  *
- * To work well with this tool, the gzipped sections of the target
- * image must have been generated using the same deflate encoder that
- * is available in applypatch, namely, the one in the zlib library.
- * In practice this means that images should be compressed using the
- * "minigzip" tool included in the zlib distribution, not the GNU gzip
- * program.
+ * To work well with this tool, the gzipped sections of the target image must have been generated
+ * using the same deflate encoder that is available in applypatch, namely, the one in the zlib
+ * library.  In practice this means that images should be compressed using the "minigzip" tool
+ * included in the zlib distribution, not the GNU gzip program.
  *
- * An "imgdiff" patch consists of a header describing the chunk structure
- * of the file and any encoding parameters needed for the gzipped
- * chunks, followed by N bsdiff patches, one per chunk.
+ * An "imgdiff" patch consists of a header describing the chunk structure of the file and any
+ * encoding parameters needed for the gzipped chunks, followed by N bsdiff patches, one per chunk.
  *
- * For a diff to be generated, the source and target images must have the
- * same "chunk" structure: that is, the same number of gzipped and normal
- * chunks in the same order.  Android boot and recovery images currently
- * consist of five chunks:  a small normal header, a gzipped kernel, a
- * small normal section, a gzipped ramdisk, and finally a small normal
- * footer.
+ * For a diff to be generated, the source and target must be in well-formed zip archive format;
+ * or they are image files with the same "chunk" structure: that is, the same number of gzipped and
+ * normal chunks in the same order.  Android boot and recovery images currently consist of five
+ * chunks: a small normal header, a gzipped kernel, a small normal section, a gzipped ramdisk, and
+ * finally a small normal footer.
  *
- * Caveats:  we locate gzipped sections within the source and target
- * images by searching for the byte sequence 1f8b0800:  1f8b is the gzip
- * magic number; 08 specifies the "deflate" encoding [the only encoding
- * supported by the gzip standard]; and 00 is the flags byte.  We do not
- * currently support any extra header fields (which would be indicated by
- * a nonzero flags byte).  We also don't handle the case when that byte
- * sequence appears spuriously in the file.  (Note that it would have to
- * occur spuriously within a normal chunk to be a problem.)
+ * Caveats:  we locate gzipped sections within the source and target images by searching for the
+ * byte sequence 1f8b0800:  1f8b is the gzip magic number; 08 specifies the "deflate" encoding
+ * [the only encoding supported by the gzip standard]; and 00 is the flags byte.  We do not
+ * currently support any extra header fields (which would be indicated by a nonzero flags byte).
+ * We also don't handle the case when that byte sequence appears spuriously in the file.  (Note
+ * that it would have to occur spuriously within a normal chunk to be a problem.)
  *
  *
  * The imgdiff patch header looks like this:
  *
- *    "IMGDIFF1"                  (8)   [magic number and version]
+ *    "IMGDIFF2"                  (8)   [magic number and version]
  *    chunk count                 (4)
  *    for each chunk:
  *        chunk type              (4)   [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}]
@@ -98,33 +89,62 @@
  *           target len           (4)
  *           data                 (target len)
  *
- * All integers are little-endian.  "source start" and "source len"
- * specify the section of the input image that comprises this chunk,
- * including the gzip header and footer for gzip chunks.  "source
- * expanded len" is the size of the uncompressed source data.  "target
- * expected len" is the size of the uncompressed data after applying
- * the bsdiff patch.  The next five parameters specify the zlib
- * parameters to be used when compressing the patched data, and the
- * next three specify the header and footer to be wrapped around the
- * compressed data to create the output chunk (so that header contents
- * like the timestamp are recreated exactly).
+ * All integers are little-endian.  "source start" and "source len" specify the section of the
+ * input image that comprises this chunk, including the gzip header and footer for gzip chunks.
+ * "source expanded len" is the size of the uncompressed source data.  "target expected len" is the
+ * size of the uncompressed data after applying the bsdiff patch.  The next five parameters
+ * specify the zlib parameters to be used when compressing the patched data, and the next three
+ * specify the header and footer to be wrapped around the compressed data to create the output
+ * chunk (so that header contents like the timestamp are recreated exactly).
  *
- * After the header there are 'chunk count' bsdiff patches; the offset
- * of each from the beginning of the file is specified in the header.
+ * After the header there are 'chunk count' bsdiff patches; the offset of each from the beginning
+ * of the file is specified in the header.
  *
- * This tool can take an optional file of "bonus data".  This is an
- * extra file of data that is appended to chunk #1 after it is
- * compressed (it must be a CHUNK_DEFLATE chunk).  The same file must
- * be available (and passed to applypatch with -b) when applying the
- * patch.  This is used to reduce the size of recovery-from-boot
- * patches by combining the boot image with recovery ramdisk
+ * This tool can take an optional file of "bonus data".  This is an extra file of data that is
+ * appended to chunk #1 after it is compressed (it must be a CHUNK_DEFLATE chunk).  The same file
+ * must be available (and passed to applypatch with -b) when applying the patch.  This is used to
+ * reduce the size of recovery-from-boot patches by combining the boot image with recovery ramdisk
  * information that is stored on the system partition.
+ *
+ * When generating the patch between two zip files, this tool has an option "--block-limit" to
+ * split the large source/target files into several pair of pieces, with each piece has at most
+ * *limit* blocks.  When this option is used, we also need to output the split info into the file
+ * path specified by "--split-info".
+ *
+ * Format of split info file:
+ *   2                                      [version of imgdiff]
+ *   n                                      [count of split pieces]
+ *   <patch_size>, <tgt_size>, <src_range>  [size and ranges for split piece#1]
+ *   ...
+ *   <patch_size>, <tgt_size>, <src_range>  [size and ranges for split piece#n]
+ *
+ * To split a pair of large zip files, we walk through the chunks in target zip and search by its
+ * entry_name in the source zip.  If the entry_name is non-empty and a matching entry in source
+ * is found, we'll add the source entry to the current split source image; otherwise we'll skip
+ * this chunk and later do bsdiff between all the skipped trunks and the whole split source image.
+ * We move on to the next pair of pieces if the size of the split source image reaches the block
+ * limit.
+ *
+ * After the split, the target pieces are continuous and block aligned, while the source pieces
+ * are mutually exclusive.  Some of the source blocks may not be used if there's no matching
+ * entry_name in the target; as a result, they won't be included in any of these split source
+ * images.  Then we will generate patches accordingly between each split image pairs; in particular,
+ * the unmatched trunks in the split target will diff against the entire split source image.
+ *
+ * For example:
+ * Input: [src_image, tgt_image]
+ * Split: [src-0, tgt-0; src-1, tgt-1, src-2, tgt-2]
+ * Diff:  [  patch-0;      patch-1;      patch-2]
+ *
+ * Patch: [(src-0, patch-0) = tgt-0; (src-1, patch-1) = tgt-1; (src-2, patch-2) = tgt-2]
+ * Concatenate: [tgt-0 + tgt-1 + tgt-2 = tgt_image]
  */
 
 #include "applypatch/imgdiff.h"
 
 #include <errno.h>
 #include <fcntl.h>
+#include <getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -139,15 +159,26 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/memory.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <bsdiff/bsdiff.h>
 #include <ziparchive/zip_archive.h>
-
-#include <bsdiff.h>
 #include <zlib.h>
 
+#include "applypatch/imgdiff_image.h"
+#include "otautil/rangeset.h"
+
 using android::base::get_unaligned;
 
-static constexpr auto BUFFER_SIZE = 0x8000;
+static constexpr size_t VERSION = 2;
+
+// We assume the header "IMGDIFF#" is 8 bytes.
+static_assert(VERSION <= 9, "VERSION occupies more than one byte");
+
+static constexpr size_t BLOCK_SIZE = 4096;
+static constexpr size_t BUFFER_SIZE = 0x8000;
 
 // If we use this function to write the offset and length (type size_t), their values should not
 // exceed 2^63; because the signed bit will be casted away.
@@ -161,99 +192,80 @@
   return android::base::WriteFully(fd, &value, sizeof(int32_t));
 }
 
-class ImageChunk {
- public:
-  static constexpr auto WINDOWBITS = -15;  // 32kb window; negative to indicate a raw stream.
-  static constexpr auto MEMLEVEL = 8;      // the default value.
-  static constexpr auto METHOD = Z_DEFLATED;
-  static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY;
+// Trim the head or tail to align with the block size. Return false if the chunk has nothing left
+// after alignment.
+static bool AlignHead(size_t* start, size_t* length) {
+  size_t residual = (*start % BLOCK_SIZE == 0) ? 0 : BLOCK_SIZE - *start % BLOCK_SIZE;
 
-  ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content, size_t raw_data_len)
-      : type_(type),
-        start_(start),
-        input_file_ptr_(file_content),
-        raw_data_len_(raw_data_len),
-        compress_level_(6),
-        source_start_(0),
-        source_len_(0),
-        source_uncompressed_len_(0) {
-    CHECK(file_content != nullptr) << "input file container can't be nullptr";
+  if (*length <= residual) {
+    *length = 0;
+    return false;
   }
 
-  int GetType() const {
-    return type_;
-  }
-  size_t GetRawDataLength() const {
-    return raw_data_len_;
-  }
-  const std::string& GetEntryName() const {
-    return entry_name_;
+  // Trim the data in the beginning.
+  *start += residual;
+  *length -= residual;
+  return true;
+}
+
+static bool AlignTail(size_t* start, size_t* length) {
+  size_t residual = (*start + *length) % BLOCK_SIZE;
+  if (*length <= residual) {
+    *length = 0;
+    return false;
   }
 
-  // CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return
-  // the raw data.
-  const uint8_t * DataForPatch() const;
-  size_t DataLengthForPatch() const;
+  // Trim the data in the end.
+  *length -= residual;
+  return true;
+}
 
-  void Dump() const {
-    printf("type %d start %zu len %zu\n", type_, start_, DataLengthForPatch());
+// Remove the used blocks from the source chunk to make sure the source ranges are mutually
+// exclusive after split. Return false if we fail to get the non-overlapped ranges. In such
+// a case, we'll skip the entire source chunk.
+static bool RemoveUsedBlocks(size_t* start, size_t* length, const SortedRangeSet& used_ranges) {
+  if (!used_ranges.Overlaps(*start, *length)) {
+    return true;
   }
 
-  void SetSourceInfo(const ImageChunk& other);
-  void SetEntryName(std::string entryname);
-  void SetUncompressedData(std::vector<uint8_t> data);
-  bool SetBonusData(const std::vector<uint8_t>& bonus_data);
+  // TODO find the largest non-overlap chunk.
+  LOG(INFO) << "Removing block " << used_ranges.ToString() << " from " << *start << " - "
+            << *start + *length - 1;
 
-  bool operator==(const ImageChunk& other) const;
-  bool operator!=(const ImageChunk& other) const {
-    return !(*this == other);
+  // If there's no duplicate entry name, we should only overlap in the head or tail block. Try to
+  // trim both blocks. Skip this source chunk in case it still overlaps with the used ranges.
+  if (AlignHead(start, length) && !used_ranges.Overlaps(*start, *length)) {
+    return true;
+  }
+  if (AlignTail(start, length) && !used_ranges.Overlaps(*start, *length)) {
+    return true;
   }
 
-  size_t GetHeaderSize(size_t patch_size) const;
-  // Return the offset of the next patch into the patch data.
-  size_t WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset);
+  LOG(WARNING) << "Failed to remove the overlapped block ranges; skip the source";
+  return false;
+}
 
-  /*
-   * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob
-   * of uninterpreted data).  The resulting patch will likely be about
-   * as big as the target file, but it lets us handle the case of images
-   * where some gzip chunks are reconstructible but others aren't (by
-   * treating the ones that aren't as normal chunks).
-   */
-  void ChangeDeflateChunkToNormal();
-  bool ChangeChunkToRaw(size_t patch_size);
-
-  /*
-   * Verify that we can reproduce exactly the same compressed data that
-   * we started with.  Sets the level, method, windowBits, memLevel, and
-   * strategy fields in the chunk to the encoding parameters needed to
-   * produce the right output.
-   */
-  bool ReconstructDeflateChunk();
-  bool IsAdjacentNormal(const ImageChunk& other) const;
-  void MergeAdjacentNormal(const ImageChunk& other);
-
- private:
-  int type_;                                    // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
-  size_t start_;                                // offset of chunk in the original input file
-  const std::vector<uint8_t>* input_file_ptr_;  // ptr to the full content of original input file
-  size_t raw_data_len_;
-
-  // --- for CHUNK_DEFLATE chunks only: ---
-  std::vector<uint8_t> uncompressed_data_;
-  std::string entry_name_;  // used for zip entries
-
-  // deflate encoder parameters
-  int compress_level_;
-
-  size_t source_start_;
-  size_t source_len_;
-  size_t source_uncompressed_len_;
-
-  const uint8_t* GetRawData() const;
-  bool TryReconstruction(int level);
+static const struct option OPTIONS[] = {
+  { "zip-mode", no_argument, nullptr, 'z' },
+  { "bonus-file", required_argument, nullptr, 'b' },
+  { "block-limit", required_argument, nullptr, 0 },
+  { "debug-dir", required_argument, nullptr, 0 },
+  { "split-info", required_argument, nullptr, 0 },
+  { "verbose", no_argument, nullptr, 'v' },
+  { nullptr, 0, nullptr, 0 },
 };
 
+ImageChunk::ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content,
+                       size_t raw_data_len, std::string entry_name)
+    : type_(type),
+      start_(start),
+      input_file_ptr_(file_content),
+      raw_data_len_(raw_data_len),
+      compress_level_(6),
+      entry_name_(std::move(entry_name)) {
+  CHECK(file_content != nullptr) << "input file container can't be nullptr";
+}
+
 const uint8_t* ImageChunk::GetRawData() const {
   CHECK_LE(start_ + raw_data_len_, input_file_ptr_->size());
   return input_file_ptr_->data() + start_;
@@ -273,6 +285,11 @@
   return raw_data_len_;
 }
 
+void ImageChunk::Dump(size_t index) const {
+  LOG(INFO) << "chunk: " << index << ", type: " << type_ << ", start: " << start_
+            << ", len: " << DataLengthForPatch() << ", name: " << entry_name_;
+}
+
 bool ImageChunk::operator==(const ImageChunk& other) const {
   if (type_ != other.type_) {
     return false;
@@ -281,20 +298,6 @@
           memcmp(GetRawData(), other.GetRawData(), raw_data_len_) == 0);
 }
 
-void ImageChunk::SetSourceInfo(const ImageChunk& src) {
-  source_start_ = src.start_;
-  if (type_ == CHUNK_NORMAL) {
-    source_len_ = src.raw_data_len_;
-  } else if (type_ == CHUNK_DEFLATE) {
-    source_len_ = src.raw_data_len_;
-    source_uncompressed_len_ = src.uncompressed_data_.size();
-  }
-}
-
-void ImageChunk::SetEntryName(std::string entryname) {
-  entry_name_ = std::move(entryname);
-}
-
 void ImageChunk::SetUncompressedData(std::vector<uint8_t> data) {
   uncompressed_data_ = std::move(data);
 }
@@ -307,80 +310,13 @@
   return true;
 }
 
-// Convert CHUNK_NORMAL & CHUNK_DEFLATE to CHUNK_RAW if the target size is
-// smaller. Also take the header size into account during size comparison.
-bool ImageChunk::ChangeChunkToRaw(size_t patch_size) {
-  if (type_ == CHUNK_RAW) {
-    return true;
-  } else if (type_ == CHUNK_NORMAL && (raw_data_len_ <= 160 || raw_data_len_ < patch_size)) {
-    type_ = CHUNK_RAW;
-    return true;
-  }
-  return false;
-}
-
 void ImageChunk::ChangeDeflateChunkToNormal() {
   if (type_ != CHUNK_DEFLATE) return;
   type_ = CHUNK_NORMAL;
-  entry_name_.clear();
+  // No need to clear the entry name.
   uncompressed_data_.clear();
 }
 
-// Header size:
-// header_type    4 bytes
-// CHUNK_NORMAL   8*3 = 24 bytes
-// CHUNK_DEFLATE  8*5 + 4*5 = 60 bytes
-// CHUNK_RAW      4 bytes + patch_size
-size_t ImageChunk::GetHeaderSize(size_t patch_size) const {
-  switch (type_) {
-    case CHUNK_NORMAL:
-      return 4 + 8 * 3;
-    case CHUNK_DEFLATE:
-      return 4 + 8 * 5 + 4 * 5;
-    case CHUNK_RAW:
-      return 4 + 4 + patch_size;
-    default:
-      CHECK(false) << "unexpected chunk type: " << type_;  // Should not reach here.
-      return 0;
-  }
-}
-
-size_t ImageChunk::WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset) {
-  Write4(fd, type_);
-  switch (type_) {
-    case CHUNK_NORMAL:
-      printf("normal   (%10zu, %10zu)  %10zu\n", start_, raw_data_len_, patch.size());
-      Write8(fd, static_cast<int64_t>(source_start_));
-      Write8(fd, static_cast<int64_t>(source_len_));
-      Write8(fd, static_cast<int64_t>(offset));
-      return offset + patch.size();
-    case CHUNK_DEFLATE:
-      printf("deflate  (%10zu, %10zu)  %10zu  %s\n", start_, raw_data_len_, patch.size(),
-             entry_name_.c_str());
-      Write8(fd, static_cast<int64_t>(source_start_));
-      Write8(fd, static_cast<int64_t>(source_len_));
-      Write8(fd, static_cast<int64_t>(offset));
-      Write8(fd, static_cast<int64_t>(source_uncompressed_len_));
-      Write8(fd, static_cast<int64_t>(uncompressed_data_.size()));
-      Write4(fd, compress_level_);
-      Write4(fd, METHOD);
-      Write4(fd, WINDOWBITS);
-      Write4(fd, MEMLEVEL);
-      Write4(fd, STRATEGY);
-      return offset + patch.size();
-    case CHUNK_RAW:
-      printf("raw      (%10zu, %10zu)\n", start_, raw_data_len_);
-      Write4(fd, static_cast<int32_t>(patch.size()));
-      if (!android::base::WriteFully(fd, patch.data(), patch.size())) {
-        CHECK(false) << "failed to write " << patch.size() <<" bytes patch";
-      }
-      return offset;
-    default:
-      CHECK(false) << "unexpected chunk type: " << type_;
-      return offset;
-  }
-}
-
 bool ImageChunk::IsAdjacentNormal(const ImageChunk& other) const {
   if (type_ != CHUNK_NORMAL || other.type_ != CHUNK_NORMAL) {
     return false;
@@ -393,14 +329,62 @@
   raw_data_len_ = raw_data_len_ + other.raw_data_len_;
 }
 
-bool ImageChunk::ReconstructDeflateChunk() {
-  if (type_ != CHUNK_DEFLATE) {
-    printf("attempt to reconstruct non-deflate chunk\n");
+bool ImageChunk::MakePatch(const ImageChunk& tgt, const ImageChunk& src,
+                           std::vector<uint8_t>* patch_data,
+                           bsdiff::SuffixArrayIndexInterface** bsdiff_cache) {
+#if defined(__ANDROID__)
+  char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX";
+#else
+  char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
+#endif
+
+  int fd = mkstemp(ptemp);
+  if (fd == -1) {
+    PLOG(ERROR) << "MakePatch failed to create a temporary file";
+    return false;
+  }
+  close(fd);
+
+  int r = bsdiff::bsdiff(src.DataForPatch(), src.DataLengthForPatch(), tgt.DataForPatch(),
+                         tgt.DataLengthForPatch(), ptemp, bsdiff_cache);
+  if (r != 0) {
+    LOG(ERROR) << "bsdiff() failed: " << r;
     return false;
   }
 
-  // We only check two combinations of encoder parameters:  level 6
-  // (the default) and level 9 (the maximum).
+  android::base::unique_fd patch_fd(open(ptemp, O_RDONLY));
+  if (patch_fd == -1) {
+    PLOG(ERROR) << "Failed to open " << ptemp;
+    return false;
+  }
+  struct stat st;
+  if (fstat(patch_fd, &st) != 0) {
+    PLOG(ERROR) << "Failed to stat patch file " << ptemp;
+    return false;
+  }
+
+  size_t sz = static_cast<size_t>(st.st_size);
+
+  patch_data->resize(sz);
+  if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) {
+    PLOG(ERROR) << "Failed to read " << ptemp;
+    unlink(ptemp);
+    return false;
+  }
+
+  unlink(ptemp);
+
+  return true;
+}
+
+bool ImageChunk::ReconstructDeflateChunk() {
+  if (type_ != CHUNK_DEFLATE) {
+    LOG(ERROR) << "Attempted to reconstruct non-deflate chunk";
+    return false;
+  }
+
+  // We only check two combinations of encoder parameters:  level 6 (the default) and level 9
+  // (the maximum).
   for (int level = 6; level <= 9; level += 3) {
     if (TryReconstruction(level)) {
       compress_level_ = level;
@@ -412,10 +396,9 @@
 }
 
 /*
- * Takes the uncompressed data stored in the chunk, compresses it
- * using the zlib parameters stored in the chunk, and checks that it
- * matches exactly the compressed data we started with (also stored in
- * the chunk).
+ * Takes the uncompressed data stored in the chunk, compresses it using the zlib parameters stored
+ * in the chunk, and checks that it matches exactly the compressed data we started with (also
+ * stored in the chunk).
  */
 bool ImageChunk::TryReconstruction(int level) {
   z_stream strm;
@@ -426,7 +409,7 @@
   strm.next_in = uncompressed_data_.data();
   int ret = deflateInit2(&strm, level, METHOD, WINDOWBITS, MEMLEVEL, STRATEGY);
   if (ret < 0) {
-    printf("failed to initialize deflate: %d\n", ret);
+    LOG(ERROR) << "Failed to initialize deflate: " << ret;
     return false;
   }
 
@@ -437,7 +420,7 @@
     strm.next_out = buffer.data();
     ret = deflate(&strm, Z_FINISH);
     if (ret < 0) {
-      printf("failed to deflate: %d\n", ret);
+      LOG(ERROR) << "Failed to deflate: " << ret;
       return false;
     }
 
@@ -458,29 +441,381 @@
   return true;
 }
 
+PatchChunk::PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector<uint8_t> data)
+    : type_(tgt.GetType()),
+      source_start_(src.GetStartOffset()),
+      source_len_(src.GetRawDataLength()),
+      source_uncompressed_len_(src.DataLengthForPatch()),
+      target_start_(tgt.GetStartOffset()),
+      target_len_(tgt.GetRawDataLength()),
+      target_uncompressed_len_(tgt.DataLengthForPatch()),
+      target_compress_level_(tgt.GetCompressLevel()),
+      data_(std::move(data)) {}
+
+// Construct a CHUNK_RAW patch from the target data directly.
+PatchChunk::PatchChunk(const ImageChunk& tgt)
+    : type_(CHUNK_RAW),
+      source_start_(0),
+      source_len_(0),
+      source_uncompressed_len_(0),
+      target_start_(tgt.GetStartOffset()),
+      target_len_(tgt.GetRawDataLength()),
+      target_uncompressed_len_(tgt.DataLengthForPatch()),
+      target_compress_level_(tgt.GetCompressLevel()),
+      data_(tgt.DataForPatch(), tgt.DataForPatch() + tgt.DataLengthForPatch()) {}
+
+// 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));
+}
+
+void PatchChunk::UpdateSourceOffset(const SortedRangeSet& src_range) {
+  if (type_ == CHUNK_DEFLATE) {
+    source_start_ = src_range.GetOffsetInRangeSet(source_start_);
+  }
+}
+
+// Header size:
+// header_type    4 bytes
+// CHUNK_NORMAL   8*3 = 24 bytes
+// CHUNK_DEFLATE  8*5 + 4*5 = 60 bytes
+// CHUNK_RAW      4 bytes + patch_size
+size_t PatchChunk::GetHeaderSize() const {
+  switch (type_) {
+    case CHUNK_NORMAL:
+      return 4 + 8 * 3;
+    case CHUNK_DEFLATE:
+      return 4 + 8 * 5 + 4 * 5;
+    case CHUNK_RAW:
+      return 4 + 4 + data_.size();
+    default:
+      CHECK(false) << "unexpected chunk type: " << type_;  // Should not reach here.
+      return 0;
+  }
+}
+
+// Return the offset of the next patch into the patch data.
+size_t PatchChunk::WriteHeaderToFd(int fd, size_t offset, size_t index) const {
+  Write4(fd, type_);
+  switch (type_) {
+    case CHUNK_NORMAL:
+      LOG(INFO) << android::base::StringPrintf("chunk %zu: normal   (%10zu, %10zu)  %10zu", index,
+                                               target_start_, target_len_, data_.size());
+      Write8(fd, static_cast<int64_t>(source_start_));
+      Write8(fd, static_cast<int64_t>(source_len_));
+      Write8(fd, static_cast<int64_t>(offset));
+      return offset + data_.size();
+    case CHUNK_DEFLATE:
+      LOG(INFO) << android::base::StringPrintf("chunk %zu: deflate  (%10zu, %10zu)  %10zu", index,
+                                               target_start_, target_len_, data_.size());
+      Write8(fd, static_cast<int64_t>(source_start_));
+      Write8(fd, static_cast<int64_t>(source_len_));
+      Write8(fd, static_cast<int64_t>(offset));
+      Write8(fd, static_cast<int64_t>(source_uncompressed_len_));
+      Write8(fd, static_cast<int64_t>(target_uncompressed_len_));
+      Write4(fd, target_compress_level_);
+      Write4(fd, ImageChunk::METHOD);
+      Write4(fd, ImageChunk::WINDOWBITS);
+      Write4(fd, ImageChunk::MEMLEVEL);
+      Write4(fd, ImageChunk::STRATEGY);
+      return offset + data_.size();
+    case CHUNK_RAW:
+      LOG(INFO) << android::base::StringPrintf("chunk %zu: raw      (%10zu, %10zu)", index,
+                                               target_start_, target_len_);
+      Write4(fd, static_cast<int32_t>(data_.size()));
+      if (!android::base::WriteFully(fd, data_.data(), data_.size())) {
+        CHECK(false) << "Failed to write " << data_.size() << " bytes patch";
+      }
+      return offset;
+    default:
+      CHECK(false) << "unexpected chunk type: " << type_;
+      return offset;
+  }
+}
+
+size_t PatchChunk::PatchSize() const {
+  if (type_ == CHUNK_RAW) {
+    return GetHeaderSize();
+  }
+  return GetHeaderSize() + data_.size();
+}
+
+// Write the contents of |patch_chunks| to |patch_fd|.
+bool PatchChunk::WritePatchDataToFd(const std::vector<PatchChunk>& patch_chunks, int patch_fd) {
+  // Figure out how big the imgdiff file header is going to be, so that we can correctly compute
+  // the offset of each bsdiff patch within the file.
+  size_t total_header_size = 12;
+  for (const auto& patch : patch_chunks) {
+    total_header_size += patch.GetHeaderSize();
+  }
+
+  size_t offset = total_header_size;
+
+  // Write out the headers.
+  if (!android::base::WriteStringToFd("IMGDIFF" + std::to_string(VERSION), patch_fd)) {
+    PLOG(ERROR) << "Failed to write \"IMGDIFF" << VERSION << "\"";
+    return false;
+  }
+
+  Write4(patch_fd, static_cast<int32_t>(patch_chunks.size()));
+  LOG(INFO) << "Writing " << patch_chunks.size() << " patch headers...";
+  for (size_t i = 0; i < patch_chunks.size(); ++i) {
+    offset = patch_chunks[i].WriteHeaderToFd(patch_fd, offset, i);
+  }
+
+  // Append each chunk's bsdiff patch, in order.
+  for (const auto& patch : patch_chunks) {
+    if (patch.type_ == CHUNK_RAW) {
+      continue;
+    }
+    if (!android::base::WriteFully(patch_fd, patch.data_.data(), patch.data_.size())) {
+      PLOG(ERROR) << "Failed to write " << patch.data_.size() << " bytes patch to patch_fd";
+      return false;
+    }
+  }
+
+  return true;
+}
+
+ImageChunk& Image::operator[](size_t i) {
+  CHECK_LT(i, chunks_.size());
+  return chunks_[i];
+}
+
+const ImageChunk& Image::operator[](size_t i) const {
+  CHECK_LT(i, chunks_.size());
+  return chunks_[i];
+}
+
+void Image::MergeAdjacentNormalChunks() {
+  size_t merged_last = 0, cur = 0;
+  while (cur < chunks_.size()) {
+    // Look for normal chunks adjacent to the current one. If such chunk exists, extend the
+    // length of the current normal chunk.
+    size_t to_check = cur + 1;
+    while (to_check < chunks_.size() && chunks_[cur].IsAdjacentNormal(chunks_[to_check])) {
+      chunks_[cur].MergeAdjacentNormal(chunks_[to_check]);
+      to_check++;
+    }
+
+    if (merged_last != cur) {
+      chunks_[merged_last] = std::move(chunks_[cur]);
+    }
+    merged_last++;
+    cur = to_check;
+  }
+  if (merged_last < chunks_.size()) {
+    chunks_.erase(chunks_.begin() + merged_last, chunks_.end());
+  }
+}
+
+void Image::DumpChunks() const {
+  std::string type = is_source_ ? "source" : "target";
+  LOG(INFO) << "Dumping chunks for " << type;
+  for (size_t i = 0; i < chunks_.size(); ++i) {
+    chunks_[i].Dump(i);
+  }
+}
+
+bool Image::ReadFile(const std::string& filename, std::vector<uint8_t>* file_content) {
+  CHECK(file_content != nullptr);
+
+  android::base::unique_fd fd(open(filename.c_str(), O_RDONLY));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open " << filename;
+    return false;
+  }
+  struct stat st;
+  if (fstat(fd, &st) != 0) {
+    PLOG(ERROR) << "Failed to stat " << filename;
+    return false;
+  }
+
+  size_t sz = static_cast<size_t>(st.st_size);
+  file_content->resize(sz);
+  if (!android::base::ReadFully(fd, file_content->data(), sz)) {
+    PLOG(ERROR) << "Failed to read " << filename;
+    return false;
+  }
+  fd.reset();
+
+  return true;
+}
+
+bool ZipModeImage::Initialize(const std::string& filename) {
+  if (!ReadFile(filename, &file_content_)) {
+    return false;
+  }
+
+  // Omit the trailing zeros before we pass the file to ziparchive handler.
+  size_t zipfile_size;
+  if (!GetZipFileSize(&zipfile_size)) {
+    LOG(ERROR) << "Failed to parse the actual size of " << filename;
+    return false;
+  }
+  ZipArchiveHandle handle;
+  int err = OpenArchiveFromMemory(const_cast<uint8_t*>(file_content_.data()), zipfile_size,
+                                  filename.c_str(), &handle);
+  if (err != 0) {
+    LOG(ERROR) << "Failed to open zip file " << filename << ": " << ErrorCodeString(err);
+    CloseArchive(handle);
+    return false;
+  }
+
+  if (!InitializeChunks(filename, handle)) {
+    CloseArchive(handle);
+    return false;
+  }
+
+  CloseArchive(handle);
+  return true;
+}
+
+// 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);
+  if (ret != 0) {
+    LOG(ERROR) << "Failed to iterate over entries in " << filename << ": " << ErrorCodeString(ret);
+    return false;
+  }
+
+  // Create a list of deflated zip entries, sorted by offset.
+  std::vector<std::pair<std::string, ZipEntry>> temp_entries;
+  ZipString name;
+  ZipEntry entry;
+  while ((ret = Next(cookie, &entry, &name)) == 0) {
+    if (entry.method == kCompressDeflated || limit_ > 0) {
+      std::string entry_name(name.name, name.name + name.name_length);
+      temp_entries.emplace_back(entry_name, entry);
+    }
+  }
+
+  if (ret != -1) {
+    LOG(ERROR) << "Error while iterating over zip entries: " << ErrorCodeString(ret);
+    return false;
+  }
+  std::sort(temp_entries.begin(), temp_entries.end(),
+            [](auto& entry1, auto& entry2) { return entry1.second.offset < entry2.second.offset; });
+
+  EndIteration(cookie);
+
+  // For source chunks, we don't need to compose chunks for the metadata.
+  if (is_source_) {
+    for (auto& entry : temp_entries) {
+      if (!AddZipEntryToChunks(handle, entry.first, &entry.second)) {
+        LOG(ERROR) << "Failed to add " << entry.first << " to source chunks";
+        return false;
+      }
+    }
+
+    // Add the end of zip file (mainly central directory) as a normal chunk.
+    size_t entries_end = 0;
+    if (!temp_entries.empty()) {
+      entries_end = static_cast<size_t>(temp_entries.back().second.offset +
+                                        temp_entries.back().second.compressed_length);
+    }
+    CHECK_LT(entries_end, file_content_.size());
+    chunks_.emplace_back(CHUNK_NORMAL, entries_end, &file_content_,
+                         file_content_.size() - entries_end);
+
+    return true;
+  }
+
+  // For target chunks, add the deflate entries as CHUNK_DEFLATE and the contents between two
+  // deflate entries as CHUNK_NORMAL.
+  size_t pos = 0;
+  size_t nextentry = 0;
+  while (pos < file_content_.size()) {
+    if (nextentry < temp_entries.size() &&
+        static_cast<off64_t>(pos) == temp_entries[nextentry].second.offset) {
+      // Add the next zip entry.
+      std::string entry_name = temp_entries[nextentry].first;
+      if (!AddZipEntryToChunks(handle, entry_name, &temp_entries[nextentry].second)) {
+        LOG(ERROR) << "Failed to add " << entry_name << " to target chunks";
+        return false;
+      }
+
+      pos += temp_entries[nextentry].second.compressed_length;
+      ++nextentry;
+      continue;
+    }
+
+    // Use a normal chunk to take all the data up to the start of the next entry.
+    size_t raw_data_len;
+    if (nextentry < temp_entries.size()) {
+      raw_data_len = temp_entries[nextentry].second.offset - pos;
+    } else {
+      raw_data_len = file_content_.size() - pos;
+    }
+    chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, raw_data_len);
+
+    pos += raw_data_len;
+  }
+
+  return true;
+}
+
+bool ZipModeImage::AddZipEntryToChunks(ZipArchiveHandle handle, const std::string& entry_name,
+                                       ZipEntry* entry) {
+  size_t compressed_len = entry->compressed_length;
+  if (compressed_len == 0) return true;
+
+  // Split the entry into several normal chunks if it's too large.
+  if (limit_ > 0 && compressed_len > limit_) {
+    int count = 0;
+    while (compressed_len > 0) {
+      size_t length = std::min(limit_, compressed_len);
+      std::string name = entry_name + "-" + std::to_string(count);
+      chunks_.emplace_back(CHUNK_NORMAL, entry->offset + limit_ * count, &file_content_, length,
+                           name);
+
+      count++;
+      compressed_len -= length;
+    }
+  } else if (entry->method == kCompressDeflated) {
+    size_t uncompressed_len = entry->uncompressed_length;
+    std::vector<uint8_t> uncompressed_data(uncompressed_len);
+    int ret = ExtractToMemory(handle, entry, uncompressed_data.data(), uncompressed_len);
+    if (ret != 0) {
+      LOG(ERROR) << "Failed to extract " << entry_name << " with size " << uncompressed_len << ": "
+                 << ErrorCodeString(ret);
+      return false;
+    }
+    ImageChunk curr(CHUNK_DEFLATE, entry->offset, &file_content_, compressed_len, entry_name);
+    curr.SetUncompressedData(std::move(uncompressed_data));
+    chunks_.push_back(std::move(curr));
+  } else {
+    chunks_.emplace_back(CHUNK_NORMAL, entry->offset, &file_content_, compressed_len, entry_name);
+  }
+
+  return true;
+}
+
 // EOCD record
 // offset 0: signature 0x06054b50, 4 bytes
 // offset 4: number of this disk, 2 bytes
 // ...
 // offset 20: comment length, 2 bytes
 // offset 22: comment, n bytes
-static bool GetZipFileSize(const std::vector<uint8_t>& zip_file, size_t* input_file_size) {
-  if (zip_file.size() < 22) {
-    printf("file is too small to be a zip file\n");
+bool ZipModeImage::GetZipFileSize(size_t* input_file_size) {
+  if (file_content_.size() < 22) {
+    LOG(ERROR) << "File is too small to be a zip file";
     return false;
   }
 
   // Look for End of central directory record of the zip file, and calculate the actual
   // zip_file size.
-  for (int i = zip_file.size() - 22; i >= 0; i--) {
-    if (zip_file[i] == 0x50) {
-      if (get_unaligned<uint32_t>(&zip_file[i]) == 0x06054b50) {
+  for (int i = file_content_.size() - 22; i >= 0; i--) {
+    if (file_content_[i] == 0x50) {
+      if (get_unaligned<uint32_t>(&file_content_[i]) == 0x06054b50) {
         // double-check: this archive consists of a single "disk".
-        CHECK_EQ(get_unaligned<uint16_t>(&zip_file[i + 4]), 0);
+        CHECK_EQ(get_unaligned<uint16_t>(&file_content_[i + 4]), 0);
 
-        uint16_t comment_length = get_unaligned<uint16_t>(&zip_file[i + 20]);
+        uint16_t comment_length = get_unaligned<uint16_t>(&file_content_[i + 20]);
         size_t file_size = i + 22 + comment_length;
-        CHECK_LE(file_size, zip_file.size());
+        CHECK_LE(file_size, file_content_.size());
         *input_file_size = file_size;
         return true;
       }
@@ -491,162 +826,445 @@
   return false;
 }
 
-static bool ReadZip(const char* filename, std::vector<ImageChunk>* chunks,
-                    std::vector<uint8_t>* zip_file, bool include_pseudo_chunk) {
-  CHECK(chunks != nullptr && zip_file != nullptr);
+ImageChunk ZipModeImage::PseudoSource() const {
+  CHECK(is_source_);
+  return ImageChunk(CHUNK_NORMAL, 0, &file_content_, file_content_.size());
+}
 
-  android::base::unique_fd fd(open(filename, O_RDONLY));
-  if (fd == -1) {
-    printf("failed to open \"%s\" %s\n", filename, strerror(errno));
-    return false;
+const ImageChunk* ZipModeImage::FindChunkByName(const std::string& name, bool find_normal) const {
+  if (name.empty()) {
+    return nullptr;
   }
-  struct stat st;
-  if (fstat(fd, &st) != 0) {
-    printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
-    return false;
-  }
-
-  size_t sz = static_cast<size_t>(st.st_size);
-  zip_file->resize(sz);
-  if (!android::base::ReadFully(fd, zip_file->data(), sz)) {
-    printf("failed to read \"%s\" %s\n", filename, strerror(errno));
-    return false;
-  }
-  fd.reset();
-
-  // Trim the trailing zeros before we pass the file to ziparchive handler.
-  size_t zipfile_size;
-  if (!GetZipFileSize(*zip_file, &zipfile_size)) {
-    printf("failed to parse the actual size of %s\n", filename);
-    return false;
-  }
-  ZipArchiveHandle handle;
-  int err = OpenArchiveFromMemory(zip_file->data(), zipfile_size, filename, &handle);
-  if (err != 0) {
-    printf("failed to open zip file %s: %s\n", filename, ErrorCodeString(err));
-    CloseArchive(handle);
-    return false;
-  }
-
-  // Create a list of deflated zip entries, sorted by offset.
-  std::vector<std::pair<std::string, ZipEntry>> temp_entries;
-  void* cookie;
-  int ret = StartIteration(handle, &cookie, nullptr, nullptr);
-  if (ret != 0) {
-    printf("failed to iterate over entries in %s: %s\n", filename, ErrorCodeString(ret));
-    CloseArchive(handle);
-    return false;
-  }
-
-  ZipString name;
-  ZipEntry entry;
-  while ((ret = Next(cookie, &entry, &name)) == 0) {
-    if (entry.method == kCompressDeflated) {
-      std::string entryname(name.name, name.name + name.name_length);
-      temp_entries.push_back(std::make_pair(entryname, entry));
-    }
-  }
-
-  if (ret != -1) {
-    printf("Error while iterating over zip entries: %s\n", ErrorCodeString(ret));
-    CloseArchive(handle);
-    return false;
-  }
-  std::sort(temp_entries.begin(), temp_entries.end(),
-            [](auto& entry1, auto& entry2) {
-              return entry1.second.offset < entry2.second.offset;
-            });
-
-  EndIteration(cookie);
-
-  if (include_pseudo_chunk) {
-    chunks->emplace_back(CHUNK_NORMAL, 0, zip_file, zip_file->size());
-  }
-
-  size_t pos = 0;
-  size_t nextentry = 0;
-  while (pos < zip_file->size()) {
-    if (nextentry < temp_entries.size() &&
-        static_cast<off64_t>(pos) == temp_entries[nextentry].second.offset) {
-      // compose the next deflate chunk.
-      std::string entryname = temp_entries[nextentry].first;
-      size_t uncompressed_len = temp_entries[nextentry].second.uncompressed_length;
-      std::vector<uint8_t> uncompressed_data(uncompressed_len);
-      if ((ret = ExtractToMemory(handle, &temp_entries[nextentry].second, uncompressed_data.data(),
-                                 uncompressed_len)) != 0) {
-        printf("failed to extract %s with size %zu: %s\n", entryname.c_str(), uncompressed_len,
-               ErrorCodeString(ret));
-        CloseArchive(handle);
-        return false;
-      }
-
-      size_t compressed_len = temp_entries[nextentry].second.compressed_length;
-      ImageChunk curr(CHUNK_DEFLATE, pos, zip_file, compressed_len);
-      curr.SetEntryName(std::move(entryname));
-      curr.SetUncompressedData(std::move(uncompressed_data));
-      chunks->push_back(curr);
-
-      pos += compressed_len;
-      ++nextentry;
+  for (auto& chunk : chunks_) {
+    if (chunk.GetType() != CHUNK_DEFLATE && !find_normal) {
       continue;
     }
 
-    // Use a normal chunk to take all the data up to the start of the next deflate section.
-    size_t raw_data_len;
-    if (nextentry < temp_entries.size()) {
-      raw_data_len = temp_entries[nextentry].second.offset - pos;
-    } else {
-      raw_data_len = zip_file->size() - pos;
+    if (chunk.GetEntryName() == name) {
+      return &chunk;
     }
-    chunks->emplace_back(CHUNK_NORMAL, pos, zip_file, raw_data_len);
 
-    pos += raw_data_len;
+    // Edge case when target chunk is split due to size limit but source chunk isn't.
+    if (name == (chunk.GetEntryName() + "-0") || chunk.GetEntryName() == (name + "-0")) {
+      return &chunk;
+    }
+
+    // TODO handle the .so files with incremental version number.
+    // (e.g. lib/arm64-v8a/libcronet.59.0.3050.4.so)
   }
 
-  CloseArchive(handle);
+  return nullptr;
+}
+
+ImageChunk* ZipModeImage::FindChunkByName(const std::string& name, bool find_normal) {
+  return const_cast<ImageChunk*>(
+      static_cast<const ZipModeImage*>(this)->FindChunkByName(name, find_normal));
+}
+
+bool ZipModeImage::CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image) {
+  for (auto& tgt_chunk : *tgt_image) {
+    if (tgt_chunk.GetType() != CHUNK_DEFLATE) {
+      continue;
+    }
+
+    ImageChunk* src_chunk = src_image->FindChunkByName(tgt_chunk.GetEntryName());
+    if (src_chunk == nullptr) {
+      tgt_chunk.ChangeDeflateChunkToNormal();
+    } else if (tgt_chunk == *src_chunk) {
+      // If two deflate chunks are identical (eg, the kernel has not changed between two builds),
+      // treat them as normal chunks. This makes applypatch much faster -- it can apply a trivial
+      // patch to the compressed data, rather than uncompressing and recompressing to apply the
+      // trivial patch to the uncompressed data.
+      tgt_chunk.ChangeDeflateChunkToNormal();
+      src_chunk->ChangeDeflateChunkToNormal();
+    } else if (!tgt_chunk.ReconstructDeflateChunk()) {
+      // We cannot recompress the data and get exactly the same bits as are in the input target
+      // image. Treat the chunk as a normal non-deflated chunk.
+      LOG(WARNING) << "Failed to reconstruct target deflate chunk [" << tgt_chunk.GetEntryName()
+                   << "]; treating as normal";
+
+      tgt_chunk.ChangeDeflateChunkToNormal();
+      src_chunk->ChangeDeflateChunkToNormal();
+    }
+  }
+
+  // For zips, we only need merge normal chunks for the target:  deflated chunks are matched via
+  // filename, and normal chunks are patched using the entire source file as the source.
+  if (tgt_image->limit_ == 0) {
+    tgt_image->MergeAdjacentNormalChunks();
+    tgt_image->DumpChunks();
+  }
+
   return true;
 }
 
-// Read the given file and break it up into chunks, and putting the data in to a vector.
-static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
-                      std::vector<uint8_t>* img) {
-  CHECK(chunks != nullptr && img != nullptr);
+// For each target chunk, look for the corresponding source chunk by the zip_entry name. If
+// found, add the range of this chunk in the original source file to the block aligned source
+// ranges. Construct the split src & tgt image once the size of source range reaches limit.
+bool ZipModeImage::SplitZipModeImageWithLimit(const ZipModeImage& tgt_image,
+                                              const ZipModeImage& src_image,
+                                              std::vector<ZipModeImage>* split_tgt_images,
+                                              std::vector<ZipModeImage>* split_src_images,
+                                              std::vector<SortedRangeSet>* split_src_ranges) {
+  CHECK_EQ(tgt_image.limit_, src_image.limit_);
+  size_t limit = tgt_image.limit_;
 
-  android::base::unique_fd fd(open(filename, O_RDONLY));
-  if (fd == -1) {
-    printf("failed to open \"%s\" %s\n", filename, strerror(errno));
-    return false;
+  src_image.DumpChunks();
+  LOG(INFO) << "Splitting " << tgt_image.NumOfChunks() << " tgt chunks...";
+
+  SortedRangeSet used_src_ranges;  // ranges used for previous split source images.
+
+  // Reserve the central directory in advance for the last split image.
+  const auto& central_directory = src_image.cend() - 1;
+  CHECK_EQ(CHUNK_NORMAL, central_directory->GetType());
+  used_src_ranges.Insert(central_directory->GetStartOffset(),
+                         central_directory->DataLengthForPatch());
+
+  SortedRangeSet src_ranges;
+  std::vector<ImageChunk> split_src_chunks;
+  std::vector<ImageChunk> split_tgt_chunks;
+  for (auto tgt = tgt_image.cbegin(); tgt != tgt_image.cend(); tgt++) {
+    const ImageChunk* src = src_image.FindChunkByName(tgt->GetEntryName(), true);
+    if (src == nullptr) {
+      split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_,
+                                    tgt->GetRawDataLength());
+      continue;
+    }
+
+    size_t src_offset = src->GetStartOffset();
+    size_t src_length = src->GetRawDataLength();
+
+    CHECK(src_length > 0);
+    CHECK_LE(src_length, limit);
+
+    // Make sure this source range hasn't been used before so that the src_range pieces don't
+    // overlap with each other.
+    if (!RemoveUsedBlocks(&src_offset, &src_length, used_src_ranges)) {
+      split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_,
+                                    tgt->GetRawDataLength());
+    } else if (src_ranges.blocks() * BLOCK_SIZE + src_length <= limit) {
+      src_ranges.Insert(src_offset, src_length);
+
+      // Add the deflate source chunk if it hasn't been aligned.
+      if (src->GetType() == CHUNK_DEFLATE && src_length == src->GetRawDataLength()) {
+        split_src_chunks.push_back(*src);
+        split_tgt_chunks.push_back(*tgt);
+      } else {
+        // TODO split smarter to avoid alignment of large deflate chunks
+        split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_,
+                                      tgt->GetRawDataLength());
+      }
+    } else {
+      bool added_image = ZipModeImage::AddSplitImageFromChunkList(
+          tgt_image, src_image, src_ranges, split_tgt_chunks, split_src_chunks, split_tgt_images,
+          split_src_images);
+
+      split_tgt_chunks.clear();
+      split_src_chunks.clear();
+      // No need to update the split_src_ranges if we don't update the split source images.
+      if (added_image) {
+        used_src_ranges.Insert(src_ranges);
+        split_src_ranges->push_back(std::move(src_ranges));
+      }
+      src_ranges.Clear();
+
+      // We don't have enough space for the current chunk; start a new split image and handle
+      // this chunk there.
+      tgt--;
+    }
   }
-  struct stat st;
-  if (fstat(fd, &st) != 0) {
-    printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
+
+  // TODO Trim it in case the CD exceeds limit too much.
+  src_ranges.Insert(central_directory->GetStartOffset(), central_directory->DataLengthForPatch());
+  bool added_image = ZipModeImage::AddSplitImageFromChunkList(tgt_image, src_image, src_ranges,
+                                                              split_tgt_chunks, split_src_chunks,
+                                                              split_tgt_images, split_src_images);
+  if (added_image) {
+    split_src_ranges->push_back(std::move(src_ranges));
+  }
+
+  ValidateSplitImages(*split_tgt_images, *split_src_images, *split_src_ranges,
+                      tgt_image.file_content_.size());
+
+  return true;
+}
+
+bool ZipModeImage::AddSplitImageFromChunkList(const ZipModeImage& tgt_image,
+                                              const ZipModeImage& src_image,
+                                              const SortedRangeSet& split_src_ranges,
+                                              const std::vector<ImageChunk>& split_tgt_chunks,
+                                              const std::vector<ImageChunk>& split_src_chunks,
+                                              std::vector<ZipModeImage>* split_tgt_images,
+                                              std::vector<ZipModeImage>* split_src_images) {
+  CHECK(!split_tgt_chunks.empty());
+
+  std::vector<ImageChunk> aligned_tgt_chunks;
+
+  // Align the target chunks in the beginning with BLOCK_SIZE.
+  size_t i = 0;
+  while (i < split_tgt_chunks.size()) {
+    size_t tgt_start = split_tgt_chunks[i].GetStartOffset();
+    size_t tgt_length = split_tgt_chunks[i].GetRawDataLength();
+
+    // Current ImageChunk is long enough to align.
+    if (AlignHead(&tgt_start, &tgt_length)) {
+      aligned_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt_start, &tgt_image.file_content_,
+                                      tgt_length);
+      break;
+    }
+
+    i++;
+  }
+
+  // Nothing left after alignment in the current split tgt chunks; skip adding the split_tgt_image.
+  if (i == split_tgt_chunks.size()) {
     return false;
   }
 
-  size_t sz = static_cast<size_t>(st.st_size);
-  img->resize(sz);
-  if (!android::base::ReadFully(fd, img->data(), sz)) {
-    printf("failed to read \"%s\" %s\n", filename, strerror(errno));
+  aligned_tgt_chunks.insert(aligned_tgt_chunks.end(), split_tgt_chunks.begin() + i + 1,
+                            split_tgt_chunks.end());
+  CHECK(!aligned_tgt_chunks.empty());
+
+  // Add a normal chunk to align the contents in the end.
+  size_t end_offset =
+      aligned_tgt_chunks.back().GetStartOffset() + aligned_tgt_chunks.back().GetRawDataLength();
+  if (end_offset % BLOCK_SIZE != 0 && end_offset < tgt_image.file_content_.size()) {
+    size_t tail_block_length = std::min<size_t>(tgt_image.file_content_.size() - end_offset,
+                                                BLOCK_SIZE - (end_offset % BLOCK_SIZE));
+    aligned_tgt_chunks.emplace_back(CHUNK_NORMAL, end_offset, &tgt_image.file_content_,
+                                    tail_block_length);
+  }
+
+  ZipModeImage split_tgt_image(false);
+  split_tgt_image.Initialize(std::move(aligned_tgt_chunks), {});
+  split_tgt_image.MergeAdjacentNormalChunks();
+
+  // Construct the dummy source file based on the src_ranges.
+  std::vector<uint8_t> src_content;
+  for (const auto& r : split_src_ranges) {
+    size_t end = std::min(src_image.file_content_.size(), r.second * BLOCK_SIZE);
+    src_content.insert(src_content.end(), src_image.file_content_.begin() + r.first * BLOCK_SIZE,
+                       src_image.file_content_.begin() + end);
+  }
+
+  // We should not have an empty src in our design; otherwise we will encounter an error in
+  // bsdiff since src_content.data() == nullptr.
+  CHECK(!src_content.empty());
+
+  ZipModeImage split_src_image(true);
+  split_src_image.Initialize(split_src_chunks, std::move(src_content));
+
+  split_tgt_images->push_back(std::move(split_tgt_image));
+  split_src_images->push_back(std::move(split_src_image));
+
+  return true;
+}
+
+void ZipModeImage::ValidateSplitImages(const std::vector<ZipModeImage>& split_tgt_images,
+                                       const std::vector<ZipModeImage>& split_src_images,
+                                       std::vector<SortedRangeSet>& split_src_ranges,
+                                       size_t total_tgt_size) {
+  CHECK_EQ(split_tgt_images.size(), split_src_images.size());
+
+  LOG(INFO) << "Validating " << split_tgt_images.size() << " images";
+
+  // Verify that the target image pieces is continuous and can add up to the total size.
+  size_t last_offset = 0;
+  for (const auto& tgt_image : split_tgt_images) {
+    CHECK(!tgt_image.chunks_.empty());
+
+    CHECK_EQ(last_offset, tgt_image.chunks_.front().GetStartOffset());
+    CHECK(last_offset % BLOCK_SIZE == 0);
+
+    // Check the target chunks within the split image are continuous.
+    for (const auto& chunk : tgt_image.chunks_) {
+      CHECK_EQ(last_offset, chunk.GetStartOffset());
+      last_offset += chunk.GetRawDataLength();
+    }
+  }
+  CHECK_EQ(total_tgt_size, last_offset);
+
+  // Verify that the source ranges are mutually exclusive.
+  CHECK_EQ(split_src_images.size(), split_src_ranges.size());
+  SortedRangeSet used_src_ranges;
+  for (size_t i = 0; i < split_src_ranges.size(); i++) {
+    CHECK(!used_src_ranges.Overlaps(split_src_ranges[i]))
+        << "src range " << split_src_ranges[i].ToString() << " overlaps "
+        << used_src_ranges.ToString();
+    used_src_ranges.Insert(split_src_ranges[i]);
+  }
+}
+
+bool ZipModeImage::GeneratePatchesInternal(const ZipModeImage& tgt_image,
+                                           const ZipModeImage& src_image,
+                                           std::vector<PatchChunk>* patch_chunks) {
+  LOG(INFO) << "Constructing patches for " << tgt_image.NumOfChunks() << " chunks...";
+  patch_chunks->clear();
+
+  bsdiff::SuffixArrayIndexInterface* bsdiff_cache = nullptr;
+  for (size_t i = 0; i < tgt_image.NumOfChunks(); i++) {
+    const auto& tgt_chunk = tgt_image[i];
+
+    if (PatchChunk::RawDataIsSmaller(tgt_chunk, 0)) {
+      patch_chunks->emplace_back(tgt_chunk);
+      continue;
+    }
+
+    const ImageChunk* src_chunk = (tgt_chunk.GetType() != CHUNK_DEFLATE)
+                                      ? nullptr
+                                      : src_image.FindChunkByName(tgt_chunk.GetEntryName());
+
+    const auto& src_ref = (src_chunk == nullptr) ? src_image.PseudoSource() : *src_chunk;
+    bsdiff::SuffixArrayIndexInterface** bsdiff_cache_ptr =
+        (src_chunk == nullptr) ? &bsdiff_cache : nullptr;
+
+    std::vector<uint8_t> patch_data;
+    if (!ImageChunk::MakePatch(tgt_chunk, src_ref, &patch_data, bsdiff_cache_ptr)) {
+      LOG(ERROR) << "Failed to generate patch, name: " << tgt_chunk.GetEntryName();
+      return false;
+    }
+
+    LOG(INFO) << "patch " << i << " is " << patch_data.size() << " bytes (of "
+              << tgt_chunk.GetRawDataLength() << ")";
+
+    if (PatchChunk::RawDataIsSmaller(tgt_chunk, patch_data.size())) {
+      patch_chunks->emplace_back(tgt_chunk);
+    } else {
+      patch_chunks->emplace_back(tgt_chunk, src_ref, std::move(patch_data));
+    }
+  }
+  delete bsdiff_cache;
+
+  CHECK_EQ(patch_chunks->size(), tgt_image.NumOfChunks());
+  return true;
+}
+
+bool ZipModeImage::GeneratePatches(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
+                                   const std::string& patch_name) {
+  std::vector<PatchChunk> patch_chunks;
+
+  ZipModeImage::GeneratePatchesInternal(tgt_image, src_image, &patch_chunks);
+
+  CHECK_EQ(tgt_image.NumOfChunks(), patch_chunks.size());
+
+  android::base::unique_fd patch_fd(
+      open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+  if (patch_fd == -1) {
+    PLOG(ERROR) << "Failed to open " << patch_name;
     return false;
   }
 
+  return PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd);
+}
+
+bool ZipModeImage::GeneratePatches(const std::vector<ZipModeImage>& split_tgt_images,
+                                   const std::vector<ZipModeImage>& split_src_images,
+                                   const std::vector<SortedRangeSet>& split_src_ranges,
+                                   const std::string& patch_name,
+                                   const std::string& split_info_file,
+                                   const std::string& debug_dir) {
+  LOG(INFO) << "Constructing patches for " << split_tgt_images.size() << " split images...";
+
+  android::base::unique_fd patch_fd(
+      open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+  if (patch_fd == -1) {
+    PLOG(ERROR) << "Failed to open " << patch_name;
+    return false;
+  }
+
+  std::vector<std::string> split_info_list;
+  for (size_t i = 0; i < split_tgt_images.size(); i++) {
+    std::vector<PatchChunk> patch_chunks;
+    if (!ZipModeImage::GeneratePatchesInternal(split_tgt_images[i], split_src_images[i],
+                                               &patch_chunks)) {
+      LOG(ERROR) << "Failed to generate split patch";
+      return false;
+    }
+
+    size_t total_patch_size = 12;
+    for (auto& p : patch_chunks) {
+      p.UpdateSourceOffset(split_src_ranges[i]);
+      total_patch_size += p.PatchSize();
+    }
+
+    if (!PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd)) {
+      return false;
+    }
+
+    size_t split_tgt_size = split_tgt_images[i].chunks_.back().GetStartOffset() +
+                            split_tgt_images[i].chunks_.back().GetRawDataLength() -
+                            split_tgt_images[i].chunks_.front().GetStartOffset();
+    std::string split_info = android::base::StringPrintf(
+        "%zu %zu %s", total_patch_size, split_tgt_size, split_src_ranges[i].ToString().c_str());
+    split_info_list.push_back(split_info);
+
+    // Write the split source & patch into the debug directory.
+    if (!debug_dir.empty()) {
+      std::string src_name = android::base::StringPrintf("%s/src-%zu", debug_dir.c_str(), i);
+      android::base::unique_fd fd(
+          open(src_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+
+      if (fd == -1) {
+        PLOG(ERROR) << "Failed to open " << src_name;
+        return false;
+      }
+      if (!android::base::WriteFully(fd, split_src_images[i].PseudoSource().DataForPatch(),
+                                     split_src_images[i].PseudoSource().DataLengthForPatch())) {
+        PLOG(ERROR) << "Failed to write split source data into " << src_name;
+        return false;
+      }
+
+      std::string patch_name = android::base::StringPrintf("%s/patch-%zu", debug_dir.c_str(), i);
+      fd.reset(open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+
+      if (fd == -1) {
+        PLOG(ERROR) << "Failed to open " << patch_name;
+        return false;
+      }
+      if (!PatchChunk::WritePatchDataToFd(patch_chunks, fd)) {
+        return false;
+      }
+    }
+  }
+
+  // Store the split in the following format:
+  // Line 0:   imgdiff version#
+  // Line 1:   number of pieces
+  // Line 2:   patch_size_1 tgt_size_1 src_range_1
+  // ...
+  // Line n+1: patch_size_n tgt_size_n src_range_n
+  std::string split_info_string = android::base::StringPrintf(
+      "%zu\n%zu\n", VERSION, split_info_list.size()) + android::base::Join(split_info_list, '\n');
+  if (!android::base::WriteStringToFile(split_info_string, split_info_file)) {
+    PLOG(ERROR) << "Failed to write split info to " << split_info_file;
+    return false;
+  }
+
+  return true;
+}
+
+bool ImageModeImage::Initialize(const std::string& filename) {
+  if (!ReadFile(filename, &file_content_)) {
+    return false;
+  }
+
+  size_t sz = file_content_.size();
   size_t pos = 0;
-
   while (pos < sz) {
     // 0x00 no header flags, 0x08 deflate compression, 0x1f8b gzip magic number
-    if (sz - pos >= 4 && get_unaligned<uint32_t>(img->data() + pos) == 0x00088b1f) {
+    if (sz - pos >= 4 && get_unaligned<uint32_t>(file_content_.data() + pos) == 0x00088b1f) {
       // 'pos' is the offset of the start of a gzip chunk.
       size_t chunk_offset = pos;
 
       // The remaining data is too small to be a gzip chunk; treat them as a normal chunk.
       if (sz - pos < GZIP_HEADER_LEN + GZIP_FOOTER_LEN) {
-        chunks->emplace_back(CHUNK_NORMAL, pos, img, sz - pos);
+        chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, sz - pos);
         break;
       }
 
       // We need three chunks for the deflated image in total, one normal chunk for the header,
       // one deflated chunk for the body, and another normal chunk for the footer.
-      chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_HEADER_LEN);
+      chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, GZIP_HEADER_LEN);
       pos += GZIP_HEADER_LEN;
 
       // We must decompress this chunk in order to discover where it ends, and so we can update
@@ -657,13 +1275,13 @@
       strm.zfree = Z_NULL;
       strm.opaque = Z_NULL;
       strm.avail_in = sz - pos;
-      strm.next_in = img->data() + pos;
+      strm.next_in = file_content_.data() + pos;
 
       // -15 means we are decoding a 'raw' deflate stream; zlib will
       // not expect zlib headers.
       int ret = inflateInit2(&strm, -15);
       if (ret < 0) {
-        printf("failed to initialize inflate: %d\n", ret);
+        LOG(ERROR) << "Failed to initialize inflate: " << ret;
         return false;
       }
 
@@ -675,8 +1293,8 @@
         strm.next_out = uncompressed_data.data() + uncompressed_len;
         ret = inflate(&strm, Z_NO_FLUSH);
         if (ret < 0) {
-          printf("Warning: inflate failed [%s] at offset [%zu], treating as a normal chunk\n",
-                 strm.msg, chunk_offset);
+          LOG(WARNING) << "Inflate failed [" << strm.msg << "] at offset [" << chunk_offset
+                       << "]; treating as a normal chunk";
           break;
         }
         uncompressed_len = allocated - strm.avail_out;
@@ -697,25 +1315,25 @@
       // matches the size of the data we got when we actually did the decompression.
       size_t footer_index = pos + raw_data_len + GZIP_FOOTER_LEN - 4;
       if (sz - footer_index < 4) {
-        printf("Warning: invalid footer position; treating as a nomal chunk\n");
+        LOG(WARNING) << "invalid footer position; treating as a normal chunk";
         continue;
       }
-      size_t footer_size = get_unaligned<uint32_t>(img->data() + footer_index);
+      size_t footer_size = get_unaligned<uint32_t>(file_content_.data() + footer_index);
       if (footer_size != uncompressed_len) {
-        printf("Warning: footer size %zu != decompressed size %zu; treating as a nomal chunk\n",
-               footer_size, uncompressed_len);
+        LOG(WARNING) << "footer size " << footer_size << " != " << uncompressed_len
+                     << "; treating as a normal chunk";
         continue;
       }
 
-      ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len);
+      ImageChunk body(CHUNK_DEFLATE, pos, &file_content_, raw_data_len);
       uncompressed_data.resize(uncompressed_len);
       body.SetUncompressedData(std::move(uncompressed_data));
-      chunks->push_back(body);
+      chunks_.push_back(std::move(body));
 
       pos += raw_data_len;
 
       // create a normal chunk for the footer
-      chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_FOOTER_LEN);
+      chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, GZIP_FOOTER_LEN);
 
       pos += GZIP_FOOTER_LEN;
     } else {
@@ -726,12 +1344,12 @@
       size_t data_len = 0;
       while (data_len + pos < sz) {
         if (data_len + pos + 4 <= sz &&
-            get_unaligned<uint32_t>(img->data() + pos + data_len) == 0x00088b1f) {
+            get_unaligned<uint32_t>(file_content_.data() + pos + data_len) == 0x00088b1f) {
           break;
         }
         data_len++;
       }
-      chunks->emplace_back(CHUNK_NORMAL, pos, img, data_len);
+      chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, data_len);
 
       pos += data_len;
     }
@@ -740,346 +1358,256 @@
   return true;
 }
 
-/*
- * Given source and target chunks, compute a bsdiff patch between them.
- * Store the result in the patch_data.
- * |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk
- * is used repeatedly, pass nullptr if not needed.
- */
-static bool MakePatch(const ImageChunk* src, ImageChunk* tgt, std::vector<uint8_t>* patch_data,
-                      saidx_t** bsdiff_cache) {
-  if (tgt->ChangeChunkToRaw(0)) {
-    size_t patch_size = tgt->DataLengthForPatch();
-    patch_data->resize(patch_size);
-    std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin());
-    return true;
-  }
-
-#if defined(__ANDROID__)
-  char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX";
-#else
-  char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
-#endif
-
-  int fd = mkstemp(ptemp);
-  if (fd == -1) {
-    printf("MakePatch failed to create a temporary file: %s\n", strerror(errno));
-    return false;
-  }
-  close(fd);
-
-  int r = bsdiff::bsdiff(src->DataForPatch(), src->DataLengthForPatch(), tgt->DataForPatch(),
-                         tgt->DataLengthForPatch(), ptemp, bsdiff_cache);
-  if (r != 0) {
-    printf("bsdiff() failed: %d\n", r);
+bool ImageModeImage::SetBonusData(const std::vector<uint8_t>& bonus_data) {
+  CHECK(is_source_);
+  if (chunks_.size() < 2 || !chunks_[1].SetBonusData(bonus_data)) {
+    LOG(ERROR) << "Failed to set bonus data";
+    DumpChunks();
     return false;
   }
 
-  android::base::unique_fd patch_fd(open(ptemp, O_RDONLY));
-  if (patch_fd == -1) {
-    printf("failed to open %s: %s\n", ptemp, strerror(errno));
+  LOG(INFO) << "  using " << bonus_data.size() << " bytes of bonus data";
+  return true;
+}
+
+// In Image Mode, verify that the source and target images have the same chunk structure (ie, the
+// same sequence of deflate and normal chunks).
+bool ImageModeImage::CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image) {
+  // In image mode, merge the gzip header and footer in with any adjacent normal chunks.
+  tgt_image->MergeAdjacentNormalChunks();
+  src_image->MergeAdjacentNormalChunks();
+
+  if (tgt_image->NumOfChunks() != src_image->NumOfChunks()) {
+    LOG(ERROR) << "Source and target don't have same number of chunks!";
+    tgt_image->DumpChunks();
+    src_image->DumpChunks();
     return false;
   }
-  struct stat st;
-  if (fstat(patch_fd, &st) != 0) {
-    printf("failed to stat patch file %s: %s\n", ptemp, strerror(errno));
-    return false;
+  for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) {
+    if ((*tgt_image)[i].GetType() != (*src_image)[i].GetType()) {
+      LOG(ERROR) << "Source and target don't have same chunk structure! (chunk " << i << ")";
+      tgt_image->DumpChunks();
+      src_image->DumpChunks();
+      return false;
+    }
   }
 
-  size_t sz = static_cast<size_t>(st.st_size);
-  // Change the chunk type to raw if the patch takes less space that way.
-  if (tgt->ChangeChunkToRaw(sz)) {
-    unlink(ptemp);
-    size_t patch_size = tgt->DataLengthForPatch();
-    patch_data->resize(patch_size);
-    std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin());
-    return true;
-  }
-  patch_data->resize(sz);
-  if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) {
-    printf("failed to read \"%s\" %s\n", ptemp, strerror(errno));
-    return false;
+  for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) {
+    auto& tgt_chunk = (*tgt_image)[i];
+    auto& src_chunk = (*src_image)[i];
+    if (tgt_chunk.GetType() != CHUNK_DEFLATE) {
+      continue;
+    }
+
+    // If two deflate chunks are identical treat them as normal chunks.
+    if (tgt_chunk == src_chunk) {
+      tgt_chunk.ChangeDeflateChunkToNormal();
+      src_chunk.ChangeDeflateChunkToNormal();
+    } else if (!tgt_chunk.ReconstructDeflateChunk()) {
+      // We cannot recompress the data and get exactly the same bits as are in the input target
+      // image, fall back to normal
+      LOG(WARNING) << "Failed to reconstruct target deflate chunk " << i << " ["
+                   << tgt_chunk.GetEntryName() << "]; treating as normal";
+      tgt_chunk.ChangeDeflateChunkToNormal();
+      src_chunk.ChangeDeflateChunkToNormal();
+    }
   }
 
-  unlink(ptemp);
-  tgt->SetSourceInfo(*src);
+  // For images, we need to maintain the parallel structure of the chunk lists, so do the merging
+  // in both the source and target lists.
+  tgt_image->MergeAdjacentNormalChunks();
+  src_image->MergeAdjacentNormalChunks();
+  if (tgt_image->NumOfChunks() != src_image->NumOfChunks()) {
+    // This shouldn't happen.
+    LOG(ERROR) << "Merging normal chunks went awry";
+    return false;
+  }
 
   return true;
 }
 
-/*
- * Look for runs of adjacent normal chunks and compress them down into
- * a single chunk.  (Such runs can be produced when deflate chunks are
- * changed to normal chunks.)
- */
-static void MergeAdjacentNormalChunks(std::vector<ImageChunk>* chunks) {
-  size_t merged_last = 0, cur = 0;
-  while (cur < chunks->size()) {
-    // Look for normal chunks adjacent to the current one. If such chunk exists, extend the
-    // length of the current normal chunk.
-    size_t to_check = cur + 1;
-    while (to_check < chunks->size() && chunks->at(cur).IsAdjacentNormal(chunks->at(to_check))) {
-      chunks->at(cur).MergeAdjacentNormal(chunks->at(to_check));
-      to_check++;
+// In image mode, generate patches against the given source chunks and bonus_data; write the
+// result to |patch_name|.
+bool ImageModeImage::GeneratePatches(const ImageModeImage& tgt_image,
+                                     const ImageModeImage& src_image,
+                                     const std::string& patch_name) {
+  LOG(INFO) << "Constructing patches for " << tgt_image.NumOfChunks() << " chunks...";
+  std::vector<PatchChunk> patch_chunks;
+  patch_chunks.reserve(tgt_image.NumOfChunks());
+
+  for (size_t i = 0; i < tgt_image.NumOfChunks(); i++) {
+    const auto& tgt_chunk = tgt_image[i];
+    const auto& src_chunk = src_image[i];
+
+    if (PatchChunk::RawDataIsSmaller(tgt_chunk, 0)) {
+      patch_chunks.emplace_back(tgt_chunk);
+      continue;
     }
 
-    if (merged_last != cur) {
-      chunks->at(merged_last) = std::move(chunks->at(cur));
+    std::vector<uint8_t> patch_data;
+    if (!ImageChunk::MakePatch(tgt_chunk, src_chunk, &patch_data, nullptr)) {
+      LOG(ERROR) << "Failed to generate patch for target chunk " << i;
+      return false;
     }
-    merged_last++;
-    cur = to_check;
-  }
-  if (merged_last < chunks->size()) {
-    chunks->erase(chunks->begin() + merged_last, chunks->end());
-  }
-}
+    LOG(INFO) << "patch " << i << " is " << patch_data.size() << " bytes (of "
+              << tgt_chunk.GetRawDataLength() << ")";
 
-static ImageChunk* FindChunkByName(const std::string& name, std::vector<ImageChunk>& chunks) {
-  for (size_t i = 0; i < chunks.size(); ++i) {
-    if (chunks[i].GetType() == CHUNK_DEFLATE && chunks[i].GetEntryName() == name) {
-      return &chunks[i];
+    if (PatchChunk::RawDataIsSmaller(tgt_chunk, patch_data.size())) {
+      patch_chunks.emplace_back(tgt_chunk);
+    } else {
+      patch_chunks.emplace_back(tgt_chunk, src_chunk, std::move(patch_data));
     }
   }
-  return nullptr;
-}
 
-static void DumpChunks(const std::vector<ImageChunk>& chunks) {
-  for (size_t i = 0; i < chunks.size(); ++i) {
-    printf("chunk %zu: ", i);
-    chunks[i].Dump();
+  CHECK_EQ(tgt_image.NumOfChunks(), patch_chunks.size());
+
+  android::base::unique_fd patch_fd(
+      open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+  if (patch_fd == -1) {
+    PLOG(ERROR) << "Failed to open " << patch_name;
+    return false;
   }
+
+  return PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd);
 }
 
 int imgdiff(int argc, const char** argv) {
+  bool verbose = false;
   bool zip_mode = false;
-
-  if (argc >= 2 && strcmp(argv[1], "-z") == 0) {
-    zip_mode = true;
-    --argc;
-    ++argv;
-  }
-
   std::vector<uint8_t> bonus_data;
-  if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
-    android::base::unique_fd fd(open(argv[2], O_RDONLY));
-    if (fd == -1) {
-      printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno));
-      return 1;
-    }
-    struct stat st;
-    if (fstat(fd, &st) != 0) {
-      printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno));
-      return 1;
-    }
+  size_t blocks_limit = 0;
+  std::string split_info_file;
+  std::string debug_dir;
 
-    size_t bonus_size = st.st_size;
-    bonus_data.resize(bonus_size);
-    if (!android::base::ReadFully(fd, bonus_data.data(), bonus_size)) {
-      printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno));
-      return 1;
-    }
+  int opt;
+  int option_index;
+  optind = 0;  // Reset the getopt state so that we can call it multiple times for test.
 
-    argc -= 2;
-    argv += 2;
+  while ((opt = getopt_long(argc, const_cast<char**>(argv), "zb:v", OPTIONS, &option_index)) !=
+         -1) {
+    switch (opt) {
+      case 'z':
+        zip_mode = true;
+        break;
+      case 'b': {
+        android::base::unique_fd fd(open(optarg, O_RDONLY));
+        if (fd == -1) {
+          PLOG(ERROR) << "Failed to open bonus file " << optarg;
+          return 1;
+        }
+        struct stat st;
+        if (fstat(fd, &st) != 0) {
+          PLOG(ERROR) << "Failed to stat bonus file " << optarg;
+          return 1;
+        }
+
+        size_t bonus_size = st.st_size;
+        bonus_data.resize(bonus_size);
+        if (!android::base::ReadFully(fd, bonus_data.data(), bonus_size)) {
+          PLOG(ERROR) << "Failed to read bonus file " << optarg;
+          return 1;
+        }
+        break;
+      }
+      case 'v':
+        verbose = true;
+        break;
+      case 0: {
+        std::string name = OPTIONS[option_index].name;
+        if (name == "block-limit" && !android::base::ParseUint(optarg, &blocks_limit)) {
+          LOG(ERROR) << "Failed to parse size blocks_limit: " << optarg;
+          return 1;
+        } else if (name == "split-info") {
+          split_info_file = optarg;
+        } else if (name == "debug-dir") {
+          debug_dir = optarg;
+        }
+        break;
+      }
+      default:
+        LOG(ERROR) << "unexpected opt: " << static_cast<char>(opt);
+        return 2;
+    }
   }
 
-  if (argc != 4) {
-    printf("usage: %s [-z] [-b <bonus-file>] <src-img> <tgt-img> <patch-file>\n",
-            argv[0]);
+  if (!verbose) {
+    android::base::SetMinimumLogSeverity(android::base::WARNING);
+  }
+
+  if (argc - optind != 3) {
+    LOG(ERROR) << "usage: " << argv[0] << " [options] <src-img> <tgt-img> <patch-file>";
+    LOG(ERROR)
+        << "  -z <zip-mode>,    Generate patches in zip mode, src and tgt should be zip files.\n"
+           "  -b <bonus-file>,  Bonus file in addition to src, image mode only.\n"
+           "  --block-limit,    For large zips, split the src and tgt based on the block limit;\n"
+           "                    and generate patches between each pair of pieces. Concatenate "
+           "these\n"
+           "                    patches together and output them into <patch-file>.\n"
+           "  --split-info,     Output the split information (patch_size, tgt_size, src_ranges);\n"
+           "                    zip mode with block-limit only.\n"
+           "  --debug-dir,      Debug directory to put the split srcs and patches, zip mode only.\n"
+           "  -v, --verbose,    Enable verbose logging.";
     return 2;
   }
 
-  std::vector<ImageChunk> src_chunks;
-  std::vector<ImageChunk> tgt_chunks;
-  std::vector<uint8_t> src_file;
-  std::vector<uint8_t> tgt_file;
-
   if (zip_mode) {
-    if (!ReadZip(argv[1], &src_chunks, &src_file, true)) {
-      printf("failed to break apart source zip file\n");
+    ZipModeImage src_image(true, blocks_limit * BLOCK_SIZE);
+    ZipModeImage tgt_image(false, blocks_limit * BLOCK_SIZE);
+
+    if (!src_image.Initialize(argv[optind])) {
       return 1;
     }
-    if (!ReadZip(argv[2], &tgt_chunks, &tgt_file, false)) {
-      printf("failed to break apart target zip file\n");
+    if (!tgt_image.Initialize(argv[optind + 1])) {
+      return 1;
+    }
+
+    if (!ZipModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) {
+      return 1;
+    }
+
+    // Compute bsdiff patches for each chunk's data (the uncompressed data, in the case of
+    // deflate chunks).
+    if (blocks_limit > 0) {
+      if (split_info_file.empty()) {
+        LOG(ERROR) << "split-info path cannot be empty when generating patches with a block-limit";
+        return 1;
+      }
+
+      std::vector<ZipModeImage> split_tgt_images;
+      std::vector<ZipModeImage> split_src_images;
+      std::vector<SortedRangeSet> split_src_ranges;
+      ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images,
+                                               &split_src_images, &split_src_ranges);
+
+      if (!ZipModeImage::GeneratePatches(split_tgt_images, split_src_images, split_src_ranges,
+                                         argv[optind + 2], split_info_file, debug_dir)) {
+        return 1;
+      }
+
+    } else if (!ZipModeImage::GeneratePatches(tgt_image, src_image, argv[optind + 2])) {
       return 1;
     }
   } else {
-    if (!ReadImage(argv[1], &src_chunks, &src_file)) {
-      printf("failed to break apart source image\n");
+    ImageModeImage src_image(true);
+    ImageModeImage tgt_image(false);
+
+    if (!src_image.Initialize(argv[optind])) {
       return 1;
     }
-    if (!ReadImage(argv[2], &tgt_chunks, &tgt_file)) {
-      printf("failed to break apart target image\n");
+    if (!tgt_image.Initialize(argv[optind + 1])) {
       return 1;
     }
 
-    // Verify that the source and target images have the same chunk
-    // structure (ie, the same sequence of deflate and normal chunks).
-
-    // Merge the gzip header and footer in with any adjacent normal chunks.
-    MergeAdjacentNormalChunks(&tgt_chunks);
-    MergeAdjacentNormalChunks(&src_chunks);
-
-    if (src_chunks.size() != tgt_chunks.size()) {
-      printf("source and target don't have same number of chunks!\n");
-      printf("source chunks:\n");
-      DumpChunks(src_chunks);
-      printf("target chunks:\n");
-      DumpChunks(tgt_chunks);
+    if (!ImageModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) {
       return 1;
     }
-    for (size_t i = 0; i < src_chunks.size(); ++i) {
-      if (src_chunks[i].GetType() != tgt_chunks[i].GetType()) {
-        printf("source and target don't have same chunk structure! (chunk %zu)\n", i);
-        printf("source chunks:\n");
-        DumpChunks(src_chunks);
-        printf("target chunks:\n");
-        DumpChunks(tgt_chunks);
-        return 1;
-      }
-    }
-  }
 
-  for (size_t i = 0; i < tgt_chunks.size(); ++i) {
-    if (tgt_chunks[i].GetType() == CHUNK_DEFLATE) {
-      // Confirm that given the uncompressed chunk data in the target, we
-      // can recompress it and get exactly the same bits as are in the
-      // input target image.  If this fails, treat the chunk as a normal
-      // non-deflated chunk.
-      if (!tgt_chunks[i].ReconstructDeflateChunk()) {
-        printf("failed to reconstruct target deflate chunk %zu [%s]; treating as normal\n", i,
-               tgt_chunks[i].GetEntryName().c_str());
-        tgt_chunks[i].ChangeDeflateChunkToNormal();
-        if (zip_mode) {
-          ImageChunk* src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks);
-          if (src != nullptr) {
-            src->ChangeDeflateChunkToNormal();
-          }
-        } else {
-          src_chunks[i].ChangeDeflateChunkToNormal();
-        }
-        continue;
-      }
-
-      // If two deflate chunks are identical (eg, the kernel has not
-      // changed between two builds), treat them as normal chunks.
-      // This makes applypatch much faster -- it can apply a trivial
-      // patch to the compressed data, rather than uncompressing and
-      // recompressing to apply the trivial patch to the uncompressed
-      // data.
-      ImageChunk* src;
-      if (zip_mode) {
-        src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks);
-      } else {
-        src = &src_chunks[i];
-      }
-
-      if (src == nullptr) {
-        tgt_chunks[i].ChangeDeflateChunkToNormal();
-      } else if (tgt_chunks[i] == *src) {
-        tgt_chunks[i].ChangeDeflateChunkToNormal();
-        src->ChangeDeflateChunkToNormal();
-      }
-    }
-  }
-
-  // Merging neighboring normal chunks.
-  if (zip_mode) {
-    // For zips, we only need to do this to the target:  deflated
-    // chunks are matched via filename, and normal chunks are patched
-    // using the entire source file as the source.
-    MergeAdjacentNormalChunks(&tgt_chunks);
-
-  } else {
-    // For images, we need to maintain the parallel structure of the
-    // chunk lists, so do the merging in both the source and target
-    // lists.
-    MergeAdjacentNormalChunks(&tgt_chunks);
-    MergeAdjacentNormalChunks(&src_chunks);
-    if (src_chunks.size() != tgt_chunks.size()) {
-      // This shouldn't happen.
-      printf("merging normal chunks went awry\n");
+    if (!bonus_data.empty() && !src_image.SetBonusData(bonus_data)) {
       return 1;
     }
-  }
 
-  // Compute bsdiff patches for each chunk's data (the uncompressed
-  // data, in the case of deflate chunks).
-
-  DumpChunks(src_chunks);
-
-  printf("Construct patches for %zu chunks...\n", tgt_chunks.size());
-  std::vector<std::vector<uint8_t>> patch_data(tgt_chunks.size());
-  saidx_t* bsdiff_cache = nullptr;
-  for (size_t i = 0; i < tgt_chunks.size(); ++i) {
-    if (zip_mode) {
-      ImageChunk* src;
-      if (tgt_chunks[i].GetType() == CHUNK_DEFLATE &&
-          (src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks))) {
-        if (!MakePatch(src, &tgt_chunks[i], &patch_data[i], nullptr)) {
-          printf("Failed to generate patch for target chunk %zu: ", i);
-          return 1;
-        }
-      } else {
-        if (!MakePatch(&src_chunks[0], &tgt_chunks[i], &patch_data[i], &bsdiff_cache)) {
-          printf("Failed to generate patch for target chunk %zu: ", i);
-          return 1;
-        }
-      }
-    } else {
-      if (i == 1 && !bonus_data.empty()) {
-        printf("  using %zu bytes of bonus data for chunk %zu\n", bonus_data.size(), i);
-        src_chunks[i].SetBonusData(bonus_data);
-      }
-
-      if (!MakePatch(&src_chunks[i], &tgt_chunks[i], &patch_data[i], nullptr)) {
-        printf("Failed to generate patch for target chunk %zu: ", i);
-        return 1;
-      }
-    }
-    printf("patch %3zu is %zu bytes (of %zu)\n", i, patch_data[i].size(),
-           src_chunks[i].GetRawDataLength());
-  }
-
-  if (bsdiff_cache != nullptr) {
-    free(bsdiff_cache);
-  }
-
-  // Figure out how big the imgdiff file header is going to be, so
-  // that we can correctly compute the offset of each bsdiff patch
-  // within the file.
-
-  size_t total_header_size = 12;
-  for (size_t i = 0; i < tgt_chunks.size(); ++i) {
-    total_header_size += tgt_chunks[i].GetHeaderSize(patch_data[i].size());
-  }
-
-  size_t offset = total_header_size;
-
-  android::base::unique_fd patch_fd(open(argv[3], O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
-  if (patch_fd == -1) {
-    printf("failed to open \"%s\": %s\n", argv[3], strerror(errno));
-    return 1;
-  }
-
-  // Write out the headers.
-  if (!android::base::WriteStringToFd("IMGDIFF2", patch_fd)) {
-    printf("failed to write \"IMGDIFF2\" to \"%s\": %s\n", argv[3], strerror(errno));
-    return 1;
-  }
-  Write4(patch_fd, static_cast<int32_t>(tgt_chunks.size()));
-  for (size_t i = 0; i < tgt_chunks.size(); ++i) {
-    printf("chunk %zu: ", i);
-    offset = tgt_chunks[i].WriteHeaderToFd(patch_fd, patch_data[i], offset);
-  }
-
-  // Append each chunk's bsdiff patch, in order.
-  for (size_t i = 0; i < tgt_chunks.size(); ++i) {
-    if (tgt_chunks[i].GetType() != CHUNK_RAW) {
-      if (!android::base::WriteFully(patch_fd, patch_data[i].data(), patch_data[i].size())) {
-        CHECK(false) << "failed to write " << patch_data[i].size() << " bytes patch for chunk "
-                     << i;
-      }
+    if (!ImageModeImage::GeneratePatches(tgt_image, src_image, argv[optind + 2])) {
+      return 1;
     }
   }
 
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index df75f98..3682d61 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -37,6 +37,8 @@
 #include <openssl/sha.h>
 #include <zlib.h>
 
+#include "edify/expr.h"
+
 static inline int64_t Read8(const void *address) {
   return android::base::get_unaligned<int64_t>(address);
 }
@@ -48,7 +50,7 @@
 // This function is a wrapper of ApplyBSDiffPatch(). It has a custom sink function to deflate the
 // 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 Value& patch, size_t patch_offset,
                                             const char* deflate_header, SinkFn sink, SHA_CTX* ctx) {
   size_t expected_target_length = static_cast<size_t>(Read8(deflate_header + 32));
   int level = Read4(deflate_header + 40);
@@ -57,13 +59,13 @@
   int mem_level = Read4(deflate_header + 52);
   int strategy = Read4(deflate_header + 56);
 
-  std::unique_ptr<z_stream, decltype(&deflateEnd)> strm(new z_stream(), deflateEnd);
-  strm->zalloc = Z_NULL;
-  strm->zfree = Z_NULL;
-  strm->opaque = Z_NULL;
-  strm->avail_in = 0;
-  strm->next_in = nullptr;
-  int ret = deflateInit2(strm.get(), level, method, window_bits, mem_level, strategy);
+  z_stream strm;
+  strm.zalloc = Z_NULL;
+  strm.zfree = Z_NULL;
+  strm.opaque = Z_NULL;
+  strm.avail_in = 0;
+  strm.next_in = nullptr;
+  int ret = deflateInit2(&strm, level, method, window_bits, mem_level, strategy);
   if (ret != Z_OK) {
     LOG(ERROR) << "Failed to init uncompressed data deflation: " << ret;
     return false;
@@ -74,18 +76,19 @@
   size_t actual_target_length = 0;
   size_t total_written = 0;
   static constexpr size_t buffer_size = 32768;
-  auto compression_sink = [&](const uint8_t* data, size_t len) -> size_t {
+  auto compression_sink = [&strm, &actual_target_length, &expected_target_length, &total_written,
+                           &ret, &ctx, &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;
+    strm.avail_in = len;
+    strm.next_in = data;
     do {
       std::vector<uint8_t> buffer(buffer_size);
-      strm->avail_out = buffer_size;
-      strm->next_out = buffer.data();
+      strm.avail_out = buffer_size;
+      strm.next_out = buffer.data();
       if (actual_target_length + len < expected_target_length) {
-        ret = deflate(strm.get(), Z_NO_FLUSH);
+        ret = deflate(&strm, Z_NO_FLUSH);
       } else {
-        ret = deflate(strm.get(), Z_FINISH);
+        ret = deflate(&strm, Z_FINISH);
       }
       if (ret != Z_OK && ret != Z_STREAM_END) {
         LOG(ERROR) << "Failed to deflate stream: " << ret;
@@ -93,20 +96,24 @@
         return 0;
       }
 
-      size_t have = buffer_size - strm->avail_out;
+      size_t have = buffer_size - strm.avail_out;
       total_written += have;
       if (sink(buffer.data(), have) != have) {
         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);
+    } while ((strm.avail_in != 0 || strm.avail_out == 0) && ret != Z_STREAM_END);
 
     actual_target_length += len;
     return len;
   };
 
-  if (ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr) != 0) {
+  int bspatch_result =
+      ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr);
+  deflateEnd(&strm);
+
+  if (bspatch_result != 0) {
     return false;
   }
 
@@ -128,48 +135,39 @@
 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);
+  return ApplyImagePatch(old_data, old_size, patch, sink, nullptr, nullptr);
 }
 
-/*
- * Apply the patch given in 'patch_filename' to the source data given
- * by (old_data, old_size).  Write the patched output to the 'output'
- * file, and update the SHA context with the output data as well.
- * Return 0 on success.
- */
-int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink,
+int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
                     SHA_CTX* ctx, const Value* bonus_data) {
-  if (patch->data.size() < 12) {
+  if (patch.data.size() < 12) {
     printf("patch too short to contain header\n");
     return -1;
   }
 
-  // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW.
-  // (IMGDIFF1, which is no longer supported, used CHUNK_NORMAL and
-  // CHUNK_GZIP.)
-  size_t pos = 12;
-  const char* header = &patch->data[0];
-  if (memcmp(header, "IMGDIFF2", 8) != 0) {
+  // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW. (IMGDIFF1, which is no longer
+  // supported, used CHUNK_NORMAL and CHUNK_GZIP.)
+  const char* const patch_header = patch.data.data();
+  if (memcmp(patch_header, "IMGDIFF2", 8) != 0) {
     printf("corrupt patch file header (magic number)\n");
     return -1;
   }
 
-  int num_chunks = Read4(header + 8);
-
+  int num_chunks = Read4(patch_header + 8);
+  size_t pos = 12;
   for (int i = 0; i < num_chunks; ++i) {
     // each chunk's header record starts with 4 bytes.
-    if (pos + 4 > patch->data.size()) {
+    if (pos + 4 > patch.data.size()) {
       printf("failed to read chunk %d record\n", i);
       return -1;
     }
-    int type = Read4(&patch->data[pos]);
+    int type = Read4(patch_header + pos);
     pos += 4;
 
     if (type == CHUNK_NORMAL) {
-      const char* normal_header = &patch->data[pos];
+      const char* normal_header = patch_header + pos;
       pos += 24;
-      if (pos > patch->data.size()) {
+      if (pos > patch.data.size()) {
         printf("failed to read chunk %d normal header data\n", i);
         return -1;
       }
@@ -187,30 +185,32 @@
         return -1;
       }
     } else if (type == CHUNK_RAW) {
-      const char* raw_header = &patch->data[pos];
+      const char* raw_header = patch_header + pos;
       pos += 4;
-      if (pos > patch->data.size()) {
+      if (pos > patch.data.size()) {
         printf("failed to read chunk %d raw header data\n", i);
         return -1;
       }
 
       size_t data_len = static_cast<size_t>(Read4(raw_header));
 
-      if (pos + data_len > patch->data.size()) {
+      if (pos + data_len > patch.data.size()) {
         printf("failed to read chunk %d raw data\n", i);
         return -1;
       }
-      if (ctx) SHA1_Update(ctx, &patch->data[pos], data_len);
-      if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len) != data_len) {
+      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;
     } else if (type == CHUNK_DEFLATE) {
       // deflate chunks have an additional 60 bytes in their chunk header.
-      const char* deflate_header = &patch->data[pos];
+      const char* deflate_header = patch_header + pos;
       pos += 60;
-      if (pos > patch->data.size()) {
+      if (pos > patch.data.size()) {
         printf("failed to read chunk %d deflate header data\n", i);
         return -1;
       }
diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h
index 581360e..912ead1 100644
--- a/applypatch/include/applypatch/applypatch.h
+++ b/applypatch/include/applypatch/applypatch.h
@@ -18,7 +18,6 @@
 #define _APPLYPATCH_H
 
 #include <stdint.h>
-#include <sys/stat.h>
 
 #include <functional>
 #include <memory>
@@ -27,24 +26,18 @@
 
 #include <openssl/sha.h>
 
-#include "edify/expr.h"
+// Forward declaration to avoid including "edify/expr.h" in the header.
+struct Value;
 
 struct FileContents {
   uint8_t sha1[SHA_DIGEST_LENGTH];
   std::vector<unsigned char> data;
-  struct stat st;
 };
 
-// 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 this file contains the bits we want
-// and use it as the source instead.
-#define CACHE_TEMP_SOURCE "/cache/saved.file"
-
 using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
 
 // applypatch.cpp
+
 int ShowLicenses();
 size_t FreeSpaceForFile(const char* filename);
 int CacheSizeCheck(size_t bytes);
@@ -66,15 +59,25 @@
 int SaveFileContents(const char* filename, const FileContents* file);
 
 // bspatch.cpp
+
 void ShowBSDiffLicense();
-int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch,
+
+// 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.
+int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch,
                      size_t patch_offset, SinkFn sink, SHA_CTX* ctx);
 
 // imgpatch.cpp
-int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink,
+
+// 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.
+int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
                     SHA_CTX* ctx, const Value* bonus_data);
 
 // freecache.cpp
+
 int MakeFreeSpaceOnCache(size_t bytes_needed);
 
 #endif
diff --git a/applypatch/include/applypatch/imgdiff_image.h b/applypatch/include/applypatch/imgdiff_image.h
new file mode 100644
index 0000000..0848072
--- /dev/null
+++ b/applypatch/include/applypatch/imgdiff_image.h
@@ -0,0 +1,306 @@
+/*
+ * 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.
+ */
+
+#ifndef _APPLYPATCH_IMGDIFF_IMAGE_H
+#define _APPLYPATCH_IMGDIFF_IMAGE_H
+
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <bsdiff/bsdiff.h>
+#include <ziparchive/zip_archive.h>
+#include <zlib.h>
+
+#include "imgdiff.h"
+#include "otautil/rangeset.h"
+
+class ImageChunk {
+ public:
+  static constexpr auto WINDOWBITS = -15;  // 32kb window; negative to indicate a raw stream.
+  static constexpr auto MEMLEVEL = 8;      // the default value.
+  static constexpr auto METHOD = Z_DEFLATED;
+  static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY;
+
+  ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content, size_t raw_data_len,
+             std::string entry_name = {});
+
+  int GetType() const {
+    return type_;
+  }
+  size_t GetRawDataLength() const {
+    return raw_data_len_;
+  }
+  const std::string& GetEntryName() const {
+    return entry_name_;
+  }
+  size_t GetStartOffset() const {
+    return start_;
+  }
+  int GetCompressLevel() const {
+    return compress_level_;
+  }
+
+  // CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return
+  // the raw data.
+  const uint8_t* DataForPatch() const;
+  size_t DataLengthForPatch() const;
+
+  void Dump(size_t index) const;
+
+  void SetUncompressedData(std::vector<uint8_t> data);
+  bool SetBonusData(const std::vector<uint8_t>& bonus_data);
+
+  bool operator==(const ImageChunk& other) const;
+  bool operator!=(const ImageChunk& other) const {
+    return !(*this == other);
+  }
+
+  /*
+   * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob of uninterpreted data).
+   * The resulting patch will likely be about as big as the target file, but it lets us handle
+   * the case of images where some gzip chunks are reconstructible but others aren't (by treating
+   * the ones that aren't as normal chunks).
+   */
+  void ChangeDeflateChunkToNormal();
+
+  /*
+   * Verify that we can reproduce exactly the same compressed data that we started with.  Sets the
+   * level, method, windowBits, memLevel, and strategy fields in the chunk to the encoding
+   * parameters needed to produce the right output.
+   */
+  bool ReconstructDeflateChunk();
+  bool IsAdjacentNormal(const ImageChunk& other) const;
+  void MergeAdjacentNormal(const ImageChunk& other);
+
+  /*
+   * Compute a bsdiff patch between |src| and |tgt|; Store the result in the patch_data.
+   * |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk is used
+   * repeatedly, pass nullptr if not needed.
+   */
+  static bool MakePatch(const ImageChunk& tgt, const ImageChunk& src,
+                        std::vector<uint8_t>* patch_data,
+                        bsdiff::SuffixArrayIndexInterface** bsdiff_cache);
+
+ private:
+  const uint8_t* GetRawData() const;
+  bool TryReconstruction(int level);
+
+  int type_;                                    // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
+  size_t start_;                                // offset of chunk in the original input file
+  const std::vector<uint8_t>* input_file_ptr_;  // ptr to the full content of original input file
+  size_t raw_data_len_;
+
+  // deflate encoder parameters
+  int compress_level_;
+
+  // --- for CHUNK_DEFLATE chunks only: ---
+  std::vector<uint8_t> uncompressed_data_;
+  std::string entry_name_;  // used for zip entries
+};
+
+// PatchChunk stores the patch data between a source chunk and a target chunk. It also keeps track
+// of the metadata of src&tgt chunks (e.g. offset, raw data length, uncompressed data length).
+class PatchChunk {
+ public:
+  PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector<uint8_t> data);
+
+  // Construct a CHUNK_RAW patch from the target data directly.
+  explicit PatchChunk(const ImageChunk& tgt);
+
+  // Return true if raw data size is smaller than the patch size.
+  static bool RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size);
+
+  // Update the source start with the new offset within the source range.
+  void UpdateSourceOffset(const SortedRangeSet& src_range);
+
+  // Return the total size (header + data) of the patch.
+  size_t PatchSize() const;
+
+  static bool WritePatchDataToFd(const std::vector<PatchChunk>& patch_chunks, int patch_fd);
+
+ private:
+  size_t GetHeaderSize() const;
+  size_t WriteHeaderToFd(int fd, size_t offset, size_t index) const;
+
+  // The patch chunk type is the same as the target chunk type. The only exception is we change
+  // the |type_| to CHUNK_RAW if target length is smaller than the patch size.
+  int type_;
+
+  size_t source_start_;
+  size_t source_len_;
+  size_t source_uncompressed_len_;
+
+  size_t target_start_;  // offset of the target chunk within the target file
+  size_t target_len_;
+  size_t target_uncompressed_len_;
+  size_t target_compress_level_;  // the deflate compression level of the target chunk.
+
+  std::vector<uint8_t> data_;  // storage for the patch data
+};
+
+// Interface for zip_mode and image_mode images. We initialize the image from an input file and
+// split the file content into a list of image chunks.
+class Image {
+ public:
+  explicit Image(bool is_source) : is_source_(is_source) {}
+
+  virtual ~Image() {}
+
+  // Create a list of image chunks from input file.
+  virtual bool Initialize(const std::string& filename) = 0;
+
+  // Look for runs of adjacent normal chunks and compress them down into a single chunk.  (Such
+  // runs can be produced when deflate chunks are changed to normal chunks.)
+  void MergeAdjacentNormalChunks();
+
+  void DumpChunks() const;
+
+  // Non const iterators to access the stored ImageChunks.
+  std::vector<ImageChunk>::iterator begin() {
+    return chunks_.begin();
+  }
+
+  std::vector<ImageChunk>::iterator end() {
+    return chunks_.end();
+  }
+
+  std::vector<ImageChunk>::const_iterator cbegin() const {
+    return chunks_.cbegin();
+  }
+
+  std::vector<ImageChunk>::const_iterator cend() const {
+    return chunks_.cend();
+  }
+
+  ImageChunk& operator[](size_t i);
+  const ImageChunk& operator[](size_t i) const;
+
+  size_t NumOfChunks() const {
+    return chunks_.size();
+  }
+
+ protected:
+  bool ReadFile(const std::string& filename, std::vector<uint8_t>* file_content);
+
+  bool is_source_;                     // True if it's for source chunks.
+  std::vector<ImageChunk> chunks_;     // Internal storage of ImageChunk.
+  std::vector<uint8_t> file_content_;  // Store the whole input file in memory.
+};
+
+class ZipModeImage : public Image {
+ public:
+  explicit ZipModeImage(bool is_source, size_t limit = 0) : Image(is_source), limit_(limit) {}
+
+  bool Initialize(const std::string& filename) override;
+
+  // Initialize a dummy ZipModeImage from an existing ImageChunk vector. For src img pieces, we
+  // reconstruct a new file_content based on the source ranges; but it's not needed for the tgt img
+  // pieces; because for each chunk both the data and their offset within the file are unchanged.
+  void Initialize(const std::vector<ImageChunk>& chunks, const std::vector<uint8_t>& file_content) {
+    chunks_ = chunks;
+    file_content_ = file_content;
+  }
+
+  // The pesudo source chunk for bsdiff if there's no match for the given target chunk. It's in
+  // fact the whole source file.
+  ImageChunk PseudoSource() const;
+
+  // Find the matching deflate source chunk by entry name. Search for normal chunks also if
+  // |find_normal| is true.
+  ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false);
+
+  const ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false) const;
+
+  // Verify that we can reconstruct the deflate chunks; also change the type to CHUNK_NORMAL if
+  // src and tgt are identical.
+  static bool CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image);
+
+  // Compute the patch between tgt & src images, and write the data into |patch_name|.
+  static bool GeneratePatches(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
+                              const std::string& patch_name);
+
+  // Compute the patch based on the lists of split src and tgt images. Generate patches for each
+  // pair of split pieces and write the data to |patch_name|. If |debug_dir| is specified, write
+  // each split src data and patch data into that directory.
+  static bool GeneratePatches(const std::vector<ZipModeImage>& split_tgt_images,
+                              const std::vector<ZipModeImage>& split_src_images,
+                              const std::vector<SortedRangeSet>& split_src_ranges,
+                              const std::string& patch_name, const std::string& split_info_file,
+                              const std::string& debug_dir);
+
+  // Split the tgt chunks and src chunks based on the size limit.
+  static bool SplitZipModeImageWithLimit(const ZipModeImage& tgt_image,
+                                         const ZipModeImage& src_image,
+                                         std::vector<ZipModeImage>* split_tgt_images,
+                                         std::vector<ZipModeImage>* split_src_images,
+                                         std::vector<SortedRangeSet>* split_src_ranges);
+
+ private:
+  // Initialize image chunks based on the zip entries.
+  bool InitializeChunks(const std::string& filename, ZipArchiveHandle handle);
+  // Add the a zip entry to the list.
+  bool AddZipEntryToChunks(ZipArchiveHandle handle, const std::string& entry_name, ZipEntry* entry);
+  // Return the real size of the zip file. (omit the trailing zeros that used for alignment)
+  bool GetZipFileSize(size_t* input_file_size);
+
+  static void ValidateSplitImages(const std::vector<ZipModeImage>& split_tgt_images,
+                                  const std::vector<ZipModeImage>& split_src_images,
+                                  std::vector<SortedRangeSet>& split_src_ranges,
+                                  size_t total_tgt_size);
+  // Construct the dummy split images based on the chunks info and source ranges; and move them into
+  // the given vectors. Return true if we add a new split image into |split_tgt_images|, and
+  // false otherwise.
+  static bool AddSplitImageFromChunkList(const ZipModeImage& tgt_image,
+                                         const ZipModeImage& src_image,
+                                         const SortedRangeSet& split_src_ranges,
+                                         const std::vector<ImageChunk>& split_tgt_chunks,
+                                         const std::vector<ImageChunk>& split_src_chunks,
+                                         std::vector<ZipModeImage>* split_tgt_images,
+                                         std::vector<ZipModeImage>* split_src_images);
+
+  // Function that actually iterates the tgt_chunks and makes patches.
+  static bool GeneratePatchesInternal(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
+                                      std::vector<PatchChunk>* patch_chunks);
+
+  // size limit in bytes of each chunk. Also, if the length of one zip_entry exceeds the limit,
+  // we'll split that entry into several smaller chunks in advance.
+  size_t limit_;
+};
+
+class ImageModeImage : public Image {
+ public:
+  explicit ImageModeImage(bool is_source) : Image(is_source) {}
+
+  // Initialize the image chunks list by searching the magic numbers in an image file.
+  bool Initialize(const std::string& filename) override;
+
+  bool SetBonusData(const std::vector<uint8_t>& bonus_data);
+
+  // In Image Mode, verify that the source and target images have the same chunk structure (ie, the
+  // same sequence of deflate and normal chunks).
+  static bool CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image);
+
+  // In image mode, generate patches against the given source chunks and bonus_data; write the
+  // result to |patch_name|.
+  static bool GeneratePatches(const ImageModeImage& tgt_image, const ImageModeImage& src_image,
+                              const std::string& patch_name);
+};
+
+#endif  // _APPLYPATCH_IMGDIFF_IMAGE_H
diff --git a/applypatch/libimgpatch.pc b/applypatch/libimgpatch.pc
deleted file mode 100644
index e500293..0000000
--- a/applypatch/libimgpatch.pc
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file is for libimgpatch in Chrome OS.
-
-Name: libimgpatch
-Description: Apply imgdiff patch
-Version: 0.0.1
-Libs: -limgpatch -lbz2 -lz
diff --git a/bmlutils/bmlutils.c b/bmlutils/bmlutils.c
index 7875f6f..b5da078 100644
--- a/bmlutils/bmlutils.c
+++ b/bmlutils/bmlutils.c
@@ -27,7 +27,7 @@
 static int restore_internal(const char* bml, const char* filename)
 {
     char buf[4096];
-    int dstfd, srcfd, bytes_read, bytes_written, total_read = 0;
+    int dstfd, srcfd, bytes_read, total_read = 0;
     if (filename == NULL)
         srcfd = 0;
     else {
@@ -82,7 +82,7 @@
 
 int cmd_bml_backup_raw_partition(const char *partition, const char *out_file)
 {
-    char* bml;
+    const char* bml;
     if (strcmp("boot", partition) == 0)
         bml = BOARD_BML_BOOT;
     else if (strcmp("recovery", partition) == 0)
@@ -99,12 +99,11 @@
     int ch;
     FILE *in;
     FILE *out;
-    int val = 0;
     char buf[512];
     unsigned sz = 0;
     unsigned i;
     int ret = -1;
-    char *in_file = bml;
+    const char *in_file = bml;
 
     in  = fopen ( in_file,  "r" );
     if (in == NULL)
@@ -144,23 +143,23 @@
     return ret;
 }
 
-int cmd_bml_erase_raw_partition(const char *partition)
+int cmd_bml_erase_raw_partition(const char *partition __unused)
 {
     // TODO: implement raw wipe
     return 0;
 }
 
-int cmd_bml_erase_partition(const char *partition, const char *filesystem)
+int cmd_bml_erase_partition(const char *partition __unused, const char *filesystem __unused)
 {
     return -1;
 }
 
-int cmd_bml_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only)
+int cmd_bml_mount_partition(const char *partition __unused, const char *mount_point __unused, const char *filesystem __unused, int read_only __unused)
 {
     return -1;
 }
 
-int cmd_bml_get_partition_device(const char *partition, char *device)
+int cmd_bml_get_partition_device(const char *partition __unused, char *device __unused)
 {
     return -1;
 }
diff --git a/boot_control/Android.mk b/boot_control/Android.mk
index 27e3d97..9814d71 100644
--- a/boot_control/Android.mk
+++ b/boot_control/Android.mk
@@ -24,8 +24,7 @@
   -D_FILE_OFFSET_BITS=64 \
   -Werror \
   -Wall \
-  -Wextra \
-  -Wno-unused-parameter
+  -Wextra
 LOCAL_SHARED_LIBRARIES := liblog
 LOCAL_STATIC_LIBRARIES := libbootloader_message libfs_mgr libbase
 LOCAL_POST_INSTALL_CMD := \
diff --git a/bootloader_message/Android.NObp b/bootloader_message/Android.bp
similarity index 93%
rename from bootloader_message/Android.NObp
rename to bootloader_message/Android.bp
index f0d76e7..c81c67b 100644
--- a/bootloader_message/Android.NObp
+++ b/bootloader_message/Android.bp
@@ -17,7 +17,10 @@
 cc_library_static {
     name: "libbootloader_message",
     srcs: ["bootloader_message.cpp"],
-    cppflags: ["-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
     static_libs: [
         "libbase",
         "libfs_mgr",
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index 6c237e6..72ec8bc 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -186,14 +186,8 @@
 
 bool write_bootloader_message(const std::vector<std::string>& options, std::string* err) {
   bootloader_message boot = {};
-  strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
-  strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
-  for (const auto& s : options) {
-    strlcat(boot.recovery, s.c_str(), sizeof(boot.recovery));
-    if (s.back() != '\n') {
-      strlcat(boot.recovery, "\n", sizeof(boot.recovery));
-    }
-  }
+  update_bootloader_message_in_struct(&boot, options);
+
   return write_bootloader_message(boot, err);
 }
 
@@ -202,20 +196,27 @@
   if (!read_bootloader_message(&boot, err)) {
     return false;
   }
+  update_bootloader_message_in_struct(&boot, options);
 
-  // Zero out the entire fields.
-  memset(boot.command, 0, sizeof(boot.command));
-  memset(boot.recovery, 0, sizeof(boot.recovery));
+  return write_bootloader_message(boot, err);
+}
 
-  strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
-  strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
+bool update_bootloader_message_in_struct(bootloader_message* boot,
+                                         const std::vector<std::string>& options) {
+  if (!boot) return false;
+  // Replace the command & recovery fields.
+  memset(boot->command, 0, sizeof(boot->command));
+  memset(boot->recovery, 0, sizeof(boot->recovery));
+
+  strlcpy(boot->command, "boot-recovery", sizeof(boot->command));
+  strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery));
   for (const auto& s : options) {
-    strlcat(boot.recovery, s.c_str(), sizeof(boot.recovery));
+    strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery));
     if (s.back() != '\n') {
-      strlcat(boot.recovery, "\n", sizeof(boot.recovery));
+      strlcat(boot->recovery, "\n", sizeof(boot->recovery));
     }
   }
-  return write_bootloader_message(boot, err);
+  return true;
 }
 
 bool write_reboot_bootloader(std::string* err) {
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index 4da1171..29f5601 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -114,13 +114,17 @@
  * implementations are free to use all 32 bytes and may store private
  * data past the first NUL-byte in this field. It is encouraged, but
  * not mandatory, to use 'struct bootloader_control' described below.
+ *
+ * The update_channel field is used to store the Omaha update channel
+ * if update_engine is compiled with Omaha support.
  */
 struct bootloader_message_ab {
     struct bootloader_message message;
     char slot_suffix[32];
+    char update_channel[128];
 
     // Round up the entire struct to 4096-byte.
-    char reserved[2016];
+    char reserved[1888];
 };
 
 /**
@@ -218,6 +222,11 @@
 // only update the command and recovery fields.
 bool update_bootloader_message(const std::vector<std::string>& options, std::string* err);
 
+// Update bootloader message (boots into recovery with the |options|) in |boot|. Will only update
+// the command and recovery fields.
+bool update_bootloader_message_in_struct(bootloader_message* boot,
+                                         const std::vector<std::string>& options);
+
 // Clear BCB.
 bool clear_bootloader_message(std::string* err);
 
diff --git a/common.h b/common.h
index b0901f4..e9585c1 100644
--- a/common.h
+++ b/common.h
@@ -40,6 +40,10 @@
 #define STRINGIFY(x) #x
 #define EXPAND(x) STRINGIFY(x)
 
+// Not using the command-line defined macro here because this header could be included by
+// device-specific recovery libraries. We static assert the value consistency in recovery.cpp.
+//static constexpr int kRecoveryApiVersion = 3;
+
 class RecoveryUI;
 
 extern RecoveryUI* ui;
@@ -57,7 +61,7 @@
 
 void ui_print(const char* format, ...);
 
-static bool is_ro_debuggable();
+//static bool is_ro_debuggable();
 
 #ifdef __cplusplus
 }
diff --git a/crypto/ext4crypt/Android.mk b/crypto/ext4crypt/Android.mk
index 693b675..4aba9ef 100644
--- a/crypto/ext4crypt/Android.mk
+++ b/crypto/ext4crypt/Android.mk
@@ -6,7 +6,7 @@
 LOCAL_MODULE_TAGS := eng optional
 LOCAL_CFLAGS :=
 LOCAL_SRC_FILES := Decrypt.cpp Ext4Crypt.cpp ScryptParameters.cpp Utils.cpp HashPassword.cpp ext4_crypt.cpp
-LOCAL_SHARED_LIBRARIES := libselinux libc libc++ libext4_utils libsoftkeymaster libbase libcrypto libcutils libkeymaster_messages libhardware libprotobuf-cpp-lite
+LOCAL_SHARED_LIBRARIES := libselinux libc libc++ libext4_utils libbase libcrypto libcutils libkeymaster_messages libhardware libprotobuf-cpp-lite
 LOCAL_STATIC_LIBRARIES := libscrypt_static
 LOCAL_C_INCLUDES := system/extras/ext4_utils system/extras/ext4_utils/include/ext4_utils external/scrypt/lib/crypto system/security/keystore hardware/libhardware/include/hardware system/security/softkeymaster/include/keymaster system/keymaster/include
 
@@ -28,10 +28,13 @@
         LOCAL_CFLAGS += -DHAVE_LIBKEYUTILS
         LOCAL_SHARED_LIBRARIES += libkeyutils
     endif
-    LOCAL_ADDITIONAL_DEPENDENCIES := keystore_auth
+    LOCAL_REQUIRED_MODULES := keystore_auth
 else
     LOCAL_SRC_FILES += Keymaster.cpp KeyStorage.cpp
 endif
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 28; echo $$?),0)
+    LOCAL_SHARED_LIBRARIES += libsoftkeymaster
+endif
 
 include $(BUILD_SHARED_LIBRARY)
 
diff --git a/crypto/ext4crypt/Ext4Crypt.cpp b/crypto/ext4crypt/Ext4Crypt.cpp
index ea5b1cf..048a0bd 100644
--- a/crypto/ext4crypt/Ext4Crypt.cpp
+++ b/crypto/ext4crypt/Ext4Crypt.cpp
@@ -312,7 +312,7 @@
     return true;
 }
 
-static bool ensure_policy(const std::string& raw_ref, const std::string& path) {
+static bool ensure_policy(const std::string& raw_ref __unused, const std::string& path) {
     LOG(INFO) << "ensure_policy '" << path << "'\n";
     return true;
     return access(path.c_str(), F_OK) == 0; // ensure policy will set a policy if one is not set on an empty folder - we don't want to do this in recovery
@@ -438,7 +438,7 @@
 }
 
 // TODO: rename to 'install' for consistency, and take flags to know which keys to install
-bool e4crypt_unlock_user_key(userid_t user_id, int serial, const char* token_hex,
+bool e4crypt_unlock_user_key(userid_t user_id, int serial __unused, const char* token_hex,
                              const char* secret_hex) {
     if (e4crypt_is_native()) {
         if (s_ce_key_raw_refs.count(user_id) != 0) {
@@ -469,7 +469,7 @@
     return true;
 }
 
-bool e4crypt_prepare_user_storage(const char* volume_uuid, userid_t user_id, int serial,
+bool e4crypt_prepare_user_storage(const char* volume_uuid, userid_t user_id, int serial __unused,
         int flags) {
 
     if (flags & FLAG_STORAGE_DE) {
diff --git a/crypto/lollipop/Android.mk b/crypto/lollipop/Android.mk
index 6dc386a..1be4121 100644
--- a/crypto/lollipop/Android.mk
+++ b/crypto/lollipop/Android.mk
@@ -24,6 +24,8 @@
     LOCAL_C_INCLUDES +=  external/boringssl/src/include
 endif
 
+LOCAL_CFLAGS += -Wno-unused-function
+
 LOCAL_WHOLE_STATIC_LIBRARIES += libscrypttwrp_static
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/crypto/lollipop/cryptfs.c b/crypto/lollipop/cryptfs.c
index 80f433b..d3bf629 100644
--- a/crypto/lollipop/cryptfs.c
+++ b/crypto/lollipop/cryptfs.c
@@ -89,10 +89,7 @@
 
 char *me = "cryptfs";
 
-static unsigned char saved_master_key[KEY_LEN_BYTES];
-static char *saved_mount_point;
 static int  master_key_saved = 0;
-static struct crypt_persist_data *persist_data = NULL;
 static char key_fname[PROPERTY_VALUE_MAX] = "";
 static char real_blkdev[PROPERTY_VALUE_MAX] = "";
 static char file_system[PROPERTY_VALUE_MAX] = "";
@@ -629,24 +626,6 @@
 }
 #endif //#ifndef TW_CRYPTO_HAVE_KEYMASTERX
 
-/* Store password when userdata is successfully decrypted and mounted.
- * Cleared by cryptfs_clear_password
- *
- * To avoid a double prompt at boot, we need to store the CryptKeeper
- * password and pass it to KeyGuard, which uses it to unlock KeyStore.
- * Since the entire framework is torn down and rebuilt after encryption,
- * we have to use a daemon or similar to store the password. Since vold
- * is secured against IPC except from system processes, it seems a reasonable
- * place to store this.
- *
- * password should be cleared once it has been used.
- *
- * password is aged out after password_max_age_seconds seconds.
- */
-static char* password = 0;
-static int password_expiry_time = 0;
-static const int password_max_age_seconds = 60;
-
 static void ioctl_init(struct dm_ioctl *io, size_t dataSize, const char *name, unsigned flags)
 {
     memset(io, 0, dataSize);
@@ -774,7 +753,7 @@
 static int get_crypt_ftr_and_key(struct crypt_mnt_ftr *crypt_ftr)
 {
   int fd;
-  unsigned int nr_sec, cnt;
+  unsigned int cnt;
   off64_t starting_off;
   int rc = -1;
   char *fname = NULL;
@@ -979,8 +958,6 @@
     char buffer[DM_CRYPT_BUF_SIZE];
     struct dm_ioctl *io;
     struct dm_target_versions *v;
-    int flag;
-    int i;
 
     io = (struct dm_ioctl *) buffer;
 
@@ -996,6 +973,7 @@
     v = (struct dm_target_versions *) &buffer[sizeof(struct dm_ioctl)];
     while (v->next) {
 #ifdef CONFIG_HW_DISK_ENCRYPTION
+        int flag;
         if (is_hw_fde_enabled()) {
             flag = (!strcmp(v->name, "crypt") || !strcmp(v->name, "req-crypt"));
         } else {
@@ -1022,13 +1000,9 @@
                                  const char *real_blk_name, char *crypto_blk_name, const char *name)
 {
   char buffer[DM_CRYPT_BUF_SIZE];
-  char master_key_ascii[129]; /* Large enough to hold 512 bit key and null */
-  char *crypt_params;
   struct dm_ioctl *io;
-  struct dm_target_spec *tgt;
   unsigned int minor;
   int fd=0;
-  int i;
   int retval = -1;
   int version[3];
   char *extra_params;
@@ -1223,7 +1197,7 @@
     unsigned char* master_key = convert_hex_ascii_to_key(passwd, &key_size);
     if (!master_key) {
         printf("Failed to convert passwd from hex, using passwd instead\n");
-        master_key = strdup(passwd);
+        master_key = (unsigned char*)strdup(passwd);
     }
 
     rc = crypto_scrypt(master_key, key_size, salt, SALT_LEN,
@@ -1365,10 +1339,6 @@
   char crypto_blkdev[MAXPATHLEN];
   char tmp_mount_point[64];
   int rc = 0;
-  kdf_func kdf;
-  void *kdf_params;
-  int use_keymaster = 0;
-  int upgrade = 0;
   unsigned char* intermediate_key = 0;
   size_t intermediate_key_size = 0;
 
diff --git a/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c b/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c
index d05ee1b..158bf96 100644
--- a/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c
+++ b/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c
@@ -135,9 +135,7 @@
 	uint8x16_t * X = XY;
 	uint8x16_t * Y = (void *)((uintptr_t)(XY) + 128 * r);
         uint8x16_t * Z = (void *)((uintptr_t)(XY) + 256 * r);
-        uint32_t * X32 = (void *)X;
 	uint64_t i, j;
-        size_t k;
 
 	/* 1: X <-- B */
 	blkcpy(X, B, 128 * r);
diff --git a/device.cpp b/device.cpp
index 6150186..f881daf 100644
--- a/device.cpp
+++ b/device.cpp
@@ -17,34 +17,36 @@
 #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",
+  "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",
+  "Wipe cache partition",
 #endif  // !AB_OTA_UPDATER
-    "Mount /system",
-    "View recovery logs",
-    "Run graphics test",
-    "Power off",
-    NULL,
+  "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,
+  Device::REBOOT,
+  Device::REBOOT_BOOTLOADER,
+  Device::APPLY_ADB_SIDELOAD,
+  Device::APPLY_SDCARD,
+  Device::WIPE_DATA,
 #ifndef AB_OTA_UPDATER
-    Device::WIPE_CACHE,
+  Device::WIPE_CACHE,
 #endif  // !AB_OTA_UPDATER
-    Device::MOUNT_SYSTEM,
-    Device::VIEW_RECOVERY_LOGS,
-    Device::RUN_GRAPHICS_TEST,
-    Device::SHUTDOWN,
+  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]) ==
diff --git a/device.h b/device.h
index 639e2bf..74745b3 100644
--- a/device.h
+++ b/device.h
@@ -66,6 +66,7 @@
     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
diff --git a/dosfstools/Android.mk b/dosfstools/Android.mk
index b241a73..7b6d623 100644
--- a/dosfstools/Android.mk
+++ b/dosfstools/Android.mk
@@ -15,6 +15,7 @@
 LOCAL_SHARED_LIBRARIES := libc
 LOCAL_CFLAGS += -D_USING_BIONIC_
 LOCAL_CFLAGS += -DUSE_ANDROID_RETVALS
+LOCAL_CFLAGS += -Wno-sign-compare
 LOCAL_MODULE = fsck.fat
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
@@ -35,6 +36,7 @@
 LOCAL_C_INCLUDES += bionic/libc/kernel/common
 LOCAL_SHARED_LIBRARIES := libc
 LOCAL_CFLAGS += -D_USING_BIONIC_
+LOCAL_CFLAGS += -Wno-sign-compare
 LOCAL_MODULE = fatlabel
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
@@ -46,6 +48,7 @@
 
 LOCAL_SHARED_LIBRARIES := libc
 LOCAL_CFLAGS += -D_USING_BIONIC_
+LOCAL_CFLAGS += -Wno-sign-compare
 LOCAL_MODULE = mkfs.fat
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
diff --git a/bootloader_message/Android.NObp b/edify/Android.bp
similarity index 63%
copy from bootloader_message/Android.NObp
copy to edify/Android.bp
index f0d76e7..42947eb 100644
--- a/bootloader_message/Android.NObp
+++ b/edify/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2017 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,15 +11,35 @@
 // 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: "libbootloader_message",
-    srcs: ["bootloader_message.cpp"],
-    cppflags: ["-Werror"],
+    name: "libedify",
+
+    host_supported: true,
+
+    srcs: [
+        "expr.cpp",
+        "lexer.ll",
+        "parser.yy",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-deprecated-register",
+        "-Wno-unused-parameter",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    local_include_dirs: [
+        "include",
+    ],
+
     static_libs: [
         "libbase",
-        "libfs_mgr",
+        "libotautil",
     ],
-    export_include_dirs: ["include"],
 }
diff --git a/edify/edify_parser.cpp b/edify/edify_parser.cpp
deleted file mode 100644
index f1b5628..0000000
--- a/edify/edify_parser.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2009 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.
- */
-
-/**
- * This is a host-side tool for validating a given edify script file.
- *
- * We used to have edify test cases here, which have been moved to
- * tests/component/edify_test.cpp.
- *
- * Caveat: It doesn't recognize functions defined through updater, which
- * makes the tool less useful. We should either extend the tool or remove it.
- */
-
-#include <errno.h>
-#include <stdio.h>
-
-#include <memory>
-#include <string>
-
-#include <android-base/file.h>
-
-#include "expr.h"
-
-static void ExprDump(int depth, const std::unique_ptr<Expr>& n, const std::string& script) {
-    printf("%*s", depth*2, "");
-    printf("%s %p (%d-%d) \"%s\"\n",
-           n->name.c_str(), n->fn, n->start, n->end,
-           script.substr(n->start, n->end - n->start).c_str());
-    for (size_t i = 0; i < n->argv.size(); ++i) {
-        ExprDump(depth+1, n->argv[i], script);
-    }
-}
-
-int main(int argc, char** argv) {
-    RegisterBuiltins();
-
-    if (argc != 2) {
-        printf("Usage: %s <edify script>\n", argv[0]);
-        return 1;
-    }
-
-    std::string buffer;
-    if (!android::base::ReadFileToString(argv[1], &buffer)) {
-        printf("%s: failed to read %s: %s\n", argv[0], argv[1], strerror(errno));
-        return 1;
-    }
-
-    std::unique_ptr<Expr> root;
-    int error_count = 0;
-    int error = parse_string(buffer.data(), &root, &error_count);
-    printf("parse returned %d; %d errors encountered\n", error, error_count);
-    if (error == 0 || error_count > 0) {
-
-        ExprDump(0, root, buffer);
-
-        State state(buffer, nullptr);
-        std::string result;
-        if (!Evaluate(&state, root, &result)) {
-            printf("result was NULL, message is: %s\n",
-                   (state.errmsg.empty() ? "(NULL)" : state.errmsg.c_str()));
-        } else {
-            printf("result is [%s]\n", result.c_str());
-        }
-    }
-    return 0;
-}
diff --git a/edify/expr.cpp b/edify/expr.cpp
index 54ab332..6823b73 100644
--- a/edify/expr.cpp
+++ b/edify/expr.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "expr.h"
+#include "edify/expr.h"
 
 #include <stdarg.h>
 #include <stdio.h>
@@ -31,6 +31,8 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
+#include "otautil/error_code.h"
+
 // Functions should:
 //
 //    - return a malloc()'d string
@@ -112,9 +114,9 @@
 Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
     std::string msg;
     if (!argv.empty() && Evaluate(state, argv[0], &msg)) {
-        state->errmsg = msg;
+      state->errmsg += msg;
     } else {
-        state->errmsg = "called abort()";
+      state->errmsg += "called abort()";
     }
     return nullptr;
 }
@@ -408,16 +410,16 @@
 }
 
 Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) {
-    va_list ap;
-    va_start(ap, format);
-    android::base::StringAppendV(&state->errmsg, format, ap);
-    va_end(ap);
-    state->cause_code = cause_code;
-    return nullptr;
+  std::string err_message;
+  va_list ap;
+  va_start(ap, format);
+  android::base::StringAppendV(&err_message, format, ap);
+  va_end(ap);
+  // Ensure that there's exactly one line break at the end of the error message.
+  state->errmsg = android::base::Trim(err_message) + "\n";
+  state->cause_code = cause_code;
+  return nullptr;
 }
 
-State::State(const std::string& script, void* cookie) :
-    script(script),
-    cookie(cookie) {
-}
-
+State::State(const std::string& script, void* cookie)
+    : script(script), cookie(cookie), error_code(kNoError), cause_code(kNoCause) {}
diff --git a/edify/expr.h b/edify/include/edify/expr.h
similarity index 86%
rename from edify/expr.h
rename to edify/include/edify/expr.h
index 4838d20..770d1cf 100644
--- a/edify/expr.h
+++ b/edify/include/edify/expr.h
@@ -23,32 +23,34 @@
 #include <string>
 #include <vector>
 
-#include "error_code.h"
+// Forward declaration to avoid including "otautil/error_code.h".
+enum ErrorCode : int;
+enum CauseCode : int;
 
 struct State {
-    State(const std::string& script, void* cookie);
+  State(const std::string& script, void* cookie);
 
-    // The source of the original script.
-    const std::string& script;
+  // The source of the original script.
+  const std::string& script;
 
-    // Optional pointer to app-specific data; the core of edify never
-    // uses this value.
-    void* cookie;
+  // Optional pointer to app-specific data; the core of edify never
+  // uses this value.
+  void* cookie;
 
-    // The error message (if any) returned if the evaluation aborts.
-    // Should be empty initially, will be either empty or a string that
-    // Evaluate() returns.
-    std::string errmsg;
+  // The error message (if any) returned if the evaluation aborts.
+  // Should be empty initially, will be either empty or a string that
+  // Evaluate() returns.
+  std::string errmsg;
 
-    // error code indicates the type of failure (e.g. failure to update system image)
-    // during the OTA process.
-    ErrorCode error_code = kNoError;
+  // error code indicates the type of failure (e.g. failure to update system image)
+  // during the OTA process.
+  ErrorCode error_code;
 
-    // cause code provides more detailed reason of an OTA failure (e.g. fsync error)
-    // in addition to the error code.
-    CauseCode cause_code = kNoCause;
+  // cause code provides more detailed reason of an OTA failure (e.g. fsync error)
+  // in addition to the error code.
+  CauseCode cause_code;
 
-    bool is_retry = false;
+  bool is_retry = false;
 };
 
 enum ValueType {
diff --git a/edify/lexer.ll b/edify/lexer.ll
index b764d16..4e04003 100644
--- a/edify/lexer.ll
+++ b/edify/lexer.ll
@@ -18,7 +18,7 @@
 #include <string.h>
 #include <string>
 
-#include "expr.h"
+#include "edify/expr.h"
 #include "yydefs.h"
 #include "parser.h"
 
@@ -35,6 +35,8 @@
 
 %x STR
 
+%option noinput
+%option nounput
 %option noyywrap
 
 %%
diff --git a/edify/parser.yy b/edify/parser.yy
index b1685eb..bd2e010 100644
--- a/edify/parser.yy
+++ b/edify/parser.yy
@@ -25,7 +25,7 @@
 
 #include <android-base/macros.h>
 
-#include "expr.h"
+#include "edify/expr.h"
 #include "yydefs.h"
 #include "parser.h"
 
diff --git a/etc/init.rc b/etc/init.rc
index d1c4526..bcd519b 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -29,8 +29,9 @@
     export ANDROID_DATA /data
     export EXTERNAL_STORAGE /sdcard
 
-    mkdir /boot
-    mkdir /recovery
+    mount cgroup none /acct cpuacct
+    mkdir /acct/uid
+
     mkdir /system
     mkdir /data
     mkdir /cache
diff --git a/exfat/fsck/Android.mk b/exfat/fsck/Android.mk
index 5ad0302..64ebc0f 100644
--- a/exfat/fsck/Android.mk
+++ b/exfat/fsck/Android.mk
@@ -6,7 +6,7 @@
 LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
-LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64
+LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -Wno-sign-compare
 LOCAL_SRC_FILES =  main.c
 LOCAL_C_INCLUDES += $(LOCAL_PATH) \
 					$(commands_recovery_local_path)/exfat/libexfat \
diff --git a/exfat/fuse/Android.mk b/exfat/fuse/Android.mk
index 1b5d084..57f35be 100644
--- a/exfat/fuse/Android.mk
+++ b/exfat/fuse/Android.mk
@@ -5,7 +5,7 @@
 LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
-LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64
+LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -Wno-sign-compare -Wno-unused-parameter
 LOCAL_SRC_FILES = main.c 
 LOCAL_C_INCLUDES += $(LOCAL_PATH) \
 					$(commands_recovery_local_path)/exfat/libexfat \
diff --git a/exfat/libexfat/Android.mk b/exfat/libexfat/Android.mk
index 5476486..64adef7 100644
--- a/exfat/libexfat/Android.mk
+++ b/exfat/libexfat/Android.mk
@@ -3,7 +3,7 @@
 
 LOCAL_MODULE := libexfat_twrp
 LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64
+LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -Wno-sign-compare -Wno-address-of-packed-member -Wno-missing-field-initializers
 LOCAL_SRC_FILES = cluster.c io.c log.c lookup.c mount.c node.c time.c utf.c utils.c
 LOCAL_C_INCLUDES += $(LOCAL_PATH)
 LOCAL_SHARED_LIBRARIES += libc
diff --git a/exfat/libexfat/lookup.c b/exfat/libexfat/lookup.c
index d5bb389..3caef74 100644
--- a/exfat/libexfat/lookup.c
+++ b/exfat/libexfat/lookup.c
@@ -46,7 +46,7 @@
 	it->current = NULL;
 }
 
-struct exfat_node* exfat_readdir(struct exfat* ef, struct exfat_iterator* it)
+struct exfat_node* exfat_readdir(struct exfat* ef __unused, struct exfat_iterator* it)
 {
 	if (it->current == NULL)
 		it->current = it->parent->child;
diff --git a/exfat/mkfs/Android.mk b/exfat/mkfs/Android.mk
index 0b81aa4..9f3e7cb 100644
--- a/exfat/mkfs/Android.mk
+++ b/exfat/mkfs/Android.mk
@@ -5,7 +5,7 @@
 LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
-LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64
+LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -Wno-sign-compare
 LOCAL_SRC_FILES = cbm.c fat.c main.c mkexfat.c rootdir.c uct.c uctc.c vbr.c
 LOCAL_C_INCLUDES += $(LOCAL_PATH) \
 					$(commands_recovery_local_path)/exfat/libexfat \
diff --git a/fuse/Android.mk b/fuse/Android.mk
index 5260e14..00763ea 100644
--- a/fuse/Android.mk
+++ b/fuse/Android.mk
@@ -45,6 +45,8 @@
 	-DFUSE_USE_VERSION=26 \
 	-fno-strict-aliasing
 
+LOCAL_CFLAGS += -Wno-pointer-arith -Wno-sign-compare -Wno-unused-parameter
+
 LOCAL_MODULE := libfusetwrp
 LOCAL_MODULE_TAGS := optional
 
diff --git a/fuse/mount.c b/fuse/mount.c
index eb0bb17..8736856 100644
--- a/fuse/mount.c
+++ b/fuse/mount.c
@@ -282,7 +282,7 @@
 	}
 
 	cmsg = CMSG_FIRSTHDR(&msg);
-	if (!cmsg->cmsg_type == SCM_RIGHTS) {
+	if (cmsg->cmsg_type != SCM_RIGHTS) {
 		fprintf(stderr, "got control message of unknown type %d\n",
 			cmsg->cmsg_type);
 		return -1;
diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp
index b0ecf96..46bdf17 100644
--- a/fuse_sdcard_provider.cpp
+++ b/fuse_sdcard_provider.cpp
@@ -14,72 +14,70 @@
  * limitations under the License.
  */
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
+#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 <fcntl.h>
+
+#include <functional>
 
 #include <android-base/file.h>
 
 #include "fuse_sideload.h"
 
 struct file_data {
-    int fd;  // the underlying sdcard file
+  int fd;  // the underlying sdcard file
 
-    uint64_t file_size;
-    uint32_t block_size;
+  uint64_t file_size;
+  uint32_t block_size;
 };
 
-static int read_block_file(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
-    file_data* fd = reinterpret_cast<file_data*>(cookie);
+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;
+  }
 
-    off64_t offset = ((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;
+  }
 
-    if (!android::base::ReadFully(fd->fd, buffer, fetch_size)) {
-        fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno));
-        return -EIO;
-    }
-
-    return 0;
-}
-
-static void close_file(void* cookie) {
-    file_data* fd = reinterpret_cast<file_data*>(cookie);
-    close(fd->fd);
+  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;
-    }
+  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;
+  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 = read_block_file;
-    vtab.close = close_file;
+  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);
+  // 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, fd.file_size, fd.block_size) == 0;
+  return run_fuse_sideload(vtab, fd.file_size, fd.block_size) == 0;
 }
diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp
index f667cd4..45c79f9 100644
--- a/fuse_sideload.cpp
+++ b/fuse_sideload.cpp
@@ -41,23 +41,20 @@
 // two files is implemented.  In particular, you can't opendir() or
 // readdir() on the "/sideload" directory; ls on it won't work.
 
-#include <ctype.h>
-#include <dirent.h>
+#include "fuse_sideload.h"
+
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
 #include "fuse.h"
 #include <pthread.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/inotify.h>
 #include <sys/mount.h>
-#include <sys/param.h>
-#include <sys/resource.h>
+#include <sys/param.h>  // MIN
 #include <sys/stat.h>
-#include <sys/statfs.h>
-#include <sys/time.h>
 #include <sys/uio.h>
 #include <unistd.h>
 
@@ -68,320 +65,304 @@
 #include <openssl/sha.h>
 #endif
 
-#include "fuse_sideload.h"
+#include <array>
+#include <string>
+#include <vector>
 
-#define PACKAGE_FILE_ID   (FUSE_ROOT_ID+1)
-#define EXIT_FLAG_ID      (FUSE_ROOT_ID+2)
+//#include <android-base/stringprintf.h>
+//#include <android-base/unique_fd.h>
 
-#define NO_STATUS         1
-#define NO_STATUS_EXIT    2
+static constexpr uint64_t PACKAGE_FILE_ID = FUSE_ROOT_ID + 1;
+static constexpr uint64_t EXIT_FLAG_ID = FUSE_ROOT_ID + 2;
+
+static constexpr int NO_STATUS = 1;
+static constexpr int NO_STATUS_EXIT = 2;
+
+using SHA256Digest = std::array<uint8_t, SHA256_DIGEST_LENGTH>;
 
 #ifndef MIN
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
 #endif
 
 struct fuse_data {
-    int ffd;   // file descriptor for the fuse socket
+  int ffd;  // file descriptor for the fuse socket
 
-    struct provider_vtab* vtab;
-    void* cookie;
+  provider_vtab vtab;
 
-    uint64_t file_size;     // bytes
+  uint64_t file_size;  // bytes
 
-    uint32_t block_size;    // block size that the adb host is using to send the file to us
-    uint32_t file_blocks;   // file size in block_size blocks
+  uint32_t block_size;   // block size that the adb host is using to send the file to us
+  uint32_t file_blocks;  // file size in block_size blocks
 
-    uid_t uid;
-    gid_t gid;
+  uid_t uid;
+  gid_t gid;
 
-    uint32_t curr_block;    // cache the block most recently read from the host
-    uint8_t* block_data;
+  uint32_t curr_block;  // cache the block most recently read from the host
+  uint8_t* block_data;
 
-    uint8_t* extra_block;   // another block of storage for reads that
-                            // span two blocks
+  uint8_t* extra_block;  // another block of storage for reads that span two blocks
 
-    uint8_t* hashes;        // SHA-256 hash of each block (all zeros
-                            // if block hasn't been read yet)
+  uint8_t* hashes;        // SHA-256 hash of each block (all zeros
+                          // if block hasn't been read yet)
 };
 
-static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, size_t len)
-{
-    struct fuse_out_header hdr;
-    struct iovec vec[2];
-    int res;
+static void fuse_reply(const fuse_data* fd, uint64_t unique, const void* data, size_t len) {
+  fuse_out_header hdr;
+  hdr.len = len + sizeof(hdr);
+  hdr.error = 0;
+  hdr.unique = unique;
 
-    hdr.len = len + sizeof(hdr);
-    hdr.error = 0;
-    hdr.unique = unique;
+  struct iovec vec[2];
+  vec[0].iov_base = &hdr;
+  vec[0].iov_len = sizeof(hdr);
+  vec[1].iov_base = const_cast<void*>(data);
+  vec[1].iov_len = len;
 
-    vec[0].iov_base = &hdr;
-    vec[0].iov_len = sizeof(hdr);
-    vec[1].iov_base = /* const_cast */(void*)(data);
-    vec[1].iov_len = len;
-
-    res = writev(fd->ffd, vec, 2);
-    if (res < 0) {
-        printf("*** REPLY FAILED *** %s\n", strerror(errno));
-    }
+  int res = writev(fd->ffd, vec, 2);
+  if (res == -1) {
+    printf("*** REPLY FAILED *** %s\n", strerror(errno));
+  }
 }
 
-static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
-    const struct fuse_init_in* req = reinterpret_cast<const struct fuse_init_in*>(data);
-    struct fuse_init_out out;
-    size_t fuse_struct_size;
+static int handle_init(void* data, fuse_data* fd, const fuse_in_header* hdr) {
+  const fuse_init_in* req = static_cast<const fuse_init_in*>(data);
 
+  // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out defined (fuse version 7.6).
+  // The structure is the same from 7.6 through 7.22. Beginning with 7.23, the structure increased
+  // in size and added new parameters.
+  if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) {
+    printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6", req->major,
+           req->minor, FUSE_KERNEL_VERSION);
+    return -1;
+  }
 
-    /* Kernel 2.6.16 is the first stable kernel with struct fuse_init_out
-     * defined (fuse version 7.6). The structure is the same from 7.6 through
-     * 7.22. Beginning with 7.23, the structure increased in size and added
-     * new parameters.
-     */
-    if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) {
-        printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6",
-               req->major, req->minor, FUSE_KERNEL_VERSION);
-        return -1;
-    }
-
-    out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION);
-    fuse_struct_size = sizeof(out);
+  fuse_init_out out;
+  out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION);
+  size_t fuse_struct_size = sizeof(out);
 #if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
-    /* FUSE_KERNEL_VERSION >= 23. */
+  /* FUSE_KERNEL_VERSION >= 23. */
 
-    /* If the kernel only works on minor revs older than or equal to 22,
-     * then use the older structure size since this code only uses the 7.22
-     * version of the structure. */
-    if (req->minor <= 22) {
-        fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
-    }
+  // If the kernel only works on minor revs older than or equal to 22, then use the older structure
+  // size since this code only uses the 7.22 version of the structure.
+  if (req->minor <= 22) {
+    fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
+  }
 #endif
 
-    out.major = FUSE_KERNEL_VERSION;
-    out.max_readahead = req->max_readahead;
-    out.flags = 0;
-    out.max_background = 32;
-    out.congestion_threshold = 32;
-    out.max_write = 4096;
-    fuse_reply(fd, hdr->unique, &out, fuse_struct_size);
+  out.major = FUSE_KERNEL_VERSION;
+  out.max_readahead = req->max_readahead;
+  out.flags = 0;
+  out.max_background = 32;
+  out.congestion_threshold = 32;
+  out.max_write = 4096;
+  fuse_reply(fd, hdr->unique, &out, fuse_struct_size);
 
-    return NO_STATUS;
+  return NO_STATUS;
 }
 
-static void fill_attr(struct fuse_attr* attr, struct fuse_data* fd,
-                      uint64_t nodeid, uint64_t size, uint32_t mode) {
-    memset(attr, 0, sizeof(*attr));
-    attr->nlink = 1;
-    attr->uid = fd->uid;
-    attr->gid = fd->gid;
-    attr->blksize = 4096;
+static void fill_attr(fuse_attr* attr, const fuse_data* fd, uint64_t nodeid, uint64_t size,
+                      uint32_t mode) {
+  memset(attr, 0, sizeof(*attr));
+  attr->nlink = 1;
+  attr->uid = fd->uid;
+  attr->gid = fd->gid;
+  attr->blksize = 4096;
 
-    attr->ino = nodeid;
-    attr->size = size;
-    attr->blocks = (size == 0) ? 0 : (((size-1) / attr->blksize) + 1);
-    attr->mode = mode;
+  attr->ino = nodeid;
+  attr->size = size;
+  attr->blocks = (size == 0) ? 0 : (((size - 1) / attr->blksize) + 1);
+  attr->mode = mode;
 }
 
-static int handle_getattr(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) {
-    struct fuse_attr_out out;
-    memset(&out, 0, sizeof(out));
-    out.attr_valid = 10;
+static int handle_getattr(void* /* data */, const fuse_data* fd, const fuse_in_header* hdr) {
+  struct fuse_attr_out out;
+  memset(&out, 0, sizeof(out));
+  out.attr_valid = 10;
 
-    if (hdr->nodeid == FUSE_ROOT_ID) {
-        fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555);
-    } else if (hdr->nodeid == PACKAGE_FILE_ID) {
-        fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
-    } else if (hdr->nodeid == EXIT_FLAG_ID) {
-        fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
-    } else {
-        return -ENOENT;
-    }
+  if (hdr->nodeid == FUSE_ROOT_ID) {
+    fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555);
+  } else if (hdr->nodeid == PACKAGE_FILE_ID) {
+    fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
+  } else if (hdr->nodeid == EXIT_FLAG_ID) {
+    fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
+  } else {
+    return -ENOENT;
+  }
 
-    fuse_reply(fd, hdr->unique, &out, sizeof(out));
-    return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
+  fuse_reply(fd, hdr->unique, &out, sizeof(out));
+  return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
 }
 
-static int handle_lookup(void* data, struct fuse_data* fd,
-                         const struct fuse_in_header* hdr) {
-    struct fuse_entry_out out;
-    memset(&out, 0, sizeof(out));
-    out.entry_valid = 10;
-    out.attr_valid = 10;
+static int handle_lookup(void* data, const fuse_data* fd, const fuse_in_header* hdr) {
+  if (data == nullptr) return -ENOENT;
 
-    if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, reinterpret_cast<const char*>(data),
-                sizeof(FUSE_SIDELOAD_HOST_FILENAME)) == 0) {
-        out.nodeid = PACKAGE_FILE_ID;
-        out.generation = PACKAGE_FILE_ID;
-        fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
-    } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, reinterpret_cast<const char*>(data),
-                       sizeof(FUSE_SIDELOAD_HOST_EXIT_FLAG)) == 0) {
-        out.nodeid = EXIT_FLAG_ID;
-        out.generation = EXIT_FLAG_ID;
-        fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
-    } else {
-        return -ENOENT;
-    }
+  struct fuse_entry_out out;
+  memset(&out, 0, sizeof(out));
+  out.entry_valid = 10;
+  out.attr_valid = 10;
 
-    fuse_reply(fd, hdr->unique, &out, sizeof(out));
-    return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
+  std::string filename(static_cast<const char*>(data));
+  if (filename == FUSE_SIDELOAD_HOST_FILENAME) {
+    out.nodeid = PACKAGE_FILE_ID;
+    out.generation = PACKAGE_FILE_ID;
+    fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
+  } else if (filename == FUSE_SIDELOAD_HOST_EXIT_FLAG) {
+    out.nodeid = EXIT_FLAG_ID;
+    out.generation = EXIT_FLAG_ID;
+    fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
+  } else {
+    return -ENOENT;
+  }
+
+  fuse_reply(fd, hdr->unique, &out, sizeof(out));
+  return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
 }
 
-static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) {
-    if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM;
-    if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
+static int handle_open(void* /* data */, const fuse_data* fd, const fuse_in_header* hdr) {
+  if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM;
+  if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
 
-    struct fuse_open_out out;
-    memset(&out, 0, sizeof(out));
-    out.fh = 10;  // an arbitrary number; we always use the same handle
-    fuse_reply(fd, hdr->unique, &out, sizeof(out));
-    return NO_STATUS;
+  struct fuse_open_out out;
+  memset(&out, 0, sizeof(out));
+  out.fh = 10;  // an arbitrary number; we always use the same handle
+  fuse_reply(fd, hdr->unique, &out, sizeof(out));
+  return NO_STATUS;
 }
 
-static int handle_flush(void* /* data */, struct fuse_data* /* fd */,
-                        const struct fuse_in_header* /* hdr */) {
-    return 0;
+static int handle_flush(void* /* data */, fuse_data* /* fd */, const fuse_in_header* /* hdr */) {
+  return 0;
 }
 
-static int handle_release(void* /* data */, struct fuse_data* /* fd */,
-                          const struct fuse_in_header* /* hdr */) {
-    return 0;
+static int handle_release(void* /* data */, fuse_data* /* fd */, const fuse_in_header* /* hdr */) {
+  return 0;
 }
 
 // Fetch a block from the host into fd->curr_block and fd->block_data.
 // Returns 0 on successful fetch, negative otherwise.
-static int fetch_block(struct fuse_data* fd, uint32_t block) {
-    if (block == fd->curr_block) {
-        return 0;
-    }
-
-    if (block >= fd->file_blocks) {
-        memset(fd->block_data, 0, fd->block_size);
-        fd->curr_block = block;
-        return 0;
-    }
-
-    size_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.
-        fetch_size = fd->file_size - (block * fd->block_size);
-        memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size);
-    }
-
-    int result = fd->vtab->read_block(fd->cookie, block, fd->block_data, fetch_size);
-    if (result < 0) return result;
-
-    fd->curr_block = block;
-
-    // Verify the hash of the block we just got from the host.
-    //
-    // - If the hash of the just-received data matches the stored hash
-    //   for the block, accept it.
-    // - If the stored hash is all zeroes, store the new hash and
-    //   accept the block (this is the first time we've read this
-    //   block).
-    // - Otherwise, return -EINVAL for the read.
-
-    uint8_t hash[SHA256_DIGEST_LENGTH];
-#ifdef USE_MINCRYPT
-    SHA256_hash(fd->block_data, fd->block_size, hash);
-#else
-    SHA256(fd->block_data, fd->block_size, hash);
-#endif
-    uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH;
-    if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) {
-        return 0;
-    }
-
-    int i;
-    for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
-        if (blockhash[i] != 0) {
-            fd->curr_block = -1;
-            return -EIO;
-        }
-    }
-
-    memcpy(blockhash, hash, SHA256_DIGEST_LENGTH);
+static int fetch_block(fuse_data* fd, uint32_t block) {
+  if (block == fd->curr_block) {
     return 0;
+  }
+
+  if (block >= fd->file_blocks) {
+    memset(fd->block_data, 0, fd->block_size);
+    fd->curr_block = block;
+    return 0;
+  }
+
+  size_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.
+    fetch_size = fd->file_size - (block * fd->block_size);
+    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;
+
+  fd->curr_block = block;
+
+  // Verify the hash of the block we just got from the host.
+  //
+  // - If the hash of the just-received data matches the stored hash for the block, accept it.
+  // - If the stored hash is all zeroes, store the new hash and accept the block (this is the first
+  //   time we've read this block).
+  // - Otherwise, return -EINVAL for the read.
+
+  uint8_t hash[SHA256_DIGEST_LENGTH];
+#ifdef USE_MINCRYPT
+  SHA256_hash(fd->block_data, fd->block_size, hash);
+#else
+  SHA256(fd->block_data, fd->block_size, hash);
+#endif
+  uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH;
+  if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) {
+    return 0;
+  }
+
+  int i;
+  for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
+    if (blockhash[i] != 0) {
+      fd->curr_block = -1;
+      return -EIO;
+    }
+  }
+
+  memcpy(blockhash, hash, SHA256_DIGEST_LENGTH);
+  return 0;
 }
 
-static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
-    const struct fuse_read_in* req = reinterpret_cast<const struct fuse_read_in*>(data);
-    struct fuse_out_header outhdr;
-    struct iovec vec[3];
-    int vec_used;
-    int result;
+static int handle_read(void* data, fuse_data* fd, const fuse_in_header* hdr) {
+  if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
 
-    if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
+  const fuse_read_in* req = static_cast<const fuse_read_in*>(data);
+  uint64_t offset = req->offset;
+  uint32_t size = req->size;
 
-    uint64_t offset = req->offset;
-    uint32_t size = req->size;
+  // The docs on the fuse kernel interface are vague about what to do when a read request extends
+  // past the end of the file. We can return a short read -- the return structure does include a
+  // length field -- but in testing that caused the program using the file to segfault. (I
+  // speculate that this is due to the reading program accessing it via mmap; maybe mmap dislikes
+  // when you return something short of a whole page?) To fix this we zero-pad reads that extend
+  // past the end of the file so we're always returning exactly as many bytes as were requested.
+  // (Users of the mapped file have to know its real length anyway.)
 
-    // The docs on the fuse kernel interface are vague about what to
-    // do when a read request extends past the end of the file.  We
-    // can return a short read -- the return structure does include a
-    // length field -- but in testing that caused the program using
-    // the file to segfault.  (I speculate that this is due to the
-    // reading program accessing it via mmap; maybe mmap dislikes when
-    // you return something short of a whole page?)  To fix this we
-    // zero-pad reads that extend past the end of the file so we're
-    // always returning exactly as many bytes as were requested.
-    // (Users of the mapped file have to know its real length anyway.)
+  fuse_out_header outhdr;
+  outhdr.len = sizeof(outhdr) + size;
+  outhdr.error = 0;
+  outhdr.unique = hdr->unique;
 
-    outhdr.len = sizeof(outhdr) + size;
-    outhdr.error = 0;
-    outhdr.unique = hdr->unique;
-    vec[0].iov_base = &outhdr;
-    vec[0].iov_len = sizeof(outhdr);
+  struct iovec vec[3];
+  vec[0].iov_base = &outhdr;
+  vec[0].iov_len = sizeof(outhdr);
 
-    uint32_t block = offset / fd->block_size;
-    result = fetch_block(fd, block);
+  uint32_t block = offset / fd->block_size;
+  int result = fetch_block(fd, block);
+  if (result != 0) return result;
+
+  // Two cases:
+  //
+  //   - the read request is entirely within this block. In this case we can reply immediately.
+  //
+  //   - the read request goes over into the next block. Note that since we mount the filesystem
+  //     with max_read=block_size, a read can never span more than two blocks. In this case we copy
+  //     the block to extra_block and issue a fetch for the following block.
+
+  uint32_t block_offset = offset - (block * fd->block_size);
+
+  int vec_used;
+  if (size + block_offset <= fd->block_size) {
+    // First case: the read fits entirely in the first block.
+
+    vec[1].iov_base = fd->block_data + block_offset;
+    vec[1].iov_len = size;
+    vec_used = 2;
+  } else {
+    // Second case: the read spills over into the next block.
+
+    memcpy(fd->extra_block, fd->block_data + block_offset, fd->block_size - block_offset);
+    vec[1].iov_base = fd->extra_block;
+    vec[1].iov_len = fd->block_size - block_offset;
+
+    result = fetch_block(fd, block + 1);
     if (result != 0) return result;
+    vec[2].iov_base = fd->block_data;
+    vec[2].iov_len = size - vec[1].iov_len;
+    vec_used = 3;
+  }
 
-    // Two cases:
-    //
-    //   - the read request is entirely within this block.  In this
-    //     case we can reply immediately.
-    //
-    //   - the read request goes over into the next block.  Note that
-    //     since we mount the filesystem with max_read=block_size, a
-    //     read can never span more than two blocks.  In this case we
-    //     copy the block to extra_block and issue a fetch for the
-    //     following block.
-
-    uint32_t block_offset = offset - (block * fd->block_size);
-
-    if (size + block_offset <= fd->block_size) {
-        // First case: the read fits entirely in the first block.
-
-        vec[1].iov_base = fd->block_data + block_offset;
-        vec[1].iov_len = size;
-        vec_used = 2;
-    } else {
-        // Second case: the read spills over into the next block.
-
-        memcpy(fd->extra_block, fd->block_data + block_offset,
-               fd->block_size - block_offset);
-        vec[1].iov_base = fd->extra_block;
-        vec[1].iov_len = fd->block_size - block_offset;
-
-        result = fetch_block(fd, block+1);
-        if (result != 0) return result;
-        vec[2].iov_base = fd->block_data;
-        vec[2].iov_len = size - vec[1].iov_len;
-        vec_used = 3;
-    }
-
-    if (writev(fd->ffd, vec, vec_used) < 0) {
-        printf("*** READ REPLY FAILED: %s ***\n", strerror(errno));
-    }
-    return NO_STATUS;
+  if (writev(fd->ffd, vec, vec_used) == -1) {
+    printf("*** READ REPLY FAILED: %s ***\n", strerror(errno));
+  }
+  return NO_STATUS;
 }
 
-int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size,
-                      uint32_t block_size) {
+int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size,
+                      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(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE);
+  umount2(mount_point, MNT_FORCE);
 
   // fs/fuse/inode.c in kernel code uses the greater of 4096 and the passed-in max_read.
   if (block_size < 4096) {
@@ -393,9 +374,9 @@
     return -1;
   }
 
-  struct fuse_data fd = {};
+  fuse_data fd;
+  memset(&fd, 0, sizeof(fd));
   fd.vtab = vtab;
-  fd.cookie = cookie;
   fd.file_size = file_size;
   fd.block_size = block_size;
   fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1);
@@ -419,42 +400,42 @@
   fd.gid = getgid();
 
   fd.curr_block = -1;
-  fd.block_data = (uint8_t*)malloc(block_size);
-  if (fd.block_data == NULL) {
+  fd.block_data = static_cast<uint8_t*>(malloc(block_size));
+  if (fd.block_data == nullptr) {
     fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size);
     result = -1;
     goto done;
   }
-  fd.extra_block = (uint8_t*)malloc(block_size);
-  if (fd.extra_block == NULL) {
+  fd.extra_block = static_cast<uint8_t*>(malloc(block_size));
+  if (fd.extra_block == nullptr) {
     fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size);
     result = -1;
     goto done;
   }
 
   fd.ffd = open("/dev/fuse", O_RDWR);
-  if (fd.ffd < 0) {
+  if (!fd.ffd) {
     perror("open /dev/fuse");
     result = -1;
     goto done;
   }
 
   {
-    char opts[256];
-    snprintf(opts, sizeof(opts),
-             ("fd=%d,user_id=%d,group_id=%d,max_read=%u,"
-              "allow_other,rootmode=040000"),
-             fd.ffd, fd.uid, fd.gid, block_size);
+  char opts[256];
+  snprintf(opts, sizeof(opts),
+          ("fd=%d,user_id=%d,group_id=%d,max_read=%u,"
+           "allow_other,rootmode=040000"),
+           fd.ffd, fd.uid, fd.gid, block_size);
 
-    result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, "fuse",
-                   MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts);
-    if (result < 0) {
+  result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, "fuse",
+                 MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts);
+  if (result < 0) {
       perror("mount");
       goto done;
     }
   }
 
-  uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX * 8];
+  uint8_t request_buffer[sizeof(fuse_in_header) + PATH_MAX * 8];
   for (;;) {
     ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer)));
     if (len == -1) {
@@ -466,13 +447,13 @@
       continue;
     }
 
-    if (static_cast<size_t>(len) < sizeof(struct fuse_in_header)) {
+    if (static_cast<size_t>(len) < sizeof(fuse_in_header)) {
       fprintf(stderr, "request too short: len=%zd\n", len);
       continue;
     }
 
-    struct fuse_in_header* hdr = reinterpret_cast<struct fuse_in_header*>(request_buffer);
-    void* data = request_buffer + sizeof(struct fuse_in_header);
+    fuse_in_header* hdr = reinterpret_cast<fuse_in_header*>(request_buffer);
+    void* data = request_buffer + sizeof(fuse_in_header);
 
     result = -ENOSYS;
 
@@ -516,7 +497,7 @@
     }
 
     if (result != NO_STATUS) {
-      struct fuse_out_header outhdr;
+      fuse_out_header outhdr;
       outhdr.len = sizeof(outhdr);
       outhdr.error = result;
       outhdr.unique = hdr->unique;
@@ -525,23 +506,20 @@
   }
 
 done:
-  fd.vtab->close(fd.cookie);
+  fd.vtab.close();
 
-  result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH);
-  if (result < 0) {
-    printf("fuse_sideload umount failed: %s\n", strerror(errno));
+  if (umount2(mount_point, MNT_DETACH) == -1) {
+    fprintf(stderr, "fuse_sideload umount failed: %s\n", strerror(errno));
   }
 
-  if (fd.ffd) close(fd.ffd);
-  free(fd.hashes);
   free(fd.block_data);
   free(fd.extra_block);
 
   return result;
 }
 
-extern "C" int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie,
+extern "C" int run_old_fuse_sideload(const struct provider_vtab& vtab, void* cookie __unused,
                       uint64_t file_size, uint32_t block_size)
 {
-    return run_fuse_sideload(vtab, cookie, file_size, block_size);
+    return run_fuse_sideload(vtab, file_size, block_size, FUSE_SIDELOAD_HOST_MOUNTPOINT);
 }
diff --git a/fuse_sideload.h b/fuse_sideload.h
index 2a8ebfc..1ea1eb8 100644
--- a/fuse_sideload.h
+++ b/fuse_sideload.h
@@ -17,31 +17,39 @@
 #ifndef __FUSE_SIDELOAD_H
 #define __FUSE_SIDELOAD_H
 
-// define the filenames created by the sideload FUSE filesystem
-#define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload"
-#define FUSE_SIDELOAD_HOST_FILENAME "package.zip"
-#define FUSE_SIDELOAD_HOST_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_FILENAME)
-#define FUSE_SIDELOAD_HOST_EXIT_FLAG "exit"
-#define FUSE_SIDELOAD_HOST_EXIT_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_EXIT_FLAG)
+#ifdef USE_FUSE_SIDELOAD22
+#include "fuse_sideload22.h"
+#else
+
+#include <functional>
+
+// Define the filenames created by the sideload FUSE filesystem.
+static constexpr const char* FUSE_SIDELOAD_HOST_MOUNTPOINT = "/sideload";
+static constexpr const char* FUSE_SIDELOAD_HOST_FILENAME = "package.zip";
+static constexpr const char* FUSE_SIDELOAD_HOST_PATHNAME = "/sideload/package.zip";
+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
-    int (*read_block)(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size);
+  // read a block
+  std::function<int(uint32_t block, uint8_t* buffer, uint32_t fetch_size)> read_block;
 
-    // close down
-    void (*close)(void* cookie);
+  // close down
+  std::function<void(void)> close;
 };
 
-int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
-                      uint64_t file_size, uint32_t block_size);
+int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size,
+                      const char* mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT);
 
 #ifdef __cplusplus
 extern "C" {
 #endif
-int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie,
+int run_old_fuse_sideload(const struct provider_vtab& vtab, void* cookie,
                       uint64_t file_size, uint32_t block_size);
 #ifdef __cplusplus
 }
 #endif
 
 #endif
+
+#endif
diff --git a/fuse_sideload22.cpp b/fuse_sideload22.cpp
new file mode 100644
index 0000000..f57d479
--- /dev/null
+++ b/fuse_sideload22.cpp
@@ -0,0 +1,544 @@
+/*
+ * 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.
+ */
+
+// This module creates a special filesystem containing two files.
+//
+// "/sideload/package.zip" appears to be a normal file, but reading
+// from it causes data to be fetched from the adb host.  We can use
+// this to sideload packages over an adb connection without having to
+// store the entire package in RAM on the device.
+//
+// Because we may not trust the adb host, this filesystem maintains
+// the following invariant: each read of a given position returns the
+// same data as the first read at that position.  That is, once a
+// section of the file is read, future reads of that section return
+// the same data.  (Otherwise, a malicious adb host process could
+// return one set of bits when the package is read for signature
+// verification, and then different bits for when the package is
+// accessed by the installer.)  If the adb host returns something
+// different than it did on the first read, the reader of the file
+// will see their read fail with EINVAL.
+//
+// The other file, "/sideload/exit", is used to control the subprocess
+// that creates this filesystem.  Calling stat() on the exit file
+// causes the filesystem to be unmounted and the adb process on the
+// device shut down.
+//
+// Note that only the minimal set of file operations needed for these
+// two files is implemented.  In particular, you can't opendir() or
+// readdir() on the "/sideload" directory; ls on it won't work.
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include "fuse.h"
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#ifdef USE_MINCRYPT
+#include "mincrypt/sha256.h"
+#define  SHA256_DIGEST_LENGTH SHA256_DIGEST_SIZE
+#else
+#include <openssl/sha.h>
+#endif
+
+#include "fuse_sideload.h"
+
+#define PACKAGE_FILE_ID   (FUSE_ROOT_ID+1)
+#define EXIT_FLAG_ID      (FUSE_ROOT_ID+2)
+
+#define NO_STATUS         1
+#define NO_STATUS_EXIT    2
+
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+struct fuse_data {
+    int ffd;   // file descriptor for the fuse socket
+
+    struct provider_vtab* vtab;
+    void* cookie;
+
+    uint64_t file_size;     // bytes
+
+    uint32_t block_size;    // block size that the adb host is using to send the file to us
+    uint32_t file_blocks;   // file size in block_size blocks
+
+    uid_t uid;
+    gid_t gid;
+
+    uint32_t curr_block;    // cache the block most recently read from the host
+    uint8_t* block_data;
+
+    uint8_t* extra_block;   // another block of storage for reads that
+                            // span two blocks
+
+    uint8_t* hashes;        // SHA-256 hash of each block (all zeros
+                            // if block hasn't been read yet)
+};
+
+static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, size_t len)
+{
+    struct fuse_out_header hdr;
+    struct iovec vec[2];
+    int res;
+
+    hdr.len = len + sizeof(hdr);
+    hdr.error = 0;
+    hdr.unique = unique;
+
+    vec[0].iov_base = &hdr;
+    vec[0].iov_len = sizeof(hdr);
+    vec[1].iov_base = /* const_cast */(void*)(data);
+    vec[1].iov_len = len;
+
+    res = writev(fd->ffd, vec, 2);
+    if (res < 0) {
+        printf("*** REPLY FAILED *** %s\n", strerror(errno));
+    }
+}
+
+static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    const struct fuse_init_in* req = reinterpret_cast<const struct fuse_init_in*>(data);
+    struct fuse_init_out out;
+    size_t fuse_struct_size;
+
+
+    /* Kernel 2.6.16 is the first stable kernel with struct fuse_init_out
+     * defined (fuse version 7.6). The structure is the same from 7.6 through
+     * 7.22. Beginning with 7.23, the structure increased in size and added
+     * new parameters.
+     */
+    if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) {
+        printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6",
+               req->major, req->minor, FUSE_KERNEL_VERSION);
+        return -1;
+    }
+
+    out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION);
+    fuse_struct_size = sizeof(out);
+#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
+    /* FUSE_KERNEL_VERSION >= 23. */
+
+    /* If the kernel only works on minor revs older than or equal to 22,
+     * then use the older structure size since this code only uses the 7.22
+     * version of the structure. */
+    if (req->minor <= 22) {
+        fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
+    }
+#endif
+
+    out.major = FUSE_KERNEL_VERSION;
+    out.max_readahead = req->max_readahead;
+    out.flags = 0;
+    out.max_background = 32;
+    out.congestion_threshold = 32;
+    out.max_write = 4096;
+    fuse_reply(fd, hdr->unique, &out, fuse_struct_size);
+
+    return NO_STATUS;
+}
+
+static void fill_attr(struct fuse_attr* attr, struct fuse_data* fd,
+                      uint64_t nodeid, uint64_t size, uint32_t mode) {
+    memset(attr, 0, sizeof(*attr));
+    attr->nlink = 1;
+    attr->uid = fd->uid;
+    attr->gid = fd->gid;
+    attr->blksize = 4096;
+
+    attr->ino = nodeid;
+    attr->size = size;
+    attr->blocks = (size == 0) ? 0 : (((size-1) / attr->blksize) + 1);
+    attr->mode = mode;
+}
+
+static int handle_getattr(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    struct fuse_attr_out out;
+    memset(&out, 0, sizeof(out));
+    out.attr_valid = 10;
+
+    if (hdr->nodeid == FUSE_ROOT_ID) {
+        fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555);
+    } else if (hdr->nodeid == PACKAGE_FILE_ID) {
+        fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
+    } else if (hdr->nodeid == EXIT_FLAG_ID) {
+        fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
+    } else {
+        return -ENOENT;
+    }
+
+    fuse_reply(fd, hdr->unique, &out, sizeof(out));
+    return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
+}
+
+static int handle_lookup(void* data, struct fuse_data* fd,
+                         const struct fuse_in_header* hdr) {
+    struct fuse_entry_out out;
+    memset(&out, 0, sizeof(out));
+    out.entry_valid = 10;
+    out.attr_valid = 10;
+
+    if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, reinterpret_cast<const char*>(data),
+                sizeof(FUSE_SIDELOAD_HOST_FILENAME)) == 0) {
+        out.nodeid = PACKAGE_FILE_ID;
+        out.generation = PACKAGE_FILE_ID;
+        fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
+    } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, reinterpret_cast<const char*>(data),
+                       sizeof(FUSE_SIDELOAD_HOST_EXIT_FLAG)) == 0) {
+        out.nodeid = EXIT_FLAG_ID;
+        out.generation = EXIT_FLAG_ID;
+        fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
+    } else {
+        return -ENOENT;
+    }
+
+    fuse_reply(fd, hdr->unique, &out, sizeof(out));
+    return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
+}
+
+static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM;
+    if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
+
+    struct fuse_open_out out;
+    memset(&out, 0, sizeof(out));
+    out.fh = 10;  // an arbitrary number; we always use the same handle
+    fuse_reply(fd, hdr->unique, &out, sizeof(out));
+    return NO_STATUS;
+}
+
+static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    return 0;
+}
+
+static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    return 0;
+}
+
+// Fetch a block from the host into fd->curr_block and fd->block_data.
+// Returns 0 on successful fetch, negative otherwise.
+static int fetch_block(struct fuse_data* fd, uint32_t block) {
+    if (block == fd->curr_block) {
+        return 0;
+    }
+
+    if (block >= fd->file_blocks) {
+        memset(fd->block_data, 0, fd->block_size);
+        fd->curr_block = block;
+        return 0;
+    }
+
+    size_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.
+        fetch_size = fd->file_size - (block * fd->block_size);
+        memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size);
+    }
+
+    int result = fd->vtab->read_block(fd->cookie, block, fd->block_data, fetch_size);
+    if (result < 0) return result;
+
+    fd->curr_block = block;
+
+    // Verify the hash of the block we just got from the host.
+    //
+    // - If the hash of the just-received data matches the stored hash
+    //   for the block, accept it.
+    // - If the stored hash is all zeroes, store the new hash and
+    //   accept the block (this is the first time we've read this
+    //   block).
+    // - Otherwise, return -EINVAL for the read.
+
+    uint8_t hash[SHA256_DIGEST_LENGTH];
+#ifdef USE_MINCRYPT
+    SHA256_hash(fd->block_data, fd->block_size, hash);
+#else
+    SHA256(fd->block_data, fd->block_size, hash);
+#endif
+    uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH;
+    if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) {
+        return 0;
+    }
+
+    int i;
+    for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
+        if (blockhash[i] != 0) {
+            fd->curr_block = -1;
+            return -EIO;
+        }
+    }
+
+    memcpy(blockhash, hash, SHA256_DIGEST_LENGTH);
+    return 0;
+}
+
+static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    const struct fuse_read_in* req = reinterpret_cast<const struct fuse_read_in*>(data);
+    struct fuse_out_header outhdr;
+    struct iovec vec[3];
+    int vec_used;
+    int result;
+
+    if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
+
+    uint64_t offset = req->offset;
+    uint32_t size = req->size;
+
+    // The docs on the fuse kernel interface are vague about what to
+    // do when a read request extends past the end of the file.  We
+    // can return a short read -- the return structure does include a
+    // length field -- but in testing that caused the program using
+    // the file to segfault.  (I speculate that this is due to the
+    // reading program accessing it via mmap; maybe mmap dislikes when
+    // you return something short of a whole page?)  To fix this we
+    // zero-pad reads that extend past the end of the file so we're
+    // always returning exactly as many bytes as were requested.
+    // (Users of the mapped file have to know its real length anyway.)
+
+    outhdr.len = sizeof(outhdr) + size;
+    outhdr.error = 0;
+    outhdr.unique = hdr->unique;
+    vec[0].iov_base = &outhdr;
+    vec[0].iov_len = sizeof(outhdr);
+
+    uint32_t block = offset / fd->block_size;
+    result = fetch_block(fd, block);
+    if (result != 0) return result;
+
+    // Two cases:
+    //
+    //   - the read request is entirely within this block.  In this
+    //     case we can reply immediately.
+    //
+    //   - the read request goes over into the next block.  Note that
+    //     since we mount the filesystem with max_read=block_size, a
+    //     read can never span more than two blocks.  In this case we
+    //     copy the block to extra_block and issue a fetch for the
+    //     following block.
+
+    uint32_t block_offset = offset - (block * fd->block_size);
+
+    if (size + block_offset <= fd->block_size) {
+        // First case: the read fits entirely in the first block.
+
+        vec[1].iov_base = fd->block_data + block_offset;
+        vec[1].iov_len = size;
+        vec_used = 2;
+    } else {
+        // Second case: the read spills over into the next block.
+
+        memcpy(fd->extra_block, fd->block_data + block_offset,
+               fd->block_size - block_offset);
+        vec[1].iov_base = fd->extra_block;
+        vec[1].iov_len = fd->block_size - block_offset;
+
+        result = fetch_block(fd, block+1);
+        if (result != 0) return result;
+        vec[2].iov_base = fd->block_data;
+        vec[2].iov_len = size - vec[1].iov_len;
+        vec_used = 3;
+    }
+
+    if (writev(fd->ffd, vec, vec_used) < 0) {
+        printf("*** READ REPLY FAILED: %s ***\n", strerror(errno));
+    }
+    return NO_STATUS;
+}
+
+int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
+                      uint64_t file_size, uint32_t block_size)
+{
+    int result;
+
+    // If something's already mounted on our mountpoint, try to remove
+    // it.  (Mostly in case of a previous abnormal exit.)
+    umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE);
+
+    if (block_size < 1024) {
+        fprintf(stderr, "block size (%u) is too small\n", block_size);
+        return -1;
+    }
+    if (block_size > (1<<22)) {   // 4 MiB
+        fprintf(stderr, "block size (%u) is too large\n", block_size);
+        return -1;
+    }
+
+    struct fuse_data fd;
+    memset(&fd, 0, sizeof(fd));
+    fd.vtab = vtab;
+    fd.cookie = cookie;
+    fd.file_size = file_size;
+    fd.block_size = block_size;
+    fd.file_blocks = (file_size == 0) ? 0 : (((file_size-1) / block_size) + 1);
+
+    if (fd.file_blocks > (1<<18)) {
+        fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks);
+        result = -1;
+        goto done;
+    }
+
+    fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH);
+    if (fd.hashes == NULL) {
+        fprintf(stderr, "failed to allocate %d bites for hashes\n",
+                fd.file_blocks * SHA256_DIGEST_LENGTH);
+        result = -1;
+        goto done;
+    }
+
+    fd.uid = getuid();
+    fd.gid = getgid();
+
+    fd.curr_block = -1;
+    fd.block_data = (uint8_t*)malloc(block_size);
+    if (fd.block_data == NULL) {
+        fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size);
+        result = -1;
+        goto done;
+    }
+    fd.extra_block = (uint8_t*)malloc(block_size);
+    if (fd.extra_block == NULL) {
+        fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size);
+        result = -1;
+        goto done;
+    }
+
+    fd.ffd = open("/dev/fuse", O_RDWR);
+    if (fd.ffd < 0) {
+        perror("open /dev/fuse");
+        result = -1;
+        goto done;
+    }
+
+    char opts[256];
+    snprintf(opts, sizeof(opts),
+             ("fd=%d,user_id=%d,group_id=%d,max_read=%u,"
+              "allow_other,rootmode=040000"),
+             fd.ffd, fd.uid, fd.gid, block_size);
+
+    result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT,
+                   "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts);
+    if (result < 0) {
+        perror("mount");
+        goto done;
+    }
+    uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX*8];
+    for (;;) {
+        ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer)));
+        if (len == -1) {
+            perror("read request");
+            if (errno == ENODEV) {
+                result = -1;
+                break;
+            }
+            continue;
+        }
+
+        if ((size_t)len < sizeof(struct fuse_in_header)) {
+            fprintf(stderr, "request too short: len=%zu\n", (size_t)len);
+            continue;
+        }
+
+        struct fuse_in_header* hdr = (struct fuse_in_header*) request_buffer;
+        void* data = request_buffer + sizeof(struct fuse_in_header);
+
+        result = -ENOSYS;
+
+        switch (hdr->opcode) {
+             case FUSE_INIT:
+                result = handle_init(data, &fd, hdr);
+                break;
+
+             case FUSE_LOOKUP:
+                result = handle_lookup(data, &fd, hdr);
+                break;
+
+            case FUSE_GETATTR:
+                result = handle_getattr(data, &fd, hdr);
+                break;
+
+            case FUSE_OPEN:
+                result = handle_open(data, &fd, hdr);
+                break;
+
+            case FUSE_READ:
+                result = handle_read(data, &fd, hdr);
+                break;
+
+            case FUSE_FLUSH:
+                result = handle_flush(data, &fd, hdr);
+                break;
+
+            case FUSE_RELEASE:
+                result = handle_release(data, &fd, hdr);
+                break;
+
+            default:
+                fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode);
+                break;
+        }
+
+        if (result == NO_STATUS_EXIT) {
+            result = 0;
+            break;
+        }
+
+        if (result != NO_STATUS) {
+            struct fuse_out_header outhdr;
+            outhdr.len = sizeof(outhdr);
+            outhdr.error = result;
+            outhdr.unique = hdr->unique;
+            TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr)));
+        }
+    }
+
+  done:
+    fd.vtab->close(fd.cookie);
+
+    result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH);
+    if (result < 0) {
+        printf("fuse_sideload umount failed: %s\n", strerror(errno));
+    }
+
+    if (fd.ffd) close(fd.ffd);
+    free(fd.hashes);
+    free(fd.block_data);
+    free(fd.extra_block);
+
+    return result;
+}
+
+extern "C" int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie,
+                      uint64_t file_size, uint32_t block_size)
+{
+    return run_fuse_sideload(vtab, cookie, file_size, block_size);
+}
diff --git a/fuse_sideload22.h b/fuse_sideload22.h
new file mode 100644
index 0000000..98254d4
--- /dev/null
+++ b/fuse_sideload22.h
@@ -0,0 +1,47 @@
+/*
+ * 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_SIDELOAD22_H
+#define __FUSE_SIDELOAD22_H
+
+// define the filenames created by the sideload FUSE filesystem
+#define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload"
+#define FUSE_SIDELOAD_HOST_FILENAME "package.zip"
+#define FUSE_SIDELOAD_HOST_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_FILENAME)
+#define FUSE_SIDELOAD_HOST_EXIT_FLAG "exit"
+#define FUSE_SIDELOAD_HOST_EXIT_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_EXIT_FLAG)
+
+struct provider_vtab {
+    // read a block
+    int (*read_block)(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size);
+
+    // close down
+    void (*close)(void* cookie);
+};
+
+int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
+                      uint64_t file_size, uint32_t block_size);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie,
+                      uint64_t file_size, uint32_t block_size);
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/gpt/Android.mk b/gpt/Android.mk
index 3c2ec3b..bbd71eb 100644
--- a/gpt/Android.mk
+++ b/gpt/Android.mk
@@ -13,5 +13,7 @@
     gpt.c \
     gptcrc32.c
 
+LOCAL_CFLAGS := -Wno-format
+
 LOCAL_SHARED_LIBRARIES := libc
 include $(BUILD_SHARED_LIBRARY)
diff --git a/gui/Android.mk b/gui/Android.mk
index 909850f..22b5347 100644
--- a/gui/Android.mk
+++ b/gui/Android.mk
@@ -41,6 +41,7 @@
 LOCAL_SHARED_LIBRARIES += libminuitwrp libc libstdc++ libaosprecovery libselinux
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0)
     LOCAL_SHARED_LIBRARIES += libziparchive
+    LOCAL_C_INCLUDES += $(LOCAL_PATH)/../otautil/include
 else
     LOCAL_SHARED_LIBRARIES += libminzip
     LOCAL_CFLAGS += -DUSE_MINZIP
@@ -87,6 +88,7 @@
 
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
     LOCAL_C_INCLUDES += external/stlport/stlport
+    LOCAL_CFLAGS += -DUSE_FUSE_SIDELOAD22
 endif
 
 LOCAL_CFLAGS += -DTWRES=\"$(TWRES_PATH)\"
@@ -107,7 +109,7 @@
   Could not find ui.xml for TW_THEME: $(TW_THEME)
   Set TARGET_SCREEN_WIDTH and TARGET_SCREEN_HEIGHT to automatically select
   an appropriate theme, or set TW_THEME to one of the following:
-    $(notdir $(wildcard $(commands_recovery_local_path)/gui/theme/*_*))
+    $(notdir $(wildcard $(LOCAL_PATH)/theme/*_*))
 ****************************************************************************
 endef
 define TW_CUSTOM_THEME_WARNING_MSG
@@ -117,15 +119,15 @@
   Expected to find custom theme's ui.xml at:
     $(TWRP_THEME_LOC)/ui.xml
   Please fix this or set TW_THEME to one of the following:
-    $(notdir $(wildcard $(commands_recovery_local_path)/gui/theme/*_*))
+    $(notdir $(wildcard $(LOCAL_PATH)/theme/*_*))
 ****************************************************************************
 endef
 
-TWRP_RES := $(commands_recovery_local_path)/gui/theme/common/fonts
-TWRP_RES += $(commands_recovery_local_path)/gui/theme/common/languages
+TWRP_RES := $(LOCAL_PATH)/theme/common/fonts
+TWRP_RES += $(LOCAL_PATH)/theme/common/languages
 ifeq ($(TW_EXTRA_LANGUAGES),true)
-    TWRP_RES += $(commands_recovery_local_path)/gui/theme/extra-languages/fonts
-    TWRP_RES += $(commands_recovery_local_path)/gui/theme/extra-languages/languages
+    TWRP_RES += $(LOCAL_PATH)/theme/extra-languages/fonts
+    TWRP_RES += $(LOCAL_PATH)/theme/extra-languages/languages
 endif
 
 ifeq ($(TW_CUSTOM_THEME),)
@@ -163,13 +165,13 @@
         endif
     endif
 
-    TWRP_THEME_LOC := $(commands_recovery_local_path)/gui/theme/$(TW_THEME)
+    TWRP_THEME_LOC := $(LOCAL_PATH)/theme/$(TW_THEME)
     ifeq ($(wildcard $(TWRP_THEME_LOC)/ui.xml),)
         $(warning $(TW_THEME_WARNING_MSG))
         $(error Theme selection failed; exiting)
     endif
 
-    TWRP_RES += $(commands_recovery_local_path)/gui/theme/common/$(word 1,$(subst _, ,$(TW_THEME))).xml
+    TWRP_RES += $(LOCAL_PATH)/theme/common/$(word 1,$(subst _, ,$(TW_THEME))).xml
     # for future copying of used include xmls and fonts:
     # UI_XML := $(TWRP_THEME_LOC)/ui.xml
     # TWRP_INCLUDE_XMLS := $(shell xmllint --xpath '/recovery/include/xmlfile/@name' $(UI_XML)|sed -n 's/[^\"]*\"\([^\"]*\)\"[^\"]*/\1\n/gp'|sort|uniq)
diff --git a/gui/blanktimer.cpp b/gui/blanktimer.cpp
index 2205253..63cd05c 100644
--- a/gui/blanktimer.cpp
+++ b/gui/blanktimer.cpp
@@ -16,7 +16,6 @@
         along with TWRP.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-using namespace std;
 #include <string>
 #include <pthread.h>
 #include <sys/time.h>
diff --git a/gui/button.cpp b/gui/button.cpp
index a9b02a3..dfae5e2 100644
--- a/gui/button.cpp
+++ b/gui/button.cpp
@@ -45,9 +45,6 @@
 GUIButton::GUIButton(xml_node<>* node)
 	: GUIObject(node)
 {
-	xml_attribute<>* attr;
-	xml_node<>* child;
-
 	mButtonImg = NULL;
 	mButtonIcon = NULL;
 	mButtonLabel = NULL;
@@ -201,9 +198,12 @@
 		mRenderW = w;
 		mRenderH = h;
 	}
+	mIconW = mIconH = 0;
 
-	mIconW = mButtonIcon->GetWidth();
-	mIconH = mButtonIcon->GetHeight();
+	if (mButtonIcon && mButtonIcon->GetResource()) {
+		mIconW = mButtonIcon->GetWidth();
+		mIconH = mButtonIcon->GetHeight();
+	}
 
 	mTextH = 0;
 	mTextW = 0;
diff --git a/gui/checkbox.cpp b/gui/checkbox.cpp
index a2958db..de63cba 100644
--- a/gui/checkbox.cpp
+++ b/gui/checkbox.cpp
@@ -82,10 +82,11 @@
 			DataManager::SetValue(mVarName, attr->value());
 	}
 
-	mCheckW = mChecked->GetWidth();
-	mCheckH = mChecked->GetHeight();
-	if (mCheckW == 0)
-	{
+	mCheckW = mCheckH = 0;
+	if (mChecked && mChecked->GetResource()) {
+		mCheckW = mChecked->GetWidth();
+		mCheckH = mChecked->GetHeight();
+	} else if (mUnchecked && mUnchecked->GetResource()) {
 		mCheckW = mUnchecked->GetWidth();
 		mCheckH = mUnchecked->GetHeight();
 	}
diff --git a/gui/fileselector.cpp b/gui/fileselector.cpp
index c4aaab1..fe378c8 100644
--- a/gui/fileselector.cpp
+++ b/gui/fileselector.cpp
@@ -112,8 +112,17 @@
 		mFolderIcon = LoadAttrImage(child, "folder");
 		mFileIcon = LoadAttrImage(child, "file");
 	}
-	int iconWidth = std::max(mFolderIcon->GetWidth(), mFileIcon->GetWidth());
-	int iconHeight = std::max(mFolderIcon->GetHeight(), mFileIcon->GetHeight());
+	int iconWidth = 0, iconHeight = 0;
+	if (mFolderIcon && mFolderIcon->GetResource() && mFileIcon && mFileIcon->GetResource()) {
+		iconWidth = std::max(mFolderIcon->GetWidth(), mFileIcon->GetWidth());
+		iconHeight = std::max(mFolderIcon->GetHeight(), mFileIcon->GetHeight());
+	} else if (mFolderIcon && mFolderIcon->GetResource()) {
+		iconWidth = mFolderIcon->GetWidth();
+		iconHeight = mFolderIcon->GetHeight();
+	} else if (mFileIcon && mFileIcon->GetResource()) {
+		iconWidth = mFileIcon->GetWidth();
+		iconHeight = mFileIcon->GetHeight();
+	}
 	SetMaxIconSize(iconWidth, iconHeight);
 
 	// Fetch the file/folder list
diff --git a/gui/input.cpp b/gui/input.cpp
index 8dd981c..91a1c11 100644
--- a/gui/input.cpp
+++ b/gui/input.cpp
@@ -122,7 +122,8 @@
 	if (child)
 	{
 		mFont = LoadAttrFont(child, "resource");
-		mFontHeight = mFont->GetHeight();
+		if (mFont && mFont->GetResource())
+			mFontHeight = mFont->GetHeight();
 	}
 
 	child = FindNode(node, "data");
@@ -299,9 +300,6 @@
 		return 0;
 	}
 
-	void* fontResource = NULL;
-	if (mFont)  fontResource = mFont->GetResource();
-
 	// First step, fill background
 	gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, 255);
 	gr_fill(mRenderX, mRenderY, mRenderW, mRenderH);
@@ -359,9 +357,6 @@
 int GUIInput::NotifyTouch(TOUCH_STATE state, int x, int y)
 {
 	static int startSelection = -1;
-	void* fontResource = NULL;
-
-	if (mFont)  fontResource = mFont->GetResource();
 
 	if (!isConditionTrue())
 		return -1;
diff --git a/gui/keyboard.cpp b/gui/keyboard.cpp
index 849cf19..ca7006d 100644
--- a/gui/keyboard.cpp
+++ b/gui/keyboard.cpp
@@ -322,7 +322,7 @@
 		gr_color(mFontColorSmall.red, mFontColorSmall.green, mFontColorSmall.blue, mFontColorSmall.alpha);
 	}
 
-	if (labelImage)
+	if (labelImage && labelImage->GetResource())
 	{
 		int w = labelImage->GetWidth();
 		int h = labelImage->GetHeight();
@@ -330,7 +330,7 @@
 		int y = keyY + (keyH - h) / 2;
 		gr_blit(labelImage->GetResource(), 0, 0, w, h, x, y);
 	}
-	else if (!labelText.empty())
+	else if (!labelText.empty() && labelFont && labelFont->GetResource())
 	{
 		void* fontResource = labelFont->GetResource();
 		int textW = gr_ttf_measureEx(labelText.c_str(), fontResource);
@@ -342,7 +342,7 @@
 
 	// longpress key label (only if font is defined)
 	keychar = key.longpresskey;
-	if (keychar > 32 && keychar < 127 && mLongpressFont->GetResource()) {
+	if (keychar > 32 && keychar < 127 && mLongpressFont && mLongpressFont->GetResource()) {
 		void* fontResource = mLongpressFont->GetResource();
 		gr_color(mLongpressFontColor.red, mLongpressFontColor.green, mLongpressFontColor.blue, mLongpressFontColor.alpha);
 		string text(1, keychar);
diff --git a/gui/listbox.cpp b/gui/listbox.cpp
index 05276e8..9d36bae 100644
--- a/gui/listbox.cpp
+++ b/gui/listbox.cpp
@@ -44,8 +44,17 @@
 		mIconSelected = LoadAttrImage(child, "selected");
 		mIconUnselected = LoadAttrImage(child, "unselected");
 	}
-	int iconWidth = std::max(mIconSelected->GetWidth(), mIconUnselected->GetWidth());
-	int iconHeight = std::max(mIconSelected->GetHeight(), mIconUnselected->GetHeight());
+	int iconWidth = 0, iconHeight = 0;
+	if (mIconSelected && mIconSelected->GetResource() && mIconUnselected && mIconUnselected->GetResource()) {
+		iconWidth = std::max(mIconSelected->GetWidth(), mIconUnselected->GetWidth());
+		iconHeight = std::max(mIconSelected->GetHeight(), mIconUnselected->GetHeight());
+	} else if (mIconSelected && mIconSelected->GetResource()) {
+		iconWidth = mIconSelected->GetWidth();
+		iconHeight = mIconSelected->GetHeight();
+	} else if (mIconUnselected && mIconUnselected->GetResource()) {
+		iconWidth = mIconUnselected->GetWidth();
+		iconHeight = mIconUnselected->GetHeight();
+	}
 	SetMaxIconSize(iconWidth, iconHeight);
 
 	// Handle the result variable
diff --git a/gui/mousecursor.cpp b/gui/mousecursor.cpp
index b87cac4..93c635a 100644
--- a/gui/mousecursor.cpp
+++ b/gui/mousecursor.cpp
@@ -78,7 +78,7 @@
 	{
 		m_color = LoadAttrColor(child, "color", m_color);
 		m_image = LoadAttrImage(child, "resource");
-		if (m_image)
+		if (m_image && m_image->GetResource())
 		{
 			mRenderW = m_image->GetWidth();
 			mRenderH = m_image->GetHeight();
@@ -99,7 +99,7 @@
 	if (!m_present)
 		return 0;
 
-	if (m_image)
+	if (m_image && m_image->GetResource())
 	{
 		gr_blit(m_image->GetResource(), 0, 0, mRenderW, mRenderH, mRenderX, mRenderY);
 	}
diff --git a/gui/pages.cpp b/gui/pages.cpp
index a3a1df3..fd0ad9b 100644
--- a/gui/pages.cpp
+++ b/gui/pages.cpp
@@ -42,7 +42,7 @@
 #ifdef USE_MINZIP
 #include "../minzip/SysUtil.h"
 #else
-#include "../otautil/SysUtil.h"
+#include <otautil/SysUtil.h>
 #endif
 
 extern "C" {
diff --git a/gui/partitionlist.cpp b/gui/partitionlist.cpp
index c853391..16e0997 100644
--- a/gui/partitionlist.cpp
+++ b/gui/partitionlist.cpp
@@ -58,8 +58,17 @@
 			selectedList = attr->value();
 	}
 
-	int iconWidth = std::max(mIconSelected->GetWidth(), mIconUnselected->GetWidth());
-	int iconHeight = std::max(mIconSelected->GetHeight(), mIconUnselected->GetHeight());
+	int iconWidth = 0, iconHeight = 0;
+	if (mIconSelected && mIconSelected->GetResource() && mIconUnselected && mIconUnselected->GetResource()) {
+		iconWidth = std::max(mIconSelected->GetWidth(), mIconUnselected->GetWidth());
+		iconHeight = std::max(mIconSelected->GetHeight(), mIconUnselected->GetHeight());
+	} else if (mIconSelected && mIconSelected->GetResource()) {
+		iconWidth = mIconSelected->GetWidth();
+		iconHeight = mIconSelected->GetHeight();
+	} else if (mIconUnselected && mIconUnselected->GetResource()) {
+		iconWidth = mIconUnselected->GetWidth();
+		iconHeight = mIconUnselected->GetHeight();
+	}
 	SetMaxIconSize(iconWidth, iconHeight);
 
 	child = FindNode(node, "listtype");
diff --git a/gui/patternpassword.cpp b/gui/patternpassword.cpp
index c31737f..5559f9d 100644
--- a/gui/patternpassword.cpp
+++ b/gui/patternpassword.cpp
@@ -40,7 +40,6 @@
 GUIPatternPassword::GUIPatternPassword(xml_node<>* node)
 	: GUIObject(node)
 {
-	xml_attribute<>* attr;
 	xml_node<>* child;
 
 	// 3x3 is the default.
@@ -107,7 +106,7 @@
 		mDotCircle = gr_render_circle(mDotRadius, mDotColor.red, mDotColor.green, mDotColor.blue, mDotColor.alpha);
 		mActiveDotCircle = gr_render_circle(mDotRadius/2, mActiveDotColor.red, mActiveDotColor.green, mActiveDotColor.blue, mActiveDotColor.alpha);
 	}
-	else
+	else if (mDotImage && mDotImage->GetResource())
 		mDotRadius = mDotImage->GetWidth()/2;
 
 	SetRenderPos(mRenderX, mRenderY, mRenderW, mRenderH);
@@ -210,10 +209,10 @@
 				gr_blit(mActiveDotCircle, 0, 0, gr_get_width(mActiveDotCircle), gr_get_height(mActiveDotCircle), mDots[i].x + mDotRadius/2, mDots[i].y + mDotRadius/2);
 			}
 		} else {
-			if (mDots[i].active) {
+			if (mDots[i].active && mActiveDotImage && mActiveDotImage->GetResource()) {
 				gr_blit(mActiveDotImage->GetResource(), 0, 0, mActiveDotImage->GetWidth(), mActiveDotImage->GetHeight(),
 						mDots[i].x + (mDotRadius - mActiveDotImage->GetWidth()/2), mDots[i].y + (mDotRadius - mActiveDotImage->GetHeight()/2));
-			} else {
+			} else if (mDotImage && mDotImage->GetResource()) {
 				gr_blit(mDotImage->GetResource(), 0, 0, mDotImage->GetWidth(), mDotImage->GetHeight(), mDots[i].x, mDots[i].y);
 			}
 		}
diff --git a/gui/progressbar.cpp b/gui/progressbar.cpp
index 56ce480..defd951 100644
--- a/gui/progressbar.cpp
+++ b/gui/progressbar.cpp
@@ -45,7 +45,6 @@
 
 GUIProgressBar::GUIProgressBar(xml_node<>* node) : GUIObject(node)
 {
-	xml_attribute<>* attr;
 	xml_node<>* child;
 
 	mEmptyBar = NULL;
@@ -79,8 +78,12 @@
 		mCurValVar = LoadAttrString(child, "name");
 	}
 
-	mRenderW = mEmptyBar->GetWidth();
-	mRenderH = mEmptyBar->GetHeight();
+	if (mEmptyBar && mEmptyBar->GetResource()) {
+		mRenderW = mEmptyBar->GetWidth();
+		mRenderH = mEmptyBar->GetHeight();
+	} else {
+		mRenderW = mRenderH = 0;
+	}
 }
 
 int GUIProgressBar::Render(void)
diff --git a/gui/resources.cpp b/gui/resources.cpp
index 59b8ed4..bb2fd50 100644
--- a/gui/resources.cpp
+++ b/gui/resources.cpp
@@ -294,38 +294,38 @@
 
 std::string ResourceManager::FindString(const std::string& name) const
 {
-	if (this != NULL) {
+	//if (this != NULL) {
 		std::map<std::string, string_resource_struct>::const_iterator it = mStrings.find(name);
 		if (it != mStrings.end())
 			return it->second.value;
 		LOGERR("String resource '%s' not found. No default value.\n", name.c_str());
 		PageManager::AddStringResource("NO DEFAULT", name, "[" + name + ("]"));
-	} else {
+	/*} else {
 		LOGINFO("String resources not loaded when looking for '%s'. No default value.\n", name.c_str());
-	}
+	}*/
 	return "[" + name + ("]");
 }
 
 std::string ResourceManager::FindString(const std::string& name, const std::string& default_string) const
 {
-	if (this != NULL) {
+	//if (this != NULL) {
 		std::map<std::string, string_resource_struct>::const_iterator it = mStrings.find(name);
 		if (it != mStrings.end())
 			return it->second.value;
 		LOGERR("String resource '%s' not found. Using default value.\n", name.c_str());
 		PageManager::AddStringResource("DEFAULT", name, default_string);
-	} else {
+	/*} else {
 		LOGINFO("String resources not loaded when looking for '%s'. Using default value.\n", name.c_str());
-	}
+	}*/
 	return default_string;
 }
 
 void ResourceManager::DumpStrings() const
 {
-	if (this == NULL) {
+	/*if (this == NULL) {
 		gui_print("No string resources\n");
 		return;
-	}
+	}*/
 	std::map<std::string, string_resource_struct>::const_iterator it;
 	gui_print("Dumping all strings:\n");
 	for (it = mStrings.begin(); it != mStrings.end(); it++)
@@ -363,7 +363,7 @@
 		if (type == "font")
 		{
 			FontResource* res = new FontResource(child, pZip);
-			if (res->GetResource())
+			if (res && res->GetResource())
 				mFonts.push_back(res);
 			else {
 				error = true;
@@ -393,7 +393,7 @@
 		else if (type == "image")
 		{
 			ImageResource* res = new ImageResource(child, pZip);
-			if (res->GetResource())
+			if (res && res->GetResource())
 				mImages.push_back(res);
 			else {
 				error = true;
@@ -403,7 +403,7 @@
 		else if (type == "animation")
 		{
 			AnimationResource* res = new AnimationResource(child, pZip);
-			if (res->GetResourceCount())
+			if (res && res->GetResourceCount())
 				mAnimations.push_back(res);
 			else {
 				error = true;
diff --git a/gui/resources.hpp b/gui/resources.hpp
index de67318..69bebc7 100644
--- a/gui/resources.hpp
+++ b/gui/resources.hpp
@@ -57,8 +57,8 @@
 	virtual ~FontResource();
 
 public:
-	void* GetResource() { return this ? mFont : NULL; }
-	int GetHeight() { return gr_ttf_getMaxFontHeight(this ? mFont : NULL); }
+	void* GetResource() { return mFont; }
+	int GetHeight() { return gr_ttf_getMaxFontHeight(mFont); }
 	void Override(xml_node<>* node, ZipWrap* pZip);
 
 protected:
@@ -80,9 +80,9 @@
 	virtual ~ImageResource();
 
 public:
-	gr_surface GetResource() { return this ? mSurface : NULL; }
-	int GetWidth() { return gr_get_width(this ? mSurface : NULL); }
-	int GetHeight() { return gr_get_height(this ? mSurface : NULL); }
+	gr_surface GetResource() { return mSurface; }
+	int GetWidth() { return gr_get_width(mSurface); }
+	int GetHeight() { return gr_get_height(mSurface); }
 
 protected:
 	gr_surface mSurface;
@@ -95,10 +95,10 @@
 	virtual ~AnimationResource();
 
 public:
-	gr_surface GetResource() { return (!this || mSurfaces.empty()) ? NULL : mSurfaces.at(0); }
-	gr_surface GetResource(int entry) { return (!this || mSurfaces.empty()) ? NULL : mSurfaces.at(entry); }
-	int GetWidth() { return gr_get_width(this ? GetResource() : NULL); }
-	int GetHeight() { return gr_get_height(this ? GetResource() : NULL); }
+	gr_surface GetResource() { return mSurfaces.empty() ? NULL : mSurfaces.at(0); }
+	gr_surface GetResource(int entry) { return mSurfaces.empty() ? NULL : mSurfaces.at(entry); }
+	int GetWidth() { return gr_get_width(GetResource()); }
+	int GetHeight() { return gr_get_height(GetResource()); }
 	int GetResourceCount() { return mSurfaces.size(); }
 
 protected:
diff --git a/gui/scrolllist.cpp b/gui/scrolllist.cpp
index 7540356..ecfb5fe 100644
--- a/gui/scrolllist.cpp
+++ b/gui/scrolllist.cpp
@@ -32,7 +32,6 @@
 
 GUIScrollList::GUIScrollList(xml_node<>* node) : GUIObject(node)
 {
-	xml_attribute<>* attr;
 	xml_node<>* child;
 
 	firstDisplayedItem = mItemSpacing = mFontHeight = mSeparatorH = y_offset = scrollingSpeed = 0;
@@ -265,8 +264,10 @@
 		}
 
 		// render the text
-		gr_color(mHeaderFontColor.red, mHeaderFontColor.green, mHeaderFontColor.blue, mHeaderFontColor.alpha);
-		gr_textEx_scaleW(mRenderX + IconOffsetX + 5, yPos + (int)(mHeaderH / 2), mLastHeaderValue.c_str(), mFont->GetResource(), mRenderW, TEXT_ONLY_RIGHT, 0);
+		if (mFont && mFont->GetResource()) {
+			gr_color(mHeaderFontColor.red, mHeaderFontColor.green, mHeaderFontColor.blue, mHeaderFontColor.alpha);
+			gr_textEx_scaleW(mRenderX + IconOffsetX + 5, yPos + (int)(mHeaderH / 2), mLastHeaderValue.c_str(), mFont->GetResource(), mRenderW, TEXT_ONLY_RIGHT, 0);
+		}
 
 		// Add the separator
 		gr_color(mHeaderSeparatorColor.red, mHeaderSeparatorColor.green, mHeaderSeparatorColor.blue, mHeaderSeparatorColor.alpha);
@@ -345,9 +346,11 @@
 	}
 
 	// render label text
-	int textX = mRenderX + maxIconWidth + 5;
-	int textY = yPos + (iconAndTextH / 2);
-	gr_textEx_scaleW(textX, textY, text, mFont->GetResource(), mRenderW, TEXT_ONLY_RIGHT, 0);
+	if (mFont && mFont->GetResource()) {
+		int textX = mRenderX + maxIconWidth + 5;
+		int textY = yPos + (iconAndTextH / 2);
+		gr_textEx_scaleW(textX, textY, text, mFont->GetResource(), mRenderW, TEXT_ONLY_RIGHT, 0);
+	}
 }
 
 int GUIScrollList::Update(void)
@@ -610,6 +613,8 @@
 
 bool GUIScrollList::AddLines(std::vector<std::string>* origText, std::vector<std::string>* origColor, size_t* lastCount, std::vector<std::string>* rText, std::vector<std::string>* rColor)
 {
+	if (!mFont || !mFont->GetResource())
+		return false;
 	if (*lastCount == origText->size())
 		return false; // nothing to add
 
diff --git a/gui/slider.cpp b/gui/slider.cpp
index ed5c615..fab5a2e 100644
--- a/gui/slider.cpp
+++ b/gui/slider.cpp
@@ -47,7 +47,6 @@
 
 GUISlider::GUISlider(xml_node<>* node) : GUIObject(node)
 {
-	xml_attribute<>* attr;
 	xml_node<>* child;
 
 	sAction = NULL;
@@ -84,8 +83,12 @@
 	Placement TextPlacement = CENTER;
 	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH, &TextPlacement);
 
-	mRenderW = sSlider->GetWidth();
-	mRenderH = sSlider->GetHeight();
+	if (sSlider && sSlider->GetResource()) {
+		mRenderW = sSlider->GetWidth();
+		mRenderH = sSlider->GetHeight();
+	} else {
+		mRenderW = mRenderH = 0;
+	}
 	if (TextPlacement == CENTER || TextPlacement == CENTER_X_ONLY) {
 		mRenderX = mRenderX - (mRenderW / 2);
 		if (TextPlacement == CENTER) {
diff --git a/gui/slidervalue.cpp b/gui/slidervalue.cpp
index 6f007e2..3aaffcc 100644
--- a/gui/slidervalue.cpp
+++ b/gui/slidervalue.cpp
@@ -172,7 +172,10 @@
 		mSliderH = LoadAttrIntScaleY(child, "sliderh", mSliderH);
 	}
 
-	mFontHeight = mFont->GetHeight();
+	if (mFont && mFont->GetResource())
+		mFontHeight = mFont->GetHeight();
+	else
+		mFontHeight = 0;
 
 	if (mShowCurr)
 	{
diff --git a/gui/terminal.cpp b/gui/terminal.cpp
index 65ad2c0..b1799ce 100644
--- a/gui/terminal.cpp
+++ b/gui/terminal.cpp
@@ -861,9 +861,12 @@
 	return engine->getLinesCount();
 }
 
-void GUITerminal::RenderItem(size_t itemindex, int yPos, bool selected)
+void GUITerminal::RenderItem(size_t itemindex, int yPos, bool selected __unused)
 {
 	const TerminalEngine::Line& line = engine->getLine(itemindex);
+	
+	if (!mFont || !mFont->GetResource())
+		return;
 
 	gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
 	// later: handle attributes here
@@ -887,7 +890,7 @@
 	}
 }
 
-void GUITerminal::NotifySelect(size_t item_selected)
+void GUITerminal::NotifySelect(size_t item_selected __unused)
 {
 	// do nothing - terminal ignores selections
 }
@@ -897,8 +900,10 @@
 	// make sure the shell is started
 	engine->initPty();
 	// send window resize
-	int charWidth = gr_ttf_measureEx("N", mFont->GetResource());
-	engine->setSize(mRenderW / charWidth, GetDisplayItemCount(), mRenderW, mRenderH);
+	if (mFont && mFont->GetResource()) {
+		int charWidth = gr_ttf_measureEx("N", mFont->GetResource());
+		engine->setSize(mRenderW / charWidth, GetDisplayItemCount(), mRenderW, mRenderH);
+	}
 }
 
 void GUITerminal::SetPageFocus(int inFocus)
diff --git a/gui/text.cpp b/gui/text.cpp
index 61940ff..123b249 100644
--- a/gui/text.cpp
+++ b/gui/text.cpp
@@ -64,7 +64,7 @@
 
 	// Load the font, and possibly override the color
 	mFont = LoadAttrFont(FindNode(node, "font"), "resource");
-	if (!mFont)
+	if (!mFont || !mFont->GetResource())
 		return;
 	mColor = LoadAttrColor(FindNode(node, "font"), "color", mColor);
 	mHighlightColor = LoadAttrColor(FindNode(node, "font"), "highlightcolor", mColor);
diff --git a/gui/textbox.cpp b/gui/textbox.cpp
index 2c7d09f..824daf3 100644
--- a/gui/textbox.cpp
+++ b/gui/textbox.cpp
@@ -81,6 +81,9 @@
 
 void GUITextBox::RenderItem(size_t itemindex, int yPos, bool selected __unused)
 {
+	if (!mFont || !mFont->GetResource())
+		return;
+
 	// Set the color for the font
 	gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
 
diff --git a/install.cpp b/install.cpp
index 4848755..5d7bda7 100644
--- a/install.cpp
+++ b/install.cpp
@@ -49,9 +49,9 @@
 #include <ziparchive/zip_archive.h>
 
 #include "common.h"
-#include "error_code.h"
 #include "otautil/SysUtil.h"
 #include "otautil/ThermalUtil.h"
+#include "otautil/error_code.h"
 #include "private/install.h"
 #include "roots.h"
 #include "ui.h"
@@ -148,13 +148,23 @@
     return INSTALL_ERROR;
   }
 
-  // We allow the package to not have any serialno, but if it has a non-empty
-  // value it should match.
+  // 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() && pkg_serial_no != value) {
-    LOG(ERROR) << "Package is for serial " << pkg_serial_no;
-    return INSTALL_ERROR;
+  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") {
@@ -258,7 +268,7 @@
 
   *cmd = {
     binary_path,
-    EXPAND(RECOVERY_API_VERSION),  // defined in Android.mk
+    std::to_string(kRecoveryApiVersion),
     std::to_string(status_fd),
     package,
   };
@@ -299,6 +309,7 @@
   if (ret) {
     close(pipefd[0]);
     close(pipefd[1]);
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
     return ret;
   }
 
@@ -363,6 +374,7 @@
     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;
   }
 
@@ -551,6 +563,7 @@
   MemMapping map;
   if (!map.MapFile(path)) {
     LOG(ERROR) << "failed to map file";
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
     return INSTALL_CORRUPT;
   }
 
@@ -618,7 +631,7 @@
   std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
   int time_total = static_cast<int>(duration.count());
 
-  bool has_cache = volume_for_path("/cache") != nullptr;
+  bool has_cache = volume_for_mount_point("/cache") != nullptr;
   // Skip logging the uncrypt_status on devices without /cache.
   if (has_cache) {
     static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
diff --git a/installcommand.cpp b/installcommand.cpp
index b306e87..99f4c53 100644
--- a/installcommand.cpp
+++ b/installcommand.cpp
@@ -75,7 +75,7 @@
 }
 
 // Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
-static void read_source_target_build(ZipWrap* zip, std::vector<std::string>& log_buffer) {
+void read_source_target_build(ZipWrap* zip/*, std::vector<std::string>& log_buffer*/) {
     std::string meta_data;
     if (!read_metadata_from_package(zip, &meta_data)) {
         return;
@@ -89,14 +89,16 @@
         if (android::base::StartsWith(str, "pre-build-incremental")){
             int source_build = parse_build_number(str);
             if (source_build != -1) {
-                log_buffer.push_back(android::base::StringPrintf("source_build: %d",
-                        source_build));
+                printf("source_build: %d\n", source_build);
+                /*log_buffer.push_back(android::base::StringPrintf("source_build: %d",
+                        source_build));*/
             }
         } else if (android::base::StartsWith(str, "post-build-incremental")) {
             int target_build = parse_build_number(str);
             if (target_build != -1) {
-                log_buffer.push_back(android::base::StringPrintf("target_build: %d",
-                        target_build));
+                printf("target_build: %d\n", target_build);
+                /*log_buffer.push_back(android::base::StringPrintf("target_build: %d",
+                        target_build));*/
             }
         }
     }
@@ -163,9 +165,10 @@
 }
 
 int
-abupdate_binary_command(const char* path, ZipWrap* zip, int retry_count,
+abupdate_binary_command(const char* path, ZipWrap* zip, int retry_count __unused,
                       int status_fd, std::vector<std::string>* cmd)
 {
+    read_source_target_build(zip);
     int ret = check_newer_ab_build(zip);
     if (ret) {
         return ret;
@@ -202,6 +205,8 @@
 
 #else
 
+void read_source_target_build(ZipWrap* zip __unused /*, std::vector<std::string>& log_buffer*/) {return;}
+
 int
 abupdate_binary_command(__unused const char* path, __unused ZipWrap* zip, __unused int retry_count,
                       __unused int status_fd, __unused std::vector<std::string>* cmd)
@@ -298,7 +303,7 @@
   }
   CloseArchive(zip_handle);
 
-  // VintfObjectRecovery::CheckCompatibility returns zero on success.
+  // VintfObjectRecovery::CheckCompatibility returns zero on success. TODO THIS CAUSES A WEIRD COMPILE ERROR
   std::string err;
   int result = android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err);
   if (result == 0) {
diff --git a/installcommand.h b/installcommand.h
index 16c034f..957f016 100644
--- a/installcommand.h
+++ b/installcommand.h
@@ -34,4 +34,6 @@
 
 bool verify_package_compatibility(ZipWrap *package_zip);
 
+void read_source_target_build(ZipWrap* zip/*, std::vector<std::string>& log_buffer*/);
+
 #endif  // RECOVERY_INSTALL_COMMAND_H_
diff --git a/legacy_property_service.cpp b/legacy_property_service.cpp
index 90a0d47..c3990f7 100644
--- a/legacy_property_service.cpp
+++ b/legacy_property_service.cpp
@@ -40,11 +40,8 @@
 #define INT32_MAX	(2147483647)
 #endif
 
-static int persistent_properties_loaded = 0;
 static int property_area_inited = 0;
 
-static int property_set_fd = -1;
-
 typedef struct {
     void *data;
     size_t size;
@@ -203,7 +200,7 @@
     *sz = pa_workspace.size;
 }
 
-static void copy_property_to_legacy(const char *key, const char *value, void *cookie)
+static void copy_property_to_legacy(const char *key, const char *value, void *cookie __unused)
 {
     legacy_property_set(key, value);
 }
diff --git a/libblkid/Android.mk b/libblkid/Android.mk
index ab20bf5..42762e4 100644
--- a/libblkid/Android.mk
+++ b/libblkid/Android.mk
@@ -5,7 +5,8 @@
 LOCAL_MODULE := libutil-linux
 LOCAL_MODULE_TAGS := optional
 #LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
-LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS := -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS += -Wno-missing-field-initializers -Wno-sign-compare -Wno-unused-parameter -Wno-format -Wno-pointer-arith
 LOCAL_SRC_FILES = 	lib/at.c \
 			lib/blkdev.c \
 			lib/canonicalize.c \
@@ -43,7 +44,8 @@
 LOCAL_MODULE := libuuid
 LOCAL_MODULE_TAGS := optional
 #LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
-LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS := -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS += -Wno-missing-field-initializers -Wno-sign-compare -Wno-unused-parameter -Wno-format -Wno-pointer-arith
 LOCAL_SRC_FILES =	libuuid/src/clear.c \
 			libuuid/src/copy.c \
 			libuuid/src/isnull.c \
@@ -69,7 +71,8 @@
 LOCAL_MODULE := libfdisk
 LOCAL_MODULE_TAGS := optional
 #LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
-LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS := -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS += -Wno-missing-field-initializers -Wno-sign-compare -Wno-unused-parameter -Wno-format -Wno-pointer-arith
 LOCAL_SRC_FILES = 	libfdisk/src/alignment.c \
 			libfdisk/src/context.c  \
 			libfdisk/src/init.c   \
@@ -101,7 +104,8 @@
 LOCAL_MODULE := libblkid
 LOCAL_MODULE_TAGS := optional
 #LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
-LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS := -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS += -Wno-missing-field-initializers -Wno-sign-compare -Wno-unused-parameter -Wno-format -Wno-pointer-arith
 LOCAL_SRC_FILES = 	src/cache.c \
 			src/config.c \
 			src/dev.c \
diff --git a/libblkid/lib/ismounted.c b/libblkid/lib/ismounted.c
index 8099bd7..00b575c 100644
--- a/libblkid/lib/ismounted.c
+++ b/libblkid/lib/ismounted.c
@@ -327,7 +327,7 @@
 		retval = check_getmntinfo(device, mount_flags, mtpt, mtlen);
 #else
 #ifdef __GNUC__
- #warning "Can't use getmntent or getmntinfo to check for mounted filesystems!"
+ //#warning "Can't use getmntent or getmntinfo to check for mounted filesystems!"
 #endif
 		*mount_flags = 0;
 #endif /* HAVE_GETMNTINFO */
diff --git a/libpixelflinger/Android.mk b/libpixelflinger/Android.mk
index 1cbde57..026e8ad 100644
--- a/libpixelflinger/Android.mk
+++ b/libpixelflinger/Android.mk
@@ -104,6 +104,7 @@
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 LOCAL_C_INCLUDES += $(LOCAL_EXPORT_C_INCLUDE_DIRS)
 LOCAL_CFLAGS := $(PIXELFLINGER_CFLAGS)
+LOCAL_CPPFLAGS := -Wno-unused-function
 LOCAL_C_INCLUDES_x86 := $(PIXELFLINGER_C_INCLUDES_x86)
 ifeq ($(TW_HAVE_X86_ACCELERATED_PIXELFLINGER),true)
 LOCAL_WHOLE_STATIC_LIBRARIES += libenc
diff --git a/libtar/append.c b/libtar/append.c
index 66e3aa1..8f09de2 100644
--- a/libtar/append.c
+++ b/libtar/append.c
@@ -416,8 +416,7 @@
 tar_append_buffer(TAR *t, void *buf, size_t len)
 {
 	char block[T_BLOCKSIZE];
-	int filefd;
-	int i, j;
+	int i;
 	size_t size = len;
 
 	for (i = size; i > T_BLOCKSIZE; i -= T_BLOCKSIZE)
diff --git a/libtar/basename.c b/libtar/basename.c
index 32108d6..3f4d315 100644
--- a/libtar/basename.c
+++ b/libtar/basename.c
@@ -28,7 +28,7 @@
  */
 
 #ifndef lint
-static char rcsid[] = "$OpenBSD: basename.c,v 1.4 1999/05/30 17:10:30 espie Exp $";
+//static char rcsid[] = "$OpenBSD: basename.c,v 1.4 1999/05/30 17:10:30 espie Exp $";
 #endif /* not lint */
 
 #include <errno.h>
diff --git a/libtar/block.c b/libtar/block.c
index d0adb2b..834c164 100644
--- a/libtar/block.c
+++ b/libtar/block.c
@@ -357,7 +357,8 @@
 				if (*start == '2')
 				{
 					start++;
-					if (start + sizeof(struct ext4_encryption_policy) != '\n')
+					char *newline_check = start + sizeof(struct ext4_encryption_policy);
+					if (*newline_check != '\n')
 						printf("did not find newline char in expected location, continuing anyway...\n");
 					memcpy(t->th_buf.eep, start, sizeof(struct ext4_encryption_policy));
 #ifdef DEBUG
diff --git a/libtar/dirname.c b/libtar/dirname.c
index 4e06067..31dbe13 100644
--- a/libtar/dirname.c
+++ b/libtar/dirname.c
@@ -28,7 +28,7 @@
  */
 
 #ifndef lint
-static char rcsid[] = "$OpenBSD: dirname.c,v 1.4 1999/05/30 17:10:30 espie Exp $";
+//static char rcsid[] = "$OpenBSD: dirname.c,v 1.4 1999/05/30 17:10:30 espie Exp $";
 #endif /* not lint */
 
 #include <errno.h>
diff --git a/libtar/extract.c b/libtar/extract.c
index fcd403a..ea86c23 100644
--- a/libtar/extract.c
+++ b/libtar/extract.c
@@ -12,6 +12,7 @@
 
 #include <internal.h>
 
+#include <inttypes.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/param.h>
@@ -227,7 +228,7 @@
 	if (mkdirhier(dirname(filename)) == -1)
 		return -1;
 
-	printf("  ==> extracting: %s (file size %lld bytes)\n",
+	printf("  ==> extracting: %s (file size %" PRId64 " bytes)\n",
 			filename, size);
 
 	fdout = open(filename, O_WRONLY | O_CREAT | O_TRUNC
diff --git a/libtar/handle.c b/libtar/handle.c
index 28a7dc2..a26c094 100644
--- a/libtar/handle.c
+++ b/libtar/handle.c
@@ -32,7 +32,7 @@
 
 static int
 tar_init(TAR **t, const char *pathname, tartype_t *type,
-	 int oflags, int mode, int options)
+	 int oflags, int mode __unused, int options)
 {
 	if ((oflags & O_ACCMODE) == O_RDWR)
 	{
diff --git a/minadbd/Android.mk b/minadbd/Android.mk
index dfeb85e..a50d844 100644
--- a/minadbd/Android.mk
+++ b/minadbd/Android.mk
@@ -1,14 +1,26 @@
 # 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 \
-    -Wno-unused-parameter \
-    -Wno-missing-field-initializers \
     -DADB_HOST=0 \
     -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION)
 
+# libminadbd (static library)
+# ===============================
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
@@ -17,9 +29,8 @@
     minadbd.cpp \
     minadbd_services.cpp \
 
-LOCAL_CLANG := true
 LOCAL_MODULE := libminadbd
-LOCAL_CFLAGS := $(minadbd_cflags)
+LOCAL_CFLAGS := $(minadbd_cflags) -Wno-unused-parameter
 LOCAL_CONLY_FLAGS := -Wimplicit-function-declaration
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. system/core/adb
 LOCAL_WHOLE_STATIC_LIBRARIES := libadbd
@@ -32,6 +43,10 @@
 else
     LOCAL_SHARED_LIBRARIES += libcrypto \
     $(if $(WITH_CRYPTO_UTILS),libcrypto_utils)
+    ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 27; echo $$?),0)
+        # Needed in Android 9.0
+        LOCAL_WHOLE_STATIC_LIBRARIES += libasyncio
+    endif
 endif
 
 include $(BUILD_SHARED_LIBRARY)
@@ -46,7 +61,7 @@
 
 LOCAL_CLANG := true
 LOCAL_MODULE := libminadbd
-LOCAL_CFLAGS := $(minadbd_cflags)
+LOCAL_CFLAGS := $(minadbd_cflags) -Wno-unused-parameter
 LOCAL_CONLY_FLAGS := -Wimplicit-function-declaration
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. system/core/adb
 LOCAL_WHOLE_STATIC_LIBRARIES := libadbd
@@ -59,19 +74,29 @@
 else
     LOCAL_SHARED_LIBRARIES += libcrypto \
     $(if $(WITH_CRYPTO_UTILS),libcrypto_utils)
+    ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 27; echo $$?),0)
+        # Needed in Android 9.0
+        LOCAL_WHOLE_STATIC_LIBRARIES += libasyncio
+    endif
 endif
 
 include $(BUILD_STATIC_LIBRARY)
 
+# minadbd_test (native test)
+# ===============================
 include $(CLEAR_VARS)
 
-LOCAL_CLANG := true
 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 := libminadbd
-LOCAL_SHARED_LIBRARIES := liblog libbase libcutils
+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 0f4c256..9bd3f23 100644
--- a/minadbd/fuse_adb_provider.cpp
+++ b/minadbd/fuse_adb_provider.cpp
@@ -14,46 +14,43 @@
  * limitations under the License.
  */
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
+#include "fuse_adb_provider.h"
+
 #include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <functional>
 
 #include "adb.h"
 #include "adb_io.h"
-#include "fuse_adb_provider.h"
 #include "fuse_sideload.h"
 
-int read_block_adb(void* data, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
-    adb_data* ad = reinterpret_cast<adb_data*>(data);
+int read_block_adb(const adb_data& ad, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
+  if (!WriteFdFmt(ad.sfd, "%08u", block)) {
+    fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno));
+    return -EIO;
+  }
 
-    if (!WriteFdFmt(ad->sfd, "%08u", block)) {
-        fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno));
-        return -EIO;
-    }
+  if (!ReadFdExactly(ad.sfd, buffer, fetch_size)) {
+    fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno));
+    return -EIO;
+  }
 
-    if (!ReadFdExactly(ad->sfd, buffer, fetch_size)) {
-        fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno));
-        return -EIO;
-    }
-
-    return 0;
-}
-
-static void close_adb(void* data) {
-    adb_data* ad = reinterpret_cast<adb_data*>(data);
-    WriteFdExactly(ad->sfd, "DONEDONE");
+  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;
+  adb_data ad;
+  ad.sfd = sfd;
+  ad.file_size = file_size;
+  ad.block_size = block_size;
 
-    provider_vtab vtab;
-    vtab.read_block = read_block_adb;
-    vtab.close = close_adb;
+  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, &ad, file_size, block_size);
+  return run_fuse_sideload(vtab, file_size, block_size);
 }
diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h
index 9941709..36d86d5 100644
--- a/minadbd/fuse_adb_provider.h
+++ b/minadbd/fuse_adb_provider.h
@@ -20,13 +20,13 @@
 #include <stdint.h>
 
 struct adb_data {
-    int sfd;  // file descriptor for the adb channel
+  int sfd;  // file descriptor for the adb channel
 
-    uint64_t file_size;
-    uint32_t block_size;
+  uint64_t file_size;
+  uint32_t block_size;
 };
 
-int read_block_adb(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size);
+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 31be2a6..00250e5 100644
--- a/minadbd/fuse_adb_provider_test.cpp
+++ b/minadbd/fuse_adb_provider_test.cpp
@@ -46,8 +46,8 @@
 
   uint32_t block = 1234U;
   const char expected_block[] = "00001234";
-  ASSERT_EQ(0, read_block_adb(static_cast<void*>(&data), block,
-                              reinterpret_cast<uint8_t*>(block_data), sizeof(expected_data) - 1));
+  ASSERT_EQ(0, read_block_adb(data, block, reinterpret_cast<uint8_t*>(block_data),
+                              sizeof(expected_data) - 1));
 
   // Check that read_block_adb requested the right block.
   char block_req[sizeof(expected_block)] = {};
@@ -84,7 +84,7 @@
   signal(SIGPIPE, SIG_IGN);
 
   char buf[1];
-  ASSERT_EQ(-EIO, read_block_adb(static_cast<void*>(&data), 0, reinterpret_cast<uint8_t*>(buf), 1));
+  ASSERT_EQ(-EIO, read_block_adb(data, 0, reinterpret_cast<uint8_t*>(buf), 1));
 
   close(sockets[0]);
 }
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
index 6919cdd..03692d0 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -117,26 +117,30 @@
 }
 #endif
 
-int service_to_fd(const char* name, const atransport* transport) {
-    int ret = -1;
-
-    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)) {
-#if PLATFORM_SDK_VERSION < 26
-        char* arg = strdup(name + 14);
+#if PLATFORM_SDK_VERSION >= 28
+int service_to_fd(const char* name, atransport* /* transport */) {
 #else
-        std::string arg(name + 14);
+int service_to_fd(const char* name, const atransport* transport __unused) {
 #endif
-        ret = create_service_thread(sideload_host_service, arg);
-    }
-    if (ret >= 0) {
-        close_on_exec(ret);
-    }
-    return ret;
+  int ret = -1;
+
+  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)) {
+#if PLATFORM_SDK_VERSION < 26
+    char* arg = strdup(name + 14);
+#else
+    std::string arg(name + 14);
+#endif
+    ret = create_service_thread(sideload_host_service, arg);
+  }
+  if (ret >= 0) {
+    close_on_exec(ret);
+  }
+  return ret;
 }
 
 #if PLATFORM_SDK_VERSION == 23
diff --git a/minadbd21/Android.mk b/minadbd21/Android.mk
index 19c8b60..201c795 100644
--- a/minadbd21/Android.mk
+++ b/minadbd21/Android.mk
@@ -23,6 +23,7 @@
 
 LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter
 LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
+LOCAL_CFLAGS += -DUSE_FUSE_SIDELOAD22
 LOCAL_MODULE_TAGS := eng
 LOCAL_MODULE := libminadbd
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/../
diff --git a/minui/Android.mk b/minui/Android.mk
index cb56b73..ddec62b 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -13,6 +13,9 @@
 # limitations under the License.
 
 LOCAL_PATH := $(call my-dir)
+
+# libminui (static library)
+# ===============================
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
@@ -33,13 +36,13 @@
     LOCAL_C_INCLUDES += $(TARGET_OUT_INTERMEDIATES)/KERNEL_OBJ/usr/include
   else
     ifeq ($(TARGET_CUSTOM_KERNEL_HEADERS),)
-      LOCAL_C_INCLUDES += $(commands_recovery_local_path)/minui/include
+      LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
     else
       LOCAL_C_INCLUDES += $(TARGET_CUSTOM_KERNEL_HEADERS)
     endif
   endif
 else
-  LOCAL_C_INCLUDES += $(commands_recovery_local_path)/minui/include
+  LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
   # The header files required for adf graphics can cause compile errors
   # with adf graphics.
   LOCAL_SRC_FILES += graphics_adf.cpp
@@ -50,7 +53,7 @@
   LOCAL_CFLAGS += -DNEW_ION_HEAP
 endif
 
-LOCAL_STATIC_LIBRARIES += libpng
+LOCAL_STATIC_LIBRARIES += libpng libbase
 ifneq ($(wildcard external/libdrm/Android.common.mk),)
 LOCAL_WHOLE_STATIC_LIBRARIES += libdrm_platform
 else
@@ -61,11 +64,7 @@
     LOCAL_WHOLE_STATIC_LIBRARIES += libsync_recovery
 endif
 
-LOCAL_STATIC_LIBRARIES := \
-    libpng \
-    libbase
-
-LOCAL_CFLAGS += -Werror -std=c++14
+LOCAL_CFLAGS += -Wall -Werror -std=c++14 -Wno-unused-private-field
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 
@@ -114,8 +113,16 @@
     TARGET_GLOBAL_CFLAGS += -DTW_NO_MINUI_CUSTOM_FONTS
     CLANG_TARGET_GLOBAL_CFLAGS += -DTW_NO_MINUI_CUSTOM_FONTS
 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_CLANG := true
@@ -125,8 +132,7 @@
     libpng \
     libbase
 
-LOCAL_CFLAGS := -Werror
-
+LOCAL_CFLAGS := -Wall -Werror -std=c++14 -Wno-unused-private-field
 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 e9383ca..f67684b 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <dirent.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <linux/input.h>
 #include <stdio.h>
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index b45e22e..f3380aa 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -16,6 +16,7 @@
 
 #include "graphics.h"
 
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -43,20 +44,26 @@
 static int overscan_offset_x = 0;
 static int overscan_offset_y = 0;
 
+#ifdef TW_NO_MINUI_CUSTOM_FONTS
 static unsigned char gr_current_r = 255;
 static unsigned char gr_current_g = 255;
 static unsigned char gr_current_b = 255;
+#endif
 static unsigned char gr_current_a = 255;
 static unsigned char rgb_555[2];
 static unsigned char gr_current_r5 = 31;
 static unsigned char gr_current_g5 = 63;
 static unsigned char gr_current_b5 = 31;
 
-static GRSurface* gr_draw = NULL;
+static uint32_t gr_current = ~0;
+static constexpr uint32_t alpha_mask = 0xff000000;
 
-static bool outside(int x, int y)
-{
-    return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height;
+static GRSurface* gr_draw = NULL;
+static GRRotation rotation = ROTATION_NONE;
+
+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);
 }
 
 #ifdef TW_NO_MINUI_CUSTOM_FONTS
@@ -71,20 +78,17 @@
     *y = gr_font->char_height;
 }
 #else // TW_USE_MINUI_CUSTOM_FONTS
-const GRFont* gr_sys_font()
-{
-    return gr_font;
+const GRFont* gr_sys_font() {
+  return gr_font;
 }
 
-int gr_measure(const GRFont* font, const char *s)
-{
-    return font->char_width * strlen(s);
+int gr_measure(const GRFont* font, const char* s) {
+  return font->char_width * strlen(s);
 }
 
-void gr_font_size(const GRFont* font, int *x, int *y)
-{
-    *x = font->char_width;
-    *y = font->char_height;
+void gr_font_size(const GRFont* font, int* x, int* y) {
+  *x = font->char_width;
+  *y = font->char_height;
 }
 #endif // TW_NO_MINUI_CUSTOM_FONTS
 
@@ -112,7 +116,8 @@
     *px++ = (newred << 3) + (newgreen >> 3);
 }
 
-static void text_blend(unsigned char* src_p, int src_row_bytes,
+#ifdef TW_NO_MINUI_CUSTOM_FONTS
+static void text_blend_old(unsigned char* src_p, int src_row_bytes,
                        unsigned char* dst_p, int dst_row_bytes,
                        int width, int height)
 {
@@ -153,6 +158,154 @@
         dst_p += dst_row_bytes;
     }
 }
+#endif // TW_NO_MINUI_CUSTOM_FONTS
+
+// Blends gr_current onto pix value, assumes alpha as most significant byte.
+static inline uint16_t pixel_blend16(uint8_t a, uint16_t pix) {
+  unsigned char orig[2];
+  orig[0] = (pix & 0xFF00) >> 8;
+  orig[1] = pix & 0x00FF;
+
+  /* This code is a little easier to read
+  unsigned oldred = (orig[1] >> 3);
+  unsigned oldgreen = (((orig[0] >> 5) << 3) + (orig[1] & 0x7));
+  unsigned oldblue = (orig[0] & 0x1F);
+
+  unsigned newred = (oldred * (255-a) + r5 * a) / 255;
+  unsigned newgreen = (oldgreen * (255-a) + g5 * a) / 255;
+  unsigned newblue = (oldblue * (255-a) + b5 * a) / 255;
+  */
+
+  unsigned newred = ((orig[1] >> 3) * (255-a) + gr_current_r5 * a) / 255;
+  unsigned newgreen = ((((orig[0] >> 5) << 3) + (orig[1] & 0x7)) * (255-a) + gr_current_g5 * a) / 255;
+  unsigned newblue = ((orig[0] & 0x1F) * (255-a) + gr_current_b5 * a) / 255;
+
+  uint16_t newpix = (newred << 10) + (newgreen << 5) + newblue;
+  return newpix;
+}
+
+// Blends gr_current onto pix value, assumes alpha as most significant byte.
+static inline uint32_t pixel_blend(uint8_t alpha, uint32_t pix) {
+  if (alpha == 255) return gr_current;
+  if (alpha == 0) return pix;
+  uint32_t pix_r = pix & 0xff;
+  uint32_t pix_g = pix & 0xff00;
+  uint32_t pix_b = pix & 0xff0000;
+  uint32_t cur_r = gr_current & 0xff;
+  uint32_t cur_g = gr_current & 0xff00;
+  uint32_t cur_b = gr_current & 0xff0000;
+
+  uint32_t out_r = (pix_r * (255 - alpha) + cur_r * alpha) / 255;
+  uint32_t out_g = (pix_g * (255 - alpha) + cur_g * alpha) / 255;
+  uint32_t out_b = (pix_b * (255 - alpha) + cur_b * alpha) / 255;
+
+  return (out_r & 0xff) | (out_g & 0xff00) | (out_b & 0xff0000) | (gr_current & 0xff000000);
+}
+
+// increments pixel pointer right, with current rotation.
+static void incr_x16(uint16_t** p, int row_pixels) {
+  if (rotation % 2) {
+    *p = *p + (rotation == 1 ? 1 : -1) * row_pixels;
+  } else {
+    *p = *p + (rotation ? -1 : 1);
+  }
+}
+
+// increments pixel pointer down, with current rotation.
+static void incr_y16(uint16_t** p, int row_pixels) {
+  if (rotation % 2) {
+    *p = *p + (rotation == 1 ? -1 : 1);
+  } else {
+    *p = *p + (rotation ? -1 : 1) * row_pixels;
+  }
+}
+
+// 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);
+  }
+}
+
+// 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;
+  }
+}
+
+// returns pixel pointer at given coordinates with rotation adjustment.
+static uint16_t* pixel_at16(GRSurface* surf, int x, int y, int row_pixels) {
+  switch (rotation) {
+    case ROTATION_NONE:
+      return reinterpret_cast<uint16_t*>(surf->data) + y * row_pixels + x;
+    case ROTATION_RIGHT:
+      return reinterpret_cast<uint16_t*>(surf->data) + x * row_pixels + (surf->width - y);
+    case ROTATION_DOWN:
+      return reinterpret_cast<uint16_t*>(surf->data) + (surf->height - 1 - y) * row_pixels +
+             (surf->width - 1 - x);
+    case ROTATION_LEFT:
+      return reinterpret_cast<uint16_t*>(surf->data) + (surf->height - 1 - x) * row_pixels + y;
+    default:
+      printf("invalid rotation %d", rotation);
+  }
+  return nullptr;
+}
+
+// returns pixel pointer at given coordinates with rotation adjustment.
+static uint32_t* pixel_at(GRSurface* surf, 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;
+    default:
+      printf("invalid rotation %d", rotation);
+  }
+  return nullptr;
+}
+
+static void text_blend16(uint8_t* src_p, int src_row_bytes, uint16_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;
+    uint16_t* px = dst_p;
+    for (int i = 0; i < width; ++i, incr_x16(&px, dst_row_pixels)) {
+      uint8_t a = *sx++;
+      if (alpha_current < 255) a = (static_cast<uint32_t>(a) * alpha_current) / 255;
+      *px = pixel_blend16(a, *px);
+    }
+    src_p += src_row_bytes;
+    incr_y16(&dst_p, dst_row_pixels);
+  }
+}
+
+static void text_blend(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;
+    uint32_t* px = dst_p;
+    for (int i = 0; i < width; ++i, incr_x(&px, dst_row_pixels)) {
+      uint8_t a = *sx++;
+      if (alpha_current < 255) a = (static_cast<uint32_t>(a) * alpha_current) / 255;
+      *px = pixel_blend(a, *px);
+    }
+    src_p += src_row_bytes;
+    incr_y(&dst_p, dst_row_pixels);
+  }
+}
+
 
 #ifdef TW_NO_MINUI_CUSTOM_FONTS
 void gr_text(int x, int y, const char *s, bool bold)
@@ -178,248 +331,284 @@
                                (bold ? font->char_height * font->texture->row_bytes : 0);
         unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
 
-        text_blend(src_p, font->texture->row_bytes,
-                   dst_p, gr_draw->row_bytes,
-                   font->char_width, font->char_height);
+        text_blend_old(src_p, font->texture->row_bytes,
+                       dst_p, gr_draw->row_bytes,
+                       font->char_width, font->char_height);
 
         x += font->char_width;
     }
 }
 #else //TW_NO_MINUI_CUSTOM_FONTS
-void gr_text(const GRFont* font, int x, int y, const char *s, bool bold)
-{
-    if (!font->texture || gr_current_a == 0) return;
+void gr_text(const GRFont* font, int x, int y, const char* s, bool bold) {
+  if (!font || !font->texture || (gr_current & alpha_mask) == 0) return;
 
-    bold = bold && (font->texture->height != font->char_height);
+  if (font->texture->pixel_bytes != 1) {
+    printf("gr_text: font has wrong format\n");
+    return;
+  }
 
-    x += overscan_offset_x;
-    y += overscan_offset_y;
+  bold = bold && (font->texture->height != font->char_height);
 
-    unsigned char ch;
-    while ((ch = *s++)) {
-        if (outside(x, y) || outside(x+font->char_width-1, y+font->char_height-1)) break;
+  x += overscan_offset_x;
+  y += overscan_offset_y;
 
-        if (ch < ' ' || ch > '~') {
-            ch = '?';
-        }
+  unsigned char ch;
+  while ((ch = *s++)) {
+    if (outside(x, y) || outside(x + font->char_width - 1, y + font->char_height - 1)) break;
 
-        unsigned char* src_p = font->texture->data + ((ch - ' ') * font->char_width) +
-                               (bold ? font->char_height * font->texture->row_bytes : 0);
-        unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
-
-        text_blend(src_p, font->texture->row_bytes,
-                   dst_p, gr_draw->row_bytes,
-                   font->char_width, font->char_height);
-
-        x += font->char_width;
+    if (ch < ' ' || ch > '~') {
+      ch = '?';
     }
+
+    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);
+    if (gr_draw->pixel_bytes == 2) {
+      uint16_t* dst_p = pixel_at16(gr_draw, x, y, row_pixels);
+      
+      text_blend16(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width,
+                   font->char_height);
+    } else { // not indenting AOSP original code
+    uint32_t* dst_p = pixel_at(gr_draw, x, y, row_pixels);
+
+    text_blend(src_p, font->texture->row_bytes, dst_p, row_pixels, font->char_width,
+               font->char_height);
+    }
+    x += font->char_width;
+  }
 }
 #endif //TW_NO_MINUI_CUSTOM_FONTS
 
 void gr_texticon(int x, int y, GRSurface* icon) {
-    if (icon == NULL) return;
+  if (icon == NULL) return;
 
-    if (icon->pixel_bytes != 1) {
-        printf("gr_texticon: source has wrong format\n");
-        return;
-    }
+  if (icon->pixel_bytes != 1) {
+    printf("gr_texticon: source has wrong format\n");
+    return;
+  }
 
-    x += overscan_offset_x;
-    y += overscan_offset_y;
+  x += overscan_offset_x;
+  y += overscan_offset_y;
 
-    if (outside(x, y) || outside(x+icon->width-1, y+icon->height-1)) return;
+  if (outside(x, y) || outside(x + icon->width - 1, y + icon->height - 1)) return;
 
-    unsigned char* src_p = icon->data;
-    unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
+  int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
+  uint8_t* src_p = icon->data;
+  if (gr_draw->pixel_bytes == 2) {
+	uint16_t* dst_p = pixel_at16(gr_draw, x, y, row_pixels);
 
-    text_blend(src_p, icon->row_bytes,
-               dst_p, gr_draw->row_bytes,
-               icon->width, icon->height);
+    text_blend16(src_p, icon->row_bytes, dst_p, row_pixels, icon->width, icon->height);
+    return;
+  }
+  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);
 }
 
-void gr_convert_rgb_555()
+void gr_convert_rgb_555(unsigned char r, unsigned char g, unsigned char b)
 {
-    gr_current_r5 = (((gr_current_r & 0xFF) * 0x1F) + 0x7F) / 0xFF;
-    gr_current_g5 = (((gr_current_g & 0xFF) * 0x3F) + 0x7F) / 0xFF;
-    gr_current_b5 = (((gr_current_b & 0xFF) * 0x1F) + 0x7F) / 0xFF;
+    gr_current_r5 = (((r & 0xFF) * 0x1F) + 0x7F) / 0xFF;
+    gr_current_g5 = (((g & 0xFF) * 0x3F) + 0x7F) / 0xFF;
+    gr_current_b5 = (((b & 0xFF) * 0x1F) + 0x7F) / 0xFF;
 
     rgb_555[0] = (gr_current_g5 << 5) + (gr_current_b5);
     rgb_555[1] = (gr_current_r5 << 3) + (gr_current_g5 >> 3);
 }
 
-void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
-{
+void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
+#ifdef TW_NO_MINUI_CUSTOM_FONTS
 #if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
-    gr_current_r = b;
-    gr_current_g = g;
-    gr_current_b = r;
-    gr_current_a = a;
+  gr_current_r = b;
+  gr_current_g = g;
+  gr_current_b = r;
 #else
-    gr_current_r = r;
-    gr_current_g = g;
-    gr_current_b = b;
-    gr_current_a = a;
+  gr_current_r = r;
+  gr_current_g = g;
+  gr_current_b = b;
 #endif
-    if (gr_draw->pixel_bytes == 2) {
-        gr_convert_rgb_555();
-    }
+#endif
+
+  if (gr_draw->pixel_bytes == 2) {
+	gr_current_a = a;
+    gr_convert_rgb_555(r, g, b);
+    return;
+  }
+
+  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
 }
 
-void gr_clear()
-{
-    if (gr_draw->pixel_bytes == 2) {
-        gr_fill(0, 0, gr_fb_width(), gr_fb_height());
-        return;
-    }
+void gr_clear() {
+  if (gr_draw->pixel_bytes == 2) {
+    gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+    return;
+  }
 
-    // This code only works on 32bpp devices
-    if (gr_current_r == gr_current_g && gr_current_r == gr_current_b) {
-        memset(gr_draw->data, gr_current_r, gr_draw->height * gr_draw->row_bytes);
-    } else {
-        unsigned char* px = gr_draw->data;
-        for (int y = 0; y < gr_draw->height; ++y) {
-            for (int x = 0; x < gr_draw->width; ++x) {
-                *px++ = gr_current_r;
-                *px++ = gr_current_g;
-                *px++ = gr_current_b;
-                px++;
-            }
-            px += gr_draw->row_bytes - (gr_draw->width * gr_draw->pixel_bytes);
-        }
+  // This code only works on 32bpp devices
+  if ((gr_current & 0xff) == ((gr_current >> 8) & 0xff) &&
+      (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);
+  } else {
+    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) {
+        *px++ = gr_current;
+      }
+      px += row_diff;
     }
+  }
 }
 
-void gr_fill(int x1, int y1, int x2, int y2)
-{
-    x1 += overscan_offset_x;
-    y1 += overscan_offset_y;
+void gr_fill(int x1, int y1, int x2, int y2) {
+  x1 += overscan_offset_x;
+  y1 += overscan_offset_y;
 
-    x2 += overscan_offset_x;
-    y2 += overscan_offset_y;
+  x2 += overscan_offset_x;
+  y2 += overscan_offset_y;
 
-    if (outside(x1, y1) || outside(x2-1, y2-1)) return;
+  if (outside(x1, y1) || outside(x2 - 1, y2 - 1)) return;
 
-    unsigned char* p = gr_draw->data + y1 * gr_draw->row_bytes + x1 * gr_draw->pixel_bytes;
-    if (gr_current_a == 255) {
-        int x, y;
-        for (y = y1; y < y2; ++y) {
-            unsigned char* px = p;
-            for (x = x1; x < x2; ++x) {
-                if (gr_draw->pixel_bytes == 2) {
-                    *px++ = rgb_555[0];
-                    *px++ = rgb_555[1];
-                } else {
-                    *px++ = gr_current_r;
-                    *px++ = gr_current_g;
-                    *px++ = gr_current_b;
-                    px++;
-                }
-            }
-            p += gr_draw->row_bytes;
+  int row_pixels = gr_draw->row_bytes / gr_draw->pixel_bytes;
+  if (gr_draw->pixel_bytes == 2) {
+	uint16_t* p = pixel_at16(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) {
+        uint16_t* px = p;
+        for (int x = x1; x < x2; ++x) {
+          *px = pixel_blend16(alpha, *px);
+          incr_x16(&px, row_pixels);
         }
-    } else if (gr_current_a > 0) {
-        int x, y;
-        for (y = y1; y < y2; ++y) {
-            unsigned char* px = p;
-            for (x = x1; x < x2; ++x) {
-                if (gr_draw->pixel_bytes == 2) {
-                    blend_16bpp(px, gr_current_r5, gr_current_g5, gr_current_b5, gr_current_a);
-                    px += gr_draw->row_bytes;
-                } else {
-                    *px = (*px * (255-gr_current_a) + gr_current_r * gr_current_a) / 255;
-                    ++px;
-                    *px = (*px * (255-gr_current_a) + gr_current_g * gr_current_a) / 255;
-                    ++px;
-                    *px = (*px * (255-gr_current_a) + gr_current_b * gr_current_a) / 255;
-                    ++px;
-                    ++px;
-                }
-            }
-            p += gr_draw->row_bytes;
-        }
+        incr_y16(&p, row_pixels);
+      }
     }
+	return;
+  }
+  { // open brace to maintain separation between uint16_t p and uint32_t p
+  uint32_t* p = pixel_at(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) {
+      uint32_t* px = p;
+      for (int x = x1; x < x2; ++x) {
+        *px = pixel_blend(alpha, *px);
+        incr_x(&px, row_pixels);
+      }
+      incr_y(&p, row_pixels);
+    }
+  }
+  } // close brace to maintain separation between uint16_t p and uint32_t p
 }
 
 void gr_blit_32to16(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
-    dx += overscan_offset_x;
-    dy += overscan_offset_y;
+  if (rotation)
+    printf("gr_blit_32to16 does not support rotation!\n"); // but we'll draw something in the wrong spot anyway because, why not!
 
-    if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return;
+  dx += overscan_offset_x;
+  dy += overscan_offset_y;
 
-    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;
+  if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return;
 
-    int i, j;
-    for (i = 0; i < h; ++i) {
-        unsigned char* spx = src_p;
-        unsigned char* dpx = dst_p;
+  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;
 
-        for (j = 0; j < w; ++j) {
-            unsigned a = spx[3];
+  int i, j;
+  for (i = 0; i < h; ++i) {
+    unsigned char* spx = src_p;
+    unsigned char* dpx = dst_p;
 
-            if (a == 0) {
-                spx += source->pixel_bytes;
-                dpx += gr_draw->pixel_bytes;
-            } else {
-                unsigned r5 = (((*spx++ & 0xFF) * 0x1F) + 0x7F) / 0xFF;
-                unsigned g5 = (((*spx++ & 0xFF) * 0x3F) + 0x7F) / 0xFF;
-                unsigned b5 = (((*spx++ & 0xFF) * 0x1F) + 0x7F) / 0xFF;
-                spx++;
-                if (a == 255) {
-                    *dpx++ = (g5 << 5) + (b5);
-                    *dpx++ = (r5 << 3) + (g5 >> 3);
-                } else {
-                    blend_16bpp(dpx, r5, g5, b5, a);
-                    spx += source->pixel_bytes;
-                }
-            }
+    for (j = 0; j < w; ++j) {
+      unsigned a = spx[3];
+
+      if (a == 0) {
+        spx += source->pixel_bytes;
+        dpx += gr_draw->pixel_bytes;
+      } else {
+        unsigned r5 = (((*spx++ & 0xFF) * 0x1F) + 0x7F) / 0xFF;
+        unsigned g5 = (((*spx++ & 0xFF) * 0x3F) + 0x7F) / 0xFF;
+        unsigned b5 = (((*spx++ & 0xFF) * 0x1F) + 0x7F) / 0xFF;
+        spx++;
+        if (a == 255) {
+          *dpx++ = (g5 << 5) + (b5);
+          *dpx++ = (r5 << 3) + (g5 >> 3);
+        } else {
+          blend_16bpp(dpx, r5, g5, b5, a);
+          spx += source->pixel_bytes;
         }
-        src_p += source->row_bytes;
-        dst_p += gr_draw->row_bytes;
+      }
     }
+    src_p += source->row_bytes;
+    dst_p += gr_draw->row_bytes;
+  }
 }
 
 void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
-    if (source == NULL) return;
+  if (source == NULL) return;
 
-    if (gr_draw->pixel_bytes != source->pixel_bytes) {
-        if (gr_draw->pixel_bytes == 2 && source->pixel_bytes == 4) {
-            gr_blit_32to16(source, sx, sy, w, h, dx, dy);
-            return;
-        } else {
-            printf("gr_blit: source has wrong format\n");
-            return;
-        }
+  if (gr_draw->pixel_bytes != source->pixel_bytes) {
+    if (gr_draw->pixel_bytes == 2 && source->pixel_bytes == 4) {
+      gr_blit_32to16(source, sx, sy, w, h, dx, dy);
+      return;
+    } else {
+      printf("gr_blit: source has wrong format\n");
+      return;
     }
+  }
 
-    dx += overscan_offset_x;
-    dy += overscan_offset_y;
+  dx += overscan_offset_x;
+  dy += overscan_offset_y;
 
-    if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return;
+  if (outside(dx, dy) || outside(dx + w - 1, dy + h - 1)) return;
 
-    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;
+  if (rotation) {
+    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);
+
+    for (int y = 0; y < h; y += 1) {
+      uint32_t* src_px = src_py;
+      uint32_t* dst_px = dst_py;
+      for (int x = 0; x < w; x += 1) {
+        *dst_px = *src_px++;
+        incr_x(&dst_px, row_pixels);
+      }
+      src_py += src_row_pixels;
+      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;
 
     int i;
     for (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;
+      memcpy(dst_p, src_p, w * source->pixel_bytes);
+      src_p += source->row_bytes;
+      dst_p += gr_draw->row_bytes;
     }
+  }
 }
 
 unsigned int gr_get_width(GRSurface* surface) {
-    if (surface == NULL) {
-        return 0;
-    }
-    return surface->width;
+  if (surface == NULL) {
+    return 0;
+  }
+  return surface->width;
 }
 
 unsigned int gr_get_height(GRSurface* surface) {
-    if (surface == NULL) {
-        return 0;
-    }
-    return surface->height;
+  if (surface == NULL) {
+    return 0;
+  }
+  return surface->height;
 }
 
 #ifdef TW_NO_MINUI_CUSTOM_FONTS
@@ -466,58 +655,56 @@
 }
 #else // TW_NO_MINUI_CUSTOM_FONTS
 int gr_init_font(const char* name, GRFont** dest) {
-    GRFont* font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
-    if (font == nullptr) {
-        return -1;
-    }
+  GRFont* font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
+  if (font == nullptr) {
+    return -1;
+  }
 
-    int res = res_create_alpha_surface(name, &(font->texture));
-    if (res < 0) {
-        free(font);
-        return res;
-    }
+  int res = res_create_alpha_surface(name, &(font->texture));
+  if (res < 0) {
+    free(font);
+    return res;
+  }
 
-    // The font image should be a 96x2 array of character images.  The
-    // columns are the printable ASCII characters 0x20 - 0x7f.  The
-    // top row is regular text; the bottom row is bold.
-    font->char_width = font->texture->width / 96;
-    font->char_height = font->texture->height / 2;
+  // The font image should be a 96x2 array of character images.  The
+  // columns are the printable ASCII characters 0x20 - 0x7f.  The
+  // top row is regular text; the bottom row is bold.
+  font->char_width = font->texture->width / 96;
+  font->char_height = font->texture->height / 2;
 
-    *dest = font;
+  *dest = font;
 
-    return 0;
+  return 0;
 }
 
-static void gr_init_font(void)
-{
-    int res = gr_init_font("font", &gr_font);
-    if (res == 0) {
-        return;
-    }
+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);
+  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;
 
-    // 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* 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);
+  }
 
-    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;
+  gr_font->char_width = font.char_width;
+  gr_font->char_height = font.char_height;
 }
 #endif // TW_NO_MINUI_CUSTOM_FONTS
 
@@ -575,6 +762,12 @@
   gr_flip();
   gr_flip();
 
+  gr_rotate(DEFAULT_ROTATION);
+
+  if (gr_draw->pixel_bytes != 4) {
+    printf("gr_init: Only 4-byte pixel formats supported\n");
+  }
+
   return 0;
 }
 
@@ -583,13 +776,19 @@
 }
 
 int gr_fb_width() {
-  return gr_draw->width - 2 * overscan_offset_x;
+  return rotation % 2 ? gr_draw->height - 2 * overscan_offset_y
+                      : gr_draw->width - 2 * overscan_offset_x;
 }
 
 int gr_fb_height() {
-  return gr_draw->height - 2 * overscan_offset_y;
+  return rotation % 2 ? gr_draw->width - 2 * overscan_offset_x
+                      : gr_draw->height - 2 * overscan_offset_y;
 }
 
 void gr_fb_blank(bool blank) {
   gr_backend->Blank(blank);
 }
+
+void gr_rotate(GRRotation rot) {
+  rotation = rot;
+}
diff --git a/minui/graphics_overlay.cpp b/minui/graphics_overlay.cpp
index 082d385..b7e62d9 100644
--- a/minui/graphics_overlay.cpp
+++ b/minui/graphics_overlay.cpp
@@ -47,7 +47,6 @@
 #define ALIGN(x, align) (((x) + ((align)-1)) & ~((align)-1))
 
 MinuiBackendOverlay::MinuiBackendOverlay() :
-  gr_draw(nullptr),
   fb_fd(-1),
   isMDP5(false),
   leftSplit(0),
@@ -76,7 +75,6 @@
 
 bool MinuiBackendOverlay::target_has_overlay()
 {
-    int ret;
     int mdp_version;
     bool overlay_supported = false;
     fb_fix_screeninfo fi;
@@ -524,6 +522,7 @@
 }
 
 GRSurface* MinuiBackendOverlay::Init() {
+	gr_draw = NULL; // this should be in the constructor but 9.0 was throwing a compile error
 	if (!target_has_overlay())
 	    return NULL;
 
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index bd8c386..bff146a 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -23,23 +23,31 @@
 
 #include <functional>
 #include <string>
+#include <vector>
 
 //
 // Graphics.
 //
 
 struct GRSurface {
-    int width;
-    int height;
-    int row_bytes;
-    int pixel_bytes;
-    unsigned char* data;
+  int width;
+  int height;
+  int row_bytes;
+  int pixel_bytes;
+  unsigned char* data;
 };
 
 struct GRFont {
-    GRSurface* texture;
-    int char_width;
-    int char_height;
+  GRSurface* texture;
+  int char_width;
+  int char_height;
+};
+
+enum GRRotation {
+  ROTATION_NONE = 0,
+  ROTATION_RIGHT = 1,
+  ROTATION_DOWN = 2,
+  ROTATION_LEFT = 3,
 };
 
 int gr_init();
@@ -65,15 +73,18 @@
 
 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);
-int gr_measure(const GRFont* font, const char *s);
-void gr_font_size(const GRFont* font, int *x, int *y);
+void gr_text(const GRFont* font, int x, int y, const char* s, bool bold);
+int gr_measure(const GRFont* font, const char* s);
+void gr_font_size(const GRFont* font, int* x, int* y);
 #endif
 
 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);
 
+// Set rotation, flips gr_fb_width/height if 90 degree rotation difference
+void gr_rotate(GRRotation rotation);
+
 //
 // Input events.
 //
@@ -153,6 +164,9 @@
 int res_create_localized_alpha_surface(const char* name, const char* locale,
                                        GRSurface** pSurface);
 
+// Return a list of locale strings embedded in |png_name|. Return a empty list in case of failure.
+std::vector<std::string> get_locales_in_png(const std::string& png_name);
+
 // Free a surface allocated by any of the res_create_*_surface()
 // functions.
 void res_free_surface(GRSurface* surface);
diff --git a/minui/main.cpp b/minui/main.cpp
index 13fee87..4c3c52c 100644
--- a/minui/main.cpp
+++ b/minui/main.cpp
@@ -89,11 +89,11 @@
 	gr_color(255, 255, 255, 255);
 	gr_fill(0, 0, gr_fb_width(), gr_fb_height());
 	gr_color(255, 0, 0, 255);
-	gr_text(10, 10, "RED red RED", false);
+	gr_text(gr_sys_font(), 10, 10, "RED red RED", false);
 	gr_color(0, 255, 0, 255);
-	gr_text(10, 50, "GREEN green GREEN", false);
+	gr_text(gr_sys_font(), 10, 50, "GREEN green GREEN", false);
 	gr_color(0, 0, 255, 255);
-	gr_text(10, 90, "BLUE blue BLUE", false);
+	gr_text(gr_sys_font(), 10, 90, "BLUE blue BLUE", false);
 	gr_flip();
 	sleep(3);
 	printf("PNG test with /res/images/test.png\n");
diff --git a/minui/resources.cpp b/minui/resources.cpp
index b9797b9..fd57893 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -25,10 +25,12 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <memory>
 #include <regex>
 #include <string>
 #include <vector>
 
+//#include <android-base/stringprintf.h> // does not exist in 6.0
 //#include <android-base/strings.h> // does not exist in 6.0
 #include <png.h>
 
@@ -46,89 +48,128 @@
     return surface;
 }
 
-static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr,
-                    png_uint_32* width, png_uint_32* height, png_byte* channels) {
-    char resPath[256];
-    unsigned char header[8];
-    int result = 0;
-    int color_type, bit_depth;
-    size_t bytesRead;
+// 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);
 
-    snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
-    resPath[sizeof(resPath)-1] = '\0';
-    FILE* fp = fopen(resPath, "rbe");
-    if (fp == NULL) {
-        result = -1;
-        goto exit;
-    }
+  ~PngHandler();
 
-    bytesRead = fread(header, 1, sizeof(header), fp);
-    if (bytesRead != sizeof(header)) {
-        result = -2;
-        goto exit;
-    }
+  png_uint_32 width() const {
+    return width_;
+  }
 
-    if (png_sig_cmp(header, 0, sizeof(header))) {
-        result = -3;
-        goto exit;
-    }
+  png_uint_32 height() const {
+    return height_;
+  }
 
-    *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-    if (!*png_ptr) {
-        result = -4;
-        goto exit;
-    }
+  png_byte channels() const {
+    return channels_;
+  }
 
-    *info_ptr = png_create_info_struct(*png_ptr);
-    if (!*info_ptr) {
-        result = -5;
-        goto exit;
-    }
+  png_structp png_ptr() const {
+    return png_ptr_;
+  }
 
-    if (setjmp(png_jmpbuf(*png_ptr))) {
-        result = -6;
-        goto exit;
-    }
+  png_infop info_ptr() const {
+    return info_ptr_;
+  }
 
-    png_init_io(*png_ptr, fp);
-    png_set_sig_bytes(*png_ptr, sizeof(header));
-    png_read_info(*png_ptr, *info_ptr);
+  int error_code() const {
+    return error_code_;
+  };
 
-    png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth,
-            &color_type, NULL, NULL, NULL);
+  operator bool() const {
+    return error_code_ == 0;
+  }
 
-    *channels = png_get_channels(*png_ptr, *info_ptr);
+ private:
+  png_structp png_ptr_{ nullptr };
+  png_infop info_ptr_{ nullptr };
+  png_uint_32 width_;
+  png_uint_32 height_;
+  png_byte channels_;
 
-    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) {
-        // 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) {
-        // 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
-        // general.
-        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);
-        result = -7;
-        goto exit;
-    }
+  // 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_;
+};
 
-    return result;
+PngHandler::PngHandler(const std::string& name) : error_code_(0), png_fp_(nullptr, fclose) {
+  char res_path[PATH_MAX];
+  sprintf(res_path, "/res/images/%s.png", name.c_str());
+  //std::string res_path = sprintf("/res/images/%s.png", name.c_str());
+  png_fp_.reset(fopen(res_path, "rbe"));
+  if (!png_fp_) {
+    error_code_ = -1;
+    return;
+  }
 
-  exit:
-    if (result < 0) {
-        png_destroy_read_struct(png_ptr, info_ptr, NULL);
-    }
-    if (fp != NULL) {
-        fclose(fp);
-    }
+  unsigned char header[8];
+  size_t bytesRead = fread(header, 1, sizeof(header), png_fp_.get());
+  if (bytesRead != sizeof(header)) {
+    error_code_ = -2;
+    return;
+  }
 
-    return result;
+  if (png_sig_cmp(header, 0, sizeof(header))) {
+    error_code_ = -3;
+    return;
+  }
+
+  png_ptr_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+  if (!png_ptr_) {
+    error_code_ = -4;
+    return;
+  }
+
+  info_ptr_ = png_create_info_struct(png_ptr_);
+  if (!info_ptr_) {
+    error_code_ = -5;
+    return;
+  }
+
+  if (setjmp(png_jmpbuf(png_ptr_))) {
+    error_code_ = -6;
+    return;
+  }
+
+  png_init_io(png_ptr_, png_fp_.get());
+  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,
+               nullptr);
+
+  channels_ = png_get_channels(png_ptr_, info_ptr_);
+
+  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) {
+    // 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) {
+    // 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
+    // general.
+    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);
+    error_code_ = -7;
+  }
+}
+
+PngHandler::~PngHandler() {
+  if (png_ptr_) {
+    png_destroy_read_struct(&png_ptr_, &info_ptr_, nullptr);
+  }
 }
 
 // "display" surfaces are transformed into the framebuffer's required
@@ -198,131 +239,115 @@
 }
 
 int res_create_display_surface(const char* name, GRSurface** pSurface) {
-    GRSurface* surface = NULL;
-    int result = 0;
-    png_structp png_ptr = NULL;
-    png_infop info_ptr = NULL;
-    png_uint_32 width, height;
-    png_byte channels;
-    unsigned char* p_row;
-    unsigned int y;
+  *pSurface = nullptr;
 
-    *pSurface = NULL;
+  PngHandler png_handler(name);
+  if (!png_handler) return png_handler.error_code();
 
-    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
-    if (result < 0) return result;
+  png_structp png_ptr = png_handler.png_ptr();
+  png_uint_32 width = png_handler.width();
+  png_uint_32 height = png_handler.height();
 
-    surface = init_display_surface(width, height);
-    if (surface == NULL) {
-        result = -8;
-        goto exit;
-    }
+  GRSurface* surface = init_display_surface(width, height);
+  if (!surface) {
+    return -8;
+  }
 
 #if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
-    png_set_bgr(png_ptr);
+  png_set_bgr(png_ptr);
 #endif
 
-    p_row = static_cast<unsigned char*>(malloc(width * 4));
-    for (y = 0; y < height; ++y) {
-        png_read_row(png_ptr, p_row, NULL);
-        transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width);
-    }
-    free(p_row);
+  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);
+  }
 
-    *pSurface = surface;
+  *pSurface = surface;
 
-  exit:
-    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-    if (result < 0 && surface != NULL) free(surface);
-    return result;
+  return 0;
 }
 
 int res_create_multi_display_surface(const char* name, int* frames, int* fps,
-        GRSurface*** pSurface) {
-    GRSurface** surface = NULL;
-    int result = 0;
-    png_structp png_ptr = NULL;
-    png_infop info_ptr = NULL;
-    png_uint_32 width, height;
-    png_byte channels;
-    png_textp text;
-    int num_text;
-    unsigned char* p_row;
-    unsigned int y;
+                                     GRSurface*** pSurface) {
+  *pSurface = nullptr;
+  *frames = -1;
 
-    *pSurface = NULL;
-    *frames = -1;
+  PngHandler png_handler(name);
+  if (!png_handler) return png_handler.error_code();
 
-    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
-    if (result < 0) return result;
+  png_structp png_ptr = png_handler.png_ptr();
+  png_uint_32 width = png_handler.width();
+  png_uint_32 height = png_handler.height();
 
-    *frames = 1;
-    *fps = 20;
-    if (png_get_text(png_ptr, info_ptr, &text, &num_text)) {
-        for (int i = 0; i < num_text; ++i) {
-            if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) {
-                *frames = atoi(text[i].text);
-            } else if (text[i].key && strcmp(text[i].key, "FPS") == 0 && text[i].text) {
-                *fps = atoi(text[i].text);
-            }
-        }
-        printf("  found frames = %d\n", *frames);
-        printf("  found fps = %d\n", *fps);
+  *frames = 1;
+  *fps = 20;
+  png_textp text;
+  int num_text;
+  if (png_get_text(png_ptr, png_handler.info_ptr(), &text, &num_text)) {
+    for (int i = 0; i < num_text; ++i) {
+      if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) {
+        *frames = atoi(text[i].text);
+      } else if (text[i].key && strcmp(text[i].key, "FPS") == 0 && text[i].text) {
+        *fps = atoi(text[i].text);
+      }
     }
+    printf("  found frames = %d\n", *frames);
+    printf("  found fps = %d\n", *fps);
+  }
 
-    if (*frames <= 0 || *fps <= 0) {
-        printf("bad number of frames (%d) and/or FPS (%d)\n", *frames, *fps);
-        result = -10;
-        goto exit;
-    }
+  int result = 0;
+  GRSurface** surface = nullptr;
+  if (*frames <= 0 || *fps <= 0) {
+    printf("bad number of frames (%d) and/or FPS (%d)\n", *frames, *fps);
+    result = -10;
+    goto exit;
+  }
 
-    if (height % *frames != 0) {
-        printf("bad height (%d) for frame count (%d)\n", height, *frames);
-        result = -9;
-        goto exit;
-    }
+  if (height % *frames != 0) {
+    printf("bad height (%d) for frame count (%d)\n", height, *frames);
+    result = -9;
+    goto exit;
+  }
 
-    surface = static_cast<GRSurface**>(calloc(*frames, sizeof(GRSurface*)));
-    if (surface == NULL) {
-        result = -8;
-        goto exit;
+  surface = static_cast<GRSurface**>(calloc(*frames, sizeof(GRSurface*)));
+  if (!surface) {
+    result = -8;
+    goto exit;
+  }
+  for (int i = 0; i < *frames; ++i) {
+    surface[i] = init_display_surface(width, height / *frames);
+    if (!surface[i]) {
+      result = -8;
+      goto exit;
     }
-    for (int i = 0; i < *frames; ++i) {
-        surface[i] = init_display_surface(width, height / *frames);
-        if (surface[i] == NULL) {
-            result = -8;
-            goto exit;
-        }
-    }
+  }
 
 #if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
-    png_set_bgr(png_ptr);
+  png_set_bgr(png_ptr);
 #endif
 
-    p_row = static_cast<unsigned char*>(malloc(width * 4));
-    for (y = 0; y < height; ++y) {
-        png_read_row(png_ptr, p_row, NULL);
-        int frame = y % *frames;
-        unsigned char* out_row = surface[frame]->data +
-            (y / *frames) * surface[frame]->row_bytes;
-        transform_rgb_to_draw(p_row, out_row, channels, width);
-    }
-    free(p_row);
+  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);
+    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);
+  }
 
-    *pSurface = surface;
+  *pSurface = surface;
 
 exit:
-    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-
-    if (result < 0) {
-        if (surface) {
-            for (int i = 0; i < *frames; ++i) {
-                free(surface[i]);
-            }
-            free(surface);
-        }
+  if (result < 0) {
+    if (surface) {
+      for (int i = 0; i < *frames; ++i) {
+        free(surface[i]);
+      }
+      free(surface);
     }
-    return result;
+  }
+  return result;
 }
 
 int res_create_multi_display_surface(const char* name, int* frames,
@@ -332,50 +357,40 @@
 }
 
 int res_create_alpha_surface(const char* name, GRSurface** pSurface) {
-    GRSurface* surface = NULL;
-    int result = 0;
-    png_structp png_ptr = NULL;
-    png_infop info_ptr = NULL;
-    png_uint_32 width, height;
-    png_byte channels;
+  *pSurface = nullptr;
 
-    *pSurface = NULL;
+  PngHandler png_handler(name);
+  if (!png_handler) return png_handler.error_code();
 
-    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
-    if (result < 0) return result;
+  if (png_handler.channels() != 1) {
+    return -7;
+  }
 
-    if (channels != 1) {
-        result = -7;
-        goto exit;
-    }
+  png_structp png_ptr = png_handler.png_ptr();
+  png_uint_32 width = png_handler.width();
+  png_uint_32 height = png_handler.height();
 
-    surface = malloc_surface(width * height);
-    if (surface == NULL) {
-        result = -8;
-        goto exit;
-    }
-    surface->width = width;
-    surface->height = height;
-    surface->row_bytes = width;
-    surface->pixel_bytes = 1;
+  GRSurface* surface = malloc_surface(width * height);
+  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);
+  png_set_bgr(png_ptr);
 #endif
 
-    unsigned char* p_row;
-    unsigned int y;
-    for (y = 0; y < height; ++y) {
-        p_row = surface->data + y * surface->row_bytes;
-        png_read_row(png_ptr, p_row, NULL);
-    }
+  for (png_uint_32 y = 0; y < height; ++y) {
+    unsigned char* p_row = surface->data + y * surface->row_bytes;
+    png_read_row(png_ptr, p_row, nullptr);
+  }
 
-    *pSurface = surface;
+  *pSurface = surface;
 
-  exit:
-    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-    if (result < 0 && surface != NULL) free(surface);
-    return result;
+  return 0;
 }
 
 // This function tests if a locale string stored in PNG (prefix) matches
@@ -390,7 +405,7 @@
   // 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 (android::base::StartsWith(locale, prefix.c_str())) { // does not exist in 6.0
+  //if (android::base::StartsWith(locale, prefix)) { // does not exist in 6.0
   if (strncmp(prefix.c_str(), locale.c_str(), prefix.length()) == 0) {
     return true;
   }
@@ -403,75 +418,90 @@
   return std::regex_match(locale, loc_regex);
 }
 
+std::vector<std::string> get_locales_in_png(const std::string& png_name) {
+  PngHandler png_handler(png_name);
+  if (!png_handler) {
+    printf("Failed to open %s, error: %d\n", png_name.c_str(), png_handler.error_code());
+    return {};
+  }
+  if (png_handler.channels() != 1) {
+    printf("Expect input png to have 1 data channel, this file has %d\n", png_handler.channels());
+    return {};
+  }
+
+  std::vector<std::string> result;
+  std::vector<unsigned char> 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];
+    std::string loc(reinterpret_cast<char*>(&row[5]));
+    if (!loc.empty()) {
+      result.push_back(loc);
+    }
+    for (int i = 0; i < h; ++i, ++y) {
+      png_read_row(png_handler.png_ptr(), row.data(), nullptr);
+    }
+  }
+
+  return result;
+}
+
 int res_create_localized_alpha_surface(const char* name,
                                        const char* locale,
                                        GRSurface** pSurface) {
-    GRSurface* surface = NULL;
-    int result = 0;
-    png_structp png_ptr = NULL;
-    png_infop info_ptr = NULL;
-    png_uint_32 width, height;
-    png_byte channels;
-    png_uint_32 y;
-    std::vector<unsigned char> row;
+  *pSurface = nullptr;
+  if (locale == nullptr) {
+    return 0;
+  }
 
-    *pSurface = NULL;
+  PngHandler png_handler(name);
+  if (!png_handler) return png_handler.error_code();
 
-    if (locale == NULL) {
-        return result;
+  if (png_handler.channels() != 1) {
+    return -7;
+  }
+
+  png_structp png_ptr = png_handler.png_ptr();
+  png_uint_32 width = png_handler.width();
+  png_uint_32 height = png_handler.height();
+
+  for (png_uint_32 y = 0; y < height; ++y) {
+    std::vector<unsigned char> 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)) {
+      printf("  %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
+
+      GRSurface* surface = malloc_surface(w * h);
+      if (!surface) {
+        return -8;
+      }
+      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);
+      }
+
+      *pSurface = surface;
+      break;
     }
 
-    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
-    if (result < 0) return result;
-
-    if (channels != 1) {
-        result = -7;
-        goto exit;
+    for (int i = 0; i < h; ++i, ++y) {
+      png_read_row(png_ptr, row.data(), nullptr);
     }
+  }
 
-    row.resize(width);
-    for (y = 0; y < height; ++y) {
-        png_read_row(png_ptr, row.data(), NULL);
-        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)) {
-            printf("  %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
-
-            surface = malloc_surface(w*h);
-            if (surface == NULL) {
-                result = -8;
-                goto exit;
-            }
-            surface->width = w;
-            surface->height = h;
-            surface->row_bytes = w;
-            surface->pixel_bytes = 1;
-
-            int i;
-            for (i = 0; i < h; ++i, ++y) {
-                png_read_row(png_ptr, row.data(), NULL);
-                memcpy(surface->data + i*w, row.data(), w);
-            }
-
-            *pSurface = surface;
-            break;
-        } else {
-            int i;
-            for (i = 0; i < h; ++i, ++y) {
-                png_read_row(png_ptr, row.data(), NULL);
-            }
-        }
-    }
-
-exit:
-    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-    if (result < 0 && surface != NULL) free(surface);
-    return result;
+  return 0;
 }
 
 void res_free_surface(GRSurface* surface) {
-    free(surface);
+  free(surface);
 }
diff --git a/minuitwrp/events.cpp b/minuitwrp/events.cpp
index 04545b6..94b800e 100644
--- a/minuitwrp/events.cpp
+++ b/minuitwrp/events.cpp
@@ -101,7 +101,7 @@
 static struct ev evs[MAX_DEVICES];
 static unsigned ev_count = 0;
 static struct timeval lastInputStat;
-static unsigned long lastInputMTime;
+static time_t lastInputMTime;
 static int has_mouse = 0;
 
 static inline int ABS(int x) {
@@ -360,7 +360,7 @@
 	ev_count = 0;
 }
 
-static int vk_inside_display(__s32 value, struct input_absinfo *info, int screen_size)
+/*static int vk_inside_display(__s32 value, struct input_absinfo *info, int screen_size)
 {
     int screen_pos;
 
@@ -369,7 +369,7 @@
 
     screen_pos = (value - info->minimum) * (screen_size - 1) / (info->maximum - info->minimum);
     return (screen_pos >= 0 && screen_pos < screen_size);
-}
+}*/
 
 static int vk_tp_to_screen(struct position *p, int *x, int *y)
 {
@@ -782,7 +782,7 @@
     return -2;
 }
 
-int ev_wait(int timeout)
+int ev_wait(int timeout __unused)
 {
     return -1;
 }
@@ -792,7 +792,7 @@
     return;
 }
 
-int ev_get_input(int fd, short revents, struct input_event *ev)
+int ev_get_input(int fd __unused, short revents __unused, struct input_event *ev __unused)
 {
     return -1;
 }
diff --git a/minuitwrp/graphics.cpp b/minuitwrp/graphics.cpp
index 3dc88d9..81e77a6 100644
--- a/minuitwrp/graphics.cpp
+++ b/minuitwrp/graphics.cpp
@@ -42,7 +42,6 @@
     int cheight;
 };
 
-static GRFont* gr_font = NULL;
 static minui_backend* gr_backend = NULL;
 
 static int overscan_percent = OVERSCAN_PERCENT;
@@ -52,11 +51,6 @@
 static unsigned char gr_current_r = 255;
 static unsigned char gr_current_g = 255;
 static unsigned char gr_current_b = 255;
-static unsigned char gr_current_a = 255;
-static unsigned char rgb_555[2];
-static unsigned char gr_current_r5 = 31;
-static unsigned char gr_current_g5 = 63;
-static unsigned char gr_current_b5 = 31;
 
 GRSurface* gr_draw = NULL;
 
@@ -64,19 +58,12 @@
 GGLSurface gr_mem_surface;
 static int gr_is_curr_clr_opaque = 0;
 
-static bool outside(int x, int y)
-{
-    return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height;
-}
-
 int gr_textEx_scaleW(int x, int y, const char *s, void* pFont, int max_width, int placement, int scale)
 {
     GGLContext *gl = gr_context;
     void* vfont = pFont;
     GRFont *font = (GRFont*) pFont;
-    unsigned off;
-    unsigned cwidth;
-    int y_scale = 0, measured_width, measured_height, ret, new_height;
+    int y_scale = 0, measured_width, measured_height, new_height;
 
     if (!s || strlen(s) == 0 || !font)
         return 0;
diff --git a/minuitwrp/truetype.cpp b/minuitwrp/truetype.cpp
index 18635a8..3e5f707 100644
--- a/minuitwrp/truetype.cpp
+++ b/minuitwrp/truetype.cpp
@@ -461,7 +461,7 @@
     int utf_bytes = 0;
     unsigned int unicode = 0;
     int i, x, diff, char_idx, prev_idx = 0;
-    int height, base;
+    int height;
     FT_Vector delta;
     uint8_t *data = NULL;
     const char *text_itr = text;
@@ -547,7 +547,6 @@
 
 static StringCacheEntry *gr_ttf_string_cache_peek(TrueTypeFont *font, const char *text, int max_width)
 {
-    StringCacheEntry *res;
     StringCacheKey k = {
         .text = (char*)text,
         .max_width = max_width
@@ -785,8 +784,8 @@
             "    refcount: %d\n"
             "    max_height: %d\n"
             "    base: %d\n"
-            "    glyph_cache: %d entries\n"
-            "    string_cache: %d entries (%.2f kB)\n",
+            "    glyph_cache: %zu entries\n"
+            "    string_cache: %zu entries (%.2f kB)\n",
             k->path, k->size, k->dpi,
             f->refcount, f->max_height, f->base,
             hashmapSize(f->glyph_cache),
@@ -808,7 +807,7 @@
     else
     {
         int total_string_cache_size = 0;
-        printf("%d fonts loaded.\n", hashmapSize(font_data.fonts));
+        printf("%zu fonts loaded.\n", hashmapSize(font_data.fonts));
         hashmapForEach(font_data.fonts, gr_ttf_dump_stats_font, &total_string_cache_size);
         printf("  Total string cache size: %.2f kB\n", ((double)total_string_cache_size)/1024);
     }
diff --git a/mmcutils/mmcutils.c b/mmcutils/mmcutils.c
index 6d976b5..a003a2d 100644
--- a/mmcutils/mmcutils.c
+++ b/mmcutils/mmcutils.c
@@ -231,7 +231,6 @@
 int
 mmc_scan_partitions() {
     int i;
-    ssize_t nbytes;
 
     if (g_mmc_state.partitions == NULL) {
         const int nump = MAX_PARTITIONS;
@@ -321,7 +320,7 @@
 #define E2FSCK_BIN      "/sbin/e2fsck"
 
 int
-run_exec_process ( char **argv) {
+run_exec_process ( char *const *argv) {
     pid_t pid;
     int status;
     pid = fork();
@@ -339,7 +338,7 @@
 }
 
 int
-format_ext3_device (const char *device) {
+format_ext3_device (char *device) {
     char *const mke2fs[] = {MKE2FS_BIN, "-j", "-q", device, NULL};
     char *const tune2fs[] = {TUNE2FS_BIN, "-C", "1", device, NULL};
     // Run mke2fs
@@ -365,7 +364,7 @@
 }
 
 int
-format_ext2_device (const char *device) {
+format_ext2_device (char *device) {
     // Run mke2fs
     char *const mke2fs[] = {MKE2FS_BIN, device, NULL};
     if(run_exec_process(mke2fs))
@@ -385,7 +384,7 @@
 }
 
 int
-mmc_format_ext3 (MmcPartition *partition) {
+mmc_format_ext3 (const MmcPartition *partition) {
     char device[128];
     strcpy(device, partition->device_index);
     return format_ext3_device(device);
@@ -419,11 +418,10 @@
 }
 
 int
-mmc_raw_copy (const MmcPartition *partition, char *in_file) {
+mmc_raw_copy (const MmcPartition *partition, const char *in_file) {
     int ch;
     FILE *in;
     FILE *out;
-    int val = 0;
     char buf[512];
     unsigned sz = 0;
     unsigned i;
@@ -475,7 +473,6 @@
     int ch;
     FILE *in;
     FILE *out;
-    int val = 0;
     char buf[512];
     unsigned sz = 0;
     unsigned i;
@@ -522,19 +519,15 @@
 
 // TODO: refactor this to not be a giant copy paste mess
 int
-mmc_raw_dump (const MmcPartition *partition, char *out_file) {
+mmc_raw_dump (const MmcPartition *partition, const char *out_file) {
     return mmc_raw_dump_internal(partition->device_index, out_file);
 }
 
 
 int
 mmc_raw_read (const MmcPartition *partition, char *data, int data_size) {
-    int ch;
     FILE *in;
-    int val = 0;
-    char buf[512];
     unsigned sz = 0;
-    unsigned i;
     int ret = -1;
     char *in_file = partition->device_index;
 
@@ -549,8 +542,6 @@
     fread(data, data_size, 1, in);
 
     ret = 0;
-ERROR1:
-ERROR2:
     fclose ( in );
 ERROR3:
     return ret;
@@ -559,12 +550,7 @@
 
 int
 mmc_raw_write (const MmcPartition *partition, char *data, int data_size) {
-    int ch;
     FILE *out;
-    int val = 0;
-    char buf[512];
-    unsigned sz = 0;
-    unsigned i;
     int ret = -1;
     char *out_file = partition->device_index;
 
@@ -575,8 +561,6 @@
     fwrite(data, data_size, 1, out);
 
     ret = 0;
-ERROR1:
-ERROR2:
     fclose ( out );
 ERROR3:
     return ret;
@@ -613,12 +597,12 @@
     }
 }
 
-int cmd_mmc_erase_raw_partition(const char *partition)
+int cmd_mmc_erase_raw_partition(const char *partition __unused)
 {
     return 0;
 }
 
-int cmd_mmc_erase_partition(const char *partition, const char *filesystem)
+int cmd_mmc_erase_partition(const char *partition, const char *filesystem __unused)
 {
     mmc_scan_partitions();
     const MmcPartition *p;
@@ -628,7 +612,7 @@
     return mmc_format_ext3 (p);
 }
 
-int cmd_mmc_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only)
+int cmd_mmc_mount_partition(const char *partition, const char *mount_point, const char *filesystem __unused, int read_only)
 {
     mmc_scan_partitions();
     const MmcPartition *p;
diff --git a/mmcutils/mmcutils.h b/mmcutils/mmcutils.h
index 5b10fdc..4fece79 100644
--- a/mmcutils/mmcutils.h
+++ b/mmcutils/mmcutils.h
@@ -31,7 +31,9 @@
 #define MMCUTILS_H_
 
 /* Some useful define used to access the MBR/EBR table */
+#ifndef BLOCK_SIZE
 #define BLOCK_SIZE                0x200
+#endif
 #define TABLE_ENTRY_0             0x1BE
 #define TABLE_ENTRY_1             0x1CE
 #define TABLE_ENTRY_2             0x1DE
@@ -76,15 +78,15 @@
 /* Functions */
 int mmc_scan_partitions();
 const MmcPartition *mmc_find_partition_by_name(const char *name);
-int mmc_format_ext3 (MmcPartition *partition);
+int mmc_format_ext3 (const MmcPartition *partition);
 int mmc_mount_partition(const MmcPartition *partition, const char *mount_point, \
                         int read_only);
-int mmc_raw_copy (const MmcPartition *partition, char *in_file);
+int mmc_raw_copy (const MmcPartition *partition, const char *in_file);
 int mmc_raw_read (const MmcPartition *partition, char *data, int data_size);
 int mmc_raw_write (const MmcPartition *partition, char *data, int data_size);
 
-int format_ext2_device(const char *device);
-int format_ext3_device(const char *device);
+int format_ext2_device(char *device);
+int format_ext3_device(char *device);
 
 #endif  // MMCUTILS_H_
 
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
index 7a22efe..b19c533 100644
--- a/mtdutils/mtdutils.c
+++ b/mtdutils/mtdutils.c
@@ -605,16 +605,12 @@
     return pos;
 }
 
-#define BLOCK_SIZE    2048
-#define SPARE_SIZE    (BLOCK_SIZE >> 5)
+#define MTD_BLOCK_SIZE    2048
+#define SPARE_SIZE    (MTD_BLOCK_SIZE >> 5)
 #define HEADER_SIZE 2048
 
 int cmd_mtd_restore_raw_partition(const char *partition_name, const char *filename)
 {
-    const MtdPartition *ptn;
-    MtdWriteContext *write;
-    void *data;
-
     FILE* f = fopen(filename, "rb");
     if (f == NULL) {
         fprintf(stderr, "error opening %s", filename);
@@ -676,9 +672,8 @@
 {
     MtdReadContext *in;
     const MtdPartition *partition;
-    char buf[BLOCK_SIZE + SPARE_SIZE];
+    char buf[MTD_BLOCK_SIZE + SPARE_SIZE];
     size_t partition_size;
-    size_t read_size;
     size_t total;
     int fd;
     int wrote;
@@ -724,7 +719,7 @@
     }
 
     total = 0;
-    while ((len = mtd_read_data(in, buf, BLOCK_SIZE)) > 0) {
+    while ((len = mtd_read_data(in, buf, MTD_BLOCK_SIZE)) > 0) {
         wrote = write(fd, buf, len);
         if (wrote != len) {
             close(fd);
@@ -732,7 +727,7 @@
             printf("error writing %s", filename);
             return -1;
         }
-        total += BLOCK_SIZE;
+        total += MTD_BLOCK_SIZE;
     }
 
     mtd_read_close(in);
@@ -749,8 +744,6 @@
 {
     MtdWriteContext *out;
     size_t erased;
-    size_t total_size;
-    size_t erase_size;
 
     if (mtd_scan_partitions() <= 0)
     {
@@ -784,13 +777,13 @@
     return 0;
 }
 
-int cmd_mtd_erase_partition(const char *partition, const char *filesystem)
+int cmd_mtd_erase_partition(const char *partition, const char *filesystem __unused)
 {
     return cmd_mtd_erase_raw_partition(partition);
 }
 
 
-int cmd_mtd_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only)
+int cmd_mtd_mount_partition(const char *partition, const char *mount_point, const char *filesystem __unused, int read_only)
 {
     mtd_scan_partitions();
     const MtdPartition *p;
@@ -804,9 +797,9 @@
 int cmd_mtd_get_partition_device(const char *partition, char *device)
 {
     mtd_scan_partitions();
-    MtdPartition *p = mtd_find_partition_by_name(partition);
+    const MtdPartition *p = mtd_find_partition_by_name(partition);
     if (p == NULL)
         return -1;
     sprintf(device, "/dev/block/mtdblock%d", p->device_index);
     return 0;
-}
\ No newline at end of file
+}
diff --git a/openaes/src/oaes.c b/openaes/src/oaes.c
index 9a5d268..203da52 100644
--- a/openaes/src/oaes.c
+++ b/openaes/src/oaes.c
@@ -38,7 +38,7 @@
 #if defined(_WIN32) && !defined(__SYMBIAN32__)
 #include <io.h>
 #else
-__inline static int setmode(int a, int b)
+__inline static int setmode(int a __unused, int b __unused)
 {
 	return 0;
 }
@@ -79,7 +79,7 @@
 
 int main(int argc, char** argv)
 {
-	size_t _i = 0, _j = 0;
+	int _i = 0, _j = 0;
 	OAES_CTX * ctx = NULL;
 	uint8_t _buf_in[OAES_BUF_LEN_DEC];
 	uint8_t *_buf_out = NULL, _key_data[32] = "";
@@ -249,8 +249,8 @@
 
 	oaes_key_import_data( ctx, _key_data, _key_data_len );
 
-	while( _buf_in_len =
-		fread(_buf_in, sizeof(uint8_t), _read_len, _f_in) )
+	while( (_buf_in_len =
+		fread(_buf_in, sizeof(uint8_t), _read_len, _f_in)) )
 	{
 		switch(_op)
 		{
diff --git a/openaes/src/oaes_lib.c b/openaes/src/oaes_lib.c
index 0cc0394..2e28159 100644
--- a/openaes/src/oaes_lib.c
+++ b/openaes/src/oaes_lib.c
@@ -27,9 +27,9 @@
  * POSSIBILITY OF SUCH DAMAGE.
  * ---------------------------------------------------------------------------
  */
-static const char _NR[] = {
+/*static const char _NR[] = {
 	0x4e,0x61,0x62,0x69,0x6c,0x20,0x53,0x2e,0x20,
-	0x41,0x6c,0x20,0x52,0x61,0x6d,0x6c,0x69,0x00 };
+	0x41,0x6c,0x20,0x52,0x61,0x6d,0x6c,0x69,0x00 };*/
 
 #include <stddef.h>
 #include <time.h> 
@@ -96,162 +96,162 @@
 
 static uint8_t oaes_sub_byte_value[16][16] = {
 	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
-	/*0*/	0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
-	/*1*/	0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
-	/*2*/	0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
-	/*3*/	0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
-	/*4*/	0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
-	/*5*/	0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
-	/*6*/	0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
-	/*7*/	0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
-	/*8*/	0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
-	/*9*/	0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
-	/*a*/	0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
-	/*b*/	0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
-	/*c*/	0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
-	/*d*/	0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
-	/*e*/	0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
-	/*f*/	0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
+	/*0*/{	0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76},
+	/*1*/{	0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0},
+	/*2*/{	0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15},
+	/*3*/{	0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75},
+	/*4*/{	0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84},
+	/*5*/{	0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf},
+	/*6*/{	0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8},
+	/*7*/{	0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2},
+	/*8*/{	0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73},
+	/*9*/{	0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb},
+	/*a*/{	0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79},
+	/*b*/{	0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08},
+	/*c*/{	0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a},
+	/*d*/{	0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e},
+	/*e*/{	0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf},
+	/*f*/{	0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16},
 };
 
 static uint8_t oaes_inv_sub_byte_value[16][16] = {
 	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
-	/*0*/	0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
-	/*1*/	0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
-	/*2*/	0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
-	/*3*/	0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
-	/*4*/	0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
-	/*5*/	0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
-	/*6*/	0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
-	/*7*/	0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
-	/*8*/	0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
-	/*9*/	0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
-	/*a*/	0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
-	/*b*/	0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
-	/*c*/	0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
-	/*d*/	0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
-	/*e*/	0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
-	/*f*/	0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
+	/*0*/{	0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb},
+	/*1*/{	0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb},
+	/*2*/{	0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e},
+	/*3*/{	0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25},
+	/*4*/{	0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92},
+	/*5*/{	0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84},
+	/*6*/{	0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06},
+	/*7*/{	0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b},
+	/*8*/{	0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73},
+	/*9*/{	0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e},
+	/*a*/{	0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b},
+	/*b*/{	0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4},
+	/*c*/{	0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f},
+	/*d*/{	0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef},
+	/*e*/{	0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61},
+	/*f*/{	0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d},
 };
 
 static uint8_t oaes_gf_mul_2[16][16] = {
 	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
-	/*0*/	0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
-	/*1*/	0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
-	/*2*/	0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
-	/*3*/	0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e,
-	/*4*/	0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e,
-	/*5*/	0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe,
-	/*6*/	0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde,
-	/*7*/	0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe,
-	/*8*/	0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05,
-	/*9*/	0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25,
-	/*a*/	0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45,
-	/*b*/	0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65,
-	/*c*/	0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85,
-	/*d*/	0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5,
-	/*e*/	0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5,
-	/*f*/	0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5,
+	/*0*/{	0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e},
+	/*1*/{	0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e},
+	/*2*/{	0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e},
+	/*3*/{	0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e},
+	/*4*/{	0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e},
+	/*5*/{	0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe},
+	/*6*/{	0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde},
+	/*7*/{	0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe},
+	/*8*/{	0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05},
+	/*9*/{	0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25},
+	/*a*/{	0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45},
+	/*b*/{	0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65},
+	/*c*/{	0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85},
+	/*d*/{	0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5},
+	/*e*/{	0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5},
+	/*f*/{	0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5},
 };
 
 static uint8_t oaes_gf_mul_3[16][16] = {
 	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
-	/*0*/	0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11,
-	/*1*/	0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21,
-	/*2*/	0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71,
-	/*3*/	0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41,
-	/*4*/	0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1,
-	/*5*/	0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1,
-	/*6*/	0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1,
-	/*7*/	0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81,
-	/*8*/	0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a,
-	/*9*/	0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba,
-	/*a*/	0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea,
-	/*b*/	0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda,
-	/*c*/	0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a,
-	/*d*/	0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a,
-	/*e*/	0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a,
-	/*f*/	0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a,
+	/*0*/{	0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11},
+	/*1*/{	0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21},
+	/*2*/{	0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71},
+	/*3*/{	0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41},
+	/*4*/{	0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1},
+	/*5*/{	0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1},
+	/*6*/{	0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1},
+	/*7*/{	0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81},
+	/*8*/{	0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a},
+	/*9*/{	0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba},
+	/*a*/{	0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea},
+	/*b*/{	0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda},
+	/*c*/{	0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a},
+	/*d*/{	0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a},
+	/*e*/{	0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a},
+	/*f*/{	0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a},
 };
 
 static uint8_t oaes_gf_mul_9[16][16] = {
 	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
-	/*0*/	0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
-	/*1*/	0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7,
-	/*2*/	0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
-	/*3*/	0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc,
-	/*4*/	0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01,
-	/*5*/	0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91,
-	/*6*/	0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a,
-	/*7*/	0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa,
-	/*8*/	0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b,
-	/*9*/	0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b,
-	/*a*/	0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0,
-	/*b*/	0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30,
-	/*c*/	0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed,
-	/*d*/	0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d,
-	/*e*/	0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6,
-	/*f*/	0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46,
+	/*0*/{	0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77},
+	/*1*/{	0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7},
+	/*2*/{	0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c},
+	/*3*/{	0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc},
+	/*4*/{	0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01},
+	/*5*/{	0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91},
+	/*6*/{	0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a},
+	/*7*/{	0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa},
+	/*8*/{	0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b},
+	/*9*/{	0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b},
+	/*a*/{	0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0},
+	/*b*/{	0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30},
+	/*c*/{	0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed},
+	/*d*/{	0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d},
+	/*e*/{	0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6},
+	/*f*/{	0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46},
 };
 
 static uint8_t oaes_gf_mul_b[16][16] = {
 	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
-	/*0*/	0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69,
-	/*1*/	0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9,
-	/*2*/	0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12,
-	/*3*/	0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2,
-	/*4*/	0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f,
-	/*5*/	0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f,
-	/*6*/	0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4,
-	/*7*/	0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54,
-	/*8*/	0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e,
-	/*9*/	0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e,
-	/*a*/	0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5,
-	/*b*/	0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55,
-	/*c*/	0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68,
-	/*d*/	0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8,
-	/*e*/	0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13,
-	/*f*/	0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3,
+	/*0*/{	0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69},
+	/*1*/{	0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9},
+	/*2*/{	0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12},
+	/*3*/{	0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2},
+	/*4*/{	0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f},
+	/*5*/{	0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f},
+	/*6*/{	0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4},
+	/*7*/{	0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54},
+	/*8*/{	0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e},
+	/*9*/{	0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e},
+	/*a*/{	0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5},
+	/*b*/{	0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55},
+	/*c*/{	0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68},
+	/*d*/{	0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8},
+	/*e*/{	0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13},
+	/*f*/{	0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3},
 };
 
 static uint8_t oaes_gf_mul_d[16][16] = {
 	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
-	/*0*/	0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b,
-	/*1*/	0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b,
-	/*2*/	0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0,
-	/*3*/	0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20,
-	/*4*/	0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26,
-	/*5*/	0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6,
-	/*6*/	0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d,
-	/*7*/	0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d,
-	/*8*/	0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91,
-	/*9*/	0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41,
-	/*a*/	0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a,
-	/*b*/	0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa,
-	/*c*/	0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc,
-	/*d*/	0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c,
-	/*e*/	0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47,
-	/*f*/	0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97,
+	/*0*/{	0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b},
+	/*1*/{	0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b},
+	/*2*/{	0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0},
+	/*3*/{	0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20},
+	/*4*/{	0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26},
+	/*5*/{	0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6},
+	/*6*/{	0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d},
+	/*7*/{	0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d},
+	/*8*/{	0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91},
+	/*9*/{	0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41},
+	/*a*/{	0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a},
+	/*b*/{	0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa},
+	/*c*/{	0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc},
+	/*d*/{	0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c},
+	/*e*/{	0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47},
+	/*f*/{	0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97},
 };
 
 static uint8_t oaes_gf_mul_e[16][16] = {
 	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
-	/*0*/	0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
-	/*1*/	0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba,
-	/*2*/	0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81,
-	/*3*/	0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61,
-	/*4*/	0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7,
-	/*5*/	0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17,
-	/*6*/	0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c,
-	/*7*/	0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc,
-	/*8*/	0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b,
-	/*9*/	0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb,
-	/*a*/	0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0,
-	/*b*/	0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20,
-	/*c*/	0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6,
-	/*d*/	0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56,
-	/*e*/	0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d,
-	/*f*/	0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d,
+	/*0*/{	0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a},
+	/*1*/{	0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba},
+	/*2*/{	0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81},
+	/*3*/{	0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61},
+	/*4*/{	0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7},
+	/*5*/{	0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17},
+	/*6*/{	0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c},
+	/*7*/{	0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc},
+	/*8*/{	0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b},
+	/*9*/{	0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb},
+	/*a*/{	0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0},
+	/*b*/{	0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20},
+	/*c*/{	0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6},
+	/*d*/{	0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56},
+	/*e*/{	0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d},
+	/*f*/{	0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d},
 };
 
 static OAES_RET oaes_sub_byte( uint8_t * byte )
@@ -286,7 +286,7 @@
 	return OAES_RET_SUCCESS;
 }
 
-static OAES_RET oaes_word_rot_right( uint8_t word[OAES_COL_LEN] )
+/*static OAES_RET oaes_word_rot_right( uint8_t word[OAES_COL_LEN] )
 {
 	uint8_t _temp[OAES_COL_LEN];
 	
@@ -298,7 +298,7 @@
 	memcpy( word, _temp, OAES_COL_LEN );
 	
 	return OAES_RET_SUCCESS;
-}
+}*/
 
 static OAES_RET oaes_word_rot_left( uint8_t word[OAES_COL_LEN] )
 {
@@ -783,7 +783,7 @@
 			return OAES_RET_HEADER;
 	}
 	
-	if( data_len != _key_length + OAES_BLOCK_SIZE )
+	if( data_len != (size_t)_key_length + OAES_BLOCK_SIZE )
 			return OAES_RET_ARG3;
 	
 	if( _ctx->key )
@@ -877,7 +877,6 @@
 
 #ifdef OAES_HAVE_ISAAC
 	{
-	  ub4 _i = 0;
 		char _seed[RANDSIZ + 1];
 		
 		_ctx->rctx = (randctx *) calloc( sizeof( randctx ), 1 );
diff --git a/otafault/Android.bp b/otafault/Android.bp
new file mode 100644
index 0000000..b39d5be
--- /dev/null
+++ b/otafault/Android.bp
@@ -0,0 +1,68 @@
+// 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
index 8590833..3993948 100644
--- a/otafault/config.cpp
+++ b/otafault/config.cpp
@@ -14,17 +14,15 @@
  * limitations under the License.
  */
 
+#include "otafault/config.h"
+
 #include <map>
 #include <string>
 
-#include <stdio.h>
-#include <unistd.h>
-
 #include <android-base/stringprintf.h>
 #include <ziparchive/zip_archive.h>
 
-#include "config.h"
-#include "ota_io.h"
+#include "otafault/ota_io.h"
 
 #define OTAIO_MAX_FNAME_SIZE 128
 
@@ -69,7 +67,9 @@
     fname.resize(OTAIO_MAX_FNAME_SIZE);
     ZipString zip_type_path(type_path.c_str());
     ZipEntry entry;
-    int status = FindEntry(archive, zip_type_path, &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/config.h b/otafault/include/otafault/config.h
similarity index 76%
rename from otafault/config.h
rename to otafault/include/otafault/config.h
index 4adbdd1..cc4bfd2 100644
--- a/otafault/config.h
+++ b/otafault/include/otafault/config.h
@@ -15,13 +15,13 @@
  */
 
 /*
- * Read configuration files in the OTA package to determine which files, if any, will trigger errors.
+ * 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.
+ * 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
@@ -29,9 +29,9 @@
  *   .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.
+ * 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_
@@ -39,8 +39,6 @@
 
 #include <string>
 
-#include <stdbool.h>
-
 #include <ziparchive/zip_archive.h>
 
 #define OTAIO_BASE_DIR ".libotafault"
diff --git a/otafault/ota_io.h b/otafault/include/otafault/ota_io.h
similarity index 96%
rename from otafault/ota_io.h
rename to otafault/include/otafault/ota_io.h
index 9428f1b..45e481a 100644
--- a/otafault/ota_io.h
+++ b/otafault/include/otafault/ota_io.h
@@ -23,8 +23,9 @@
 #ifndef _UPDATER_OTA_IO_H_
 #define _UPDATER_OTA_IO_H_
 
+#include <stddef.h>
 #include <stdio.h>
-#include <sys/stat.h>
+#include <sys/stat.h>  // mode_t
 
 #include <memory>
 
diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp
index faae527..63ef18e 100644
--- a/otafault/ota_io.cpp
+++ b/otafault/ota_io.cpp
@@ -14,20 +14,23 @@
  * limitations under the License.
  */
 
-#include "ota_io.h"
+#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 <memory>
 #include <mutex>
+#include <string>
 
 #include <android-base/thread_annotations.h>
-#include "config.h"
+
+#include "otafault/config.h"
 
 static std::mutex filename_mutex;
 static std::map<intptr_t, const char*> filename_cache GUARDED_BY(filename_mutex);
diff --git a/otafault/test.cpp b/otafault/test.cpp
index 6514782..63e2445 100644
--- a/otafault/test.cpp
+++ b/otafault/test.cpp
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-#include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 
-#include "ota_io.h"
+#include "otafault/ota_io.h"
 
 int main(int /* argc */, char** /* argv */) {
     int fd = open("testdata/test.file", O_RDWR);
diff --git a/otautil/Android.bp b/otautil/Android.bp
index 0b23143..a109d39 100644
--- a/otautil/Android.bp
+++ b/otautil/Android.bp
@@ -15,11 +15,15 @@
 cc_library_static {
     name: "libotautil",
 
+    host_supported: true,
+
     srcs: [
         "SysUtil.cpp",
         "DirUtil.cpp",
         "ZipUtil.cpp",
         "ThermalUtil.cpp",
+        "cache_location.cpp",
+        "rangeset.cpp",
     ],
 
     static_libs: [
@@ -28,7 +32,12 @@
     ],
 
     cflags: [
+        "-D_FILE_OFFSET_BITS=64",
         "-Werror",
         "-Wall",
     ],
+
+    export_include_dirs: [
+        "include",
+    ],
 }
diff --git a/otautil/Android.mk b/otautil/Android.mk
index 45e0f76..feb468d 100644
--- a/otautil/Android.mk
+++ b/otautil/Android.mk
@@ -36,4 +36,6 @@
     -Werror \
     -Wall
 
+LOCAL_C_INCLUDES := include
+
 include $(BUILD_STATIC_LIBRARY)
diff --git a/otautil/DirUtil.cpp b/otautil/DirUtil.cpp
index e08e360..8d364b7 100644
--- a/otautil/DirUtil.cpp
+++ b/otautil/DirUtil.cpp
@@ -14,24 +14,107 @@
  * limitations under the License.
  */
 
-#include "DirUtil.h"
+#include "otautil/DirUtil.h"
 
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
 #include <dirent.h>
-#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utime.h>
 
 #include <string>
 
 #include <selinux/label.h>
 #include <selinux/selinux.h>
 
-typedef enum { DMISSING, DDIR, DILLEGAL } DirStatus;
+enum class DirStatus { DMISSING, DDIR, DILLEGAL };
+
+static DirStatus dir_status(const std::string& path) {
+  struct stat sb;
+  if (stat(path.c_str(), &sb) == 0) {
+    // Something's there; make sure it's a directory.
+    if (S_ISDIR(sb.st_mode)) {
+      return DirStatus::DDIR;
+    }
+    errno = ENOTDIR;
+    return DirStatus::DILLEGAL;
+  } else if (errno != ENOENT) {
+    // Something went wrong, or something in the path is bad. Can't do anything in this situation.
+    return DirStatus::DILLEGAL;
+  }
+  return DirStatus::DMISSING;
+}
+
+int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
+                      const selabel_handle* sehnd) {
+  // Check for an empty string before we bother making any syscalls.
+  if (input_path.empty()) {
+    errno = ENOENT;
+    return -1;
+  }
+
+  // Allocate a path that we can modify; stick a slash on the end to make things easier.
+  std::string path = input_path;
+  if (strip_filename) {
+    // Strip everything after the last slash.
+    size_t pos = path.rfind('/');
+    if (pos == std::string::npos) {
+      errno = ENOENT;
+      return -1;
+    }
+    path.resize(pos + 1);
+  } else {
+    // Make sure that the path ends in a slash.
+    path.push_back('/');
+  }
+
+  // See if it already exists.
+  DirStatus ds = dir_status(path);
+  if (ds == DirStatus::DDIR) {
+    return 0;
+  } else if (ds == DirStatus::DILLEGAL) {
+    return -1;
+  }
+
+  // Walk up the path from the root and make each level.
+  size_t prev_end = 0;
+  while (prev_end < path.size()) {
+    size_t next_end = path.find('/', prev_end + 1);
+    if (next_end == std::string::npos) {
+      break;
+    }
+    std::string dir_path = path.substr(0, next_end);
+    // Check this part of the path and make a new directory if necessary.
+    switch (dir_status(dir_path)) {
+      case DirStatus::DILLEGAL:
+        // Could happen if some other process/thread is messing with the filesystem.
+        return -1;
+      case DirStatus::DMISSING: {
+        char* secontext = nullptr;
+        if (sehnd) {
+          selabel_lookup(const_cast<selabel_handle*>(sehnd), &secontext, dir_path.c_str(), mode);
+          setfscreatecon(secontext);
+        }
+        int err = mkdir(dir_path.c_str(), mode);
+        if (secontext) {
+          freecon(secontext);
+          setfscreatecon(nullptr);
+        }
+        if (err != 0) {
+          return -1;
+        }
+        break;
+      }
+      default:
+        // Already exists.
+        break;
+    }
+    prev_end = next_end;
+  }
+  return 0;
+}
 
 static DirStatus
 getPathDirStatus(const char *path)
@@ -44,17 +127,17 @@
         /* Something's there; make sure it's a directory.
          */
         if (S_ISDIR(st.st_mode)) {
-            return DDIR;
+            return DirStatus::DDIR;
         }
         errno = ENOTDIR;
-        return DILLEGAL;
+        return DirStatus::DILLEGAL;
     } else if (errno != ENOENT) {
         /* Something went wrong, or something in the path
          * is bad.  Can't do anything in this situation.
          */
-        return DILLEGAL;
+        return DirStatus::DILLEGAL;
     }
-    return DMISSING;
+    return DirStatus::DMISSING;
 }
 
 int
@@ -90,9 +173,9 @@
     /* See if it already exists.
      */
     ds = getPathDirStatus(cpath.c_str());
-    if (ds == DDIR) {
+    if (ds == DirStatus::DDIR) {
         return 0;
-    } else if (ds == DILLEGAL) {
+    } else if (ds == DirStatus::DILLEGAL) {
         return -1;
     }
 
@@ -124,12 +207,12 @@
          * if necessary.
          */
         ds = getPathDirStatus(path_start);
-        if (ds == DILLEGAL) {
+        if (ds == DirStatus::DILLEGAL) {
             /* Could happen if some other process/thread is
              * messing with the filesystem.
              */
             return -1;
-        } else if (ds == DMISSING) {
+        } else if (ds == DirStatus::DMISSING) {
             int err;
 
             char *secontext = NULL;
diff --git a/otautil/SysUtil.cpp b/otautil/SysUtil.cpp
index dfa2150..48336ad 100644
--- a/otautil/SysUtil.cpp
+++ b/otautil/SysUtil.cpp
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-#include "SysUtil.h"
+#include "otautil/SysUtil.h"
 
+#include <errno.h>  // TEMP_FAILURE_RETRY
 #include <fcntl.h>
 #include <stdint.h>  // SIZE_MAX
 #include <sys/mman.h>
@@ -100,7 +101,7 @@
   }
 
   // Reserve enough contiguous address space for the whole file.
-  void* reserve = mmap64(nullptr, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
+  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;
@@ -135,8 +136,8 @@
       break;
     }
 
-    void* range_start = mmap64(next, range_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd,
-                               static_cast<off64_t>(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 " << i << ": " << line;
       success = false;
diff --git a/otautil/ThermalUtil.cpp b/otautil/ThermalUtil.cpp
index 13d3643..5d9bd45 100644
--- a/otautil/ThermalUtil.cpp
+++ b/otautil/ThermalUtil.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "ThermalUtil.h"
+#include "otautil/ThermalUtil.h"
 
 #include <dirent.h>
 #include <stdio.h>
@@ -77,4 +77,4 @@
   }
   LOG(INFO) << "current maximum temperature: " << max_temperature;
   return max_temperature;
-}
\ No newline at end of file
+}
diff --git a/otautil/ZipUtil.cpp b/otautil/ZipUtil.cpp
index 714c956..a2d0cba 100644
--- a/otautil/ZipUtil.cpp
+++ b/otautil/ZipUtil.cpp
@@ -28,7 +28,7 @@
 #include <selinux/selinux.h>
 #include <ziparchive/zip_archive.h>
 
-#include "DirUtil.h"
+#include "otautil/DirUtil.h"
 
 static constexpr mode_t UNZIP_DIRMODE = 0755;
 static constexpr mode_t UNZIP_FILEMODE = 0644;
diff --git a/otautil/cache_location.cpp b/otautil/cache_location.cpp
new file mode 100644
index 0000000..8ddefec
--- /dev/null
+++ b/otautil/cache_location.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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.h b/otautil/include/otautil/DirUtil.h
similarity index 65%
rename from otautil/DirUtil.h
rename to otautil/include/otautil/DirUtil.h
index 85b83c3..e3d0219 100644
--- a/otautil/DirUtil.h
+++ b/otautil/include/otautil/DirUtil.h
@@ -14,18 +14,28 @@
  * limitations under the License.
  */
 
-#ifndef MINZIP_DIRUTIL_H_
-#define MINZIP_DIRUTIL_H_
+#ifndef OTAUTIL_DIRUTIL_H_
+#define OTAUTIL_DIRUTIL_H_
 
-#include <stdbool.h>
-#include <utime.h>
+#include <sys/stat.h>  // mode_t
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+#include <string>
 
 struct selabel_handle;
 
+// Like "mkdir -p", try to guarantee that all directories specified in path are present, creating as
+// many directories as necessary. The specified mode is passed to all mkdir calls; no modifications
+// are made to umask.
+//
+// If strip_filename is set, everything after the final '/' is stripped before creating the
+// directory
+// hierarchy.
+//
+// Returns 0 on success; returns -1 (and sets errno) on failure (usually if some element of path is
+// not a directory).
+int mkdir_recursively(const std::string& path, mode_t mode, bool strip_filename,
+                      const struct selabel_handle* sehnd);
+
 /* Like "mkdir -p", try to guarantee that all directories
  * specified in path are present, creating as many directories
  * as necessary.  The specified mode is passed to all mkdir
@@ -47,8 +57,4 @@
  */
 int dirUnlinkHierarchy(const char *path);
 
-#ifdef __cplusplus
-}
-#endif
-
-#endif  // MINZIP_DIRUTIL_H_
+#endif  // OTAUTIL_DIRUTIL_H_
diff --git a/otautil/SysUtil.h b/otautil/include/otautil/SysUtil.h
similarity index 100%
rename from otautil/SysUtil.h
rename to otautil/include/otautil/SysUtil.h
diff --git a/otautil/ThermalUtil.h b/otautil/include/otautil/ThermalUtil.h
similarity index 100%
rename from otautil/ThermalUtil.h
rename to otautil/include/otautil/ThermalUtil.h
diff --git a/otautil/include/otautil/cache_location.h b/otautil/include/otautil/cache_location.h
new file mode 100644
index 0000000..f2f6638
--- /dev/null
+++ b/otautil/include/otautil/cache_location.h
@@ -0,0 +1,69 @@
+/*
+ * 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/error_code.h b/otautil/include/otautil/error_code.h
similarity index 87%
rename from error_code.h
rename to otautil/include/otautil/error_code.h
index 9fe047c..b0ff42d 100644
--- a/error_code.h
+++ b/otautil/include/otautil/error_code.h
@@ -17,7 +17,7 @@
 #ifndef _ERROR_CODE_H_
 #define _ERROR_CODE_H_
 
-enum ErrorCode {
+enum ErrorCode : int {
   kNoError = -1,
   kLowBattery = 20,
   kZipVerificationFailure,
@@ -25,9 +25,12 @@
   kBootreasonInBlacklist,
   kPackageCompatibilityFailure,
   kScriptExecutionFailure,
+  kMapFileFailure,
+  kForkUpdateBinaryFailure,
+  kUpdateBinaryCommandFailure,
 };
 
-enum CauseCode {
+enum CauseCode : int {
   kNoCause = -1,
   kArgsParsingFailure = 100,
   kStashCreationFailure,
@@ -48,7 +51,7 @@
   kVendorFailure = 200
 };
 
-enum UncryptErrorCode {
+enum UncryptErrorCode : int {
   kUncryptNoError = -1,
   kUncryptErrorPlaceholder = 50,
   kUncryptTimeoutError = 100,
@@ -68,6 +71,8 @@
   kUncryptFileCloseError,
   kUncryptFileRenameError,
   kUncryptPackageMissingError,
+  kUncryptRealpathFindError,
+  kUncryptBlockDeviceFindError,
 };
 
-#endif // _ERROR_CODE_H_
+#endif  // _ERROR_CODE_H_
diff --git a/otautil/include/otautil/print_sha1.h b/otautil/include/otautil/print_sha1.h
new file mode 100644
index 0000000..03a8d29
--- /dev/null
+++ b/otautil/include/otautil/print_sha1.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#ifndef RECOVERY_PRINT_SHA1_H
+#define RECOVERY_PRINT_SHA1_H
+
+#include <stdint.h>
+#include <string>
+
+#include <openssl/sha.h>
+
+static std::string print_sha1(const uint8_t* sha1, size_t len) {
+  const char* hex = "0123456789abcdef";
+  std::string result = "";
+  for (size_t i = 0; i < len; ++i) {
+    result.push_back(hex[(sha1[i] >> 4) & 0xf]);
+    result.push_back(hex[sha1[i] & 0xf]);
+  }
+  return result;
+}
+
+[[maybe_unused]] static std::string print_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) {
+  return print_sha1(sha1, SHA_DIGEST_LENGTH);
+}
+
+[[maybe_unused]] static std::string short_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) {
+  return print_sha1(sha1, 4);
+}
+
+[[maybe_unused]] static std::string print_hex(const uint8_t* bytes, size_t len) {
+  return print_sha1(bytes, len);
+}
+
+#endif  // RECOVERY_PRINT_SHA1_H
diff --git a/otautil/include/otautil/rangeset.h b/otautil/include/otautil/rangeset.h
new file mode 100644
index 0000000..e91d02c
--- /dev/null
+++ b/otautil/include/otautil/rangeset.h
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+using Range = std::pair<size_t, size_t>;
+
+class RangeSet {
+ public:
+  RangeSet() : blocks_(0) {}
+
+  explicit RangeSet(std::vector<Range>&& pairs);
+
+  // Parses the given string into a RangeSet. Returns the parsed RangeSet, or an empty RangeSet on
+  // errors.
+  static RangeSet Parse(const std::string& range_text);
+
+  // Appends the given Range to the current RangeSet.
+  bool PushBack(Range range);
+
+  // Clears all the ranges from the RangeSet.
+  void Clear();
+
+  std::string ToString() const;
+
+  // Gets the block number for the i-th (starting from 0) block in the RangeSet.
+  size_t GetBlockNumber(size_t idx) const;
+
+  // Returns whether the current RangeSet overlaps with other. 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 Overlaps(const RangeSet& other) 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,
+  // as opposed to 4 + 4 + 4 + 2. If the total number of blocks (T) is less than groups, it
+  // returns a vector of T 1-block RangeSets. Otherwise the number of the returned RangeSets must
+  // equal to groups. The current RangeSet remains intact after the split.
+  std::vector<RangeSet> Split(size_t groups) const;
+
+  // Returns the number of Range's in this RangeSet.
+  size_t size() const {
+    return ranges_.size();
+  }
+
+  // Returns the total number of blocks in this RangeSet.
+  size_t blocks() const {
+    return blocks_;
+  }
+
+  std::vector<Range>::const_iterator cbegin() const {
+    return ranges_.cbegin();
+  }
+
+  std::vector<Range>::const_iterator cend() const {
+    return ranges_.cend();
+  }
+
+  std::vector<Range>::iterator begin() {
+    return ranges_.begin();
+  }
+
+  std::vector<Range>::iterator end() {
+    return ranges_.end();
+  }
+
+  std::vector<Range>::const_iterator begin() const {
+    return ranges_.begin();
+  }
+
+  std::vector<Range>::const_iterator end() const {
+    return ranges_.end();
+  }
+
+  // Reverse const iterators for MoveRange().
+  std::vector<Range>::const_reverse_iterator crbegin() const {
+    return ranges_.crbegin();
+  }
+
+  std::vector<Range>::const_reverse_iterator crend() const {
+    return ranges_.crend();
+  }
+
+  // Returns whether the RangeSet is valid (i.e. non-empty).
+  explicit operator bool() const {
+    return !ranges_.empty();
+  }
+
+  const Range& operator[](size_t i) const {
+    return ranges_[i];
+  }
+
+  bool operator==(const RangeSet& other) const {
+    // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5".
+    return (ranges_ == other.ranges_);
+  }
+
+  bool operator!=(const RangeSet& other) const {
+    return ranges_ != other.ranges_;
+  }
+
+ protected:
+  // Actual limit for each value and the total number are both INT_MAX.
+  std::vector<Range> ranges_;
+  size_t blocks_;
+};
+
+// The class is a sorted version of a RangeSet; and it's useful in imgdiff to split the input
+// files when we're handling large zip files. Specifically, we can treat the input file as a
+// continuous RangeSet (i.e. RangeSet("0-99") for a 100 blocks file); and break it down into
+// several smaller chunks based on the zip entries.
+
+// For example, [source: 0-99] can be split into
+// [split_src1: 10-29]; [split_src2: 40-49, 60-69]; [split_src3: 70-89]
+// Here "10-29" simply means block 10th to block 29th with respect to the original input file.
+// Also, note that the split sources should be mutual exclusive, but they don't need to cover
+// every block in the original source.
+class SortedRangeSet : public RangeSet {
+ public:
+  // The block size when working with offset and file length.
+  static constexpr size_t kBlockSize = 4096;
+
+  SortedRangeSet() {}
+
+  // Ranges in the the set should be mutually exclusive; and they're sorted by the start block.
+  explicit SortedRangeSet(std::vector<Range>&& pairs);
+
+  void Insert(const Range& to_insert);
+
+  // Insert the input SortedRangeSet; keep the ranges sorted and merge the overlap ranges.
+  void Insert(const SortedRangeSet& rs);
+
+  // Compute the block range the file occupies, and insert that range.
+  void Insert(size_t start, size_t len);
+
+  using RangeSet::Overlaps;
+
+  bool Overlaps(size_t start, size_t len) const;
+
+  // Given an offset of the file, checks if the corresponding block (by considering the file as
+  // 0-based continuous block ranges) is covered by the SortedRangeSet. If so, returns the offset
+  // within this SortedRangeSet.
+  //
+  // For example, the 4106-th byte of a file is from block 1, assuming a block size of 4096-byte.
+  // The mapped offset within a SortedRangeSet("1-9 15-19") is 10.
+  //
+  // An offset of 65546 falls into the 16-th block in a file. Block 16 is contained as the 10-th
+  // item in SortedRangeSet("1-9 15-19"). So its data can be found at offset 40970 (i.e. 4096 * 10
+  // + 10) in a range represented by this SortedRangeSet.
+  size_t GetOffsetInRangeSet(size_t old_offset) const;
+};
diff --git a/otautil/rangeset.cpp b/otautil/rangeset.cpp
new file mode 100644
index 0000000..96955b9
--- /dev/null
+++ b/otautil/rangeset.cpp
@@ -0,0 +1,267 @@
+/*
+ * 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.
+ */
+
+#include "otautil/rangeset.h"
+
+#include <limits.h>
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+RangeSet::RangeSet(std::vector<Range>&& pairs) {
+  blocks_ = 0;
+  if (pairs.empty()) {
+    LOG(ERROR) << "Invalid number of tokens";
+    return;
+  }
+
+  for (const auto& range : pairs) {
+    if (!PushBack(range)) {
+      Clear();
+      return;
+    }
+  }
+}
+
+RangeSet RangeSet::Parse(const std::string& range_text) {
+  std::vector<std::string> pieces = android::base::Split(range_text, ",");
+  if (pieces.size() < 3) {
+    LOG(ERROR) << "Invalid range text: " << range_text;
+    return {};
+  }
+
+  size_t num;
+  if (!android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX))) {
+    LOG(ERROR) << "Failed to parse the number of tokens: " << range_text;
+    return {};
+  }
+  if (num == 0) {
+    LOG(ERROR) << "Invalid number of tokens: " << range_text;
+    return {};
+  }
+  if (num % 2 != 0) {
+    LOG(ERROR) << "Number of tokens must be even: " << range_text;
+    return {};
+  }
+  if (num != pieces.size() - 1) {
+    LOG(ERROR) << "Mismatching number of tokens: " << range_text;
+    return {};
+  }
+
+  std::vector<Range> pairs;
+  for (size_t i = 0; i < num; i += 2) {
+    size_t first;
+    size_t second;
+    if (!android::base::ParseUint(pieces[i + 1], &first, static_cast<size_t>(INT_MAX)) ||
+        !android::base::ParseUint(pieces[i + 2], &second, static_cast<size_t>(INT_MAX))) {
+      return {};
+    }
+    pairs.emplace_back(first, second);
+  }
+  return RangeSet(std::move(pairs));
+}
+
+bool RangeSet::PushBack(Range range) {
+  if (range.first >= range.second) {
+    LOG(ERROR) << "Empty or negative range: " << range.first << ", " << range.second;
+    return false;
+  }
+  size_t sz = range.second - range.first;
+  if (blocks_ >= SIZE_MAX - sz) {
+    LOG(ERROR) << "RangeSet size overflow";
+    return false;
+  }
+
+  ranges_.push_back(std::move(range));
+  blocks_ += sz;
+  return true;
+}
+
+void RangeSet::Clear() {
+  ranges_.clear();
+  blocks_ = 0;
+}
+
+std::vector<RangeSet> RangeSet::Split(size_t groups) const {
+  if (ranges_.empty() || groups == 0) return {};
+
+  if (blocks_ < groups) {
+    groups = blocks_;
+  }
+
+  // Evenly distribute blocks, with the first few groups possibly containing one more.
+  size_t mean = blocks_ / groups;
+  std::vector<size_t> blocks_per_group(groups, mean);
+  std::fill_n(blocks_per_group.begin(), blocks_ % groups, mean + 1);
+
+  std::vector<RangeSet> result;
+
+  // Forward iterate Ranges and fill up each group with the desired number of blocks.
+  auto it = ranges_.cbegin();
+  Range range = *it;
+  for (const auto& blocks : blocks_per_group) {
+    RangeSet buffer;
+    size_t needed = blocks;
+    while (needed > 0) {
+      size_t range_blocks = range.second - range.first;
+      if (range_blocks > needed) {
+        // Split the current range and don't advance the iterator.
+        buffer.PushBack({ range.first, range.first + needed });
+        range.first = range.first + needed;
+        break;
+      }
+      buffer.PushBack(range);
+      it++;
+      if (it != ranges_.cend()) {
+        range = *it;
+      }
+      needed -= range_blocks;
+    }
+    result.push_back(std::move(buffer));
+  }
+  return result;
+}
+
+std::string RangeSet::ToString() const {
+  if (ranges_.empty()) {
+    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);
+  }
+
+  return result;
+}
+
+// Get the block number for the i-th (starting from 0) block in the RangeSet.
+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;
+    }
+    idx -= (range.second - range.first);
+  }
+
+  CHECK(false) << "Failed to find block number for index " << idx;
+  return 0;  // Unreachable, but to make compiler happy.
+}
+
+// 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)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+// 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());
+}
+
+void SortedRangeSet::Insert(const Range& to_insert) {
+  SortedRangeSet rs({ to_insert });
+  Insert(rs);
+}
+
+// Insert the input SortedRangeSet; keep the ranges sorted and merge the overlap ranges.
+void SortedRangeSet::Insert(const SortedRangeSet& rs) {
+  if (rs.size() == 0) {
+    return;
+  }
+  // Merge and sort the two RangeSets.
+  std::vector<Range> temp = std::move(ranges_);
+  std::copy(rs.begin(), rs.end(), std::back_inserter(temp));
+  std::sort(temp.begin(), temp.end());
+
+  Clear();
+  // Trim overlaps and insert the result back to ranges_.
+  Range to_insert = temp.front();
+  for (auto it = temp.cbegin() + 1; it != temp.cend(); it++) {
+    if (it->first <= to_insert.second) {
+      to_insert.second = std::max(to_insert.second, it->second);
+    } else {
+      ranges_.push_back(to_insert);
+      blocks_ += (to_insert.second - to_insert.first);
+      to_insert = *it;
+    }
+  }
+  ranges_.push_back(to_insert);
+  blocks_ += (to_insert.second - to_insert.first);
+}
+
+// Compute the block range the file occupies, and insert that range.
+void SortedRangeSet::Insert(size_t start, size_t len) {
+  Range to_insert{ start / kBlockSize, (start + len - 1) / kBlockSize + 1 };
+  Insert(to_insert);
+}
+
+bool SortedRangeSet::Overlaps(size_t start, size_t len) const {
+  RangeSet rs({ { start / kBlockSize, (start + len - 1) / kBlockSize + 1 } });
+  return Overlaps(rs);
+}
+
+// Given an offset of the file, checks if the corresponding block (by considering the file as
+// 0-based continuous block ranges) is covered by the SortedRangeSet. If so, returns the offset
+// within this SortedRangeSet.
+//
+// For example, the 4106-th byte of a file is from block 1, assuming a block size of 4096-byte.
+// The mapped offset within a SortedRangeSet("1-9 15-19") is 10.
+//
+// An offset of 65546 falls into the 16-th block in a file. Block 16 is contained as the 10-th
+// item in SortedRangeSet("1-9 15-19"). So its data can be found at offset 40970 (i.e. 4096 * 10
+// + 10) in a range represented by this SortedRangeSet.
+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_) {
+    // 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);
+      return (new_block_start * kBlockSize + old_offset % kBlockSize);
+    } else {
+      CHECK(false) << "block_start " << old_block_start
+                   << " is missing between two ranges: " << this->ToString();
+      return 0;
+    }
+  }
+  CHECK(false) << "block_start " << old_block_start
+               << " exceeds the limit of current RangeSet: " << this->ToString();
+  return 0;
+}
diff --git a/partitionmanager.cpp b/partitionmanager.cpp
index 8784968..5c8a437 100644
--- a/partitionmanager.cpp
+++ b/partitionmanager.cpp
@@ -754,7 +754,6 @@
 int TWPartitionManager::Run_Backup(bool adbbackup) {
 	PartitionSettings part_settings;
 	int partition_count = 0, disable_free_space_check = 0, skip_digest = 0;
-	int gui_adb_backup;
 	string Backup_Name, Backup_List, backup_path;
 	unsigned long long total_bytes = 0, free_space = 0;
 	TWPartition* storage = NULL;
diff --git a/prebuilt/Android.mk b/prebuilt/Android.mk
index 6e9bcfe..fb27f0a 100644
--- a/prebuilt/Android.mk
+++ b/prebuilt/Android.mk
@@ -183,7 +183,6 @@
     ifeq ($(TW_INCLUDE_CRYPTO_FBE), true)
         RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libe4crypt.so
         RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libgatekeeper.so
-        RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libsoftkeymaster.so
         RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeymaster_messages.so
         RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeystore_binder.so
         RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libbinder.so
@@ -210,6 +209,9 @@
         else
             RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeymaster1.so
         endif
+        ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 28; echo $$?),0)
+            RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libsoftkeymaster.so
+        endif
     endif
 endif
 ifeq ($(AB_OTA_UPDATER), true)
@@ -219,7 +221,9 @@
     endif
 endif
 ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
-    RELINK_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/make_ext4fs
+    ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 28; echo $$?),0)
+        RELINK_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/make_ext4fs
+    endif
 endif
 ifneq ($(wildcard system/core/libsparse/Android.*),)
     RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libsparse.so
@@ -313,10 +317,19 @@
         RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hidl.base@1.0.so
     endif
 endif
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 27; echo $$?),0)
+    RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libclang_rt.ubsan_standalone-aarch64-android.so
+    RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/liblogwrap.so
+endif
+
+TW_BB_SYMLINKS :=
+ifneq ($(TW_USE_TOOLBOX), true)
+    TW_BB_SYMLINKS := busybox_symlinks
+endif
 
 TWRP_AUTOGEN := $(intermediates)/teamwin
 GEN := $(intermediates)/teamwin
-$(GEN): $(RELINK)
+$(GEN): $(RELINK) $(TW_BB_SYMLINKS)
 $(GEN): $(RELINK_SOURCE_FILES) $(call intermediates-dir-for,EXECUTABLES,init)/init
 	$(RELINK) $(TARGET_RECOVERY_ROOT_OUT)/sbin $(RELINK_SOURCE_FILES)
 
diff --git a/print_sha1.h b/print_sha1.h
deleted file mode 100644
index 1f85895..0000000
--- a/print_sha1.h
+++ /dev/null
@@ -1,47 +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.
- */
-
-#ifndef RECOVERY_PRINT_SHA1_H
-#define RECOVERY_PRINT_SHA1_H
-
-#include <stdint.h>
-#include <string>
-
-#include <openssl/sha.h>
-
-static std::string print_sha1(const uint8_t* sha1, size_t len) {
-    const char* hex = "0123456789abcdef";
-    std::string result = "";
-    for (size_t i = 0; i < len; ++i) {
-        result.push_back(hex[(sha1[i]>>4) & 0xf]);
-        result.push_back(hex[sha1[i] & 0xf]);
-    }
-    return result;
-}
-
-static std::string print_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) {
-    return print_sha1(sha1, SHA_DIGEST_LENGTH);
-}
-
-static std::string short_sha1(const uint8_t sha1[SHA_DIGEST_LENGTH]) {
-    return print_sha1(sha1, 4);
-}
-
-static std::string print_hex(const uint8_t* bytes, size_t len) {
-    return print_sha1(bytes, len);
-}
-
-#endif  // RECOVERY_PRINT_SHA1_H
diff --git a/recovery-persist.rc b/recovery-persist.rc
index 6761627..135a3c3 100644
--- a/recovery-persist.rc
+++ b/recovery-persist.rc
@@ -1,3 +1,3 @@
 on post-fs-data
     mkdir /data/misc/recovery 0770 system log
-    exec - system log -- /system/bin/recovery-persist
+    exec_background - system log -- /system/bin/recovery-persist
diff --git a/recovery-refresh.rc b/recovery-refresh.rc
index 14b05cc..9fefc81 100644
--- a/recovery-refresh.rc
+++ b/recovery-refresh.rc
@@ -1,2 +1,2 @@
 on post-fs
-    exec - system log -- /system/bin/recovery-refresh
+    exec_background - system log -- /system/bin/recovery-refresh
diff --git a/recovery.cpp b/recovery.cpp
index 07bd7b9..07ec5cf 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -50,9 +50,9 @@
 #include <bootloader_message/bootloader_message.h>
 #include <cutils/android_reboot.h>
 #include <cutils/properties.h> /* for property_list */
-#include <healthd/BatteryMonitor.h>
-#include <private/android_logger.h> /* private pmsg functions */
-#include <private/android_filesystem_config.h>  /* for AID_SYSTEM */
+#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>
@@ -61,13 +61,13 @@
 #include "adb_install.h"
 #include "common.h"
 #include "device.h"
-#include "error_code.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 "otautil/error_code.h"
 #include "roots.h"
 #include "rotate_logs.h"
 #include "screen_ui.h"
@@ -108,6 +108,7 @@
 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";
@@ -125,6 +126,10 @@
 static constexpr const char* RECOVERY_WIPE = "/etc/recovery.wipe";
 static constexpr const char* DEFAULT_LOCALE = "en-US";
 
+// 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;
 
@@ -179,19 +184,19 @@
  *    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 NULL;
-    }
+// 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])) dirCreateHierarchy(path, 0777, NULL, 1, sehandle);
-
-    FILE *fp = fopen(path, mode);
-    return fp;
+  // 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
@@ -594,7 +599,7 @@
   if (is_cache) {
     // Re-create the log dir and write back the log entries.
     if (ensure_path_mounted(CACHE_LOG_DIR) == 0 &&
-        dirCreateHierarchy(CACHE_LOG_DIR, 0777, nullptr, false, sehandle) == 0) {
+        mkdir_recursively(CACHE_LOG_DIR, 0777, false, sehandle) == 0) {
       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)) {
@@ -748,11 +753,19 @@
     modified_flash = true;
 
     ui->Print("\n-- Wiping data...\n");
-    bool success =
-        device->PreWipeData() &&
-        erase_volume("/data") &&
-        (has_cache ? erase_volume("/cache") : true) &&
-        device->PostWipeData();
+    bool success = device->PreWipeData();
+    if (success) {
+      success &= erase_volume(DATA_ROOT);
+      if (has_cache) {
+        success &= erase_volume(CACHE_ROOT);
+      }
+      if (volume_for_mount_point(METADATA_ROOT) != nullptr) {
+        success &= erase_volume(METADATA_ROOT);
+      }
+    }
+    if (success) {
+      success &= device->PostWipeData();
+    }
     ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
     return success;
 }
@@ -1187,6 +1200,11 @@
         run_graphics_test();
         break;
 
+      case Device::RUN_LOCALE_TEST: {
+        ScreenRecoveryUI* screen_ui = static_cast<ScreenRecoveryUI*>(ui);
+        screen_ui->CheckBackgroundTextImages(locale);
+        break;
+      }
       case Device::MOUNT_SYSTEM:
         // For a system image built with the root directory (i.e. system_root_image == "true"), we
         // mount it to /system_root, and symlink /system to /system_root/system to make adb shell
@@ -1205,9 +1223,8 @@
   }
 }
 
-static void
-print_property(const char *key, const char *name, void *cookie) {
-    printf("%s=%s\n", key, name);
+static void print_property(const char* key, const char* name, void* /* cookie */) {
+  printf("%s=%s\n", key, name);
 }
 
 static std::string load_locale_from_cache() {
@@ -1241,70 +1258,91 @@
 
 static constexpr char log_characters[] = "VDIWEF";
 
-void UiLogger(android::base::LogId id, android::base::LogSeverity severity,
-               const char* tag, const char* file, unsigned int line,
-               const char* message) {
-    if (severity >= android::base::ERROR && ui != nullptr) {
-        ui->Print("E:%s\n", message);
-    } else {
-        fprintf(stdout, "%c:%s\n", log_characters[severity], message);
-    }
+void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity,
+              const char* /* tag */, const char* /* file */, unsigned int /* line */,
+              const char* message) {
+  if (severity >= android::base::ERROR && ui != nullptr) {
+    ui->Print("E:%s\n", message);
+  } else {
+    fprintf(stdout, "%c:%s\n", log_characters[severity], message);
+  }
 }
 
 static bool is_battery_ok() {
-    struct healthd_config healthd_config = {
-            .batteryStatusPath = android::String8(android::String8::kEmptyString),
-            .batteryHealthPath = android::String8(android::String8::kEmptyString),
-            .batteryPresentPath = android::String8(android::String8::kEmptyString),
-            .batteryCapacityPath = android::String8(android::String8::kEmptyString),
-            .batteryVoltagePath = android::String8(android::String8::kEmptyString),
-            .batteryTemperaturePath = android::String8(android::String8::kEmptyString),
-            .batteryTechnologyPath = android::String8(android::String8::kEmptyString),
-            .batteryCurrentNowPath = android::String8(android::String8::kEmptyString),
-            .batteryCurrentAvgPath = android::String8(android::String8::kEmptyString),
-            .batteryChargeCounterPath = android::String8(android::String8::kEmptyString),
-            .batteryFullChargePath = android::String8(android::String8::kEmptyString),
-            .batteryCycleCountPath = android::String8(android::String8::kEmptyString),
-            .energyCounter = NULL,
-            .boot_min_cap = 0,
-            .screen_on = NULL
-    };
-    healthd_board_init(&healthd_config);
+  using android::hardware::health::V1_0::BatteryStatus;
+  using android::hardware::health::V2_0::Result;
+  using android::hardware::health::V2_0::toString;
+  using android::hardware::health::V2_0::implementation::Health;
 
-    android::BatteryMonitor monitor;
-    monitor.init(&healthd_config);
+  struct healthd_config healthd_config = {
+    .batteryStatusPath = android::String8(android::String8::kEmptyString),
+    .batteryHealthPath = android::String8(android::String8::kEmptyString),
+    .batteryPresentPath = android::String8(android::String8::kEmptyString),
+    .batteryCapacityPath = android::String8(android::String8::kEmptyString),
+    .batteryVoltagePath = android::String8(android::String8::kEmptyString),
+    .batteryTemperaturePath = android::String8(android::String8::kEmptyString),
+    .batteryTechnologyPath = android::String8(android::String8::kEmptyString),
+    .batteryCurrentNowPath = android::String8(android::String8::kEmptyString),
+    .batteryCurrentAvgPath = android::String8(android::String8::kEmptyString),
+    .batteryChargeCounterPath = android::String8(android::String8::kEmptyString),
+    .batteryFullChargePath = android::String8(android::String8::kEmptyString),
+    .batteryCycleCountPath = android::String8(android::String8::kEmptyString),
+    .energyCounter = NULL,
+    .boot_min_cap = 0,
+    .screen_on = NULL
+  };
 
-    int wait_second = 0;
-    while (true) {
-        int charge_status = monitor.getChargeStatus();
-        // Treat unknown status as charged.
-        bool charged = (charge_status != android::BATTERY_STATUS_DISCHARGING &&
-                        charge_status != android::BATTERY_STATUS_NOT_CHARGING);
-        android::BatteryProperty capacity;
-        android::status_t status = monitor.getProperty(android::BATTERY_PROP_CAPACITY, &capacity);
-        ui_print("charge_status %d, charged %d, status %d, capacity %lld\n", charge_status,
-                 charged, status, capacity.valueInt64);
-        // At startup, the battery drivers in devices like N5X/N6P take some time to load
-        // the battery profile. Before the load finishes, it reports value 50 as a fake
-        // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected
-        // to finish loading the battery profile earlier than 10 seconds after kernel startup.
-        if (status == 0 && capacity.valueInt64 == 50) {
-            if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) {
-                sleep(1);
-                wait_second++;
-                continue;
-            }
-        }
-        // If we can't read battery percentage, it may be a device without battery. In this
-        // situation, use 100 as a fake battery percentage.
-        if (status != 0) {
-            capacity.valueInt64 = 100;
-        }
-        return (charged && capacity.valueInt64 >= BATTERY_WITH_CHARGER_OK_PERCENTAGE) ||
-                (!charged && capacity.valueInt64 >= BATTERY_OK_PERCENTAGE);
+  auto health =
+      android::hardware::health::V2_0::implementation::Health::initInstance(&healthd_config);
+
+  int wait_second = 0;
+  while (true) {
+    auto charge_status = BatteryStatus::UNKNOWN;
+    health
+        ->getChargeStatus([&charge_status](auto res, auto out_status) {
+          if (res == Result::SUCCESS) {
+            charge_status = out_status;
+          }
+        })
+        .isOk();  // should not have transport error
+
+    // Treat unknown status as charged.
+    bool charged = (charge_status != BatteryStatus::DISCHARGING &&
+                    charge_status != BatteryStatus::NOT_CHARGING);
+
+    Result res = Result::UNKNOWN;
+    int32_t capacity = INT32_MIN;
+    health
+        ->getCapacity([&res, &capacity](auto out_res, auto out_capacity) {
+          res = out_res;
+          capacity = out_capacity;
+        })
+        .isOk();  // should not have transport error
+
+    ui_print("charge_status %d, charged %d, status %s, capacity %" PRId32 "\n", charge_status,
+             charged, toString(res).c_str(), capacity);
+    // At startup, the battery drivers in devices like N5X/N6P take some time to load
+    // the battery profile. Before the load finishes, it reports value 50 as a fake
+    // capacity. BATTERY_READ_TIMEOUT_IN_SEC is set that the battery drivers are expected
+    // to finish loading the battery profile earlier than 10 seconds after kernel startup.
+    if (res == Result::SUCCESS && capacity == 50) {
+      if (wait_second < BATTERY_READ_TIMEOUT_IN_SEC) {
+        sleep(1);
+        wait_second++;
+        continue;
+      }
+    }
+    // If we can't read battery percentage, it may be a device without battery. In this
+    // situation, use 100 as a fake battery percentage.
+    if (res != Result::SUCCESS) {
+      capacity = 100;
+    }
+    return (charged && capacity >= BATTERY_WITH_CHARGER_OK_PERCENTAGE) ||
+           (!charged && capacity >= BATTERY_OK_PERCENTAGE);
     }
 }
 
+// Set the retry count to |retry_count| in BCB.
 static void set_retry_bootloader_message(int retry_count, const std::vector<std::string>& args) {
   std::vector<std::string> options;
   for (const auto& arg : args) {
@@ -1313,8 +1351,8 @@
     }
   }
 
-  // Increment the retry counter by 1.
-  options.push_back(android::base::StringPrintf("--retry_count=%d", retry_count + 1));
+  // Update the retry counter in BCB.
+  options.push_back(android::base::StringPrintf("--retry_count=%d", retry_count));
   std::string err;
   if (!update_bootloader_message(options, &err)) {
     LOG(ERROR) << err;
@@ -1349,303 +1387,331 @@
 }
 
 int main(int argc, char **argv) {
-    // We don't have logcat yet under recovery; so we'll print error on screen and
-    // log to stdout (which is redirected to recovery.log) as we used to do.
-    android::base::InitLogging(argv, &UiLogger);
+  // We don't have logcat yet under recovery; so we'll print error on screen and
+  // log to stdout (which is redirected to recovery.log) as we used to do.
+  android::base::InitLogging(argv, &UiLogger);
 
-    // Take last pmsg contents and rewrite it to the current pmsg session.
-    static const char filter[] = "recovery/";
-    // Do we need to rotate?
-    bool doRotate = false;
+  // Take last pmsg contents and rewrite it to the current pmsg session.
+  static const char filter[] = "recovery/";
+  // Do we need to rotate?
+  bool doRotate = false;
 
-    __android_log_pmsg_file_read(
-        LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
-        logbasename, &doRotate);
-    // Take action to refresh pmsg contents
-    __android_log_pmsg_file_read(
-        LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
-        logrotate, &doRotate);
+  __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &doRotate);
+  // Take action to refresh pmsg contents
+  __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &doRotate);
 
-    // If this binary is started with the single argument "--adbd",
-    // instead of being the normal recovery binary, it turns into kind
-    // of a stripped-down version of adbd that only supports the
-    // 'sideload' command.  Note this must be a real argument, not
-    // anything in the command file or bootloader control block; the
-    // only way recovery should be run with this argument is when it
-    // starts a copy of itself from the apply_from_adb() function.
-    if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
-        minadbd_main();
-        return 0;
+  // If this binary is started with the single argument "--adbd",
+  // instead of being the normal recovery binary, it turns into kind
+  // of a stripped-down version of adbd that only supports the
+  // 'sideload' command.  Note this must be a real argument, not
+  // anything in the command file or bootloader control block; the
+  // only way recovery should be run with this argument is when it
+  // starts a copy of itself from the apply_from_adb() function.
+  if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
+    minadbd_main();
+    return 0;
+  }
+
+  time_t start = time(nullptr);
+
+  // redirect_stdio should be called only in non-sideload mode. Otherwise
+  // we may have two logger instances with different timestamps.
+  redirect_stdio(TEMPORARY_LOG_FILE);
+
+  printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
+
+  load_volume_table();
+  has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
+
+  std::vector<std::string> args = get_args(argc, argv);
+  std::vector<char*> args_to_parse(args.size());
+  std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
+                 [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
+
+  const char* update_package = nullptr;
+  bool should_wipe_data = false;
+  bool should_prompt_and_wipe_data = false;
+  bool should_wipe_cache = false;
+  bool should_wipe_ab = false;
+  size_t wipe_package_size = 0;
+  bool show_text = false;
+  bool sideload = false;
+  bool sideload_auto_reboot = false;
+  bool just_exit = false;
+  bool shutdown_after = false;
+  int retry_count = 0;
+  bool security_update = false;
+
+  int arg;
+  int option_index;
+  while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
+                            &option_index)) != -1) {
+    switch (arg) {
+      case 'n':
+        android::base::ParseInt(optarg, &retry_count, 0);
+        break;
+      case 'u':
+        update_package = optarg;
+        break;
+      case 'w':
+        should_wipe_data = true;
+        break;
+      case 'c':
+        should_wipe_cache = true;
+        break;
+      case 't':
+        show_text = true;
+        break;
+      case 's':
+        sideload = true;
+        break;
+      case 'a':
+        sideload = true;
+        sideload_auto_reboot = true;
+        break;
+      case 'x':
+        just_exit = true;
+        break;
+      case 'l':
+        locale = optarg;
+        break;
+      case 'p':
+        shutdown_after = true;
+        break;
+      case 'r':
+        reason = optarg;
+        break;
+      case 'e':
+        security_update = true;
+        break;
+      case 0: {
+        std::string option = OPTIONS[option_index].name;
+        if (option == "wipe_ab") {
+          should_wipe_ab = true;
+        } else if (option == "wipe_package_size") {
+          android::base::ParseUint(optarg, &wipe_package_size);
+        } else if (option == "prompt_and_wipe_data") {
+          should_prompt_and_wipe_data = true;
+        }
+        break;
+      }
+      case '?':
+        LOG(ERROR) << "Invalid command argument";
+        continue;
     }
+  }
 
-    time_t start = time(NULL);
-
-    // redirect_stdio should be called only in non-sideload mode. Otherwise
-    // we may have two logger instances with different timestamps.
-    redirect_stdio(TEMPORARY_LOG_FILE);
-
-    printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
-
-    load_volume_table();
-    has_cache = volume_for_path(CACHE_ROOT) != nullptr;
-
-    std::vector<std::string> args = get_args(argc, argv);
-    std::vector<char*> args_to_parse(args.size());
-    std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
-                   [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
-
-    const char *update_package = NULL;
-    bool should_wipe_data = false;
-    bool should_prompt_and_wipe_data = false;
-    bool should_wipe_cache = false;
-    bool should_wipe_ab = false;
-    size_t wipe_package_size = 0;
-    bool show_text = false;
-    bool sideload = false;
-    bool sideload_auto_reboot = false;
-    bool just_exit = false;
-    bool shutdown_after = false;
-    int retry_count = 0;
-    bool security_update = false;
-
-    int arg;
-    int option_index;
-    while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
-                              &option_index)) != -1) {
-        switch (arg) {
-        case 'n': android::base::ParseInt(optarg, &retry_count, 0); break;
-        case 'u': update_package = optarg; break;
-        case 'w': should_wipe_data = true; break;
-        case 'c': should_wipe_cache = true; break;
-        case 't': show_text = true; break;
-        case 's': sideload = true; break;
-        case 'a': sideload = true; sideload_auto_reboot = true; break;
-        case 'x': just_exit = true; break;
-        case 'l': locale = optarg; break;
-        case 'p': shutdown_after = true; break;
-        case 'r': reason = optarg; break;
-        case 'e': security_update = true; break;
-        case 0: {
-            std::string option = OPTIONS[option_index].name;
-            if (option == "wipe_ab") {
-                should_wipe_ab = true;
-            } else if (option == "wipe_package_size") {
-                android::base::ParseUint(optarg, &wipe_package_size);
-            } else if (option == "prompt_and_wipe_data") {
-                should_prompt_and_wipe_data = true;
-            }
-            break;
-        }
-        case '?':
-            LOG(ERROR) << "Invalid command argument";
-            continue;
-        }
+  if (locale.empty()) {
+    if (has_cache) {
+      locale = load_locale_from_cache();
     }
 
     if (locale.empty()) {
-        if (has_cache) {
-            locale = load_locale_from_cache();
-        }
-
-        if (locale.empty()) {
-            locale = DEFAULT_LOCALE;
-        }
+      locale = DEFAULT_LOCALE;
     }
+  }
 
-    printf("locale is [%s]\n", locale.c_str());
-    printf("stage is [%s]\n", stage.c_str());
-    printf("reason is [%s]\n", reason);
+  printf("locale is [%s]\n", locale.c_str());
+  printf("stage is [%s]\n", stage.c_str());
+  printf("reason is [%s]\n", reason);
 
-    Device* device = make_device();
-    if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
-        printf("Quiescent recovery mode.\n");
-        ui = new StubRecoveryUI();
+  Device* device = make_device();
+  if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
+    printf("Quiescent recovery mode.\n");
+    ui = new StubRecoveryUI();
+  } else {
+    ui = device->GetUI();
+
+    if (!ui->Init(locale)) {
+      printf("Failed to initialize UI, use stub UI instead.\n");
+      ui = new StubRecoveryUI();
+    }
+  }
+
+  // Set background string to "installing security update" for security update,
+  // otherwise set it to "installing system update".
+  ui->SetSystemUpdateText(security_update);
+
+  int st_cur, st_max;
+  if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
+    ui->SetStage(st_cur, st_max);
+  }
+
+  ui->SetBackground(RecoveryUI::NONE);
+  if (show_text) ui->ShowText(true);
+
+  sehandle = selinux_android_file_context_handle();
+  selinux_android_set_sehandle(sehandle);
+  if (!sehandle) {
+    ui->Print("Warning: No file_contexts\n");
+  }
+
+  device->StartRecovery();
+
+  printf("Command:");
+  for (const auto& arg : args) {
+    printf(" \"%s\"", arg.c_str());
+  }
+  printf("\n\n");
+
+  property_list(print_property, nullptr);
+  printf("\n");
+
+  ui->Print("Supported API: %d\n", kRecoveryApiVersion);
+
+  int status = INSTALL_SUCCESS;
+
+  if (update_package != nullptr) {
+    // It's not entirely true that we will modify the flash. But we want
+    // to log the update attempt since update_package is non-NULL.
+    modified_flash = true;
+
+    if (!is_battery_ok()) {
+      ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
+                BATTERY_OK_PERCENTAGE);
+      // Log the error code to last_install when installation skips due to
+      // low battery.
+      log_failure_code(kLowBattery, update_package);
+      status = INSTALL_SKIPPED;
+    } else if (bootreason_in_blacklist()) {
+      // Skip update-on-reboot when bootreason is kernel_panic or similar
+      ui->Print("bootreason is in the blacklist; skip OTA installation\n");
+      log_failure_code(kBootreasonInBlacklist, update_package);
+      status = INSTALL_SKIPPED;
     } else {
-        ui = device->GetUI();
-
-        if (!ui->Init(locale)) {
-            printf("Failed to initialize UI, use stub UI instead.\n");
-            ui = new StubRecoveryUI();
-        }
-    }
-
-    // Set background string to "installing security update" for security update,
-    // otherwise set it to "installing system update".
-    ui->SetSystemUpdateText(security_update);
-
-    int st_cur, st_max;
-    if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
-        ui->SetStage(st_cur, st_max);
-    }
-
-    ui->SetBackground(RecoveryUI::NONE);
-    if (show_text) ui->ShowText(true);
-
-    sehandle = selinux_android_file_context_handle();
-    selinux_android_set_sehandle(sehandle);
-    if (!sehandle) {
-        ui->Print("Warning: No file_contexts\n");
-    }
-
-    device->StartRecovery();
-
-    printf("Command:");
-    for (const auto& arg : args) {
-        printf(" \"%s\"", arg.c_str());
-    }
-    printf("\n\n");
-
-    property_list(print_property, NULL);
-    printf("\n");
-
-    ui->Print("Supported API: %d\n", RECOVERY_API_VERSION);
-
-    int status = INSTALL_SUCCESS;
-
-    if (update_package != NULL) {
-        // It's not entirely true that we will modify the flash. But we want
-        // to log the update attempt since update_package is non-NULL.
-        modified_flash = true;
-
-        if (!is_battery_ok()) {
-            ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
-                      BATTERY_OK_PERCENTAGE);
-            // Log the error code to last_install when installation skips due to
-            // low battery.
-            log_failure_code(kLowBattery, update_package);
-            status = INSTALL_SKIPPED;
-        } else if (bootreason_in_blacklist()) {
-            // Skip update-on-reboot when bootreason is kernel_panic or similar
-            ui->Print("bootreason is in the blacklist; skip OTA installation\n");
-            log_failure_code(kBootreasonInBlacklist, update_package);
-            status = INSTALL_SKIPPED;
-        } else {
-            status = install_package(update_package, &should_wipe_cache,
-                                     TEMPORARY_INSTALL_FILE, true, retry_count);
-            if (status == INSTALL_SUCCESS && should_wipe_cache) {
-                wipe_cache(false, device);
-            }
-            if (status != INSTALL_SUCCESS) {
-                ui->Print("Installation aborted.\n");
-                // When I/O error happens, reboot and retry installation RETRY_LIMIT
-                // times before we abandon this OTA update.
-                if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
-                    copy_logs();
-                    set_retry_bootloader_message(retry_count, args);
-                    // Print retry count on screen.
-                    ui->Print("Retry attempt %d\n", retry_count);
-
-                    // Reboot and retry the update
-                    if (!reboot("reboot,recovery")) {
-                        ui->Print("Reboot failed\n");
-                    } else {
-                        while (true) {
-                            pause();
-                        }
-                    }
-                }
-                // If this is an eng or userdebug build, then automatically
-                // turn the text display on if the script fails so the error
-                // message is visible.
-                if (is_ro_debuggable()) {
-                    ui->ShowText(true);
-                }
-            }
-        }
-    } else if (should_wipe_data) {
-        if (!wipe_data(device)) {
-            status = INSTALL_ERROR;
-        }
-    } else if (should_prompt_and_wipe_data) {
-        ui->ShowText(true);
-        ui->SetBackground(RecoveryUI::ERROR);
-        if (!prompt_and_wipe_data(device)) {
-            status = INSTALL_ERROR;
-        }
-        ui->ShowText(false);
-    } else if (should_wipe_cache) {
-        if (!wipe_cache(false, device)) {
-            status = INSTALL_ERROR;
-        }
-    } else if (should_wipe_ab) {
-        if (!wipe_ab_device(wipe_package_size)) {
-            status = INSTALL_ERROR;
-        }
-    } else if (sideload) {
-        // 'adb reboot sideload' acts the same as user presses key combinations
-        // to enter the sideload mode. When 'sideload-auto-reboot' is used, text
-        // display will NOT be turned on by default. And it will reboot after
-        // sideload finishes even if there are errors. Unless one turns on the
-        // text display during the installation. This is to enable automated
-        // testing.
-        if (!sideload_auto_reboot) {
-            ui->ShowText(true);
-        }
-        status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE);
-        if (status == INSTALL_SUCCESS && should_wipe_cache) {
-            if (!wipe_cache(false, device)) {
-                status = INSTALL_ERROR;
-            }
-        }
-        ui->Print("\nInstall from ADB complete (status: %d).\n", status);
-        if (sideload_auto_reboot) {
-            ui->Print("Rebooting automatically.\n");
-        }
-    } else if (!just_exit) {
-      // If this is an eng or userdebug build, automatically turn on the text display if no command
-      // is specified. Note that this should be called before setting the background to avoid
-      // flickering the background image.
-      if (is_ro_debuggable()) {
-        ui->ShowText(true);
+      // It's a fresh update. Initialize the retry_count in the BCB to 1; therefore we can later
+      // identify the interrupted update due to unexpected reboots.
+      if (retry_count == 0) {
+        set_retry_bootloader_message(retry_count + 1, args);
       }
-      status = INSTALL_NONE;  // No command specified
-      ui->SetBackground(RecoveryUI::NO_COMMAND);
-    }
 
-    if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
-        ui->SetBackground(RecoveryUI::ERROR);
-        if (!ui->IsTextVisible()) {
-            sleep(5);
+      status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true,
+                               retry_count);
+      if (status == INSTALL_SUCCESS && should_wipe_cache) {
+        wipe_cache(false, device);
+      }
+      if (status != INSTALL_SUCCESS) {
+        ui->Print("Installation aborted.\n");
+        // When I/O error happens, reboot and retry installation RETRY_LIMIT
+        // times before we abandon this OTA update.
+        if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
+          copy_logs();
+          retry_count += 1;
+          set_retry_bootloader_message(retry_count, args);
+          // Print retry count on screen.
+          ui->Print("Retry attempt %d\n", retry_count);
+
+          // Reboot and retry the update
+          if (!reboot("reboot,recovery")) {
+            ui->Print("Reboot failed\n");
+          } else {
+            while (true) {
+              pause();
+            }
+          }
         }
-    }
-
-    Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
-    // 1. If the recovery menu is visible, prompt and wait for commands.
-    // 2. If the state is INSTALL_NONE, wait for commands. (i.e. In user build, manually reboot into
-    //    recovery to sideload a package.)
-    // 3. sideload_auto_reboot is an option only available in user-debug build, reboot the device
-    //    without waiting.
-    // 4. In all other cases, reboot the device. Therefore, normal users will observe the device
-    //    reboot after it shows the "error" screen for 5s.
-    if ((status == INSTALL_NONE && !sideload_auto_reboot) || ui->IsTextVisible()) {
-        Device::BuiltinAction temp = prompt_and_wait(device, status);
-        if (temp != Device::NO_ACTION) {
-            after = temp;
+        // If this is an eng or userdebug build, then automatically
+        // turn the text display on if the script fails so the error
+        // message is visible.
+        if (is_ro_debuggable()) {
+          ui->ShowText(true);
         }
+      }
     }
-
-    // Save logs and clean up before rebooting or shutting down.
-    finish_recovery();
-
-    switch (after) {
-        case Device::SHUTDOWN:
-            ui->Print("Shutting down...\n");
-            android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
-            break;
-
-        case Device::REBOOT_BOOTLOADER:
-            ui->Print("Rebooting to bootloader...\n");
-            android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
-            break;
-
-        default:
-            ui->Print("Rebooting...\n");
-            reboot("reboot,");
-            break;
+  } else if (should_wipe_data) {
+    if (!wipe_data(device)) {
+      status = INSTALL_ERROR;
     }
-    while (true) {
-        pause();
+  } else if (should_prompt_and_wipe_data) {
+    ui->ShowText(true);
+    ui->SetBackground(RecoveryUI::ERROR);
+    if (!prompt_and_wipe_data(device)) {
+      status = INSTALL_ERROR;
     }
-    // Should be unreachable.
-    return EXIT_SUCCESS;
+    ui->ShowText(false);
+  } else if (should_wipe_cache) {
+    if (!wipe_cache(false, device)) {
+      status = INSTALL_ERROR;
+    }
+  } else if (should_wipe_ab) {
+    if (!wipe_ab_device(wipe_package_size)) {
+      status = INSTALL_ERROR;
+    }
+  } else if (sideload) {
+    // 'adb reboot sideload' acts the same as user presses key combinations
+    // to enter the sideload mode. When 'sideload-auto-reboot' is used, text
+    // display will NOT be turned on by default. And it will reboot after
+    // sideload finishes even if there are errors. Unless one turns on the
+    // text display during the installation. This is to enable automated
+    // testing.
+    if (!sideload_auto_reboot) {
+      ui->ShowText(true);
+    }
+    status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE);
+    if (status == INSTALL_SUCCESS && should_wipe_cache) {
+      if (!wipe_cache(false, device)) {
+        status = INSTALL_ERROR;
+      }
+    }
+    ui->Print("\nInstall from ADB complete (status: %d).\n", status);
+    if (sideload_auto_reboot) {
+      ui->Print("Rebooting automatically.\n");
+    }
+  } else if (!just_exit) {
+    // If this is an eng or userdebug build, automatically turn on the text display if no command
+    // is specified. Note that this should be called before setting the background to avoid
+    // flickering the background image.
+    if (is_ro_debuggable()) {
+      ui->ShowText(true);
+    }
+    status = INSTALL_NONE;  // No command specified
+    ui->SetBackground(RecoveryUI::NO_COMMAND);
+  }
+
+  if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
+    ui->SetBackground(RecoveryUI::ERROR);
+    if (!ui->IsTextVisible()) {
+      sleep(5);
+    }
+  }
+
+  Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
+  // 1. If the recovery menu is visible, prompt and wait for commands.
+  // 2. If the state is INSTALL_NONE, wait for commands. (i.e. In user build, manually reboot into
+  //    recovery to sideload a package.)
+  // 3. sideload_auto_reboot is an option only available in user-debug build, reboot the device
+  //    without waiting.
+  // 4. In all other cases, reboot the device. Therefore, normal users will observe the device
+  //    reboot after it shows the "error" screen for 5s.
+  if ((status == INSTALL_NONE && !sideload_auto_reboot) || ui->IsTextVisible()) {
+    Device::BuiltinAction temp = prompt_and_wait(device, status);
+    if (temp != Device::NO_ACTION) {
+      after = temp;
+    }
+  }
+
+  // Save logs and clean up before rebooting or shutting down.
+  finish_recovery();
+
+  switch (after) {
+    case Device::SHUTDOWN:
+      ui->Print("Shutting down...\n");
+      android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
+      break;
+
+    case Device::REBOOT_BOOTLOADER:
+      ui->Print("Rebooting to bootloader...\n");
+      android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
+      break;
+
+    default:
+      ui->Print("Rebooting...\n");
+      reboot("reboot,");
+      break;
+  }
+  while (true) {
+    pause();
+  }
+  // Should be unreachable.
+  return EXIT_SUCCESS;
 }
diff --git a/res-hdpi/images/erasing_text.png b/res-hdpi/images/erasing_text.png
new file mode 100644
index 0000000..34c56a9
--- /dev/null
+++ b/res-hdpi/images/erasing_text.png
Binary files differ
diff --git a/res-hdpi/images/error_text.png b/res-hdpi/images/error_text.png
new file mode 100644
index 0000000..2a96053
--- /dev/null
+++ b/res-hdpi/images/error_text.png
Binary files differ
diff --git a/res-hdpi/images/installing_security_text.png b/res-hdpi/images/installing_security_text.png
new file mode 100644
index 0000000..97e1f11
--- /dev/null
+++ b/res-hdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-hdpi/images/installing_text.png b/res-hdpi/images/installing_text.png
new file mode 100644
index 0000000..1d591eb
--- /dev/null
+++ b/res-hdpi/images/installing_text.png
Binary files differ
diff --git a/res-hdpi/images/no_command_text.png b/res-hdpi/images/no_command_text.png
new file mode 100644
index 0000000..977fcfa
--- /dev/null
+++ b/res-hdpi/images/no_command_text.png
Binary files differ
diff --git a/res-mdpi/images/erasing_text.png b/res-mdpi/images/erasing_text.png
new file mode 100644
index 0000000..dcd0ea6
--- /dev/null
+++ b/res-mdpi/images/erasing_text.png
Binary files differ
diff --git a/res-mdpi/images/error_text.png b/res-mdpi/images/error_text.png
new file mode 100644
index 0000000..2152dad
--- /dev/null
+++ b/res-mdpi/images/error_text.png
Binary files differ
diff --git a/res-mdpi/images/installing_security_text.png b/res-mdpi/images/installing_security_text.png
new file mode 100644
index 0000000..d1ac4ca
--- /dev/null
+++ b/res-mdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-mdpi/images/installing_text.png b/res-mdpi/images/installing_text.png
new file mode 100644
index 0000000..c9b6d71
--- /dev/null
+++ b/res-mdpi/images/installing_text.png
Binary files differ
diff --git a/res-mdpi/images/no_command_text.png b/res-mdpi/images/no_command_text.png
new file mode 100644
index 0000000..f77ad13
--- /dev/null
+++ b/res-mdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xhdpi/images/erasing_text.png b/res-xhdpi/images/erasing_text.png
new file mode 100644
index 0000000..e22b274
--- /dev/null
+++ b/res-xhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xhdpi/images/error_text.png b/res-xhdpi/images/error_text.png
new file mode 100644
index 0000000..e4c27e1
--- /dev/null
+++ b/res-xhdpi/images/error_text.png
Binary files differ
diff --git a/res-xhdpi/images/installing_security_text.png b/res-xhdpi/images/installing_security_text.png
new file mode 100644
index 0000000..7ba12b6
--- /dev/null
+++ b/res-xhdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-xhdpi/images/installing_text.png b/res-xhdpi/images/installing_text.png
new file mode 100644
index 0000000..567988e
--- /dev/null
+++ b/res-xhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xhdpi/images/no_command_text.png b/res-xhdpi/images/no_command_text.png
new file mode 100644
index 0000000..a682abb
--- /dev/null
+++ b/res-xhdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xxhdpi/images/erasing_text.png b/res-xxhdpi/images/erasing_text.png
new file mode 100644
index 0000000..6cc953b
--- /dev/null
+++ b/res-xxhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xxhdpi/images/error_text.png b/res-xxhdpi/images/error_text.png
new file mode 100644
index 0000000..0d5cea8
--- /dev/null
+++ b/res-xxhdpi/images/error_text.png
Binary files differ
diff --git a/res-xxhdpi/images/installing_security_text.png b/res-xxhdpi/images/installing_security_text.png
new file mode 100644
index 0000000..5d10598
--- /dev/null
+++ b/res-xxhdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-xxhdpi/images/installing_text.png b/res-xxhdpi/images/installing_text.png
new file mode 100644
index 0000000..6e94fa2
--- /dev/null
+++ b/res-xxhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xxhdpi/images/no_command_text.png b/res-xxhdpi/images/no_command_text.png
new file mode 100644
index 0000000..40ab484
--- /dev/null
+++ b/res-xxhdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/erasing_text.png b/res-xxxhdpi/images/erasing_text.png
new file mode 100644
index 0000000..cc73099
--- /dev/null
+++ b/res-xxxhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/error_text.png b/res-xxxhdpi/images/error_text.png
new file mode 100644
index 0000000..fea3cfc
--- /dev/null
+++ b/res-xxxhdpi/images/error_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/installing_security_text.png b/res-xxxhdpi/images/installing_security_text.png
new file mode 100644
index 0000000..ed77d08
--- /dev/null
+++ b/res-xxxhdpi/images/installing_security_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/installing_text.png b/res-xxxhdpi/images/installing_text.png
new file mode 100644
index 0000000..9659106
--- /dev/null
+++ b/res-xxxhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/no_command_text.png b/res-xxxhdpi/images/no_command_text.png
new file mode 100644
index 0000000..4e6f363
--- /dev/null
+++ b/res-xxxhdpi/images/no_command_text.png
Binary files differ
diff --git a/roots.cpp b/roots.cpp
index 29f55b9..184e799 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -16,174 +16,208 @@
 
 #include "roots.h"
 
-#include <errno.h>
+#include <ctype.h>
+#include <fcntl.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 <ctype.h>
-#include <fcntl.h>
+
+#include <algorithm>
+#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 "common.h"
 #include "mounts.h"
-#include "cryptfs.h"
 
-static struct fstab *fstab = NULL;
+static struct fstab* fstab = nullptr;
 
-extern struct selabel_handle *sehandle;
+extern struct selabel_handle* sehandle;
 
-void load_volume_table()
-{
-    int i;
-    int ret;
+void load_volume_table() {
+  fstab = fs_mgr_read_fstab_default();
+  if (!fstab) {
+    LOG(ERROR) << "Failed to read default fstab";
+    return;
+  }
 
-    fstab = fs_mgr_read_fstab_default();
-    if (!fstab) {
-        LOG(ERROR) << "failed to read default fstab";
-        return;
-    }
+  int ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
+  if (ret == -1) {
+    LOG(ERROR) << "Failed to add /tmp entry to fstab";
+    fs_mgr_free_fstab(fstab);
+    fstab = nullptr;
+    return;
+  }
 
-    ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
-    if (ret < 0 ) {
-        LOG(ERROR) << "failed to add /tmp entry to fstab";
-        fs_mgr_free_fstab(fstab);
-        fstab = NULL;
-        return;
-    }
-
-    printf("recovery filesystem table\n");
-    printf("=========================\n");
-    for (i = 0; i < fstab->num_entries; ++i) {
-        Volume* v = &fstab->recs[i];
-        printf("  %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,
-               v->blk_device, v->length);
-    }
-    printf("\n");
+  printf("recovery filesystem table\n");
+  printf("=========================\n");
+  for (int i = 0; i < fstab->num_entries; ++i) {
+    const Volume* v = &fstab->recs[i];
+    printf("  %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, v->blk_device, v->length);
+  }
+  printf("\n");
 }
 
-Volume* volume_for_path(const char* path) {
-    return fs_mgr_get_entry_for_mount_point(fstab, path);
+Volume* volume_for_mount_point(const std::string& mount_point) {
+  return fs_mgr_get_entry_for_mount_point(fstab, mount_point);
+}
+
+// Finds the volume specified by the given path. fs_mgr_get_entry_for_mount_point() does exact match
+// only, so it attempts the prefixes recursively (e.g. "/cache/recovery/last_log",
+// "/cache/recovery", "/cache", "/" for a given path of "/cache/recovery/last_log") and returns the
+// first match or nullptr.
+static Volume* volume_for_path(const char* path) {
+  if (path == nullptr || path[0] == '\0') return nullptr;
+  std::string str(path);
+  while (true) {
+    Volume* result = fs_mgr_get_entry_for_mount_point(fstab, str);
+    if (result != nullptr || str == "/") {
+      return result;
+    }
+    size_t slash = str.find_last_of('/');
+    if (slash == std::string::npos) return nullptr;
+    if (slash == 0) {
+      str = "/";
+    } else {
+      str = str.substr(0, slash);
+    }
+  }
+  return nullptr;
 }
 
 // Mount the volume specified by path at the given mount_point.
 int ensure_path_mounted_at(const char* path, const char* mount_point) {
-    Volume* v = volume_for_path(path);
-    if (v == NULL) {
-        LOG(ERROR) << "unknown volume for path [" << path << "]";
-        return -1;
-    }
-    if (strcmp(v->fs_type, "ramdisk") == 0) {
-        // the ramdisk is always mounted.
-        return 0;
-    }
-
-    if (!scan_mounted_volumes()) {
-        LOG(ERROR) << "failed to scan mounted volumes";
-        return -1;
-    }
-
-    if (!mount_point) {
-        mount_point = v->mount_point;
-    }
-
-    MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point);
-    if (mv) {
-        // volume is already mounted
-        return 0;
-    }
-
-    mkdir(mount_point, 0755);  // in case it doesn't already exist
-
-    if (strcmp(v->fs_type, "ext4") == 0 ||
-               strcmp(v->fs_type, "squashfs") == 0 ||
-               strcmp(v->fs_type, "vfat") == 0) {
-        int result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
-        if (result == -1 && fs_mgr_is_formattable(v)) {
-            LOG(ERROR) << "failed to mount " << mount_point << " (" << strerror(errno)
-                       << ") , formatting.....";
-            bool crypt_footer = fs_mgr_is_encryptable(v) && !strcmp(v->key_loc, "footer");
-            if (fs_mgr_do_format(v, crypt_footer) == 0) {
-                result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
-            } else {
-                PLOG(ERROR) << "failed to format " << mount_point;
-                return -1;
-            }
-        }
-
-        if (result == -1) {
-            PLOG(ERROR) << "failed to mount " << mount_point;
-            return -1;
-        }
-        return 0;
-    }
-
-    LOG(ERROR) << "unknown fs_type \"" << v->fs_type << "\" for " << mount_point;
+  Volume* v = volume_for_path(path);
+  if (v == nullptr) {
+    LOG(ERROR) << "unknown volume for path [" << path << "]";
     return -1;
+  }
+  if (strcmp(v->fs_type, "ramdisk") == 0) {
+    // The ramdisk is always mounted.
+    return 0;
+  }
+
+  if (!scan_mounted_volumes()) {
+    LOG(ERROR) << "Failed to scan mounted volumes";
+    return -1;
+  }
+
+  if (!mount_point) {
+    mount_point = v->mount_point;
+  }
+
+  const MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point);
+  if (mv != nullptr) {
+    // Volume is already mounted.
+    return 0;
+  }
+
+  mkdir(mount_point, 0755);  // in case it doesn't already exist
+
+  if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "squashfs") == 0 ||
+      strcmp(v->fs_type, "vfat") == 0) {
+    int result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
+    if (result == -1 && fs_mgr_is_formattable(v)) {
+      PLOG(ERROR) << "Failed to mount " << mount_point << "; formatting";
+      bool crypt_footer = fs_mgr_is_encryptable(v) && !strcmp(v->key_loc, "footer");
+      if (fs_mgr_do_format(v, crypt_footer) == 0) {
+        result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
+      } else {
+        PLOG(ERROR) << "Failed to format " << mount_point;
+        return -1;
+      }
+    }
+
+    if (result == -1) {
+      PLOG(ERROR) << "Failed to mount " << mount_point;
+      return -1;
+    }
+    return 0;
+  }
+
+  LOG(ERROR) << "unknown fs_type \"" << v->fs_type << "\" for " << mount_point;
+  return -1;
 }
 
 int ensure_path_mounted(const char* path) {
-    // Mount at the default mount point.
-    return ensure_path_mounted_at(path, nullptr);
+  // Mount at the default mount point.
+  return ensure_path_mounted_at(path, nullptr);
 }
 
 int ensure_path_unmounted(const char* path) {
-    Volume* v = volume_for_path(path);
-    if (v == NULL) {
-        LOG(ERROR) << "unknown volume for path [" << path << "]";
-        return -1;
-    }
-    if (strcmp(v->fs_type, "ramdisk") == 0) {
-        // the ramdisk is always mounted; you can't unmount it.
-        return -1;
-    }
+  const Volume* v = volume_for_path(path);
+  if (v == nullptr) {
+    LOG(ERROR) << "unknown volume for path [" << path << "]";
+    return -1;
+  }
+  if (strcmp(v->fs_type, "ramdisk") == 0) {
+    // The ramdisk is always mounted; you can't unmount it.
+    return -1;
+  }
 
-    if (!scan_mounted_volumes()) {
-        LOG(ERROR) << "failed to scan mounted volumes";
-        return -1;
-    }
+  if (!scan_mounted_volumes()) {
+    LOG(ERROR) << "Failed to scan mounted volumes";
+    return -1;
+  }
 
-    MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point);
-    if (mv == NULL) {
-        // volume is already unmounted
-        return 0;
-    }
+  MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point);
+  if (mv == nullptr) {
+    // Volume is already unmounted.
+    return 0;
+  }
 
-    return unmount_mounted_volume(mv);
+  return unmount_mounted_volume(mv);
 }
 
-static int exec_cmd(const char* path, char* const argv[]) {
-    int status;
-    pid_t child;
-    if ((child = vfork()) == 0) {
-        execv(path, argv);
-        _exit(EXIT_FAILURE);
-    }
-    waitpid(child, &status, 0);
-    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-        LOG(ERROR) << path << " failed with status " << WEXITSTATUS(status);
-    }
-    return WEXITSTATUS(status);
+static int exec_cmd(const std::vector<std::string>& args) {
+  CHECK_NE(static_cast<size_t>(0), args.size());
+
+  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;
+  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 ssize_t get_file_size(int fd, uint64_t reserve_len) {
+static int64_t get_file_size(int fd, uint64_t reserve_len) {
   struct stat buf;
   int ret = fstat(fd, &buf);
   if (ret) return 0;
 
-  ssize_t computed_size;
+  int64_t computed_size;
   if (S_ISREG(buf.st_mode)) {
     computed_size = buf.st_size - reserve_len;
   } else if (S_ISBLK(buf.st_mode)) {
-    computed_size = get_block_device_size(fd) - reserve_len;
+    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;
   }
@@ -192,136 +226,144 @@
 }
 
 int format_volume(const char* volume, const char* directory) {
-    Volume* v = volume_for_path(volume);
-    if (v == NULL) {
-        LOG(ERROR) << "unknown volume \"" << volume << "\"";
-        return -1;
-    }
-    if (strcmp(v->fs_type, "ramdisk") == 0) {
-        // you can't format the ramdisk.
-        LOG(ERROR) << "can't format_volume \"" << volume << "\"";
-        return -1;
-    }
-    if (strcmp(v->mount_point, volume) != 0) {
-        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 (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "f2fs") == 0) {
-        // 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 != NULL && v->key_loc[0] == '/') {
-            LOG(INFO) << "wiping " << v->key_loc;
-            int fd = open(v->key_loc, O_WRONLY | O_CREAT, 0644);
-            if (fd < 0) {
-                LOG(ERROR) << "format_volume: failed to open " << v->key_loc;
-                return -1;
-            }
-            wipe_block_device(fd, get_file_size(fd));
-            close(fd);
-        }
-
-        ssize_t length = 0;
-        if (v->length != 0) {
-            length = v->length;
-        } else if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0) {
-          android::base::unique_fd fd(open(v->blk_device, O_RDONLY));
-          if (fd < 0) {
-            PLOG(ERROR) << "get_file_size: failed to open " << v->blk_device;
-            return -1;
-          }
-          length = get_file_size(fd.get(), CRYPT_FOOTER_OFFSET);
-          if (length <= 0) {
-            LOG(ERROR) << "get_file_size: invalid size " << length << " for " << v->blk_device;
-            return -1;
-          }
-        }
-        int result;
-        if (strcmp(v->fs_type, "ext4") == 0) {
-          static constexpr int block_size = 4096;
-          int raid_stride = v->logical_blk_size / block_size;
-          int raid_stripe_width = v->erase_blk_size / block_size;
-
-          // 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 / block_size;
-          }
-
-          const char* mke2fs_argv[] = { "/sbin/mke2fs_static",
-                                        "-F",
-                                        "-t",
-                                        "ext4",
-                                        "-b",
-                                        nullptr,
-                                        nullptr,
-                                        nullptr,
-                                        nullptr,
-                                        nullptr,
-                                        nullptr };
-
-          int i = 5;
-          std::string block_size_str = std::to_string(block_size);
-          mke2fs_argv[i++] = block_size_str.c_str();
-
-          std::string ext_args;
-          if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
-            ext_args = android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride,
-                                                   raid_stripe_width);
-            mke2fs_argv[i++] = "-E";
-            mke2fs_argv[i++] = ext_args.c_str();
-          }
-
-          mke2fs_argv[i++] = v->blk_device;
-
-          std::string size_str = std::to_string(length / block_size);
-          if (length != 0) {
-            mke2fs_argv[i++] = size_str.c_str();
-          }
-
-          result = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv));
-          if (result == 0 && directory != nullptr) {
-            const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static",
-                                             "-e",
-                                             "-f",
-                                             directory,
-                                             "-a",
-                                             volume,
-                                             v->blk_device,
-                                             nullptr };
-
-            result = exec_cmd(e2fsdroid_argv[0], const_cast<char**>(e2fsdroid_argv));
-          }
-        } else {   /* Has to be f2fs because we checked earlier. */
-            char *num_sectors = nullptr;
-            if (length >= 512 && asprintf(&num_sectors, "%zd", length / 512) <= 0) {
-                LOG(ERROR) << "format_volume: failed to create " << v->fs_type
-                           << " command for " << v->blk_device;
-                return -1;
-            }
-            const char *f2fs_path = "/sbin/mkfs.f2fs";
-            const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, nullptr};
-
-            result = exec_cmd(f2fs_path, (char* const*)f2fs_argv);
-            free(num_sectors);
-        }
-        if (result != 0) {
-            PLOG(ERROR) << "format_volume: make " << v->fs_type << " failed on " << v->blk_device;
-            return -1;
-        }
-        return 0;
-    }
-
+  const Volume* v = volume_for_path(volume);
+  if (v == nullptr) {
+    LOG(ERROR) << "unknown volume \"" << volume << "\"";
+    return -1;
+  }
+  if (strcmp(v->fs_type, "ramdisk") == 0) {
+    LOG(ERROR) << "can't format_volume \"" << volume << "\"";
+    return -1;
+  }
+  if (strcmp(v->mount_point, volume) != 0) {
+    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 (strcmp(v->fs_type, "ext4") != 0 && strcmp(v->fs_type, "f2fs") != 0) {
     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 != nullptr && v->key_loc[0] == '/') {
+    LOG(INFO) << "Wiping " << v->key_loc;
+    int fd = open(v->key_loc, 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 != nullptr && strcmp(v->key_loc, "footer") == 0)) {
+    android::base::unique_fd fd(open(v->blk_device, 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 (strcmp(v->fs_type, "ext4") == 0) {
+    static constexpr int kBlockSize = 4096;
+    std::vector<std::string> mke2fs_args = {
+      "/sbin/mke2fs_static", "-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 != nullptr) {
+      std::vector<std::string> e2fsdroid_args = {
+        "/sbin/e2fsdroid_static",
+        "-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::string cmd("/sbin/mkfs.f2fs");
+  // clang-format off
+  std::vector<std::string> make_f2fs_cmd = {
+    cmd,
+    "-d1",
+    "-f",
+    "-O", "encrypt",
+    "-O", "quota",
+    "-O", "verity",
+    "-w", std::to_string(kSectorSize),
+    v->blk_device,
+  };
+  // clang-format on
+  if (length >= kSectorSize) {
+    make_f2fs_cmd.push_back(std::to_string(length / kSectorSize));
+  }
+
+  int result = exec_cmd(make_f2fs_cmd);
+  if (result == 0 && directory != nullptr) {
+    cmd = "/sbin/sload.f2fs";
+    // clang-format off
+    std::vector<std::string> sload_f2fs_cmd = {
+      cmd,
+      "-f", directory,
+      "-t", volume,
+      v->blk_device,
+    };
+    // clang-format on
+    result = exec_cmd(sload_f2fs_cmd);
+  }
+  if (result != 0) {
+    PLOG(ERROR) << "format_volume: Failed " << cmd << " on " << v->blk_device;
+    return -1;
+  }
+  return 0;
 }
 
 int format_volume(const char* volume) {
-    return format_volume(volume, NULL);
+  return format_volume(volume, nullptr);
 }
 
 int setup_install_mounts() {
@@ -339,12 +381,12 @@
 
     if (strcmp(v->mount_point, "/tmp") == 0 || strcmp(v->mount_point, "/cache") == 0) {
       if (ensure_path_mounted(v->mount_point) != 0) {
-        LOG(ERROR) << "failed to mount " << v->mount_point;
+        LOG(ERROR) << "Failed to mount " << v->mount_point;
         return -1;
       }
     } else {
       if (ensure_path_unmounted(v->mount_point) != 0) {
-        LOG(ERROR) << "failed to unmount " << v->mount_point;
+        LOG(ERROR) << "Failed to unmount " << v->mount_point;
         return -1;
       }
     }
diff --git a/roots.h b/roots.h
index 3246c5f..380f10f 100644
--- a/roots.h
+++ b/roots.h
@@ -17,6 +17,8 @@
 #ifndef RECOVERY_ROOTS_H_
 #define RECOVERY_ROOTS_H_
 
+#include <string>
+
 typedef struct fstab_rec Volume;
 
 typedef struct fstab_rec Volume;
@@ -24,8 +26,8 @@
 // Load and parse volume data from /etc/recovery.fstab.
 void load_volume_table();
 
-// Return the Volume* record for this path (or NULL).
-Volume* volume_for_path(const char* path);
+// Return the Volume* record for this mount point (or nullptr).
+Volume* volume_for_mount_point(const std::string& mount_point);
 
 // Make sure that the volume 'path' is on is mounted.  Returns 0 on
 // success (volume is mounted).
diff --git a/rotate_logs.cpp b/rotate_logs.cpp
index 51557b1..76b7f4b 100644
--- a/rotate_logs.cpp
+++ b/rotate_logs.cpp
@@ -31,85 +31,77 @@
 static const std::string LAST_KMSG_FILTER = "recovery/last_kmsg";
 static const std::string LAST_LOG_FILTER = "recovery/last_log";
 
-ssize_t logbasename(
-        log_id_t /* logId */,
-        char /* prio */,
-        const char *filename,
-        const char * /* buf */, size_t len,
-        void *arg) {
-    bool* doRotate  = static_cast<bool*>(arg);
-    if (LAST_KMSG_FILTER.find(filename) != std::string::npos ||
-            LAST_LOG_FILTER.find(filename) != std::string::npos) {
-        *doRotate = true;
-    }
-    return len;
+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 (LAST_KMSG_FILTER.find(filename) != std::string::npos ||
+      LAST_LOG_FILTER.find(filename) != std::string::npos) {
+    *do_rotate = true;
+  }
+  return len;
 }
 
-ssize_t logrotate(
-        log_id_t logId,
-        char prio,
-        const char *filename,
-        const char *buf, size_t len,
-        void *arg) {
-    bool* doRotate  = static_cast<bool*>(arg);
-    if (!*doRotate) {
-        return __android_log_pmsg_file_write(logId, prio, filename, buf, 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);
+  std::string name(filename);
+  size_t dot = name.find_last_of('.');
+  std::string sub = name.substr(0, dot);
 
-    if (LAST_KMSG_FILTER.find(sub) == std::string::npos &&
-            LAST_LOG_FILTER.find(sub) == std::string::npos) {
-        return __android_log_pmsg_file_write(logId, prio, filename, buf, len);
-    }
+  if (LAST_KMSG_FILTER.find(sub) == std::string::npos &&
+      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";
+  // filename rotation
+  if (dot == std::string::npos) {
+    name += ".1";
+  } else {
+    std::string number = name.substr(dot + 1);
+    if (!isdigit(number[0])) {
+      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.c_str(), &i)) {
-                LOG(ERROR) << "failed to parse uint in " << number;
-                return -1;
-            }
-            name = sub + "." + std::to_string(i + 1);
-        }
+      size_t i;
+      if (!android::base::ParseUint(number.c_str(), &i)) {
+        LOG(ERROR) << "failed to parse uint in " << number;
+        return -1;
+      }
+      name = sub + "." + std::to_string(i + 1);
     }
+  }
 
-    return __android_log_pmsg_file_write(logId, prio, name.c_str(), buf, len);
+  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;
+  // 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());
+  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());
+  }
 }
diff --git a/rotate_logs.h b/rotate_logs.h
index 809c213..d65b7de 100644
--- a/rotate_logs.h
+++ b/rotate_logs.h
@@ -17,24 +17,19 @@
 #ifndef _ROTATE_LOGS_H
 #define _ROTATE_LOGS_H
 
-#include <string>
+#include <stddef.h>
+#include <sys/types.h>
 
+//#include <log/log_id.h>
 #include <private/android_logger.h> /* private pmsg functions */
 
-constexpr int KEEP_LOG_COUNT = 10;
+static constexpr int KEEP_LOG_COUNT = 10;
 
-ssize_t logbasename(log_id_t /* logId */,
-        char /* prio */,
-        const char *filename,
-        const char * /* buf */, size_t len,
-        void *arg);
+ssize_t logbasename(log_id_t id, char prio, const char* filename, const char* buf, size_t len,
+                    void* arg);
 
-ssize_t logrotate(
-        log_id_t logId,
-        char prio,
-        const char *filename,
-        const char *buf, size_t len,
-        void *arg);
+ssize_t logrotate(log_id_t id, char prio, const char* filename, const char* buf, size_t len,
+                  void* arg);
 
 // Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max.
 // Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max.
diff --git a/screen_ui.cpp b/screen_ui.cpp
index b8f6ea2..c8fb5aa 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "screen_ui.h"
+
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -29,18 +31,19 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include <android-base/logging.h>
 #include <android-base/properties.h>
-#include <android-base/strings.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <minui/minui.h>
 
 #include "common.h"
 #include "device.h"
-#include "minui/minui.h"
-#include "screen_ui.h"
 #include "ui.h"
 
 // Return the current time as a double (including fractions of a second).
@@ -54,7 +57,7 @@
     : kMarginWidth(RECOVERY_UI_MARGIN_WIDTH),
       kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT),
       kAnimationFps(RECOVERY_UI_ANIMATION_FPS),
-      density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
+      kDensity(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
       currentIcon(NONE),
       progressBarType(EMPTY),
       progressScopeStart(0),
@@ -66,7 +69,6 @@
       text_(nullptr),
       text_col_(0),
       text_row_(0),
-      text_top_(0),
       show_text(false),
       show_text_ever(false),
       menu_headers_(nullptr),
@@ -80,6 +82,8 @@
       intro_done(false),
       stage(-1),
       max_stage(-1),
+      locale_(""),
+      rtl_locale_(false),
       updateMutex(PTHREAD_MUTEX_INITIALIZER) {}
 
 GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
@@ -105,7 +109,7 @@
 }
 
 int ScreenRecoveryUI::PixelsFromDp(int dp) const {
-  return dp * density_;
+  return dp * kDensity;
 }
 
 // Here's the intended layout:
@@ -145,8 +149,8 @@
   int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) +
                      gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) +
                      gr_get_height(progressBarFill);
-  int bottom_gap = (gr_fb_height() - elements_sum) / 2;
-  return gr_fb_height() - bottom_gap - gr_get_height(progressBarFill);
+  int bottom_gap = (ScreenHeight() - elements_sum) / 2;
+  return ScreenHeight() - bottom_gap - gr_get_height(progressBarFill);
 }
 
 // Clear the screen and draw the currently selected background icon (if any).
@@ -155,25 +159,24 @@
   pagesIdentical = false;
   gr_color(0, 0, 0, 255);
   gr_clear();
-
   if (currentIcon != NONE) {
     if (max_stage != -1) {
       int stage_height = gr_get_height(stageMarkerEmpty);
       int stage_width = gr_get_width(stageMarkerEmpty);
-      int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
-      int y = gr_fb_height() - stage_height;
+      int x = (ScreenWidth() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
+      int y = ScreenHeight() - stage_height - kMarginHeight;
       for (int i = 0; i < max_stage; ++i) {
         GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty;
-        gr_blit(stage_surface, 0, 0, stage_width, stage_height, x, y);
+        DrawSurface(stage_surface, 0, 0, stage_width, stage_height, x, y);
         x += stage_width;
       }
     }
 
     GRSurface* text_surface = GetCurrentText();
-    int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2;
+    int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2;
     int text_y = GetTextBaseline();
     gr_color(255, 255, 255, 255);
-    gr_texticon(text_x, text_y, text_surface);
+    DrawTextIcon(text_x, text_y, text_surface);
   }
 }
 
@@ -184,21 +187,21 @@
     GRSurface* frame = GetCurrentFrame();
     int frame_width = gr_get_width(frame);
     int frame_height = gr_get_height(frame);
-    int frame_x = (gr_fb_width() - frame_width) / 2;
+    int frame_x = (ScreenWidth() - frame_width) / 2;
     int frame_y = GetAnimationBaseline();
-    gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
+    DrawSurface(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
   }
 
   if (progressBarType != EMPTY) {
     int width = gr_get_width(progressBarEmpty);
     int height = gr_get_height(progressBarEmpty);
 
-    int progress_x = (gr_fb_width() - width) / 2;
+    int progress_x = (ScreenWidth() - width) / 2;
     int progress_y = GetProgressBaseline();
 
     // Erase behind the progress bar (in case this was a progress-only update)
     gr_color(0, 0, 0, 255);
-    gr_fill(progress_x, progress_y, width, height);
+    DrawFill(progress_x, progress_y, width, height);
 
     if (progressBarType == DETERMINATE) {
       float p = progressScopeStart + progress * progressScopeSize;
@@ -207,19 +210,19 @@
       if (rtl_locale_) {
         // Fill the progress bar from right to left.
         if (pos > 0) {
-          gr_blit(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos,
-                  progress_y);
+          DrawSurface(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos,
+                      progress_y);
         }
         if (pos < width - 1) {
-          gr_blit(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y);
+          DrawSurface(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y);
         }
       } else {
         // Fill the progress bar from left to right.
         if (pos > 0) {
-          gr_blit(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
+          DrawSurface(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
         }
         if (pos < width - 1) {
-          gr_blit(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y);
+          DrawSurface(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y);
         }
       }
     }
@@ -256,8 +259,96 @@
   }
 }
 
+void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries,
+                                                   size_t sel) {
+  SetLocale(locales_entries[sel]);
+  std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text",
+                                         "installing_security_text", "no_command_text" };
+  std::unordered_map<std::string, std::unique_ptr<GRSurface, decltype(&free)>> surfaces;
+  for (const auto& name : text_name) {
+    GRSurface* text_image = nullptr;
+    LoadLocalizedBitmap(name.c_str(), &text_image);
+    if (!text_image) {
+      Print("Failed to load %s\n", name.c_str());
+      return;
+    }
+    surfaces.emplace(name, std::unique_ptr<GRSurface, decltype(&free)>(text_image, &free));
+  }
+
+  pthread_mutex_lock(&updateMutex);
+  gr_color(0, 0, 0, 255);
+  gr_clear();
+
+  int text_y = kMarginHeight;
+  int text_x = kMarginWidth;
+  int line_spacing = gr_sys_font()->char_height;  // Put some extra space between images.
+  // Write the header and descriptive texts.
+  SetColor(INFO);
+  std::string header = "Show background text image";
+  text_y += DrawTextLine(text_x, text_y, header.c_str(), true);
+  std::string locale_selection = android::base::StringPrintf(
+      "Current locale: %s, %zu/%zu", locales_entries[sel].c_str(), sel, locales_entries.size());
+  const char* instruction[] = { locale_selection.c_str(),
+                                "Use volume up/down to switch locales and power to exit.",
+                                nullptr };
+  text_y += DrawWrappedTextLines(text_x, text_y, instruction);
+
+  // Iterate through the text images and display them in order for the current locale.
+  for (const auto& p : surfaces) {
+    text_y += line_spacing;
+    SetColor(LOG);
+    text_y += DrawTextLine(text_x, text_y, p.first.c_str(), false);
+    gr_color(255, 255, 255, 255);
+    gr_texticon(text_x, text_y, p.second.get());
+    text_y += gr_get_height(p.second.get());
+  }
+  // Update the whole screen.
+  gr_flip();
+  pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::CheckBackgroundTextImages(const std::string& saved_locale) {
+  // Load a list of locales embedded in one of the resource files.
+  std::vector<std::string> locales_entries = get_locales_in_png("installing_text");
+  if (locales_entries.empty()) {
+    Print("Failed to load locales from the resource files\n");
+    return;
+  }
+  size_t selected = 0;
+  SelectAndShowBackgroundText(locales_entries, selected);
+
+  FlushKeys();
+  while (true) {
+    int key = WaitKey();
+    if (key == KEY_POWER || key == KEY_ENTER) {
+      break;
+    } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
+      selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
+      SelectAndShowBackgroundText(locales_entries, selected);
+    } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) {
+      selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
+      SelectAndShowBackgroundText(locales_entries, selected);
+    }
+  }
+
+  SetLocale(saved_locale);
+}
+
+int ScreenRecoveryUI::ScreenWidth() const {
+  return gr_fb_width();
+}
+
+int ScreenRecoveryUI::ScreenHeight() const {
+  return gr_fb_height();
+}
+
+void ScreenRecoveryUI::DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx,
+                                   int dy) const {
+  gr_blit(surface, sx, sy, w, h, dx, dy);
+}
+
 int ScreenRecoveryUI::DrawHorizontalRule(int y) const {
-  gr_fill(0, y + 4, gr_fb_width(), y + 6);
+  gr_fill(0, y + 4, ScreenWidth(), y + 6);
   return 8;
 }
 
@@ -265,6 +356,14 @@
   gr_fill(x, y, x + width, y + height);
 }
 
+void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const {
+  gr_fill(x, y, w, h);
+}
+
+void ScreenRecoveryUI::DrawTextIcon(int x, int y, GRSurface* surface) const {
+  gr_texticon(x, y, surface);
+}
+
 int ScreenRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const {
   gr_text(gr_sys_font(), x, y, line, bold);
   return char_height_ + 4;
@@ -353,7 +452,7 @@
       if (i == menu_sel) {
         // Draw the highlight bar.
         SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG);
-        DrawHighlightBar(0, y - 2, gr_fb_width(), char_height_ + 4);
+        DrawHighlightBar(0, y - 2, ScreenWidth(), char_height_ + 4);
         // Bold white text for the selected item.
         SetColor(MENU_SEL_FG);
         y += DrawTextLine(x, y, menu_[i].c_str(), true);
@@ -368,9 +467,9 @@
   // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
   // we've displayed the entire text buffer.
   SetColor(LOG);
-  int row = (text_top_ + text_rows_ - 1) % text_rows_;
+  int row = text_row_;
   size_t count = 0;
-  for (int ty = gr_fb_height() - kMarginHeight - char_height_; ty >= y && count < text_rows_;
+  for (int ty = ScreenHeight() - kMarginHeight - char_height_; ty >= y && count < text_rows_;
        ty -= char_height_, ++count) {
     DrawTextLine(kMarginWidth, ty, text_[row], false);
     --row;
@@ -490,13 +589,14 @@
   }
 
   gr_font_size(gr_sys_font(), &char_width_, &char_height_);
-  text_rows_ = (gr_fb_height() - kMarginHeight * 2) / char_height_;
-  text_cols_ = (gr_fb_width() - kMarginWidth * 2) / char_width_;
+  text_rows_ = (ScreenHeight() - kMarginHeight * 2) / char_height_;
+  text_cols_ = (ScreenWidth() - kMarginWidth * 2) / char_width_;
   return true;
 }
 
 bool ScreenRecoveryUI::Init(const std::string& locale) {
   RecoveryUI::Init(locale);
+
   if (!InitTextParams()) {
     return false;
   }
@@ -510,7 +610,9 @@
   file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
 
   text_col_ = text_row_ = 0;
-  text_top_ = 1;
+
+  // Set up the locale info.
+  SetLocale(locale);
 
   LoadBitmap("icon_error", &error_icon);
 
@@ -643,7 +745,6 @@
         text_[text_row_][text_col_] = '\0';
         text_col_ = 0;
         text_row_ = (text_row_ + 1) % text_rows_;
-        if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
       }
       if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
     }
@@ -673,8 +774,6 @@
   if (ch == '\n' || text_col_ >= text_cols_) {
     text_col_ = 0;
     ++text_row_;
-
-    if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
   }
   pthread_mutex_unlock(&updateMutex);
 }
@@ -683,7 +782,6 @@
   pthread_mutex_lock(&updateMutex);
   text_col_ = 0;
   text_row_ = 0;
-  text_top_ = 1;
   for (size_t i = 0; i < text_rows_; ++i) {
     memset(text_[i], 0, text_cols_ + 1);
   }
@@ -750,7 +848,6 @@
   char** old_text = text_;
   size_t old_text_col = text_col_;
   size_t old_text_row = text_row_;
-  size_t old_text_top = text_top_;
 
   // Swap in the alternate screen and clear it.
   text_ = file_viewer_text_;
@@ -762,7 +859,6 @@
   text_ = old_text;
   text_col_ = old_text_col;
   text_row_ = old_text_row;
-  text_top_ = old_text_top;
 }
 
 void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items,
@@ -841,3 +937,23 @@
   // will change color to indicate a successful long press.
   Redraw();
 }
+
+void ScreenRecoveryUI::SetLocale(const std::string& new_locale) {
+  locale_ = new_locale;
+  rtl_locale_ = false;
+
+  if (!new_locale.empty()) {
+    size_t underscore = new_locale.find('_');
+    // lang has the language prefix prior to '_', or full string if '_' doesn't exist.
+    std::string lang = new_locale.substr(0, underscore);
+
+    // A bit cheesy: keep an explicit list of supported RTL languages.
+    if (lang == "ar" ||  // Arabic
+        lang == "fa" ||  // Persian (Farsi)
+        lang == "he" ||  // Hebrew (new language code)
+        lang == "iw" ||  // Hebrew (old language code)
+        lang == "ur") {  // Urdu
+      rtl_locale_ = true;
+    }
+  }
+}
diff --git a/screen_ui.h b/screen_ui.h
index 8231a2b..f05761c 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -32,6 +32,17 @@
 // (shows an icon + a progress bar, text logging, menu, etc.)
 class ScreenRecoveryUI : public RecoveryUI {
  public:
+  enum UIElement {
+    HEADER,
+    MENU,
+    MENU_SEL_BG,
+    MENU_SEL_BG_ACTIVE,
+    MENU_SEL_FG,
+    LOG,
+    TEXT_FILL,
+    INFO
+  };
+
   ScreenRecoveryUI();
 
   bool Init(const std::string& locale) override;
@@ -67,18 +78,12 @@
 
   void Redraw();
 
-  enum UIElement {
-    HEADER,
-    MENU,
-    MENU_SEL_BG,
-    MENU_SEL_BG_ACTIVE,
-    MENU_SEL_FG,
-    LOG,
-    TEXT_FILL,
-    INFO
-  };
   void SetColor(UIElement e) const;
 
+  // Check the background text image. Use volume up/down button to cycle through the locales
+  // embedded in the png file, and power button to go back to recovery main menu.
+  void CheckBackgroundTextImages(const std::string& saved_locale);
+
  protected:
   // The margin that we don't want to use for showing texts (e.g. round screen, or screen with
   // rounded corners).
@@ -89,7 +94,58 @@
   const int kAnimationFps;
 
   // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
-  const float density_;
+  const float kDensity;
+
+  virtual bool InitTextParams();
+
+  virtual void draw_background_locked();
+  virtual void draw_foreground_locked();
+  virtual void draw_screen_locked();
+  virtual void update_screen_locked();
+  virtual void update_progress_locked();
+
+  GRSurface* GetCurrentFrame() const;
+  GRSurface* GetCurrentText() const;
+
+  static void* ProgressThreadStartRoutine(void* data);
+  void ProgressThreadLoop();
+
+  virtual void ShowFile(FILE*);
+  virtual void PrintV(const char*, bool, va_list);
+  void PutChar(char);
+  void ClearText();
+
+  void LoadAnimation();
+  void LoadBitmap(const char* filename, GRSurface** surface);
+  void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
+
+  int PixelsFromDp(int dp) const;
+  virtual int GetAnimationBaseline() const;
+  virtual int GetProgressBaseline() const;
+  virtual int GetTextBaseline() const;
+
+  // Returns pixel width of draw buffer.
+  virtual int ScreenWidth() const;
+  // Returns pixel height of draw buffer.
+  virtual int ScreenHeight() const;
+
+  // Draws a highlight bar at (x, y) - (x + width, y + height).
+  virtual void DrawHighlightBar(int x, int y, int width, int height) const;
+  // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis.
+  virtual int DrawHorizontalRule(int y) const;
+  // Draws a line of text. Returns the offset it should be moving along Y-axis.
+  virtual int DrawTextLine(int x, int y, const char* line, bool bold) const;
+  // Draws surface portion (sx, sy, w, h) at screen location (dx, dy).
+  virtual void DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx, int dy) const;
+  // Draws rectangle at (x, y) - (x + w, y + h).
+  virtual void DrawFill(int x, int y, int w, int h) const;
+  // Draws given surface (surface->pixel_bytes = 1) as text at (x, y).
+  virtual void DrawTextIcon(int x, int y, GRSurface* surface) const;
+  // Draws multiple text lines. Returns the offset it should be moving along Y-axis.
+  int DrawTextLines(int x, int y, const char* const* lines) const;
+  // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines.
+  // Returns the offset it should be moving along Y-axis.
+  int DrawWrappedTextLines(int x, int y, const char* const* lines) const;
 
   Icon currentIcon;
 
@@ -123,7 +179,7 @@
 
   // Log text overlay, displayed when a magic key is pressed.
   char** text_;
-  size_t text_col_, text_row_, text_top_;
+  size_t text_col_, text_row_;
 
   bool show_text;
   bool show_text_ever;  // has show_text ever been true?
@@ -150,47 +206,18 @@
   int char_width_;
   int char_height_;
 
+  // The locale that's used to show the rendered texts.
+  std::string locale_;
+  bool rtl_locale_;
+
   pthread_mutex_t updateMutex;
 
-  virtual bool InitTextParams();
+ private:
+  void SetLocale(const std::string&);
 
-  virtual void draw_background_locked();
-  virtual void draw_foreground_locked();
-  virtual void draw_screen_locked();
-  virtual void update_screen_locked();
-  virtual void update_progress_locked();
-
-  GRSurface* GetCurrentFrame() const;
-  GRSurface* GetCurrentText() const;
-
-  static void* ProgressThreadStartRoutine(void* data);
-  void ProgressThreadLoop();
-
-  virtual void ShowFile(FILE*);
-  virtual void PrintV(const char*, bool, va_list);
-  void PutChar(char);
-  void ClearText();
-
-  void LoadAnimation();
-  void LoadBitmap(const char* filename, GRSurface** surface);
-  void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
-
-  int PixelsFromDp(int dp) const;
-  virtual int GetAnimationBaseline() const;
-  virtual int GetProgressBaseline() const;
-  virtual int GetTextBaseline() const;
-
-  // Draws a highlight bar at (x, y) - (x + width, y + height).
-  virtual void DrawHighlightBar(int x, int y, int width, int height) const;
-  // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis.
-  virtual int DrawHorizontalRule(int y) const;
-  // Draws a line of text. Returns the offset it should be moving along Y-axis.
-  virtual int DrawTextLine(int x, int y, const char* line, bool bold) const;
-  // Draws multiple text lines. Returns the offset it should be moving along Y-axis.
-  int DrawTextLines(int x, int y, const char* const* lines) const;
-  // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines.
-  // Returns the offset it should be moving along Y-axis.
-  int DrawWrappedTextLines(int x, int y, const char* const* lines) const;
+  // Display the background texts for "erasing", "error", "no_command" and "installing" for the
+  // selected locale.
+  void SelectAndShowBackgroundText(const std::vector<std::string>& locales_entries, size_t sel);
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/stub_ui.h b/stub_ui.h
index 85dbcfd..1f6b29a 100644
--- a/stub_ui.h
+++ b/stub_ui.h
@@ -24,18 +24,18 @@
  public:
   StubRecoveryUI() = default;
 
-  void SetBackground(Icon icon) override {}
-  void SetSystemUpdateText(bool security_update) override {}
+  void SetBackground(Icon /* icon */) override {}
+  void SetSystemUpdateText(bool /* security_update */) override {}
 
   // progress indicator
-  void SetProgressType(ProgressType type) override {}
-  void ShowProgress(float portion, float seconds) override {}
-  void SetProgress(float fraction) override {}
+  void SetProgressType(ProgressType /* type */) override {}
+  void ShowProgress(float /* portion */, float /* seconds */) override {}
+  void SetProgress(float /* fraction */) override {}
 
-  void SetStage(int current, int max) override {}
+  void SetStage(int /* current */, int /* max */) override {}
 
   // text log
-  void ShowText(bool visible) override {}
+  void ShowText(bool /* visible */) override {}
   bool IsTextVisible() override {
     return false;
   }
@@ -50,12 +50,12 @@
     vprintf(fmt, ap);
     va_end(ap);
   }
-  void PrintOnScreenOnly(const char* fmt, ...) override {}
-  void ShowFile(const char* filename) override {}
+  void PrintOnScreenOnly(const char* /* fmt */, ...) override {}
+  void ShowFile(const char* /* filename */) override {}
 
   // menu display
-  void StartMenu(const char* const* headers, const char* const* items,
-                 int initial_selection) override {}
+  void StartMenu(const char* const* /* headers */, const char* const* /* items */,
+                 int /* initial_selection */) override {}
   int SelectMenu(int sel) override {
     return sel;
   }
diff --git a/tests/Android.mk b/tests/Android.mk
index fe4de31..f19ca36 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -18,7 +18,7 @@
 
 # Unit tests
 include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_MODULE := recovery_unit_test
 LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_STATIC_LIBRARIES := \
@@ -30,7 +30,8 @@
     libutils \
     libz \
     libselinux \
-    libbase
+    libbase \
+    libBionicGtestMain
 
 LOCAL_SRC_FILES := \
     unit/asn1_decoder_test.cpp \
@@ -47,11 +48,12 @@
 
 # Manual tests
 include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_MODULE := recovery_manual_test
 LOCAL_STATIC_LIBRARIES := \
     libminui \
-    libbase
+    libbase \
+    libBionicGtestMain
 
 LOCAL_SRC_FILES := manual/recovery_test.cpp
 LOCAL_SHARED_LIBRARIES := \
@@ -82,6 +84,7 @@
 # Component tests
 include $(CLEAR_VARS)
 LOCAL_CFLAGS := \
+    -Wall \
     -Werror \
     -D_FILE_OFFSET_BITS=64
 
@@ -143,9 +146,9 @@
     libdivsufsort \
     libdivsufsort64 \
     libfs_mgr \
-    liblog \
     libvintf_recovery \
     libvintf \
+    libhidl-gen-utils \
     libtinyxml2 \
     libselinux \
     libext4_utils \
@@ -154,6 +157,7 @@
     libcrypto \
     libbz \
     libziparchive \
+    liblog \
     libutils \
     libz \
     libbase \
@@ -163,6 +167,7 @@
     libsquashfs_utils \
     libcutils \
     libbrotli \
+    libBionicGtestMain \
     $(tune2fs_static_libraries)
 
 testdata_files := $(call find-subdir-files, testdata/*)
@@ -192,7 +197,7 @@
 
 # Host tests
 include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_MODULE := recovery_host_test
 LOCAL_MODULE_HOST_OS := linux
 LOCAL_C_INCLUDES := bootable/recovery
@@ -201,16 +206,19 @@
 LOCAL_STATIC_LIBRARIES := \
     libimgdiff \
     libimgpatch \
+    libotautil \
     libbsdiff \
     libbspatch \
     libziparchive \
     libutils \
     libbase \
     libcrypto \
+    libbrotli \
     libbz \
     libdivsufsort64 \
     libdivsufsort \
-    libz
+    libz \
+    libBionicGtestMain
 LOCAL_SHARED_LIBRARIES := \
     liblog
 include $(BUILD_HOST_NATIVE_TEST)
diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h
index f6b6922..514818e 100644
--- a/tests/common/test_constants.h
+++ b/tests/common/test_constants.h
@@ -19,6 +19,8 @@
 
 #include <stdlib.h>
 
+#include <string>
+
 // Zip entries in ziptest_valid.zip.
 static const std::string kATxtContents("abcdefghabcdefgh\n");
 static const std::string kBTxtContents("abcdefgh\n");
@@ -30,10 +32,14 @@
 // echo -n -e "abcdefgh\n" | sha1sum
 static const std::string kBTxtSha1Sum("e414af7161c9554089f4106d6f1797ef14a73666");
 
-static const char* data_root = getenv("ANDROID_DATA");
-
 static std::string from_testdata_base(const std::string& fname) {
-  return std::string(data_root) + "/nativetest/recovery/testdata/" + fname;
+#ifdef __ANDROID__
+  static std::string data_root = getenv("ANDROID_DATA");
+#else
+  static std::string data_root = std::string(getenv("ANDROID_PRODUCT_OUT")) + "/data";
+#endif
+
+  return data_root + "/nativetest/recovery/testdata/" + fname;
 }
 
 #endif  // _OTA_TEST_CONSTANTS_H
diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
index 016fed9..61e06ad 100644
--- a/tests/component/applypatch_test.cpp
+++ b/tests/component/applypatch_test.cpp
@@ -30,12 +30,16 @@
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <android-base/test_utils.h>
+#include <bsdiff/bsdiff.h>
 #include <openssl/sha.h>
 
 #include "applypatch/applypatch.h"
 #include "applypatch/applypatch_modes.h"
 #include "common/test_constants.h"
-#include "print_sha1.h"
+#include "otautil/cache_location.h"
+#include "otautil/print_sha1.h"
+
+using namespace std::string_literals;
 
 static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) {
   ASSERT_NE(nullptr, sha1);
@@ -53,34 +57,20 @@
 }
 
 static void mangle_file(const std::string& fname) {
-  std::string content;
-  content.reserve(1024);
+  std::string content(1024, '\0');
   for (size_t i = 0; i < 1024; i++) {
     content[i] = rand() % 256;
   }
   ASSERT_TRUE(android::base::WriteStringToFile(content, fname));
 }
 
-static bool file_cmp(const std::string& f1, const std::string& f2) {
-  std::string c1;
-  android::base::ReadFileToString(f1, &c1);
-  std::string c2;
-  android::base::ReadFileToString(f2, &c2);
-  return c1 == c2;
-}
-
 class ApplyPatchTest : public ::testing::Test {
  public:
-  static void SetUpTestCase() {
+  virtual void SetUp() override {
     // set up files
     old_file = from_testdata_base("old.file");
     new_file = from_testdata_base("new.file");
-    patch_file = from_testdata_base("patch.bsdiff");
-    rand_file = "/cache/applypatch_test_rand.file";
-    cache_file = "/cache/saved.file";
-
-    // write stuff to rand_file
-    ASSERT_TRUE(android::base::WriteStringToFile("hello", rand_file));
+    nonexistent_file = from_testdata_base("nonexistent.file");
 
     // set up SHA constants
     sha1sum(old_file, &old_sha1, &old_size);
@@ -90,56 +80,35 @@
     bad_sha1_b = android::base::StringPrintf("%040x", rand());
   }
 
-  static std::string old_file;
-  static std::string new_file;
-  static std::string rand_file;
-  static std::string cache_file;
-  static std::string patch_file;
+  std::string old_file;
+  std::string new_file;
+  std::string nonexistent_file;
 
-  static std::string old_sha1;
-  static std::string new_sha1;
-  static std::string bad_sha1_a;
-  static std::string bad_sha1_b;
+  std::string old_sha1;
+  std::string new_sha1;
+  std::string bad_sha1_a;
+  std::string bad_sha1_b;
 
-  static size_t old_size;
-  static size_t new_size;
+  size_t old_size;
+  size_t new_size;
 };
 
-static void cp(const std::string& src, const std::string& tgt) {
-  std::string cmd = "cp " + src + " " + tgt;
-  system(cmd.c_str());
-}
-
-static void backup_old() {
-  cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file);
-}
-
-static void restore_old() {
-  cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file);
-}
-
 class ApplyPatchCacheTest : public ApplyPatchTest {
- public:
-  virtual void SetUp() {
-    backup_old();
-  }
-
-  virtual void TearDown() {
-    restore_old();
+ protected:
+  void SetUp() override {
+    ApplyPatchTest::SetUp();
+    CacheLocation::location().set_cache_temp_source(old_file);
   }
 };
 
-std::string ApplyPatchTest::old_file;
-std::string ApplyPatchTest::new_file;
-std::string ApplyPatchTest::rand_file;
-std::string ApplyPatchTest::patch_file;
-std::string ApplyPatchTest::cache_file;
-std::string ApplyPatchTest::old_sha1;
-std::string ApplyPatchTest::new_sha1;
-std::string ApplyPatchTest::bad_sha1_a;
-std::string ApplyPatchTest::bad_sha1_b;
-size_t ApplyPatchTest::old_size;
-size_t ApplyPatchTest::new_size;
+class ApplyPatchModesTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    CacheLocation::location().set_cache_temp_source(cache_source.path);
+  }
+
+  TemporaryFile cache_source;
+};
 
 TEST_F(ApplyPatchTest, CheckModeSkip) {
   std::vector<std::string> sha1s;
@@ -197,43 +166,31 @@
   ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
 }
 
-TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) {
-  mangle_file(old_file);
-  std::vector<std::string> sha1s = { old_sha1 };
-  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
+TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceSingle) {
+  TemporaryFile temp_file;
+  mangle_file(temp_file.path);
+  std::vector<std::string> sha1s_single = { old_sha1 };
+  ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_single));
+  ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_single));
 }
 
-TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedMultiple) {
-  mangle_file(old_file);
-  std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
-  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
+TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceMultiple) {
+  TemporaryFile temp_file;
+  mangle_file(temp_file.path);
+  std::vector<std::string> sha1s_multiple = { bad_sha1_a, old_sha1, bad_sha1_b };
+  ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_multiple));
+  ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_multiple));
 }
 
-TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedFailure) {
-  mangle_file(old_file);
-  std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
-  ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
+TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceFailure) {
+  TemporaryFile temp_file;
+  mangle_file(temp_file.path);
+  std::vector<std::string> sha1s_failure = { bad_sha1_a, bad_sha1_b };
+  ASSERT_NE(0, applypatch_check(temp_file.path, sha1s_failure));
+  ASSERT_NE(0, applypatch_check(nonexistent_file.c_str(), sha1s_failure));
 }
 
-TEST_F(ApplyPatchCacheTest, CheckCacheMissingSingle) {
-  unlink(&old_file[0]);
-  std::vector<std::string> sha1s = { old_sha1 };
-  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
-}
-
-TEST_F(ApplyPatchCacheTest, CheckCacheMissingMultiple) {
-  unlink(&old_file[0]);
-  std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
-  ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
-}
-
-TEST_F(ApplyPatchCacheTest, CheckCacheMissingFailure) {
-  unlink(&old_file[0]);
-  std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
-  ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
-}
-
-TEST(ApplyPatchModesTest, InvalidArgs) {
+TEST_F(ApplyPatchModesTest, InvalidArgs) {
   // At least two args (including the filename).
   ASSERT_EQ(2, applypatch_modes(1, (const char* []){ "applypatch" }));
 
@@ -241,7 +198,7 @@
   ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-x" }));
 }
 
-TEST(ApplyPatchModesTest, PatchModeEmmcTarget) {
+TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) {
   std::string boot_img = from_testdata_base("boot.img");
   size_t boot_img_size;
   std::string boot_img_sha1;
@@ -311,7 +268,55 @@
   ASSERT_EQ(0, applypatch_modes(args3.size(), args3.data()));
 }
 
-TEST(ApplyPatchModesTest, PatchModeInvalidArgs) {
+// Ensures that applypatch works with a bsdiff based recovery-from-boot.p.
+TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) {
+  std::string boot_img_file = from_testdata_base("boot.img");
+  std::string boot_img_sha1;
+  size_t boot_img_size;
+  sha1sum(boot_img_file, &boot_img_sha1, &boot_img_size);
+
+  std::string recovery_img_file = from_testdata_base("recovery.img");
+  std::string recovery_img_sha1;
+  size_t recovery_img_size;
+  sha1sum(recovery_img_file, &recovery_img_sha1, &recovery_img_size);
+
+  // Generate the bsdiff patch of recovery-from-boot.p.
+  std::string src_content;
+  ASSERT_TRUE(android::base::ReadFileToString(boot_img_file, &src_content));
+
+  std::string tgt_content;
+  ASSERT_TRUE(android::base::ReadFileToString(recovery_img_file, &tgt_content));
+
+  TemporaryFile patch_file;
+  ASSERT_EQ(0,
+            bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(),
+                           reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(),
+                           patch_file.path, nullptr));
+
+  // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
+  std::string src_file_arg =
+      "EMMC:" + boot_img_file + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1;
+  TemporaryFile tgt_file;
+  std::string tgt_file_arg = "EMMC:"s + tgt_file.path;
+  std::string recovery_img_size_arg = std::to_string(recovery_img_size);
+  std::string patch_arg = boot_img_sha1 + ":" + patch_file.path;
+  std::vector<const char*> args = { "applypatch",
+                                    src_file_arg.c_str(),
+                                    tgt_file_arg.c_str(),
+                                    recovery_img_sha1.c_str(),
+                                    recovery_img_size_arg.c_str(),
+                                    patch_arg.c_str() };
+  ASSERT_EQ(0, applypatch_modes(args.size(), args.data()));
+
+  // Double check the patched recovery image.
+  std::string tgt_file_sha1;
+  size_t tgt_file_size;
+  sha1sum(tgt_file.path, &tgt_file_sha1, &tgt_file_size);
+  ASSERT_EQ(recovery_img_size, tgt_file_size);
+  ASSERT_EQ(recovery_img_sha1, tgt_file_sha1);
+}
+
+TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) {
   // Invalid bonus file.
   ASSERT_NE(0, applypatch_modes(3, (const char* []){ "applypatch", "-b", "/doesntexist" }));
 
@@ -372,11 +377,11 @@
   ASSERT_NE(0, applypatch_modes(args6.size(), args6.data()));
 }
 
-TEST(ApplyPatchModesTest, CheckModeInvalidArgs) {
+TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) {
   // Insufficient args.
   ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
 }
 
-TEST(ApplyPatchModesTest, ShowLicenses) {
+TEST_F(ApplyPatchModesTest, ShowLicenses) {
   ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
 }
diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp
index b38bc71..6cc59a4 100644
--- a/tests/component/bootloader_message_test.cpp
+++ b/tests/component/bootloader_message_test.cpp
@@ -18,53 +18,12 @@
 #include <vector>
 
 #include <android-base/strings.h>
+#include <android-base/test_utils.h>
 #include <bootloader_message/bootloader_message.h>
 #include <gtest/gtest.h>
 
-class BootloaderMessageTest : public ::testing::Test {
- protected:
-  BootloaderMessageTest() : has_misc(true) {}
-
-  virtual void SetUp() override {
-    std::string err;
-    has_misc = !get_bootloader_message_blk_device(&err).empty();
-  }
-
-  virtual void TearDown() override {
-    // Clear the BCB.
-    if (has_misc) {
-      std::string err;
-      ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
-    }
-  }
-
-  bool has_misc;
-};
-
-TEST_F(BootloaderMessageTest, clear_bootloader_message) {
-  if (!has_misc) {
-    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
-    return;
-  }
-
-  // Clear the BCB.
-  std::string err;
-  ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
-
-  // Verify the content.
-  bootloader_message boot;
-  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
-
-  // All the bytes should be cleared.
-  ASSERT_EQ(std::string(sizeof(boot), '\0'),
-            std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
-}
-
-TEST_F(BootloaderMessageTest, read_and_write_bootloader_message) {
-  if (!has_misc) {
-    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
-    return;
-  }
+TEST(BootloaderMessageTest, read_and_write_bootloader_message) {
+  TemporaryFile temp_misc;
 
   // Write the BCB.
   bootloader_message boot = {};
@@ -73,90 +32,71 @@
   strlcpy(boot.status, "status1", sizeof(boot.status));
 
   std::string err;
-  ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
+  ASSERT_TRUE(write_bootloader_message_to(boot, temp_misc.path, &err))
+      << "Failed to write BCB: " << err;
 
   // Read and verify.
   bootloader_message boot_verify;
-  ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
+  ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_misc.path, &err))
+      << "Failed to read BCB: " << err;
 
   ASSERT_EQ(std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)),
             std::string(reinterpret_cast<const char*>(&boot_verify), sizeof(boot_verify)));
 }
 
-TEST_F(BootloaderMessageTest, write_bootloader_message_options) {
-  if (!has_misc) {
-    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
-    return;
-  }
-
+TEST(BootloaderMessageTest, update_bootloader_message_in_struct) {
   // Write the options to BCB.
   std::vector<std::string> options = { "option1", "option2" };
-  std::string err;
-  ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
 
-  // Inject some bytes into boot, which should be overwritten while reading.
-  bootloader_message boot;
+  bootloader_message boot = {};
+  // Inject some bytes into boot.
   strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
+  strlcpy(boot.status, "status bytes", sizeof(boot.status));
+  strlcpy(boot.stage, "stage bytes", sizeof(boot.stage));
   strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
 
-  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+  ASSERT_TRUE(update_bootloader_message_in_struct(&boot, options));
 
   // Verify that command and recovery fields should be set.
   ASSERT_EQ("boot-recovery", std::string(boot.command));
   std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
   ASSERT_EQ(expected, std::string(boot.recovery));
 
-  // The rest should be cleared.
-  ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
-  ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
-  ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
-            std::string(boot.reserved, sizeof(boot.reserved)));
+  // The rest should be intact.
+  ASSERT_EQ("status bytes", std::string(boot.status));
+  ASSERT_EQ("stage bytes", std::string(boot.stage));
+  ASSERT_EQ("reserved bytes", std::string(boot.reserved));
 }
 
-TEST_F(BootloaderMessageTest, write_bootloader_message_options_empty) {
-  if (!has_misc) {
-    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
-    return;
-  }
-
+TEST(BootloaderMessageTest, update_bootloader_message_recovery_options_empty) {
   // Write empty vector.
   std::vector<std::string> options;
-  std::string err;
-  ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
 
   // Read and verify.
-  bootloader_message boot;
-  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+  bootloader_message boot = {};
+  ASSERT_TRUE(update_bootloader_message_in_struct(&boot, options));
 
   // command and recovery fields should be set.
   ASSERT_EQ("boot-recovery", std::string(boot.command));
   ASSERT_EQ("recovery\n", std::string(boot.recovery));
 
-  // The rest should be cleared.
+  // The rest should be empty.
   ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
   ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
   ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
             std::string(boot.reserved, sizeof(boot.reserved)));
 }
 
-TEST_F(BootloaderMessageTest, write_bootloader_message_options_long) {
-  if (!has_misc) {
-    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
-    return;
-  }
-
+TEST(BootloaderMessageTest, update_bootloader_message_recovery_options_long) {
   // Write super long message.
   std::vector<std::string> options;
   for (int i = 0; i < 100; i++) {
     options.push_back("option: " + std::to_string(i));
   }
 
-  std::string err;
-  ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
-
   // Read and verify.
-  bootloader_message boot;
-  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+  bootloader_message boot = {};
+  ASSERT_TRUE(update_bootloader_message_in_struct(&boot, options));
 
   // Make sure it's long enough.
   std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
@@ -167,40 +107,10 @@
   ASSERT_EQ(expected.substr(0, sizeof(boot.recovery) - 1), std::string(boot.recovery));
   ASSERT_EQ('\0', boot.recovery[sizeof(boot.recovery) - 1]);
 
-  // The rest should be cleared.
+  // The rest should be empty.
   ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
   ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
   ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
             std::string(boot.reserved, sizeof(boot.reserved)));
 }
 
-TEST_F(BootloaderMessageTest, update_bootloader_message) {
-  if (!has_misc) {
-    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
-    return;
-  }
-
-  // Inject some bytes into boot, which should be not overwritten later.
-  bootloader_message boot;
-  strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
-  strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
-  std::string err;
-  ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
-
-  // Update the BCB message.
-  std::vector<std::string> options = { "option1", "option2" };
-  ASSERT_TRUE(update_bootloader_message(options, &err)) << "Failed to update BCB: " << err;
-
-  bootloader_message boot_verify;
-  ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
-
-  // Verify that command and recovery fields should be set.
-  ASSERT_EQ("boot-recovery", std::string(boot_verify.command));
-  std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
-  ASSERT_EQ(expected, std::string(boot_verify.recovery));
-
-  // The rest should be intact.
-  ASSERT_EQ(std::string(boot.status), std::string(boot_verify.status));
-  ASSERT_EQ(std::string(boot.stage), std::string(boot_verify.stage));
-  ASSERT_EQ(std::string(boot.reserved), std::string(boot_verify.reserved));
-}
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
index bf25aeb..6c23def 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/component/imgdiff_test.cpp
@@ -16,17 +16,24 @@
 
 #include <stdio.h>
 
+#include <algorithm>
 #include <string>
+#include <tuple>
 #include <vector>
 
 #include <android-base/file.h>
 #include <android-base/memory.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <android-base/test_utils.h>
 #include <applypatch/imgdiff.h>
+#include <applypatch/imgdiff_image.h>
 #include <applypatch/imgpatch.h>
 #include <gtest/gtest.h>
 #include <ziparchive/zip_writer.h>
 
+#include "common/test_constants.h"
+
 using android::base::get_unaligned;
 
 // Sanity check for the given imgdiff patch header.
@@ -75,15 +82,20 @@
   if (num_deflate != nullptr) *num_deflate = deflate;
 }
 
+static void GenerateTarget(const std::string& src, const std::string& patch, std::string* patched) {
+  patched->clear();
+  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+                               [&](const unsigned char* data, size_t len) {
+                                 patched->append(reinterpret_cast<const char*>(data), len);
+                                 return len;
+                               }));
+}
+
 static void verify_patched_image(const std::string& src, const std::string& patch,
                                  const std::string& tgt) {
   std::string patched;
-  ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
-                               reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
-                               [&patched](const unsigned char* data, size_t len) {
-                                 patched.append(reinterpret_cast<const char*>(data), len);
-                                 return len;
-                               }));
+  GenerateTarget(src, patch, &patched);
   ASSERT_EQ(tgt, patched);
 }
 
@@ -138,7 +150,7 @@
 TEST(ImgdiffTest, zip_mode_smoke_store) {
   // Construct src and tgt zip files.
   TemporaryFile src_file;
-  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
   ZipWriter src_writer(src_file_ptr);
   ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0));  // Store mode.
   const std::string src_content("abcdefg");
@@ -148,7 +160,7 @@
   ASSERT_EQ(0, fclose(src_file_ptr));
 
   TemporaryFile tgt_file;
-  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
   ZipWriter tgt_writer(tgt_file_ptr);
   ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0));  // Store mode.
   const std::string tgt_content("abcdefgxyz");
@@ -187,7 +199,7 @@
 TEST(ImgdiffTest, zip_mode_smoke_compressed) {
   // Construct src and tgt zip files.
   TemporaryFile src_file;
-  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
   ZipWriter src_writer(src_file_ptr);
   ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
   const std::string src_content("abcdefg");
@@ -197,7 +209,7 @@
   ASSERT_EQ(0, fclose(src_file_ptr));
 
   TemporaryFile tgt_file;
-  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
   ZipWriter tgt_writer(tgt_file_ptr);
   ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
   const std::string tgt_content("abcdefgxyz");
@@ -236,7 +248,7 @@
 TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
   // Construct src and tgt zip files.
   TemporaryFile src_file;
-  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
   ZipWriter src_writer(src_file_ptr);
   ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
   const std::string src_content("abcdefg");
@@ -246,7 +258,7 @@
   ASSERT_EQ(0, fclose(src_file_ptr));
 
   TemporaryFile tgt_file;
-  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
   ZipWriter tgt_writer(tgt_file_ptr);
   ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
   const std::string tgt_content("abcdefgxyz");
@@ -623,3 +635,438 @@
                                 reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
                                 [](const unsigned char* /*data*/, size_t len) { return len; }));
 }
+
+static void construct_store_entry(const std::vector<std::tuple<std::string, size_t, char>>& info,
+                                  ZipWriter* writer) {
+  for (auto& t : info) {
+    // Create t(1) blocks of t(2), and write the data to t(0)
+    ASSERT_EQ(0, writer->StartEntry(std::get<0>(t).c_str(), 0));
+    const std::string content(std::get<1>(t) * 4096, std::get<2>(t));
+    ASSERT_EQ(0, writer->WriteBytes(content.data(), content.size()));
+    ASSERT_EQ(0, writer->FinishEntry());
+  }
+}
+
+static void construct_deflate_entry(const std::vector<std::tuple<std::string, size_t, size_t>>& info,
+                                    ZipWriter* writer, const std::string& data) {
+  for (auto& t : info) {
+    // t(0): entry_name; t(1): block offset; t(2) length in blocks.
+    ASSERT_EQ(0, writer->StartEntry(std::get<0>(t).c_str(), ZipWriter::kCompress));
+    ASSERT_EQ(0, writer->WriteBytes(data.data() + std::get<1>(t) * 4096, std::get<2>(t) * 4096));
+    ASSERT_EQ(0, writer->FinishEntry());
+  }
+}
+
+// Look for the source and patch pieces in debug_dir. Generate a target piece from each pair.
+// Concatenate all the target pieces and match against the orignal one. Used pieces in debug_dir
+// will be cleaned up.
+static void GenerateAndCheckSplitTarget(const std::string& debug_dir, size_t count,
+                                        const std::string& tgt) {
+  std::string patched;
+  for (size_t i = 0; i < count; i++) {
+    std::string split_src_path = android::base::StringPrintf("%s/src-%zu", debug_dir.c_str(), i);
+    std::string split_src;
+    ASSERT_TRUE(android::base::ReadFileToString(split_src_path, &split_src));
+    ASSERT_EQ(0, unlink(split_src_path.c_str()));
+
+    std::string split_patch_path =
+        android::base::StringPrintf("%s/patch-%zu", debug_dir.c_str(), i);
+    std::string split_patch;
+    ASSERT_TRUE(android::base::ReadFileToString(split_patch_path, &split_patch));
+    ASSERT_EQ(0, unlink(split_patch_path.c_str()));
+
+    std::string split_tgt;
+    GenerateTarget(split_src, split_patch, &split_tgt);
+    patched += split_tgt;
+  }
+
+  // Verify we can get back the original target image.
+  ASSERT_EQ(tgt, patched);
+}
+
+std::vector<ImageChunk> ConstructImageChunks(
+    const std::vector<uint8_t>& content, const std::vector<std::tuple<std::string, size_t>>& info) {
+  std::vector<ImageChunk> chunks;
+  size_t start = 0;
+  for (const auto& t : info) {
+    size_t length = std::get<1>(t);
+    chunks.emplace_back(CHUNK_NORMAL, start, &content, length, std::get<0>(t));
+    start += length;
+  }
+
+  return chunks;
+}
+
+TEST(ImgdiffTest, zip_mode_split_image_smoke) {
+  std::vector<uint8_t> content;
+  content.reserve(4096 * 50);
+  uint8_t n = 0;
+  generate_n(back_inserter(content), 4096 * 50, [&n]() { return n++ / 4096; });
+
+  ZipModeImage tgt_image(false, 4096 * 10);
+  std::vector<ImageChunk> tgt_chunks = ConstructImageChunks(content, { { "a", 100 },
+                                                                       { "b", 4096 * 2 },
+                                                                       { "c", 4096 * 3 },
+                                                                       { "d", 300 },
+                                                                       { "e-0", 4096 * 10 },
+                                                                       { "e-1", 4096 * 5 },
+                                                                       { "CD", 200 } });
+  tgt_image.Initialize(std::move(tgt_chunks),
+                       std::vector<uint8_t>(content.begin(), content.begin() + 82520));
+
+  tgt_image.DumpChunks();
+
+  ZipModeImage src_image(true, 4096 * 10);
+  std::vector<ImageChunk> src_chunks = ConstructImageChunks(content, { { "b", 4096 * 3 },
+                                                                       { "c-0", 4096 * 10 },
+                                                                       { "c-1", 4096 * 2 },
+                                                                       { "a", 4096 * 5 },
+                                                                       { "e-0", 4096 * 10 },
+                                                                       { "e-1", 10000 },
+                                                                       { "CD", 5000 } });
+  src_image.Initialize(std::move(src_chunks),
+                       std::vector<uint8_t>(content.begin(), content.begin() + 137880));
+
+  std::vector<ZipModeImage> split_tgt_images;
+  std::vector<ZipModeImage> split_src_images;
+  std::vector<SortedRangeSet> split_src_ranges;
+
+  ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images,
+                                           &split_src_images, &split_src_ranges);
+
+  // src_piece 1: a 5 blocks, b 3 blocks
+  // src_piece 2: c-0 10 blocks
+  // src_piece 3: d 0 block, e-0 10 blocks
+  // src_piece 4: e-1 2 blocks; CD 2 blocks
+  ASSERT_EQ(split_tgt_images.size(), split_src_images.size());
+  ASSERT_EQ(static_cast<size_t>(4), split_tgt_images.size());
+
+  ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[0].NumOfChunks());
+  ASSERT_EQ(static_cast<size_t>(12288), split_tgt_images[0][0].DataLengthForPatch());
+  ASSERT_EQ("4,0,3,15,20", split_src_ranges[0].ToString());
+
+  ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[1].NumOfChunks());
+  ASSERT_EQ(static_cast<size_t>(12288), split_tgt_images[1][0].DataLengthForPatch());
+  ASSERT_EQ("2,3,13", split_src_ranges[1].ToString());
+
+  ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[2].NumOfChunks());
+  ASSERT_EQ(static_cast<size_t>(40960), split_tgt_images[2][0].DataLengthForPatch());
+  ASSERT_EQ("2,20,30", split_src_ranges[2].ToString());
+
+  ASSERT_EQ(static_cast<size_t>(1), split_tgt_images[3].NumOfChunks());
+  ASSERT_EQ(static_cast<size_t>(16984), split_tgt_images[3][0].DataLengthForPatch());
+  ASSERT_EQ("2,30,34", split_src_ranges[3].ToString());
+}
+
+TEST(ImgdiffTest, zip_mode_store_large_apk) {
+  // Construct src and tgt zip files with limit = 10 blocks.
+  //     src              tgt
+  //  12 blocks 'd'    3 blocks  'a'
+  //  8 blocks  'c'    3 blocks  'b'
+  //  3 blocks  'b'    8 blocks  'c' (exceeds limit)
+  //  3 blocks  'a'    12 blocks 'd' (exceeds limit)
+  //                   3 blocks  'e'
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+  construct_store_entry(
+      { { "a", 3, 'a' }, { "b", 3, 'b' }, { "c", 8, 'c' }, { "d", 12, 'd' }, { "e", 3, 'e' } },
+      &tgt_writer);
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
+  ZipWriter src_writer(src_file_ptr);
+  construct_store_entry({ { "d", 12, 'd' }, { "c", 8, 'c' }, { "b", 3, 'b' }, { "a", 3, 'a' } },
+                        &src_writer);
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  TemporaryFile split_info_file;
+  TemporaryDir debug_dir;
+  std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
+  std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
+  std::vector<const char*> args = {
+    "imgdiff", "-z", "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(),
+    src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+
+  // Expect 4 pieces of patch. (Roughly 3'a',3'b'; 8'c'; 10'd'; 2'd'3'e')
+  GenerateAndCheckSplitTarget(debug_dir.path, 4, tgt);
+}
+
+TEST(ImgdiffTest, zip_mode_deflate_large_apk) {
+  // Src and tgt zip files are constructed as follows.
+  //     src               tgt
+  //  22 blocks, "d"    4  blocks,  "a"
+  //  5 blocks,  "b"    4  blocks,  "b"
+  //  3 blocks,  "a"    8  blocks,  "c" (exceeds limit)
+  //  1 block,   "g"    20 blocks,  "d" (exceeds limit)
+  //  8 blocks,  "c"    2  blocks,  "e"
+  //  1 block,   "f"    1  block ,  "f"
+  std::string tgt_path = from_testdata_base("deflate_tgt.zip");
+  std::string src_path = from_testdata_base("deflate_src.zip");
+
+  ZipModeImage src_image(true, 10 * 4096);
+  ZipModeImage tgt_image(false, 10 * 4096);
+  ASSERT_TRUE(src_image.Initialize(src_path));
+  ASSERT_TRUE(tgt_image.Initialize(tgt_path));
+  ASSERT_TRUE(ZipModeImage::CheckAndProcessChunks(&tgt_image, &src_image));
+
+  src_image.DumpChunks();
+  tgt_image.DumpChunks();
+
+  std::vector<ZipModeImage> split_tgt_images;
+  std::vector<ZipModeImage> split_src_images;
+  std::vector<SortedRangeSet> split_src_ranges;
+  ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images,
+                                           &split_src_images, &split_src_ranges);
+
+  // Expected split images with limit = 10 blocks.
+  // src_piece 0: a 3 blocks, b 5 blocks
+  // src_piece 1: c 8 blocks
+  // src_piece 2: d-0 10 block
+  // src_piece 3: d-1 10 blocks
+  // src_piece 4: e 1 block, CD
+  ASSERT_EQ(split_tgt_images.size(), split_src_images.size());
+  ASSERT_EQ(static_cast<size_t>(5), split_tgt_images.size());
+
+  ASSERT_EQ(static_cast<size_t>(2), split_src_images[0].NumOfChunks());
+  ASSERT_EQ("a", split_src_images[0][0].GetEntryName());
+  ASSERT_EQ("b", split_src_images[0][1].GetEntryName());
+
+  ASSERT_EQ(static_cast<size_t>(1), split_src_images[1].NumOfChunks());
+  ASSERT_EQ("c", split_src_images[1][0].GetEntryName());
+
+  ASSERT_EQ(static_cast<size_t>(0), split_src_images[2].NumOfChunks());
+  ASSERT_EQ(static_cast<size_t>(0), split_src_images[3].NumOfChunks());
+  ASSERT_EQ(static_cast<size_t>(0), split_src_images[4].NumOfChunks());
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  TemporaryFile split_info_file;
+  TemporaryDir debug_dir;
+  ASSERT_TRUE(ZipModeImage::GeneratePatches(split_tgt_images, split_src_images, split_src_ranges,
+                                            patch_file.path, split_info_file.path, debug_dir.path));
+
+  // Verify the content of split info.
+  // Expect 5 pieces of patch. ["a","b"; "c"; "d-0"; "d-1"; "e"]
+  std::string split_info_string;
+  android::base::ReadFileToString(split_info_file.path, &split_info_string);
+  std::vector<std::string> info_list =
+      android::base::Split(android::base::Trim(split_info_string), "\n");
+
+  ASSERT_EQ(static_cast<size_t>(7), info_list.size());
+  ASSERT_EQ("2", android::base::Trim(info_list[0]));
+  ASSERT_EQ("5", android::base::Trim(info_list[1]));
+
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_path, &tgt));
+  ASSERT_EQ(static_cast<size_t>(160385), tgt.size());
+  std::vector<std::string> tgt_file_ranges = {
+    "36864 2,22,31", "32768 2,31,40", "40960 2,0,11", "40960 2,11,21", "8833 4,21,22,40,41",
+  };
+
+  for (size_t i = 0; i < 5; i++) {
+    struct stat st;
+    std::string path = android::base::StringPrintf("%s/patch-%zu", debug_dir.path, i);
+    ASSERT_EQ(0, stat(path.c_str(), &st));
+    ASSERT_EQ(std::to_string(st.st_size) + " " + tgt_file_ranges[i],
+              android::base::Trim(info_list[i + 2]));
+  }
+
+  GenerateAndCheckSplitTarget(debug_dir.path, 5, tgt);
+}
+
+TEST(ImgdiffTest, zip_mode_no_match_source) {
+  // Generate 20 blocks of random data.
+  std::string random_data;
+  random_data.reserve(4096 * 20);
+  generate_n(back_inserter(random_data), 4096 * 20, []() { return rand() % 256; });
+
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+
+  construct_deflate_entry({ { "a", 0, 4 }, { "b", 5, 5 }, { "c", 11, 5 } }, &tgt_writer,
+                          random_data);
+
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  // We don't have a matching source entry.
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
+  ZipWriter src_writer(src_file_ptr);
+  construct_store_entry({ { "d", 1, 'd' } }, &src_writer);
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  TemporaryFile split_info_file;
+  TemporaryDir debug_dir;
+  std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
+  std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
+  std::vector<const char*> args = {
+    "imgdiff", "-z", "--block-limit=10", debug_dir_arg.c_str(), split_info_arg.c_str(),
+    src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+
+  // Expect 1 pieces of patch due to no matching source entry.
+  GenerateAndCheckSplitTarget(debug_dir.path, 1, tgt);
+}
+
+TEST(ImgdiffTest, zip_mode_large_enough_limit) {
+  // Generate 20 blocks of random data.
+  std::string random_data;
+  random_data.reserve(4096 * 20);
+  generate_n(back_inserter(random_data), 4096 * 20, []() { return rand() % 256; });
+
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+
+  construct_deflate_entry({ { "a", 0, 10 }, { "b", 10, 5 } }, &tgt_writer, random_data);
+
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  // Construct 10 blocks of source.
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
+  ZipWriter src_writer(src_file_ptr);
+  construct_deflate_entry({ { "a", 1, 10 } }, &src_writer, random_data);
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  // Compute patch with a limit of 20 blocks.
+  TemporaryFile patch_file;
+  TemporaryFile split_info_file;
+  TemporaryDir debug_dir;
+  std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
+  std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
+  std::vector<const char*> args = {
+    "imgdiff", "-z", "--block-limit=20", split_info_arg.c_str(), debug_dir_arg.c_str(),
+    src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+
+  // Expect 1 piece of patch since limit is larger than the zip file size.
+  GenerateAndCheckSplitTarget(debug_dir.path, 1, tgt);
+}
+
+TEST(ImgdiffTest, zip_mode_large_apk_small_target_chunk) {
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+
+  // The first entry is less than 4096 bytes, followed immediately by an entry that has a very
+  // large counterpart in the source file. Therefore the first entry will be patched separately.
+  std::string small_chunk("a", 2000);
+  ASSERT_EQ(0, tgt_writer.StartEntry("a", 0));
+  ASSERT_EQ(0, tgt_writer.WriteBytes(small_chunk.data(), small_chunk.size()));
+  ASSERT_EQ(0, tgt_writer.FinishEntry());
+  construct_store_entry(
+      {
+          { "b", 12, 'b' }, { "c", 3, 'c' },
+      },
+      &tgt_writer);
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
+  ZipWriter src_writer(src_file_ptr);
+  construct_store_entry({ { "a", 1, 'a' }, { "b", 13, 'b' }, { "c", 1, 'c' } }, &src_writer);
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  TemporaryFile split_info_file;
+  TemporaryDir debug_dir;
+  std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
+  std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
+  std::vector<const char*> args = {
+    "imgdiff",     "-z",          "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(),
+    src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+
+  // Expect three split src images:
+  // src_piece 0: a 1 blocks
+  // src_piece 1: b-0 10 blocks
+  // src_piece 2: b-1 3 blocks, c 1 blocks, CD
+  GenerateAndCheckSplitTarget(debug_dir.path, 3, tgt);
+}
+
+TEST(ImgdiffTest, zip_mode_large_apk_skipped_small_target_chunk) {
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+
+  construct_store_entry(
+      {
+          { "a", 11, 'a' },
+      },
+      &tgt_writer);
+
+  // Construct a tiny target entry of 1 byte, which will be skipped due to the tail alignment of
+  // the previous entry.
+  std::string small_chunk("b", 1);
+  ASSERT_EQ(0, tgt_writer.StartEntry("b", 0));
+  ASSERT_EQ(0, tgt_writer.WriteBytes(small_chunk.data(), small_chunk.size()));
+  ASSERT_EQ(0, tgt_writer.FinishEntry());
+
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.release(), "wb");
+  ZipWriter src_writer(src_file_ptr);
+  construct_store_entry(
+      {
+          { "a", 11, 'a' }, { "b", 11, 'b' },
+      },
+      &src_writer);
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  TemporaryFile split_info_file;
+  TemporaryDir debug_dir;
+  std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path);
+  std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path);
+  std::vector<const char*> args = {
+    "imgdiff",     "-z",          "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(),
+    src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+
+  // Expect two split src images:
+  // src_piece 0: a-0 10 blocks
+  // src_piece 1: a-0 1 block, CD
+  GenerateAndCheckSplitTarget(debug_dir.path, 2, tgt);
+}
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
index 968196f..d19d788 100644
--- a/tests/component/install_test.cpp
+++ b/tests/component/install_test.cpp
@@ -19,6 +19,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <algorithm>
 #include <string>
 #include <vector>
 
@@ -36,7 +37,7 @@
 
 TEST(InstallTest, verify_package_compatibility_no_entry) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   // The archive must have something to be opened correctly.
   ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
@@ -53,7 +54,7 @@
 
 TEST(InstallTest, verify_package_compatibility_invalid_entry) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0));
   ASSERT_EQ(0, writer.FinishEntry());
@@ -69,7 +70,7 @@
 
 TEST(InstallTest, read_metadata_from_package_smoke) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
   const std::string content("abcdefg");
@@ -86,7 +87,7 @@
   CloseArchive(zip);
 
   TemporaryFile temp_file2;
-  FILE* zip_file2 = fdopen(temp_file2.fd, "w");
+  FILE* zip_file2 = fdopen(temp_file2.release(), "w");
   ZipWriter writer2(zip_file2);
   ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated));
   ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size()));
@@ -103,7 +104,7 @@
 
 TEST(InstallTest, read_metadata_from_package_no_entry) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored));
   ASSERT_EQ(0, writer.FinishEntry());
@@ -119,7 +120,7 @@
 
 TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
   TemporaryFile compatibility_zip_file;
-  FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+  FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
   ZipWriter compatibility_zip_writer(compatibility_zip);
   ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
   std::string malformed_xml = "malformed";
@@ -129,7 +130,7 @@
   ASSERT_EQ(0, fclose(compatibility_zip));
 
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
   std::string compatibility_zip_content;
@@ -164,7 +165,7 @@
   ASSERT_TRUE(
       android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
   TemporaryFile compatibility_zip_file;
-  FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+  FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
   ZipWriter compatibility_zip_writer(compatibility_zip);
   ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
   ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(),
@@ -174,7 +175,7 @@
   ASSERT_EQ(0, fclose(compatibility_zip));
 
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
   std::string compatibility_zip_content;
@@ -198,10 +199,10 @@
   CloseArchive(zip);
 }
 
-TEST(InstallTest, update_binary_command_smoke) {
 #ifdef AB_OTA_UPDATER
+static void VerifyAbUpdateBinaryCommand(const std::string& serialno, bool success = true) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
   ASSERT_EQ(0, writer.FinishEntry());
@@ -215,11 +216,13 @@
   ASSERT_NE("", device);
   std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
   ASSERT_NE("", timestamp);
-  std::string metadata = android::base::Join(
-      std::vector<std::string>{
-          "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
-      },
-      "\n");
+
+  std::vector<std::string> meta{ "ota-type=AB", "pre-device=" + device,
+                                 "post-timestamp=" + timestamp };
+  if (!serialno.empty()) {
+    meta.push_back("serialno=" + serialno);
+  }
+  std::string metadata = android::base::Join(meta, "\n");
   ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
   ASSERT_EQ(0, writer.FinishEntry());
   ASSERT_EQ(0, writer.Finish());
@@ -234,17 +237,28 @@
   std::string package = "/path/to/update.zip";
   std::string binary_path = "/sbin/update_engine_sideload";
   std::vector<std::string> cmd;
-  ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
-  ASSERT_EQ(5U, cmd.size());
-  ASSERT_EQ(binary_path, cmd[0]);
-  ASSERT_EQ("--payload=file://" + package, cmd[1]);
-  ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
-  ASSERT_EQ("--headers=" + properties, cmd[3]);
-  ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+  if (success) {
+    ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+    ASSERT_EQ(5U, cmd.size());
+    ASSERT_EQ(binary_path, cmd[0]);
+    ASSERT_EQ("--payload=file://" + package, cmd[1]);
+    ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
+    ASSERT_EQ("--headers=" + properties, cmd[3]);
+    ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+  } else {
+    ASSERT_EQ(INSTALL_ERROR, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+  }
   CloseArchive(zip);
+}
+#endif  // AB_OTA_UPDATER
+
+TEST(InstallTest, update_binary_command_smoke) {
+#ifdef AB_OTA_UPDATER
+  // Empty serialno will pass the verification.
+  VerifyAbUpdateBinaryCommand({});
 #else
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
   ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
@@ -289,7 +303,7 @@
 TEST(InstallTest, update_binary_command_invalid) {
 #ifdef AB_OTA_UPDATER
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   // Missing payload_properties.txt.
   ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
@@ -320,7 +334,7 @@
   CloseArchive(zip);
 #else
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.fd, "w");
+  FILE* zip_file = fdopen(temp_file.release(), "w");
   ZipWriter writer(zip_file);
   // The archive must have something to be opened correctly.
   ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
@@ -340,3 +354,34 @@
   CloseArchive(zip);
 #endif  // AB_OTA_UPDATER
 }
+
+#ifdef AB_OTA_UPDATER
+TEST(InstallTest, update_binary_command_multiple_serialno) {
+  std::string serialno = android::base::GetProperty("ro.serialno", "");
+  ASSERT_NE("", serialno);
+
+  // Single matching serialno will pass the verification.
+  VerifyAbUpdateBinaryCommand(serialno);
+
+  static constexpr char alphabet[] =
+      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+  auto generator = []() { return alphabet[rand() % (sizeof(alphabet) - 1)]; };
+
+  // Generate 900 random serial numbers.
+  std::string random_serial;
+  for (size_t i = 0; i < 900; i++) {
+    generate_n(back_inserter(random_serial), serialno.size(), generator);
+    random_serial.append("|");
+  }
+  // Random serialnos should fail the verification.
+  VerifyAbUpdateBinaryCommand(random_serial, false);
+
+  std::string long_serial = random_serial + serialno + "|";
+  for (size_t i = 0; i < 99; i++) {
+    generate_n(back_inserter(long_serial), serialno.size(), generator);
+    long_serial.append("|");
+  }
+  // String with the matching serialno should pass the verification.
+  VerifyAbUpdateBinaryCommand(long_serial);
+}
+#endif  // AB_OTA_UPDATER
diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp
index 40cfc69..b7109fc 100644
--- a/tests/component/sideload_test.cpp
+++ b/tests/component/sideload_test.cpp
@@ -16,6 +16,12 @@
 
 #include <unistd.h>
 
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
 #include <gtest/gtest.h>
 
 #include "fuse_sideload.h"
@@ -26,11 +32,67 @@
 
 TEST(SideloadTest, run_fuse_sideload_wrong_parameters) {
   provider_vtab vtab;
-  vtab.close = [](void*) {};
+  vtab.close = [](void) {};
 
-  ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, 4096, 4095));
-  ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, 4096, (1 << 22) + 1));
+  ASSERT_EQ(-1, run_fuse_sideload(vtab, 4096, 4095));
+  ASSERT_EQ(-1, run_fuse_sideload(vtab, 4096, (1 << 22) + 1));
 
   // Too many blocks.
-  ASSERT_EQ(-1, run_fuse_sideload(&vtab, nullptr, ((1 << 18) + 1) * 4096, 4096));
+  ASSERT_EQ(-1, run_fuse_sideload(vtab, ((1 << 18) + 1) * 4096, 4096));
+}
+
+TEST(SideloadTest, run_fuse_sideload) {
+  const std::vector<std::string> blocks = {
+    std::string(2048, 'a') + std::string(2048, 'b'),
+    std::string(2048, 'c') + std::string(2048, 'd'),
+    std::string(2048, 'e') + std::string(2048, 'f'),
+    std::string(2048, 'g') + std::string(2048, 'h'),
+  };
+  const std::string content = android::base::Join(blocks, "");
+  ASSERT_EQ(16384U, content.size());
+
+  provider_vtab vtab;
+  vtab.close = [](void) {};
+  vtab.read_block = [&blocks](uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
+    if (block >= 4) return -1;
+    blocks[block].copy(reinterpret_cast<char*>(buffer), fetch_size);
+    return 0;
+  };
+
+  TemporaryDir mount_point;
+  pid_t pid = fork();
+  if (pid == 0) {
+    ASSERT_EQ(0, run_fuse_sideload(vtab, 16384, 4096, mount_point.path));
+    _exit(EXIT_SUCCESS);
+  }
+
+  std::string package = std::string(mount_point.path) + "/" + FUSE_SIDELOAD_HOST_FILENAME;
+  int status;
+  static constexpr int kSideloadInstallTimeout = 10;
+  for (int i = 0; i < kSideloadInstallTimeout; ++i) {
+    ASSERT_NE(-1, waitpid(pid, &status, WNOHANG));
+
+    struct stat sb;
+    if (stat(package.c_str(), &sb) == 0) {
+      break;
+    }
+
+    if (errno == ENOENT && i < kSideloadInstallTimeout - 1) {
+      sleep(1);
+      continue;
+    }
+    FAIL() << "Timed out waiting for the fuse-provided package.";
+  }
+
+  std::string content_via_fuse;
+  ASSERT_TRUE(android::base::ReadFileToString(package, &content_via_fuse));
+  ASSERT_EQ(content, content_via_fuse);
+
+  std::string exit_flag = std::string(mount_point.path) + "/" + FUSE_SIDELOAD_HOST_EXIT_FLAG;
+  struct stat sb;
+  ASSERT_EQ(0, stat(exit_flag.c_str(), &sb));
+
+  waitpid(pid, &status, 0);
+  ASSERT_EQ(0, WEXITSTATUS(status));
+  ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
 }
diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp
index 3925236..55baca2 100644
--- a/tests/component/uncrypt_test.cpp
+++ b/tests/component/uncrypt_test.cpp
@@ -20,6 +20,7 @@
 #include <sys/un.h>
 #include <unistd.h>
 
+#include <algorithm>
 #include <string>
 
 #include <android-base/file.h>
@@ -38,43 +39,49 @@
 static const std::string INIT_SVC_UNCRYPT = "init.svc.uncrypt";
 static constexpr int SOCKET_CONNECTION_MAX_RETRY = 30;
 
+static void StopService() {
+  ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+  ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+  ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt"));
+
+  bool success = false;
+  for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+    std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, "");
+    std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, "");
+    std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, "");
+    GTEST_LOG_(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb
+                     << "] uncrypt: [" << uncrypt << "]";
+    if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") {
+      success = true;
+      break;
+    }
+    sleep(1);
+  }
+
+  ASSERT_TRUE(success) << "uncrypt service is not available.";
+}
+
 class UncryptTest : public ::testing::Test {
  protected:
   UncryptTest() : has_misc(true) {}
 
-  virtual void SetUp() override {
-    ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
-    ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
-    ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt"));
-
-    bool success = false;
-    for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
-      std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, "");
-      std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, "");
-      std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, "");
-      LOG(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb << "] uncrypt: ["
-                << uncrypt << "]";
-      if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") {
-        success = true;
-        break;
-      }
-      sleep(1);
-    }
-
-    ASSERT_TRUE(success) << "uncrypt service is not available.";
-
+  void SetUp() override {
     std::string err;
     has_misc = !get_bootloader_message_blk_device(&err).empty();
   }
 
+  void TearDown() override {
+    // Clear the BCB.
+    if (has_misc) {
+      std::string err;
+      ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+    }
+  }
+
   void SetupOrClearBcb(bool isSetup, const std::string& message,
                        const std::string& message_in_bcb) const {
-    if (!has_misc) {
-      GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
-      return;
-    }
-
-    // Trigger the setup-bcb service.
+    // Restart the setup-bcb service.
+    StopService();
     ASSERT_TRUE(android::base::SetProperty("ctl.start", isSetup ? "setup-bcb" : "clear-bcb"));
 
     // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
@@ -144,27 +151,49 @@
     }
   }
 
+  void VerifyBootloaderMessage(const std::string& expected) {
+    std::string err;
+    bootloader_message boot;
+    ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+    // Check that we have all the expected bytes.
+    ASSERT_EQ(expected, std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+  }
+
   bool has_misc;
 };
 
 TEST_F(UncryptTest, setup_bcb) {
+  if (!has_misc) {
+    GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+    return;
+  }
+
+  std::string random_data;
+  random_data.reserve(sizeof(bootloader_message));
+  generate_n(back_inserter(random_data), sizeof(bootloader_message), []() { return rand() % 128; });
+
+  bootloader_message boot;
+  memcpy(&boot, random_data.c_str(), random_data.size());
+
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
+  VerifyBootloaderMessage(random_data);
+
+  ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+  VerifyBootloaderMessage(std::string(sizeof(bootloader_message), '\0'));
+
   std::string message = "--update_message=abc value";
   std::string message_in_bcb = "recovery\n--update_message=abc value\n";
   SetupOrClearBcb(true, message, message_in_bcb);
-}
 
-TEST_F(UncryptTest, clear_bcb) {
   SetupOrClearBcb(false, "", "");
-}
 
-TEST_F(UncryptTest, setup_bcb_wipe_ab) {
   TemporaryFile wipe_package;
   ASSERT_TRUE(android::base::WriteStringToFile(std::string(345, 'a'), wipe_package.path));
 
   // It's expected to store a wipe package in /misc, with the package size passed to recovery.
-  std::string message =
-      "--wipe_ab\n--wipe_package="s + wipe_package.path + "\n--reason=wipePackage"s;
-  std::string message_in_bcb =
-      "recovery\n--wipe_ab\n--wipe_package_size=345\n--reason=wipePackage\n";
+  message = "--wipe_ab\n--wipe_package="s + wipe_package.path + "\n--reason=wipePackage"s;
+  message_in_bcb = "recovery\n--wipe_ab\n--wipe_package_size=345\n--reason=wipePackage\n";
   SetupOrClearBcb(true, message, message_in_bcb);
 }
diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp
index b04e118..1544bb2 100644
--- a/tests/component/update_verifier_test.cpp
+++ b/tests/component/update_verifier_test.cpp
@@ -46,7 +46,6 @@
     return;
   }
 
-  // The care map file can have only two or four lines.
   TemporaryFile temp_file;
   std::string content = "system\n2,0,1";
   ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
@@ -58,7 +57,7 @@
 }
 
 TEST_F(UpdateVerifierTest, verify_image_wrong_lines) {
-  // The care map file can have only two or four lines.
+  // The care map file can have only 2 / 4 / 6 lines.
   TemporaryFile temp_file;
   ASSERT_FALSE(verify_image(temp_file.path));
 
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index 374a99c..50e0a63 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -23,6 +23,7 @@
 #include <algorithm>
 #include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include <android-base/file.h>
@@ -32,16 +33,17 @@
 #include <android-base/test_utils.h>
 #include <bootloader_message/bootloader_message.h>
 #include <brotli/encode.h>
-#include <bsdiff.h>
+#include <bsdiff/bsdiff.h>
 #include <gtest/gtest.h>
 #include <ziparchive/zip_archive.h>
 #include <ziparchive/zip_writer.h>
 
 #include "common/test_constants.h"
 #include "edify/expr.h"
-#include "error_code.h"
 #include "otautil/SysUtil.h"
-#include "print_sha1.h"
+#include "otautil/cache_location.h"
+#include "otautil/error_code.h"
+#include "otautil/print_sha1.h"
 #include "updater/blockimg.h"
 #include "updater/install.h"
 #include "updater/updater.h"
@@ -74,6 +76,23 @@
   ASSERT_EQ(cause_code, state.cause_code);
 }
 
+static void BuildUpdatePackage(const std::unordered_map<std::string, std::string>& entries,
+                               int fd) {
+  FILE* zip_file_ptr = fdopen(fd, "wb");
+  ZipWriter zip_writer(zip_file_ptr);
+
+  for (const auto& entry : entries) {
+    ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0));
+    if (!entry.second.empty()) {
+      ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size()));
+    }
+    ASSERT_EQ(0, zip_writer.FinishEntry());
+  }
+
+  ASSERT_EQ(0, zip_writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file_ptr));
+}
+
 static std::string get_sha1(const std::string& content) {
   uint8_t digest[SHA_DIGEST_LENGTH];
   SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest);
@@ -86,7 +105,16 @@
     RegisterBuiltins();
     RegisterInstallFunctions();
     RegisterBlockImageFunctions();
+
+    // Mock the location of last_command_file.
+    CacheLocation::location().set_cache_temp_source(temp_saved_source_.path);
+    CacheLocation::location().set_last_command_file(temp_last_command_.path);
+    CacheLocation::location().set_stash_directory_base(temp_stash_base_.path);
   }
+
+  TemporaryFile temp_saved_source_;
+  TemporaryFile temp_last_command_;
+  TemporaryDir temp_stash_base_;
 };
 
 TEST_F(UpdaterTest, getprop) {
@@ -592,7 +620,7 @@
 
   TemporaryFile tf;
   UpdaterInfo updater_info;
-  updater_info.cmd_pipe = fdopen(tf.fd, "w");
+  updater_info.cmd_pipe = fdopen(tf.release(), "w");
   expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
   fflush(updater_info.cmd_pipe);
 
@@ -601,6 +629,7 @@
   ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd);
   // recovery-updater protocol expects 2 tokens ("set_progress <frac>").
   ASSERT_EQ(2U, android::base::Split(cmd, " ").size());
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
 }
 
 TEST_F(UpdaterTest, show_progress) {
@@ -616,7 +645,7 @@
 
   TemporaryFile tf;
   UpdaterInfo updater_info;
-  updater_info.cmd_pipe = fdopen(tf.fd, "w");
+  updater_info.cmd_pipe = fdopen(tf.release(), "w");
   expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
   fflush(updater_info.cmd_pipe);
 
@@ -625,32 +654,22 @@
   ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd);
   // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
   ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
 }
 
-TEST_F(UpdaterTest, block_image_update) {
-  // Create a zip file with new_data and patch_data.
-  TemporaryFile zip_file;
-  FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
-  ZipWriter zip_writer(zip_file_ptr);
-
-  // Add a dummy new data.
-  ASSERT_EQ(0, zip_writer.StartEntry("new_data", 0));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-
-  // Generate and add the patch data.
+TEST_F(UpdaterTest, block_image_update_patch_data) {
   std::string src_content = std::string(4096, 'a') + std::string(4096, 'c');
   std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd');
+
+  // Generate the patch data.
   TemporaryFile patch_file;
   ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()),
       src_content.size(), reinterpret_cast<const uint8_t*>(tgt_content.data()),
       tgt_content.size(), patch_file.path, nullptr));
   std::string patch_content;
   ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
-  ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
-  ASSERT_EQ(0, zip_writer.WriteBytes(patch_content.data(), patch_content.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
 
-  // Add two transfer lists. The first one contains a bsdiff; and we expect the update to succeed.
+  // Create the transfer list that contains a bsdiff.
   std::string src_hash = get_sha1(src_content);
   std::string tgt_hash = get_sha1(tgt_content);
   std::vector<std::string> transfer_list = {
@@ -663,27 +682,16 @@
                                 src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
     "free " + src_hash,
   };
-  ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
-  std::string commands = android::base::Join(transfer_list, '\n');
-  ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
 
-  // Stash and free some blocks, then fail the 2nd update intentionally.
-  std::vector<std::string> fail_transfer_list = {
-    "4",
-    "2",
-    "0",
-    "2",
-    "stash " + tgt_hash + " 2,0,2",
-    "free " + tgt_hash,
-    "fail",
+  std::unordered_map<std::string, std::string> entries = {
+    { "new_data", "" },
+    { "patch_data", patch_content },
+    { "transfer_list", android::base::Join(transfer_list, '\n') },
   };
-  ASSERT_EQ(0, zip_writer.StartEntry("fail_transfer_list", 0));
-  std::string fail_commands = android::base::Join(fail_transfer_list, '\n');
-  ASSERT_EQ(0, zip_writer.WriteBytes(fail_commands.data(), fail_commands.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  ASSERT_EQ(0, zip_writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file_ptr));
+
+  // Build the update package.
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
 
   MemMapping map;
   ASSERT_TRUE(map.MapFile(zip_file.path));
@@ -694,11 +702,11 @@
   UpdaterInfo updater_info;
   updater_info.package_zip = handle;
   TemporaryFile temp_pipe;
-  updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
+  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
   updater_info.package_zip_addr = map.addr;
   updater_info.package_zip_len = map.length;
 
-  // Execute the commands in the 1st transfer list.
+  // Execute the commands in the transfer list.
   TemporaryFile update_file;
   ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
   std::string script = "block_image_update(\"" + std::string(update_file.path) +
@@ -709,57 +717,28 @@
   ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
   ASSERT_EQ(tgt_hash, get_sha1(updated_content));
 
-  // Expect the 2nd update to fail, but expect the stashed blocks to be freed.
-  script = "block_image_update(\"" + std::string(update_file.path) +
-      R"(", package_extract_file("fail_transfer_list"), "new_data", "patch_data"))";
-  expect("", script.c_str(), kNoCause, &updater_info);
-  // Updater generates the stash name based on the input file name.
-  std::string name_digest = get_sha1(update_file.path);
-  std::string stash_base = "/cache/recovery/" + name_digest;
-  ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
-  ASSERT_EQ(-1, access((stash_base + tgt_hash).c_str(), F_OK));
-  ASSERT_EQ(0, rmdir(stash_base.c_str()));
-
   ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
   CloseArchive(handle);
 }
 
-TEST_F(UpdaterTest, new_data_short_write) {
-  // Create a zip file with new_data.
-  TemporaryFile zip_file;
-  FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
-  ZipWriter zip_writer(zip_file_ptr);
-
-  // Add the empty new data.
-  ASSERT_EQ(0, zip_writer.StartEntry("empty_new_data", 0));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  // Add the short written new data.
-  ASSERT_EQ(0, zip_writer.StartEntry("short_new_data", 0));
-  std::string new_data_short = std::string(10, 'a');
-  ASSERT_EQ(0, zip_writer.WriteBytes(new_data_short.data(), new_data_short.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  // Add the data of exactly one block.
-  ASSERT_EQ(0, zip_writer.StartEntry("exact_new_data", 0));
-  std::string new_data_exact = std::string(4096, 'a');
-  ASSERT_EQ(0, zip_writer.WriteBytes(new_data_exact.data(), new_data_exact.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  // Add a dummy patch data.
-  ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-
+TEST_F(UpdaterTest, block_image_update_fail) {
+  std::string src_content(4096 * 2, 'e');
+  std::string src_hash = get_sha1(src_content);
+  // Stash and free some blocks, then fail the update intentionally.
   std::vector<std::string> transfer_list = {
-    "4",
-    "1",
-    "0",
-    "0",
-    "new 2,0,1",
+    "4", "2", "0", "2", "stash " + src_hash + " 2,0,2", "free " + src_hash, "fail",
   };
-  ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
-  std::string commands = android::base::Join(transfer_list, '\n');
-  ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  ASSERT_EQ(0, zip_writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file_ptr));
+
+  // Add a new data of 10 bytes to test the deadlock.
+  std::unordered_map<std::string, std::string> entries = {
+    { "new_data", std::string(10, 0) },
+    { "patch_data", "" },
+    { "transfer_list", android::base::Join(transfer_list, '\n') },
+  };
+
+  // Build the update package.
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
 
   MemMapping map;
   ASSERT_TRUE(map.MapFile(zip_file.path));
@@ -770,7 +749,95 @@
   UpdaterInfo updater_info;
   updater_info.package_zip = handle;
   TemporaryFile temp_pipe;
-  updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
+  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
+
+  TemporaryFile update_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+  // Expect the stashed blocks to be freed.
+  std::string script = "block_image_update(\"" + std::string(update_file.path) +
+                       R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
+  expect("", script.c_str(), kNoCause, &updater_info);
+  // Updater generates the stash name based on the input file name.
+  std::string name_digest = get_sha1(update_file.path);
+  std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest;
+  ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
+  ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK));
+  ASSERT_EQ(0, rmdir(stash_base.c_str()));
+
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+  CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, new_data_over_write) {
+  std::vector<std::string> transfer_list = {
+    "4", "1", "0", "0", "new 2,0,1",
+  };
+
+  // Write 4096 + 100 bytes of new data.
+  std::unordered_map<std::string, std::string> entries = {
+    { "new_data", std::string(4196, 0) },
+    { "patch_data", "" },
+    { "transfer_list", android::base::Join(transfer_list, '\n') },
+  };
+
+  // Build the update package.
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
+
+  MemMapping map;
+  ASSERT_TRUE(map.MapFile(zip_file.path));
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+  // Set up the handler, command_pipe, patch offset & length.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+  TemporaryFile temp_pipe;
+  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
+
+  TemporaryFile update_file;
+  std::string script = "block_image_update(\"" + std::string(update_file.path) +
+                       R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+  CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, new_data_short_write) {
+  std::vector<std::string> transfer_list = {
+    "4",
+    "1",
+    "0",
+    "0",
+    "new 2,0,1",
+  };
+
+  std::unordered_map<std::string, std::string> entries = {
+    { "empty_new_data", "" },
+    { "short_new_data", std::string(10, 'a') },
+    { "exact_new_data", std::string(4096, 'a') },
+    { "patch_data", "" },
+    { "transfer_list", android::base::Join(transfer_list, '\n') },
+  };
+
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
+
+  MemMapping map;
+  ASSERT_TRUE(map.MapFile(zip_file.path));
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+  // Set up the handler, command_pipe, patch offset & length.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+  TemporaryFile temp_pipe;
+  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
   updater_info.package_zip_addr = map.addr;
   updater_info.package_zip_len = map.length;
 
@@ -788,18 +855,12 @@
   std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) +
       R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))";
   expect("t", script_exact_data.c_str(), kNoCause, &updater_info);
+
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
   CloseArchive(handle);
 }
 
 TEST_F(UpdaterTest, brotli_new_data) {
-  // Create a zip file with new_data.
-  TemporaryFile zip_file;
-  FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
-  ZipWriter zip_writer(zip_file_ptr);
-
-  // Add a brotli compressed new data entry.
-  ASSERT_EQ(0, zip_writer.StartEntry("new.dat.br", 0));
-
   auto generator = []() { return rand() % 128; };
   // Generate 100 blocks of random data.
   std::string brotli_new_data;
@@ -807,16 +868,12 @@
   generate_n(back_inserter(brotli_new_data), 4096 * 100, generator);
 
   size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size());
-  std::vector<uint8_t> encoded_data(encoded_size);
+  std::string encoded_data(encoded_size, 0);
   ASSERT_TRUE(BrotliEncoderCompress(
       BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(),
-      reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size, encoded_data.data()));
-
-  ASSERT_EQ(0, zip_writer.WriteBytes(encoded_data.data(), encoded_size));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  // Add a dummy patch data.
-  ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
+      reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size,
+      reinterpret_cast<uint8_t*>(const_cast<char*>(encoded_data.data()))));
+  encoded_data.resize(encoded_size);
 
   // Write a few small chunks of new data, then a large chunk, and finally a few small chunks.
   // This helps us to catch potential short writes.
@@ -832,12 +889,15 @@
     "new 2,98,99",
     "new 2,99,100",
   };
-  ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
-  std::string commands = android::base::Join(transfer_list, '\n');
-  ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
-  ASSERT_EQ(0, zip_writer.FinishEntry());
-  ASSERT_EQ(0, zip_writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file_ptr));
+
+  std::unordered_map<std::string, std::string> entries = {
+    { "new.dat.br", std::move(encoded_data) },
+    { "patch_data", "" },
+    { "transfer_list", android::base::Join(transfer_list, '\n') },
+  };
+
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
 
   MemMapping map;
   ASSERT_TRUE(map.MapFile(zip_file.path));
@@ -848,7 +908,7 @@
   UpdaterInfo updater_info;
   updater_info.package_zip = handle;
   TemporaryFile temp_pipe;
-  updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wb");
   updater_info.package_zip_addr = map.addr;
   updater_info.package_zip_len = map.length;
 
@@ -862,5 +922,221 @@
   std::string updated_content;
   ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
   ASSERT_EQ(brotli_new_data, updated_content);
+
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+  CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, last_command_update) {
+  std::string last_command_file = CacheLocation::location().last_command_file();
+
+  std::string block1 = std::string(4096, '1');
+  std::string block2 = std::string(4096, '2');
+  std::string block3 = std::string(4096, '3');
+  std::string block1_hash = get_sha1(block1);
+  std::string block2_hash = get_sha1(block2);
+  std::string block3_hash = get_sha1(block3);
+
+  // Compose the transfer list to fail the first update.
+  std::vector<std::string> transfer_list_fail = {
+    "4",
+    "2",
+    "0",
+    "2",
+    "stash " + block1_hash + " 2,0,1",
+    "move " + block1_hash + " 2,1,2 1 2,0,1",
+    "stash " + block3_hash + " 2,2,3",
+    "fail",
+  };
+
+  // Mimic a resumed update with the same transfer commands.
+  std::vector<std::string> transfer_list_continue = {
+    "4",
+    "2",
+    "0",
+    "2",
+    "stash " + block1_hash + " 2,0,1",
+    "move " + block1_hash + " 2,1,2 1 2,0,1",
+    "stash " + block3_hash + " 2,2,3",
+    "move " + block1_hash + " 2,2,3 1 2,0,1",
+  };
+
+  std::unordered_map<std::string, std::string> entries = {
+    { "new_data", "" },
+    { "patch_data", "" },
+    { "transfer_list_fail", android::base::Join(transfer_list_fail, '\n') },
+    { "transfer_list_continue", android::base::Join(transfer_list_continue, '\n') },
+  };
+
+  // Build the update package.
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
+
+  MemMapping map;
+  ASSERT_TRUE(map.MapFile(zip_file.path));
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+  // Set up the handler, command_pipe, patch offset & length.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+  TemporaryFile temp_pipe;
+  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
+
+  std::string src_content = block1 + block2 + block3;
+  TemporaryFile update_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+  std::string script =
+      "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("transfer_list_fail"), "new_data", "patch_data"))";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  // Expect last_command to contain the last stash command.
+  std::string last_command_content;
+  ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content));
+  EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content);
+  std::string updated_contents;
+  ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents));
+  ASSERT_EQ(block1 + block1 + block3, updated_contents);
+
+  // Resume the update, expect the first 'move' to be skipped but the second 'move' to be executed.
+  ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+  std::string script_second_update =
+      "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("transfer_list_continue"), "new_data", "patch_data"))";
+  expect("t", script_second_update.c_str(), kNoCause, &updater_info);
+  ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents));
+  ASSERT_EQ(block1 + block2 + block1, updated_contents);
+
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+  CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, last_command_update_unresumable) {
+  std::string last_command_file = CacheLocation::location().last_command_file();
+
+  std::string block1 = std::string(4096, '1');
+  std::string block2 = std::string(4096, '2');
+  std::string block1_hash = get_sha1(block1);
+  std::string block2_hash = get_sha1(block2);
+
+  // Construct an unresumable update with source blocks mismatch.
+  std::vector<std::string> transfer_list_unresumable = {
+    "4", "2", "0", "2", "stash " + block1_hash + " 2,0,1", "move " + block2_hash + " 2,1,2 1 2,0,1",
+  };
+
+  std::unordered_map<std::string, std::string> entries = {
+    { "new_data", "" },
+    { "patch_data", "" },
+    { "transfer_list_unresumable", android::base::Join(transfer_list_unresumable, '\n') },
+  };
+
+  // Build the update package.
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
+
+  MemMapping map;
+  ASSERT_TRUE(map.MapFile(zip_file.path));
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+  // Set up the handler, command_pipe, patch offset & length.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+  TemporaryFile temp_pipe;
+  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
+
+  // Set up the last_command_file
+  ASSERT_TRUE(
+      android::base::WriteStringToFile("0\nstash " + block1_hash + " 2,0,1", last_command_file));
+
+  // The last_command_file will be deleted if the update encounters an unresumable failure
+  // later.
+  std::string src_content = block1 + block1;
+  TemporaryFile update_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+  std::string script =
+      "block_image_update(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("transfer_list_unresumable"), "new_data", "patch_data"))";
+  expect("", script.c_str(), kNoCause, &updater_info);
+  ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK));
+
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+  CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, last_command_verify) {
+  std::string last_command_file = CacheLocation::location().last_command_file();
+
+  std::string block1 = std::string(4096, '1');
+  std::string block2 = std::string(4096, '2');
+  std::string block3 = std::string(4096, '3');
+  std::string block1_hash = get_sha1(block1);
+  std::string block2_hash = get_sha1(block2);
+  std::string block3_hash = get_sha1(block3);
+
+  std::vector<std::string> transfer_list_verify = {
+    "4",
+    "2",
+    "0",
+    "2",
+    "stash " + block1_hash + " 2,0,1",
+    "move " + block1_hash + " 2,0,1 1 2,0,1",
+    "move " + block1_hash + " 2,1,2 1 2,0,1",
+    "stash " + block3_hash + " 2,2,3",
+  };
+
+  std::unordered_map<std::string, std::string> entries = {
+    { "new_data", "" },
+    { "patch_data", "" },
+    { "transfer_list_verify", android::base::Join(transfer_list_verify, '\n') },
+  };
+
+  // Build the update package.
+  TemporaryFile zip_file;
+  BuildUpdatePackage(entries, zip_file.release());
+
+  MemMapping map;
+  ASSERT_TRUE(map.MapFile(zip_file.path));
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+  // Set up the handler, command_pipe, patch offset & length.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+  TemporaryFile temp_pipe;
+  updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
+  updater_info.package_zip_addr = map.addr;
+  updater_info.package_zip_len = map.length;
+
+  std::string src_content = block1 + block1 + block3;
+  TemporaryFile update_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+
+  ASSERT_TRUE(
+      android::base::WriteStringToFile("2\nstash " + block3_hash + " 2,2,3", last_command_file));
+
+  // Expect the verification to succeed and the last_command_file is intact.
+  std::string script_verify =
+      "block_image_verify(\"" + std::string(update_file.path) +
+      R"(", package_extract_file("transfer_list_verify"), "new_data","patch_data"))";
+  expect("t", script_verify.c_str(), kNoCause, &updater_info);
+
+  std::string last_command_content;
+  ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content));
+  EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content);
+
+  // Expect the verification to succeed but last_command_file to be deleted; because the target
+  // blocks don't have the expected contents for the second move command.
+  src_content = block1 + block2 + block3;
+  ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+  expect("t", script_verify.c_str(), kNoCause, &updater_info);
+  ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK));
+
+  ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
   CloseArchive(handle);
 }
diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp
index 92c6ef2..64e3b59 100644
--- a/tests/manual/recovery_test.cpp
+++ b/tests/manual/recovery_test.cpp
@@ -209,7 +209,7 @@
     ASSERT_GT(height, y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
     char* loc = reinterpret_cast<char*>(&row[5]);
     if (matches_locale(loc, kLocale.c_str())) {
-      EXPECT_TRUE(android::base::StartsWith(loc, kLocale.c_str()));
+      EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
       break;
     } else {
       for (int i = 0; i < h; ++i, ++y) {
diff --git a/tests/testdata/deflate_src.zip b/tests/testdata/deflate_src.zip
new file mode 100644
index 0000000..bdb2b32
--- /dev/null
+++ b/tests/testdata/deflate_src.zip
Binary files differ
diff --git a/tests/testdata/deflate_tgt.zip b/tests/testdata/deflate_tgt.zip
new file mode 100644
index 0000000..2a21760
--- /dev/null
+++ b/tests/testdata/deflate_tgt.zip
Binary files differ
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
index 5e2ae4f..7f85d13 100644
--- a/tests/unit/dirutil_test.cpp
+++ b/tests/unit/dirutil_test.cpp
@@ -26,23 +26,23 @@
 
 TEST(DirUtilTest, create_invalid) {
   // Requesting to create an empty dir is invalid.
-  ASSERT_EQ(-1, dirCreateHierarchy("", 0755, nullptr, false, nullptr));
+  ASSERT_EQ(-1, mkdir_recursively("", 0755, false, nullptr));
   ASSERT_EQ(ENOENT, errno);
 
   // Requesting to strip the name with no slash present.
-  ASSERT_EQ(-1, dirCreateHierarchy("abc", 0755, nullptr, true, nullptr));
+  ASSERT_EQ(-1, mkdir_recursively("abc", 0755, true, nullptr));
   ASSERT_EQ(ENOENT, errno);
 
   // Creating a dir that already exists.
   TemporaryDir td;
-  ASSERT_EQ(0, dirCreateHierarchy(td.path, 0755, nullptr, false, nullptr));
+  ASSERT_EQ(0, mkdir_recursively(td.path, 0755, false, nullptr));
 
   // "///" is a valid dir.
-  ASSERT_EQ(0, dirCreateHierarchy("///", 0755, nullptr, false, nullptr));
+  ASSERT_EQ(0, mkdir_recursively("///", 0755, false, nullptr));
 
   // Request to create a dir, but a file with the same name already exists.
   TemporaryFile tf;
-  ASSERT_EQ(-1, dirCreateHierarchy(tf.path, 0755, nullptr, false, nullptr));
+  ASSERT_EQ(-1, mkdir_recursively(tf.path, 0755, false, nullptr));
   ASSERT_EQ(ENOTDIR, errno);
 }
 
@@ -51,7 +51,7 @@
   std::string prefix(td.path);
   std::string path = prefix + "/a/b";
   constexpr mode_t mode = 0755;
-  ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, nullptr, false, nullptr));
+  ASSERT_EQ(0, mkdir_recursively(path, mode, false, nullptr));
 
   // Verify.
   struct stat sb;
@@ -69,7 +69,7 @@
   TemporaryDir td;
   std::string prefix(td.path);
   std::string path = prefix + "/a/b";
-  ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), 0755, nullptr, true, nullptr));
+  ASSERT_EQ(0, mkdir_recursively(path, 0755, true, nullptr));
 
   // Verify that "../a" exists but not "../a/b".
   struct stat sb;
@@ -83,31 +83,21 @@
   ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
 }
 
-TEST(DirUtilTest, create_mode_and_timestamp) {
+TEST(DirUtilTest, create_mode) {
   TemporaryDir td;
   std::string prefix(td.path);
   std::string path = prefix + "/a/b";
-  // Set the timestamp to 8/1/2008.
-  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
   constexpr mode_t mode = 0751;
-  ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, &timestamp, false, nullptr));
+  ASSERT_EQ(0, mkdir_recursively(path, mode, false, nullptr));
 
-  // Verify the mode and timestamp for "../a/b".
+  // Verify the mode for "../a/b".
   struct stat sb;
   ASSERT_EQ(0, stat(path.c_str(), &sb)) << strerror(errno);
   ASSERT_TRUE(S_ISDIR(sb.st_mode));
   constexpr mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;
   ASSERT_EQ(mode, sb.st_mode & mask);
 
-  timespec time;
-  time.tv_sec = 1217592000;
-  time.tv_nsec = 0;
-
-  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
-  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
-
-  // Verify the mode for "../a". Note that the timestamp for intermediate directories (e.g. "../a")
-  // may not be 'timestamp' according to the current implementation.
+  // Verify the mode for "../a".
   ASSERT_EQ(0, stat((prefix + "/a").c_str(), &sb)) << strerror(errno);
   ASSERT_TRUE(S_ISDIR(sb.st_mode));
   ASSERT_EQ(mode, sb.st_mode & mask);
@@ -116,35 +106,3 @@
   ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str()));
   ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
 }
-
-TEST(DirUtilTest, unlink_invalid) {
-  // File doesn't exist.
-  ASSERT_EQ(-1, dirUnlinkHierarchy("doesntexist"));
-
-  // Nonexistent directory.
-  TemporaryDir td;
-  std::string path(td.path);
-  ASSERT_EQ(-1, dirUnlinkHierarchy((path + "/a").c_str()));
-  ASSERT_EQ(ENOENT, errno);
-}
-
-TEST(DirUtilTest, unlink_smoke) {
-  // Unlink a file.
-  TemporaryFile tf;
-  ASSERT_EQ(0, dirUnlinkHierarchy(tf.path));
-  ASSERT_EQ(-1, access(tf.path, F_OK));
-
-  TemporaryDir td;
-  std::string path(td.path);
-  constexpr mode_t mode = 0700;
-  ASSERT_EQ(0, mkdir((path + "/a").c_str(), mode));
-  ASSERT_EQ(0, mkdir((path + "/a/b").c_str(), mode));
-  ASSERT_EQ(0, mkdir((path + "/a/b/c").c_str(), mode));
-  ASSERT_EQ(0, mkdir((path + "/a/d").c_str(), mode));
-
-  // Remove "../a" recursively.
-  ASSERT_EQ(0, dirUnlinkHierarchy((path + "/a").c_str()));
-
-  // Verify it's gone.
-  ASSERT_EQ(-1, access((path + "/a").c_str(), F_OK));
-}
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
index 3c6d77e..7ae193e 100644
--- a/tests/unit/rangeset_test.cpp
+++ b/tests/unit/rangeset_test.cpp
@@ -17,11 +17,23 @@
 #include <signal.h>
 #include <sys/types.h>
 
+#include <limits>
 #include <vector>
 
 #include <gtest/gtest.h>
 
-#include "updater/rangeset.h"
+#include "otautil/rangeset.h"
+
+TEST(RangeSetTest, ctor) {
+  RangeSet rs(std::vector<Range>{ Range{ 8, 10 }, Range{ 1, 5 } });
+  ASSERT_TRUE(rs);
+
+  RangeSet rs2(std::vector<Range>{});
+  ASSERT_FALSE(rs2);
+
+  RangeSet rs3(std::vector<Range>{ Range{ 8, 10 }, Range{ 5, 1 } });
+  ASSERT_FALSE(rs3);
+}
 
 TEST(RangeSetTest, Parse_smoke) {
   RangeSet rs = RangeSet::Parse("2,1,10");
@@ -37,27 +49,64 @@
 
   // Leading zeros are fine. But android::base::ParseUint() doesn't like trailing zeros like "10 ".
   ASSERT_EQ(rs, RangeSet::Parse(" 2, 1,   10"));
-  ASSERT_EXIT(RangeSet::Parse("2,1,10 "), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_FALSE(RangeSet::Parse("2,1,10 "));
 }
 
 TEST(RangeSetTest, Parse_InvalidCases) {
   // Insufficient number of tokens.
-  ASSERT_EXIT(RangeSet::Parse(""), ::testing::KilledBySignal(SIGABRT), "");
-  ASSERT_EXIT(RangeSet::Parse("2,1"), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_FALSE(RangeSet::Parse(""));
+  ASSERT_FALSE(RangeSet::Parse("2,1"));
 
   // The first token (i.e. the number of following tokens) is invalid.
-  ASSERT_EXIT(RangeSet::Parse("a,1,1"), ::testing::KilledBySignal(SIGABRT), "");
-  ASSERT_EXIT(RangeSet::Parse("3,1,1"), ::testing::KilledBySignal(SIGABRT), "");
-  ASSERT_EXIT(RangeSet::Parse("-3,1,1"), ::testing::KilledBySignal(SIGABRT), "");
-  ASSERT_EXIT(RangeSet::Parse("2,1,2,3"), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_FALSE(RangeSet::Parse("a,1,1"));
+  ASSERT_FALSE(RangeSet::Parse("3,1,1"));
+  ASSERT_FALSE(RangeSet::Parse("-3,1,1"));
+  ASSERT_FALSE(RangeSet::Parse("2,1,2,3"));
 
   // Invalid tokens.
-  ASSERT_EXIT(RangeSet::Parse("2,1,10a"), ::testing::KilledBySignal(SIGABRT), "");
-  ASSERT_EXIT(RangeSet::Parse("2,,10"), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_FALSE(RangeSet::Parse("2,1,10a"));
+  ASSERT_FALSE(RangeSet::Parse("2,,10"));
 
   // Empty or negative range.
-  ASSERT_EXIT(RangeSet::Parse("2,2,2"), ::testing::KilledBySignal(SIGABRT), "");
-  ASSERT_EXIT(RangeSet::Parse("2,2,1"), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_FALSE(RangeSet::Parse("2,2,2"));
+  ASSERT_FALSE(RangeSet::Parse("2,2,1"));
+}
+
+TEST(RangeSetTest, Clear) {
+  RangeSet rs = RangeSet::Parse("2,1,6");
+  ASSERT_TRUE(rs);
+  rs.Clear();
+  ASSERT_FALSE(rs);
+
+  // No-op to clear an empty RangeSet.
+  rs.Clear();
+  ASSERT_FALSE(rs);
+}
+
+TEST(RangeSetTest, PushBack) {
+  RangeSet rs;
+  ASSERT_FALSE(rs);
+
+  ASSERT_TRUE(rs.PushBack({ 3, 5 }));
+  ASSERT_EQ(RangeSet::Parse("2,3,5"), rs);
+
+  ASSERT_TRUE(rs.PushBack({ 5, 15 }));
+  ASSERT_EQ(RangeSet::Parse("4,3,5,5,15"), rs);
+  ASSERT_EQ(static_cast<size_t>(2), rs.size());
+  ASSERT_EQ(static_cast<size_t>(12), rs.blocks());
+}
+
+TEST(RangeSetTest, PushBack_InvalidInput) {
+  RangeSet rs;
+  ASSERT_FALSE(rs);
+  ASSERT_FALSE(rs.PushBack({ 5, 3 }));
+  ASSERT_FALSE(rs);
+  ASSERT_FALSE(rs.PushBack({ 15, 15 }));
+  ASSERT_FALSE(rs);
+
+  ASSERT_TRUE(rs.PushBack({ 5, 15 }));
+  ASSERT_FALSE(rs.PushBack({ 5, std::numeric_limits<size_t>::max() - 2 }));
+  ASSERT_EQ(RangeSet::Parse("2,5,15"), rs);
 }
 
 TEST(RangeSetTest, Overlaps) {
@@ -74,6 +123,86 @@
   ASSERT_FALSE(RangeSet::Parse("2,5,7").Overlaps(RangeSet::Parse("2,3,5")));
 }
 
+TEST(RangeSetTest, Split) {
+  RangeSet rs1 = RangeSet::Parse("2,1,2");
+  ASSERT_TRUE(rs1);
+  ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,1,2") }), rs1.Split(1));
+
+  RangeSet rs2 = RangeSet::Parse("2,5,10");
+  ASSERT_TRUE(rs2);
+  ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,5,8"), RangeSet::Parse("2,8,10") }),
+            rs2.Split(2));
+
+  RangeSet rs3 = RangeSet::Parse("4,0,1,5,10");
+  ASSERT_TRUE(rs3);
+  ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("4,0,1,5,7"), RangeSet::Parse("2,7,10") }),
+            rs3.Split(2));
+
+  RangeSet rs4 = RangeSet::Parse("6,1,3,3,4,4,5");
+  ASSERT_TRUE(rs4);
+  ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,1,3"), RangeSet::Parse("2,3,4"),
+                                    RangeSet::Parse("2,4,5") }),
+            rs4.Split(3));
+
+  RangeSet rs5 = RangeSet::Parse("2,0,10");
+  ASSERT_TRUE(rs5);
+  ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,0,3"), RangeSet::Parse("2,3,6"),
+                                    RangeSet::Parse("2,6,8"), RangeSet::Parse("2,8,10") }),
+            rs5.Split(4));
+
+  RangeSet rs6 = RangeSet::Parse(
+      "20,0,268,269,271,286,447,8350,32770,33022,98306,98558,163842,164094,196609,204800,229378,"
+      "229630,294914,295166,457564");
+  ASSERT_TRUE(rs6);
+  size_t rs6_blocks = rs6.blocks();
+  auto splits = rs6.Split(4);
+  ASSERT_EQ(
+      (std::vector<RangeSet>{
+          RangeSet::Parse("12,0,268,269,271,286,447,8350,32770,33022,98306,98558,118472"),
+          RangeSet::Parse("8,118472,163842,164094,196609,204800,229378,229630,237216"),
+          RangeSet::Parse("4,237216,294914,295166,347516"), RangeSet::Parse("2,347516,457564") }),
+      splits);
+  size_t sum = 0;
+  for (const auto& element : splits) {
+    sum += element.blocks();
+  }
+  ASSERT_EQ(rs6_blocks, sum);
+}
+
+TEST(RangeSetTest, Split_EdgeCases) {
+  // Empty RangeSet.
+  RangeSet rs1;
+  ASSERT_FALSE(rs1);
+  ASSERT_EQ((std::vector<RangeSet>{}), rs1.Split(2));
+  ASSERT_FALSE(rs1);
+
+  // Zero group.
+  RangeSet rs2 = RangeSet::Parse("2,1,5");
+  ASSERT_TRUE(rs2);
+  ASSERT_EQ((std::vector<RangeSet>{}), rs2.Split(0));
+
+  // The number of blocks equals to the number of groups.
+  RangeSet rs3 = RangeSet::Parse("2,1,5");
+  ASSERT_TRUE(rs3);
+  ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,1,2"), RangeSet::Parse("2,2,3"),
+                                    RangeSet::Parse("2,3,4"), RangeSet::Parse("2,4,5") }),
+            rs3.Split(4));
+
+  // Less blocks than the number of groups.
+  RangeSet rs4 = RangeSet::Parse("2,1,5");
+  ASSERT_TRUE(rs4);
+  ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,1,2"), RangeSet::Parse("2,2,3"),
+                                    RangeSet::Parse("2,3,4"), RangeSet::Parse("2,4,5") }),
+            rs4.Split(8));
+
+  // Less blocks than the number of groups.
+  RangeSet rs5 = RangeSet::Parse("2,0,3");
+  ASSERT_TRUE(rs5);
+  ASSERT_EQ((std::vector<RangeSet>{ RangeSet::Parse("2,0,1"), RangeSet::Parse("2,1,2"),
+                                    RangeSet::Parse("2,2,3") }),
+            rs5.Split(4));
+}
+
 TEST(RangeSetTest, GetBlockNumber) {
   RangeSet rs = RangeSet::Parse("2,1,10");
   ASSERT_EQ(static_cast<size_t>(1), rs.GetBlockNumber(0));
@@ -90,7 +219,7 @@
   ASSERT_NE(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,1,7"));
   ASSERT_NE(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,2,7"));
 
-  // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5".
+  // The orders of Range's matter, e.g. "4,1,5,8,10" != "4,8,10,1,5".
   ASSERT_NE(RangeSet::Parse("4,1,5,8,10"), RangeSet::Parse("4,8,10,1,5"));
 }
 
@@ -110,3 +239,51 @@
   }
   ASSERT_EQ((std::vector<Range>{ Range{ 8, 10 }, Range{ 1, 5 } }), ranges);
 }
+
+TEST(RangeSetTest, ToString) {
+  ASSERT_EQ("", RangeSet::Parse("").ToString());
+  ASSERT_EQ("2,1,6", RangeSet::Parse("2,1,6").ToString());
+  ASSERT_EQ("4,1,5,8,10", RangeSet::Parse("4,1,5,8,10").ToString());
+  ASSERT_EQ("6,1,3,4,6,15,22", RangeSet::Parse("6,1,3,4,6,15,22").ToString());
+}
+
+TEST(SortedRangeSetTest, Insert) {
+  SortedRangeSet rs({ { 2, 3 }, { 4, 6 }, { 8, 14 } });
+  rs.Insert({ 1, 2 });
+  ASSERT_EQ(SortedRangeSet({ { 1, 3 }, { 4, 6 }, { 8, 14 } }), rs);
+  ASSERT_EQ(static_cast<size_t>(10), rs.blocks());
+  rs.Insert({ 3, 5 });
+  ASSERT_EQ(SortedRangeSet({ { 1, 6 }, { 8, 14 } }), rs);
+  ASSERT_EQ(static_cast<size_t>(11), rs.blocks());
+
+  SortedRangeSet r1({ { 20, 22 }, { 15, 18 } });
+  rs.Insert(r1);
+  ASSERT_EQ(SortedRangeSet({ { 1, 6 }, { 8, 14 }, { 15, 18 }, { 20, 22 } }), rs);
+  ASSERT_EQ(static_cast<size_t>(16), rs.blocks());
+
+  SortedRangeSet r2({ { 2, 7 }, { 15, 21 }, { 20, 25 } });
+  rs.Insert(r2);
+  ASSERT_EQ(SortedRangeSet({ { 1, 7 }, { 8, 14 }, { 15, 25 } }), rs);
+  ASSERT_EQ(static_cast<size_t>(22), rs.blocks());
+}
+
+TEST(SortedRangeSetTest, file_range) {
+  SortedRangeSet rs;
+  rs.Insert(4096, 4096);
+  ASSERT_EQ(SortedRangeSet({ { 1, 2 } }), rs);
+  // insert block 2-9
+  rs.Insert(4096 * 3 - 1, 4096 * 7);
+  ASSERT_EQ(SortedRangeSet({ { 1, 10 } }), rs);
+  // insert block 15-19
+  rs.Insert(4096 * 15 + 1, 4096 * 4);
+  ASSERT_EQ(SortedRangeSet({ { 1, 10 }, { 15, 20 } }), rs);
+
+  // rs overlaps block 2-2
+  ASSERT_TRUE(rs.Overlaps(4096 * 2 - 1, 10));
+  ASSERT_FALSE(rs.Overlaps(4096 * 10, 4096 * 5));
+
+  ASSERT_EQ(static_cast<size_t>(10), rs.GetOffsetInRangeSet(4106));
+  ASSERT_EQ(static_cast<size_t>(40970), rs.GetOffsetInRangeSet(4096 * 16 + 10));
+  // block#10 not in range.
+  ASSERT_EXIT(rs.GetOffsetInRangeSet(40970), ::testing::KilledBySignal(SIGABRT), "");
+}
diff --git a/toolbox/Android.mk b/toolbox/Android.mk
index bf77ba2..7d1b602 100644
--- a/toolbox/Android.mk
+++ b/toolbox/Android.mk
@@ -160,6 +160,16 @@
     endif
 endif
 
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 27; echo $$?),0)
+    # Special rules for 9.0
+    OUR_TOOLS += getevent
+    LOCAL_C_INCLUDES += $(TWRP_TOOLBOX_PATH)
+    LOCAL_WHOLE_STATIC_LIBRARIES += libtoolbox_dd
+    ifneq ($(TW_USE_TOOLBOX), true)
+        OUR_TOOLS += newfs_msdos
+    endif
+endif
+
 ifneq (,$(filter $(PLATFORM_SDK_VERSION), 21 22))
     ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
         OUR_TOOLS += r
@@ -227,24 +237,39 @@
     # including busybox.
 ifneq ($(TW_USE_TOOLBOX), true)
     LOCAL_SRC_FILES += \
-        ../../../$(TWRP_TOOLBOX_PATH)/getprop.c \
         ../../../$(TWRP_TOOLBOX_PATH)/setprop.c \
         ../../../$(TWRP_TOOLBOX_PATH)/ls.c
-    OUR_TOOLS += getprop setprop
+    OUR_TOOLS += setprop
+    ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 28; echo $$?),0)
+        # Special rules for <= 8.1
+        LOCAL_SRC_FILES += \
+            ../../../$(TWRP_TOOLBOX_PATH)/getprop.c
+        OUR_TOOLS += getprop
+    endif
 endif
 endif
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 23; echo $$?),0)
+    ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 28; echo $$?),0)
     # Rule for making start and stop in N trees
     LOCAL_SRC_FILES += \
         ../../../$(TWRP_TOOLBOX_PATH)/start.c \
         ../../../$(TWRP_TOOLBOX_PATH)/stop.c
     OUR_TOOLS += start stop
+    endif
+endif
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 27; echo $$?),0)
+    LOCAL_SRC_FILES += getprop.cpp
+    LOCAL_SHARED_LIBRARIES += libbase
+    LOCAL_STATIC_LIBRARIES += libpropertyinfoparser
+    LOCAL_CPPFLAGS += -std=c++17
+    OUR_TOOLS += getprop
 endif
 
 LOCAL_MODULE := toolbox_recovery
 LOCAL_MODULE_STEM := toolbox
 LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
 LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-const-variable
 
 # Including this will define $(intermediates) below
 include $(BUILD_EXECUTABLE)
@@ -260,7 +285,6 @@
 TOOLS_H := $(intermediates)/tools.h
 $(TOOLS_H): PRIVATE_TOOLS := $(ALL_TOOLS)
 $(TOOLS_H): PRIVATE_CUSTOM_TOOL = echo "/* file generated automatically */" > $@ ; for t in $(PRIVATE_TOOLS) ; do echo "TOOL($$t)" >> $@ ; done
-$(TOOLS_H): $(LOCAL_PATH)/Android.mk
 $(TOOLS_H):
 	$(transform-generated-source)
 
@@ -273,7 +297,7 @@
 # Make /sbin/toolbox launchers for each tool
 SYMLINKS := $(addprefix $(TARGET_RECOVERY_ROOT_OUT)/sbin/,$(ALL_TOOLS))
 $(SYMLINKS): TOOLBOX_BINARY := $(LOCAL_MODULE_STEM)
-$(SYMLINKS): $(LOCAL_INSTALLED_MODULE) $(LOCAL_PATH)/Android.mk
+$(SYMLINKS): $(LOCAL_INSTALLED_MODULE)
 	@echo "Symlink: $@ -> $(TOOLBOX_BINARY)"
 	@mkdir -p $(dir $@)
 	@rm -rf $@
diff --git a/toolbox/input.h-labels.h b/toolbox/input.h-labels.h
new file mode 100644
index 0000000..430586e
--- /dev/null
+++ b/toolbox/input.h-labels.h
@@ -0,0 +1,741 @@
+static struct label input_prop_labels[] = {
+    LABEL(INPUT_PROP_POINTER),
+    LABEL(INPUT_PROP_DIRECT),
+    LABEL(INPUT_PROP_BUTTONPAD),
+    LABEL(INPUT_PROP_SEMI_MT),
+    LABEL(INPUT_PROP_TOPBUTTONPAD),
+    LABEL(INPUT_PROP_POINTING_STICK),
+    LABEL(INPUT_PROP_ACCELEROMETER),
+    LABEL(INPUT_PROP_MAX),
+    LABEL_END,
+};
+static struct label ev_labels[] = {
+    LABEL(EV_VERSION),
+    LABEL(EV_SYN),
+    LABEL(EV_KEY),
+    LABEL(EV_REL),
+    LABEL(EV_ABS),
+    LABEL(EV_MSC),
+    LABEL(EV_SW),
+    LABEL(EV_LED),
+    LABEL(EV_SND),
+    LABEL(EV_REP),
+    LABEL(EV_FF),
+    LABEL(EV_PWR),
+    LABEL(EV_FF_STATUS),
+    LABEL(EV_MAX),
+    LABEL_END,
+};
+static struct label syn_labels[] = {
+    LABEL(SYN_REPORT),
+    LABEL(SYN_CONFIG),
+    LABEL(SYN_MT_REPORT),
+    LABEL(SYN_DROPPED),
+    LABEL(SYN_MAX),
+    LABEL_END,
+};
+static struct label key_labels[] = {
+    LABEL(KEY_RESERVED),
+    LABEL(KEY_ESC),
+    LABEL(KEY_1),
+    LABEL(KEY_2),
+    LABEL(KEY_3),
+    LABEL(KEY_4),
+    LABEL(KEY_5),
+    LABEL(KEY_6),
+    LABEL(KEY_7),
+    LABEL(KEY_8),
+    LABEL(KEY_9),
+    LABEL(KEY_0),
+    LABEL(KEY_MINUS),
+    LABEL(KEY_EQUAL),
+    LABEL(KEY_BACKSPACE),
+    LABEL(KEY_TAB),
+    LABEL(KEY_Q),
+    LABEL(KEY_W),
+    LABEL(KEY_E),
+    LABEL(KEY_R),
+    LABEL(KEY_T),
+    LABEL(KEY_Y),
+    LABEL(KEY_U),
+    LABEL(KEY_I),
+    LABEL(KEY_O),
+    LABEL(KEY_P),
+    LABEL(KEY_LEFTBRACE),
+    LABEL(KEY_RIGHTBRACE),
+    LABEL(KEY_ENTER),
+    LABEL(KEY_LEFTCTRL),
+    LABEL(KEY_A),
+    LABEL(KEY_S),
+    LABEL(KEY_D),
+    LABEL(KEY_F),
+    LABEL(KEY_G),
+    LABEL(KEY_H),
+    LABEL(KEY_J),
+    LABEL(KEY_K),
+    LABEL(KEY_L),
+    LABEL(KEY_SEMICOLON),
+    LABEL(KEY_APOSTROPHE),
+    LABEL(KEY_GRAVE),
+    LABEL(KEY_LEFTSHIFT),
+    LABEL(KEY_BACKSLASH),
+    LABEL(KEY_Z),
+    LABEL(KEY_X),
+    LABEL(KEY_C),
+    LABEL(KEY_V),
+    LABEL(KEY_B),
+    LABEL(KEY_N),
+    LABEL(KEY_M),
+    LABEL(KEY_COMMA),
+    LABEL(KEY_DOT),
+    LABEL(KEY_SLASH),
+    LABEL(KEY_RIGHTSHIFT),
+    LABEL(KEY_KPASTERISK),
+    LABEL(KEY_LEFTALT),
+    LABEL(KEY_SPACE),
+    LABEL(KEY_CAPSLOCK),
+    LABEL(KEY_F1),
+    LABEL(KEY_F2),
+    LABEL(KEY_F3),
+    LABEL(KEY_F4),
+    LABEL(KEY_F5),
+    LABEL(KEY_F6),
+    LABEL(KEY_F7),
+    LABEL(KEY_F8),
+    LABEL(KEY_F9),
+    LABEL(KEY_F10),
+    LABEL(KEY_NUMLOCK),
+    LABEL(KEY_SCROLLLOCK),
+    LABEL(KEY_KP7),
+    LABEL(KEY_KP8),
+    LABEL(KEY_KP9),
+    LABEL(KEY_KPMINUS),
+    LABEL(KEY_KP4),
+    LABEL(KEY_KP5),
+    LABEL(KEY_KP6),
+    LABEL(KEY_KPPLUS),
+    LABEL(KEY_KP1),
+    LABEL(KEY_KP2),
+    LABEL(KEY_KP3),
+    LABEL(KEY_KP0),
+    LABEL(KEY_KPDOT),
+    LABEL(KEY_ZENKAKUHANKAKU),
+    LABEL(KEY_102ND),
+    LABEL(KEY_F11),
+    LABEL(KEY_F12),
+    LABEL(KEY_RO),
+    LABEL(KEY_KATAKANA),
+    LABEL(KEY_HIRAGANA),
+    LABEL(KEY_HENKAN),
+    LABEL(KEY_KATAKANAHIRAGANA),
+    LABEL(KEY_MUHENKAN),
+    LABEL(KEY_KPJPCOMMA),
+    LABEL(KEY_KPENTER),
+    LABEL(KEY_RIGHTCTRL),
+    LABEL(KEY_KPSLASH),
+    LABEL(KEY_SYSRQ),
+    LABEL(KEY_RIGHTALT),
+    LABEL(KEY_LINEFEED),
+    LABEL(KEY_HOME),
+    LABEL(KEY_UP),
+    LABEL(KEY_PAGEUP),
+    LABEL(KEY_LEFT),
+    LABEL(KEY_RIGHT),
+    LABEL(KEY_END),
+    LABEL(KEY_DOWN),
+    LABEL(KEY_PAGEDOWN),
+    LABEL(KEY_INSERT),
+    LABEL(KEY_DELETE),
+    LABEL(KEY_MACRO),
+    LABEL(KEY_MUTE),
+    LABEL(KEY_VOLUMEDOWN),
+    LABEL(KEY_VOLUMEUP),
+    LABEL(KEY_POWER),
+    LABEL(KEY_KPEQUAL),
+    LABEL(KEY_KPPLUSMINUS),
+    LABEL(KEY_PAUSE),
+    LABEL(KEY_SCALE),
+    LABEL(KEY_KPCOMMA),
+    LABEL(KEY_HANGEUL),
+    LABEL(KEY_HANJA),
+    LABEL(KEY_YEN),
+    LABEL(KEY_LEFTMETA),
+    LABEL(KEY_RIGHTMETA),
+    LABEL(KEY_COMPOSE),
+    LABEL(KEY_STOP),
+    LABEL(KEY_AGAIN),
+    LABEL(KEY_PROPS),
+    LABEL(KEY_UNDO),
+    LABEL(KEY_FRONT),
+    LABEL(KEY_COPY),
+    LABEL(KEY_OPEN),
+    LABEL(KEY_PASTE),
+    LABEL(KEY_FIND),
+    LABEL(KEY_CUT),
+    LABEL(KEY_HELP),
+    LABEL(KEY_MENU),
+    LABEL(KEY_CALC),
+    LABEL(KEY_SETUP),
+    LABEL(KEY_SLEEP),
+    LABEL(KEY_WAKEUP),
+    LABEL(KEY_FILE),
+    LABEL(KEY_SENDFILE),
+    LABEL(KEY_DELETEFILE),
+    LABEL(KEY_XFER),
+    LABEL(KEY_PROG1),
+    LABEL(KEY_PROG2),
+    LABEL(KEY_WWW),
+    LABEL(KEY_MSDOS),
+    LABEL(KEY_COFFEE),
+    LABEL(KEY_ROTATE_DISPLAY),
+    LABEL(KEY_CYCLEWINDOWS),
+    LABEL(KEY_MAIL),
+    LABEL(KEY_BOOKMARKS),
+    LABEL(KEY_COMPUTER),
+    LABEL(KEY_BACK),
+    LABEL(KEY_FORWARD),
+    LABEL(KEY_CLOSECD),
+    LABEL(KEY_EJECTCD),
+    LABEL(KEY_EJECTCLOSECD),
+    LABEL(KEY_NEXTSONG),
+    LABEL(KEY_PLAYPAUSE),
+    LABEL(KEY_PREVIOUSSONG),
+    LABEL(KEY_STOPCD),
+    LABEL(KEY_RECORD),
+    LABEL(KEY_REWIND),
+    LABEL(KEY_PHONE),
+    LABEL(KEY_ISO),
+    LABEL(KEY_CONFIG),
+    LABEL(KEY_HOMEPAGE),
+    LABEL(KEY_REFRESH),
+    LABEL(KEY_EXIT),
+    LABEL(KEY_MOVE),
+    LABEL(KEY_EDIT),
+    LABEL(KEY_SCROLLUP),
+    LABEL(KEY_SCROLLDOWN),
+    LABEL(KEY_KPLEFTPAREN),
+    LABEL(KEY_KPRIGHTPAREN),
+    LABEL(KEY_NEW),
+    LABEL(KEY_REDO),
+    LABEL(KEY_F13),
+    LABEL(KEY_F14),
+    LABEL(KEY_F15),
+    LABEL(KEY_F16),
+    LABEL(KEY_F17),
+    LABEL(KEY_F18),
+    LABEL(KEY_F19),
+    LABEL(KEY_F20),
+    LABEL(KEY_F21),
+    LABEL(KEY_F22),
+    LABEL(KEY_F23),
+    LABEL(KEY_F24),
+    LABEL(KEY_PLAYCD),
+    LABEL(KEY_PAUSECD),
+    LABEL(KEY_PROG3),
+    LABEL(KEY_PROG4),
+    LABEL(KEY_DASHBOARD),
+    LABEL(KEY_SUSPEND),
+    LABEL(KEY_CLOSE),
+    LABEL(KEY_PLAY),
+    LABEL(KEY_FASTFORWARD),
+    LABEL(KEY_BASSBOOST),
+    LABEL(KEY_PRINT),
+    LABEL(KEY_HP),
+    LABEL(KEY_CAMERA),
+    LABEL(KEY_SOUND),
+    LABEL(KEY_QUESTION),
+    LABEL(KEY_EMAIL),
+    LABEL(KEY_CHAT),
+    LABEL(KEY_SEARCH),
+    LABEL(KEY_CONNECT),
+    LABEL(KEY_FINANCE),
+    LABEL(KEY_SPORT),
+    LABEL(KEY_SHOP),
+    LABEL(KEY_ALTERASE),
+    LABEL(KEY_CANCEL),
+    LABEL(KEY_BRIGHTNESSDOWN),
+    LABEL(KEY_BRIGHTNESSUP),
+    LABEL(KEY_MEDIA),
+    LABEL(KEY_SWITCHVIDEOMODE),
+    LABEL(KEY_KBDILLUMTOGGLE),
+    LABEL(KEY_KBDILLUMDOWN),
+    LABEL(KEY_KBDILLUMUP),
+    LABEL(KEY_SEND),
+    LABEL(KEY_REPLY),
+    LABEL(KEY_FORWARDMAIL),
+    LABEL(KEY_SAVE),
+    LABEL(KEY_DOCUMENTS),
+    LABEL(KEY_BATTERY),
+    LABEL(KEY_BLUETOOTH),
+    LABEL(KEY_WLAN),
+    LABEL(KEY_UWB),
+    LABEL(KEY_UNKNOWN),
+    LABEL(KEY_VIDEO_NEXT),
+    LABEL(KEY_VIDEO_PREV),
+    LABEL(KEY_BRIGHTNESS_CYCLE),
+    LABEL(KEY_BRIGHTNESS_AUTO),
+    LABEL(KEY_DISPLAY_OFF),
+    LABEL(KEY_WWAN),
+    LABEL(KEY_RFKILL),
+    LABEL(KEY_MICMUTE),
+    LABEL(BTN_MISC),
+    LABEL(BTN_0),
+    LABEL(BTN_1),
+    LABEL(BTN_2),
+    LABEL(BTN_3),
+    LABEL(BTN_4),
+    LABEL(BTN_5),
+    LABEL(BTN_6),
+    LABEL(BTN_7),
+    LABEL(BTN_8),
+    LABEL(BTN_9),
+    LABEL(BTN_MOUSE),
+    LABEL(BTN_LEFT),
+    LABEL(BTN_RIGHT),
+    LABEL(BTN_MIDDLE),
+    LABEL(BTN_SIDE),
+    LABEL(BTN_EXTRA),
+    LABEL(BTN_FORWARD),
+    LABEL(BTN_BACK),
+    LABEL(BTN_TASK),
+    LABEL(BTN_JOYSTICK),
+    LABEL(BTN_TRIGGER),
+    LABEL(BTN_THUMB),
+    LABEL(BTN_THUMB2),
+    LABEL(BTN_TOP),
+    LABEL(BTN_TOP2),
+    LABEL(BTN_PINKIE),
+    LABEL(BTN_BASE),
+    LABEL(BTN_BASE2),
+    LABEL(BTN_BASE3),
+    LABEL(BTN_BASE4),
+    LABEL(BTN_BASE5),
+    LABEL(BTN_BASE6),
+    LABEL(BTN_DEAD),
+    LABEL(BTN_GAMEPAD),
+    LABEL(BTN_SOUTH),
+    LABEL(BTN_EAST),
+    LABEL(BTN_C),
+    LABEL(BTN_NORTH),
+    LABEL(BTN_WEST),
+    LABEL(BTN_Z),
+    LABEL(BTN_TL),
+    LABEL(BTN_TR),
+    LABEL(BTN_TL2),
+    LABEL(BTN_TR2),
+    LABEL(BTN_SELECT),
+    LABEL(BTN_START),
+    LABEL(BTN_MODE),
+    LABEL(BTN_THUMBL),
+    LABEL(BTN_THUMBR),
+    LABEL(BTN_DIGI),
+    LABEL(BTN_TOOL_PEN),
+    LABEL(BTN_TOOL_RUBBER),
+    LABEL(BTN_TOOL_BRUSH),
+    LABEL(BTN_TOOL_PENCIL),
+    LABEL(BTN_TOOL_AIRBRUSH),
+    LABEL(BTN_TOOL_FINGER),
+    LABEL(BTN_TOOL_MOUSE),
+    LABEL(BTN_TOOL_LENS),
+    LABEL(BTN_TOOL_QUINTTAP),
+    LABEL(BTN_STYLUS3),
+    LABEL(BTN_TOUCH),
+    LABEL(BTN_STYLUS),
+    LABEL(BTN_STYLUS2),
+    LABEL(BTN_TOOL_DOUBLETAP),
+    LABEL(BTN_TOOL_TRIPLETAP),
+    LABEL(BTN_TOOL_QUADTAP),
+    LABEL(BTN_WHEEL),
+    LABEL(BTN_GEAR_DOWN),
+    LABEL(BTN_GEAR_UP),
+    LABEL(KEY_OK),
+    LABEL(KEY_SELECT),
+    LABEL(KEY_GOTO),
+    LABEL(KEY_CLEAR),
+    LABEL(KEY_POWER2),
+    LABEL(KEY_OPTION),
+    LABEL(KEY_INFO),
+    LABEL(KEY_TIME),
+    LABEL(KEY_VENDOR),
+    LABEL(KEY_ARCHIVE),
+    LABEL(KEY_PROGRAM),
+    LABEL(KEY_CHANNEL),
+    LABEL(KEY_FAVORITES),
+    LABEL(KEY_EPG),
+    LABEL(KEY_PVR),
+    LABEL(KEY_MHP),
+    LABEL(KEY_LANGUAGE),
+    LABEL(KEY_TITLE),
+    LABEL(KEY_SUBTITLE),
+    LABEL(KEY_ANGLE),
+    LABEL(KEY_ZOOM),
+    LABEL(KEY_MODE),
+    LABEL(KEY_KEYBOARD),
+    LABEL(KEY_SCREEN),
+    LABEL(KEY_PC),
+    LABEL(KEY_TV),
+    LABEL(KEY_TV2),
+    LABEL(KEY_VCR),
+    LABEL(KEY_VCR2),
+    LABEL(KEY_SAT),
+    LABEL(KEY_SAT2),
+    LABEL(KEY_CD),
+    LABEL(KEY_TAPE),
+    LABEL(KEY_RADIO),
+    LABEL(KEY_TUNER),
+    LABEL(KEY_PLAYER),
+    LABEL(KEY_TEXT),
+    LABEL(KEY_DVD),
+    LABEL(KEY_AUX),
+    LABEL(KEY_MP3),
+    LABEL(KEY_AUDIO),
+    LABEL(KEY_VIDEO),
+    LABEL(KEY_DIRECTORY),
+    LABEL(KEY_LIST),
+    LABEL(KEY_MEMO),
+    LABEL(KEY_CALENDAR),
+    LABEL(KEY_RED),
+    LABEL(KEY_GREEN),
+    LABEL(KEY_YELLOW),
+    LABEL(KEY_BLUE),
+    LABEL(KEY_CHANNELUP),
+    LABEL(KEY_CHANNELDOWN),
+    LABEL(KEY_FIRST),
+    LABEL(KEY_LAST),
+    LABEL(KEY_AB),
+    LABEL(KEY_NEXT),
+    LABEL(KEY_RESTART),
+    LABEL(KEY_SLOW),
+    LABEL(KEY_SHUFFLE),
+    LABEL(KEY_BREAK),
+    LABEL(KEY_PREVIOUS),
+    LABEL(KEY_DIGITS),
+    LABEL(KEY_TEEN),
+    LABEL(KEY_TWEN),
+    LABEL(KEY_VIDEOPHONE),
+    LABEL(KEY_GAMES),
+    LABEL(KEY_ZOOMIN),
+    LABEL(KEY_ZOOMOUT),
+    LABEL(KEY_ZOOMRESET),
+    LABEL(KEY_WORDPROCESSOR),
+    LABEL(KEY_EDITOR),
+    LABEL(KEY_SPREADSHEET),
+    LABEL(KEY_GRAPHICSEDITOR),
+    LABEL(KEY_PRESENTATION),
+    LABEL(KEY_DATABASE),
+    LABEL(KEY_NEWS),
+    LABEL(KEY_VOICEMAIL),
+    LABEL(KEY_ADDRESSBOOK),
+    LABEL(KEY_MESSENGER),
+    LABEL(KEY_DISPLAYTOGGLE),
+    LABEL(KEY_SPELLCHECK),
+    LABEL(KEY_LOGOFF),
+    LABEL(KEY_DOLLAR),
+    LABEL(KEY_EURO),
+    LABEL(KEY_FRAMEBACK),
+    LABEL(KEY_FRAMEFORWARD),
+    LABEL(KEY_CONTEXT_MENU),
+    LABEL(KEY_MEDIA_REPEAT),
+    LABEL(KEY_10CHANNELSUP),
+    LABEL(KEY_10CHANNELSDOWN),
+    LABEL(KEY_IMAGES),
+    LABEL(KEY_DEL_EOL),
+    LABEL(KEY_DEL_EOS),
+    LABEL(KEY_INS_LINE),
+    LABEL(KEY_DEL_LINE),
+    LABEL(KEY_FN),
+    LABEL(KEY_FN_ESC),
+    LABEL(KEY_FN_F1),
+    LABEL(KEY_FN_F2),
+    LABEL(KEY_FN_F3),
+    LABEL(KEY_FN_F4),
+    LABEL(KEY_FN_F5),
+    LABEL(KEY_FN_F6),
+    LABEL(KEY_FN_F7),
+    LABEL(KEY_FN_F8),
+    LABEL(KEY_FN_F9),
+    LABEL(KEY_FN_F10),
+    LABEL(KEY_FN_F11),
+    LABEL(KEY_FN_F12),
+    LABEL(KEY_FN_1),
+    LABEL(KEY_FN_2),
+    LABEL(KEY_FN_D),
+    LABEL(KEY_FN_E),
+    LABEL(KEY_FN_F),
+    LABEL(KEY_FN_S),
+    LABEL(KEY_FN_B),
+    LABEL(KEY_BRL_DOT1),
+    LABEL(KEY_BRL_DOT2),
+    LABEL(KEY_BRL_DOT3),
+    LABEL(KEY_BRL_DOT4),
+    LABEL(KEY_BRL_DOT5),
+    LABEL(KEY_BRL_DOT6),
+    LABEL(KEY_BRL_DOT7),
+    LABEL(KEY_BRL_DOT8),
+    LABEL(KEY_BRL_DOT9),
+    LABEL(KEY_BRL_DOT10),
+    LABEL(KEY_NUMERIC_0),
+    LABEL(KEY_NUMERIC_1),
+    LABEL(KEY_NUMERIC_2),
+    LABEL(KEY_NUMERIC_3),
+    LABEL(KEY_NUMERIC_4),
+    LABEL(KEY_NUMERIC_5),
+    LABEL(KEY_NUMERIC_6),
+    LABEL(KEY_NUMERIC_7),
+    LABEL(KEY_NUMERIC_8),
+    LABEL(KEY_NUMERIC_9),
+    LABEL(KEY_NUMERIC_STAR),
+    LABEL(KEY_NUMERIC_POUND),
+    LABEL(KEY_NUMERIC_A),
+    LABEL(KEY_NUMERIC_B),
+    LABEL(KEY_NUMERIC_C),
+    LABEL(KEY_NUMERIC_D),
+    LABEL(KEY_CAMERA_FOCUS),
+    LABEL(KEY_WPS_BUTTON),
+    LABEL(KEY_TOUCHPAD_TOGGLE),
+    LABEL(KEY_TOUCHPAD_ON),
+    LABEL(KEY_TOUCHPAD_OFF),
+    LABEL(KEY_CAMERA_ZOOMIN),
+    LABEL(KEY_CAMERA_ZOOMOUT),
+    LABEL(KEY_CAMERA_UP),
+    LABEL(KEY_CAMERA_DOWN),
+    LABEL(KEY_CAMERA_LEFT),
+    LABEL(KEY_CAMERA_RIGHT),
+    LABEL(KEY_ATTENDANT_ON),
+    LABEL(KEY_ATTENDANT_OFF),
+    LABEL(KEY_ATTENDANT_TOGGLE),
+    LABEL(KEY_LIGHTS_TOGGLE),
+    LABEL(BTN_DPAD_UP),
+    LABEL(BTN_DPAD_DOWN),
+    LABEL(BTN_DPAD_LEFT),
+    LABEL(BTN_DPAD_RIGHT),
+    LABEL(KEY_ALS_TOGGLE),
+    LABEL(KEY_BUTTONCONFIG),
+    LABEL(KEY_TASKMANAGER),
+    LABEL(KEY_JOURNAL),
+    LABEL(KEY_CONTROLPANEL),
+    LABEL(KEY_APPSELECT),
+    LABEL(KEY_SCREENSAVER),
+    LABEL(KEY_VOICECOMMAND),
+    LABEL(KEY_ASSISTANT),
+    LABEL(KEY_BRIGHTNESS_MIN),
+    LABEL(KEY_BRIGHTNESS_MAX),
+    LABEL(KEY_KBDINPUTASSIST_PREV),
+    LABEL(KEY_KBDINPUTASSIST_NEXT),
+    LABEL(KEY_KBDINPUTASSIST_PREVGROUP),
+    LABEL(KEY_KBDINPUTASSIST_NEXTGROUP),
+    LABEL(KEY_KBDINPUTASSIST_ACCEPT),
+    LABEL(KEY_KBDINPUTASSIST_CANCEL),
+    LABEL(KEY_RIGHT_UP),
+    LABEL(KEY_RIGHT_DOWN),
+    LABEL(KEY_LEFT_UP),
+    LABEL(KEY_LEFT_DOWN),
+    LABEL(KEY_ROOT_MENU),
+    LABEL(KEY_MEDIA_TOP_MENU),
+    LABEL(KEY_NUMERIC_11),
+    LABEL(KEY_NUMERIC_12),
+    LABEL(KEY_AUDIO_DESC),
+    LABEL(KEY_3D_MODE),
+    LABEL(KEY_NEXT_FAVORITE),
+    LABEL(KEY_STOP_RECORD),
+    LABEL(KEY_PAUSE_RECORD),
+    LABEL(KEY_VOD),
+    LABEL(KEY_UNMUTE),
+    LABEL(KEY_FASTREVERSE),
+    LABEL(KEY_SLOWREVERSE),
+    LABEL(KEY_DATA),
+    LABEL(KEY_ONSCREEN_KEYBOARD),
+    LABEL(BTN_TRIGGER_HAPPY),
+    LABEL(BTN_TRIGGER_HAPPY1),
+    LABEL(BTN_TRIGGER_HAPPY2),
+    LABEL(BTN_TRIGGER_HAPPY3),
+    LABEL(BTN_TRIGGER_HAPPY4),
+    LABEL(BTN_TRIGGER_HAPPY5),
+    LABEL(BTN_TRIGGER_HAPPY6),
+    LABEL(BTN_TRIGGER_HAPPY7),
+    LABEL(BTN_TRIGGER_HAPPY8),
+    LABEL(BTN_TRIGGER_HAPPY9),
+    LABEL(BTN_TRIGGER_HAPPY10),
+    LABEL(BTN_TRIGGER_HAPPY11),
+    LABEL(BTN_TRIGGER_HAPPY12),
+    LABEL(BTN_TRIGGER_HAPPY13),
+    LABEL(BTN_TRIGGER_HAPPY14),
+    LABEL(BTN_TRIGGER_HAPPY15),
+    LABEL(BTN_TRIGGER_HAPPY16),
+    LABEL(BTN_TRIGGER_HAPPY17),
+    LABEL(BTN_TRIGGER_HAPPY18),
+    LABEL(BTN_TRIGGER_HAPPY19),
+    LABEL(BTN_TRIGGER_HAPPY20),
+    LABEL(BTN_TRIGGER_HAPPY21),
+    LABEL(BTN_TRIGGER_HAPPY22),
+    LABEL(BTN_TRIGGER_HAPPY23),
+    LABEL(BTN_TRIGGER_HAPPY24),
+    LABEL(BTN_TRIGGER_HAPPY25),
+    LABEL(BTN_TRIGGER_HAPPY26),
+    LABEL(BTN_TRIGGER_HAPPY27),
+    LABEL(BTN_TRIGGER_HAPPY28),
+    LABEL(BTN_TRIGGER_HAPPY29),
+    LABEL(BTN_TRIGGER_HAPPY30),
+    LABEL(BTN_TRIGGER_HAPPY31),
+    LABEL(BTN_TRIGGER_HAPPY32),
+    LABEL(BTN_TRIGGER_HAPPY33),
+    LABEL(BTN_TRIGGER_HAPPY34),
+    LABEL(BTN_TRIGGER_HAPPY35),
+    LABEL(BTN_TRIGGER_HAPPY36),
+    LABEL(BTN_TRIGGER_HAPPY37),
+    LABEL(BTN_TRIGGER_HAPPY38),
+    LABEL(BTN_TRIGGER_HAPPY39),
+    LABEL(BTN_TRIGGER_HAPPY40),
+    LABEL(KEY_MAX),
+    LABEL_END,
+};
+static struct label rel_labels[] = {
+    LABEL(REL_X),
+    LABEL(REL_Y),
+    LABEL(REL_Z),
+    LABEL(REL_RX),
+    LABEL(REL_RY),
+    LABEL(REL_RZ),
+    LABEL(REL_HWHEEL),
+    LABEL(REL_DIAL),
+    LABEL(REL_WHEEL),
+    LABEL(REL_MISC),
+    LABEL(REL_MAX),
+    LABEL_END,
+};
+static struct label abs_labels[] = {
+    LABEL(ABS_X),
+    LABEL(ABS_Y),
+    LABEL(ABS_Z),
+    LABEL(ABS_RX),
+    LABEL(ABS_RY),
+    LABEL(ABS_RZ),
+    LABEL(ABS_THROTTLE),
+    LABEL(ABS_RUDDER),
+    LABEL(ABS_WHEEL),
+    LABEL(ABS_GAS),
+    LABEL(ABS_BRAKE),
+    LABEL(ABS_HAT0X),
+    LABEL(ABS_HAT0Y),
+    LABEL(ABS_HAT1X),
+    LABEL(ABS_HAT1Y),
+    LABEL(ABS_HAT2X),
+    LABEL(ABS_HAT2Y),
+    LABEL(ABS_HAT3X),
+    LABEL(ABS_HAT3Y),
+    LABEL(ABS_PRESSURE),
+    LABEL(ABS_DISTANCE),
+    LABEL(ABS_TILT_X),
+    LABEL(ABS_TILT_Y),
+    LABEL(ABS_TOOL_WIDTH),
+    LABEL(ABS_VOLUME),
+    LABEL(ABS_MISC),
+    LABEL(ABS_MT_SLOT),
+    LABEL(ABS_MT_TOUCH_MAJOR),
+    LABEL(ABS_MT_TOUCH_MINOR),
+    LABEL(ABS_MT_WIDTH_MAJOR),
+    LABEL(ABS_MT_WIDTH_MINOR),
+    LABEL(ABS_MT_ORIENTATION),
+    LABEL(ABS_MT_POSITION_X),
+    LABEL(ABS_MT_POSITION_Y),
+    LABEL(ABS_MT_TOOL_TYPE),
+    LABEL(ABS_MT_BLOB_ID),
+    LABEL(ABS_MT_TRACKING_ID),
+    LABEL(ABS_MT_PRESSURE),
+    LABEL(ABS_MT_DISTANCE),
+    LABEL(ABS_MT_TOOL_X),
+    LABEL(ABS_MT_TOOL_Y),
+    LABEL(ABS_MAX),
+    LABEL_END,
+};
+static struct label sw_labels[] = {
+    LABEL(SW_LID),
+    LABEL(SW_TABLET_MODE),
+    LABEL(SW_HEADPHONE_INSERT),
+    LABEL(SW_RFKILL_ALL),
+    LABEL(SW_MICROPHONE_INSERT),
+    LABEL(SW_DOCK),
+    LABEL(SW_LINEOUT_INSERT),
+    LABEL(SW_JACK_PHYSICAL_INSERT),
+    LABEL(SW_VIDEOOUT_INSERT),
+    LABEL(SW_CAMERA_LENS_COVER),
+    LABEL(SW_KEYPAD_SLIDE),
+    LABEL(SW_FRONT_PROXIMITY),
+    LABEL(SW_ROTATE_LOCK),
+    LABEL(SW_LINEIN_INSERT),
+    LABEL(SW_MUTE_DEVICE),
+    LABEL(SW_PEN_INSERTED),
+    LABEL(SW_MAX),
+    LABEL_END,
+};
+static struct label msc_labels[] = {
+    LABEL(MSC_SERIAL),
+    LABEL(MSC_PULSELED),
+    LABEL(MSC_GESTURE),
+    LABEL(MSC_RAW),
+    LABEL(MSC_SCAN),
+    LABEL(MSC_TIMESTAMP),
+    LABEL(MSC_MAX),
+    LABEL_END,
+};
+static struct label led_labels[] = {
+    LABEL(LED_NUML),
+    LABEL(LED_CAPSL),
+    LABEL(LED_SCROLLL),
+    LABEL(LED_COMPOSE),
+    LABEL(LED_KANA),
+    LABEL(LED_SLEEP),
+    LABEL(LED_SUSPEND),
+    LABEL(LED_MUTE),
+    LABEL(LED_MISC),
+    LABEL(LED_MAIL),
+    LABEL(LED_CHARGING),
+    LABEL(LED_MAX),
+    LABEL_END,
+};
+static struct label rep_labels[] = {
+    LABEL(REP_DELAY),
+    LABEL(REP_PERIOD),
+    LABEL(REP_MAX),
+    LABEL_END,
+};
+static struct label snd_labels[] = {
+    LABEL(SND_CLICK),
+    LABEL(SND_BELL),
+    LABEL(SND_TONE),
+    LABEL(SND_MAX),
+    LABEL_END,
+};
+static struct label mt_tool_labels[] = {
+    LABEL(MT_TOOL_FINGER),
+    LABEL(MT_TOOL_PEN),
+    LABEL(MT_TOOL_PALM),
+    LABEL(MT_TOOL_MAX),
+    LABEL_END,
+};
+static struct label ff_status_labels[] = {
+    LABEL(FF_STATUS_STOPPED),
+    LABEL(FF_STATUS_PLAYING),
+    LABEL(FF_STATUS_MAX),
+    LABEL_END,
+};
+static struct label ff_labels[] = {
+    LABEL(FF_RUMBLE),
+    LABEL(FF_PERIODIC),
+    LABEL(FF_CONSTANT),
+    LABEL(FF_SPRING),
+    LABEL(FF_FRICTION),
+    LABEL(FF_DAMPER),
+    LABEL(FF_INERTIA),
+    LABEL(FF_RAMP),
+    LABEL(FF_SQUARE),
+    LABEL(FF_TRIANGLE),
+    LABEL(FF_SINE),
+    LABEL(FF_SAW_UP),
+    LABEL(FF_SAW_DOWN),
+    LABEL(FF_CUSTOM),
+    LABEL(FF_GAIN),
+    LABEL(FF_AUTOCENTER),
+    LABEL(FF_MAX),
+    LABEL_END,
+};
diff --git a/tools/recovery_l10n/Android.mk b/tools/recovery_l10n/Android.mk
index 9fb797c..fd53c59 100644
--- a/tools/recovery_l10n/Android.mk
+++ b/tools/recovery_l10n/Android.mk
@@ -9,6 +9,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_PACKAGE_NAME := RecoveryLocalizer
+LOCAL_SDK_VERSION := current
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tools/recovery_l10n/res/values-as/strings.xml b/tools/recovery_l10n/res/values-as/strings.xml
new file mode 100644
index 0000000..2624ceb
--- /dev/null
+++ b/tools/recovery_l10n/res/values-as/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="recovery_installing" msgid="2013591905463558223">"আপডেইট ইনষ্টল কৰি থকা হৈছে"</string>
+    <string name="recovery_erasing" msgid="7334826894904037088">"মচি থকা হৈছে"</string>
+    <string name="recovery_no_command" msgid="4465476568623024327">"কোনো আদেশ নাই"</string>
+    <string name="recovery_error" msgid="5748178989622716736">"ত্ৰুটি!"</string>
+    <string name="recovery_installing_security" msgid="9184031299717114342">"সুৰক্ষা আপডেইট ইনষ্টল কৰি থকা হৈছে"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-or/strings.xml b/tools/recovery_l10n/res/values-or/strings.xml
new file mode 100644
index 0000000..2b0851c
--- /dev/null
+++ b/tools/recovery_l10n/res/values-or/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="recovery_installing" msgid="2013591905463558223">"ସିଷ୍ଟମ ଅପଡେଟ ଇନଷ୍ଟଲ କରୁଛି"</string>
+    <string name="recovery_erasing" msgid="7334826894904037088">"ଲିଭାଉଛି"</string>
+    <string name="recovery_no_command" msgid="4465476568623024327">"କୌଣସି କମାଣ୍ଡ ନାହିଁ"</string>
+    <string name="recovery_error" msgid="5748178989622716736">"ତ୍ରୁଟି!"</string>
+    <string name="recovery_installing_security" msgid="9184031299717114342">"ସୁରକ୍ଷା ଅପ୍‌ଡେଟ୍‌ ଇନ୍‌ଷ୍ଟଲ୍‌ କରୁଛି"</string>
+</resources>
diff --git a/toybox/Android.mk b/toybox/Android.mk
index 8c3d7c5..5b2f32f 100644
--- a/toybox/Android.mk
+++ b/toybox/Android.mk
@@ -275,7 +275,6 @@
     toys/net/tunctl.c \
     toys/pending/getfattr.c \
     toys/pending/modprobe.c \
-    toys/pending/setfattr.c \
     toys/posix/file.c \
     toys/posix/uudecode.c \
     toys/posix/uuencode.c
@@ -288,6 +287,16 @@
 LOCAL_SRC_FILES += \
     toys/pending/chrt.c
 endif
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 27; echo $$?),0)
+# Android 9.0
+LOCAL_SRC_FILES += \
+    toys/other/setfattr.c \
+    toys/pending/fmt.c \
+    toys/pending/stty.c
+else
+LOCAL_SRC_FILES += \
+    toys/pending/setfattr.c
+endif
 LOCAL_SHARED_LIBRARIES += liblog
 else
 LOCAL_SRC_FILES += \
@@ -310,7 +319,7 @@
 endif
 
 LOCAL_CFLAGS += \
-    -std=c99 \
+    -std=gnu11 \
     -Os \
     -Wno-char-subscripts \
     -Wno-sign-compare \
@@ -380,7 +389,6 @@
     find \
     free \
     getenforce \
-    getprop \
     groups \
     head \
     hostname \
@@ -526,6 +534,7 @@
     xxd
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 27; echo $$?),0)
 ALL_TOOLS += \
+    getprop \
     xzcat
 endif
 ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 24; echo $$?),0)
@@ -534,6 +543,13 @@
     gzip \
     gunzip \
     zcat
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 27; echo $$?),0)
+ALL_TOOLS += \
+    fmt \
+    start \
+    stop \
+    stty
+endif
 endif
 # Account for master branch changes pulld into CM14.1
 ifneq ($(CM_BUILD),)
diff --git a/twinstall.cpp b/twinstall.cpp
index d63088c..a2f54c9 100644
--- a/twinstall.cpp
+++ b/twinstall.cpp
@@ -193,7 +193,7 @@
 
 	void *data = mmap(NULL, finfo.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
 	if (data == MAP_FAILED) {
-		LOGINFO("has_legacy_properties: mmap (size=%lld) failed: %s!\n", finfo.st_size, strerror(errno));
+		LOGINFO("has_legacy_properties: mmap (size=%zu) failed: %s!\n", finfo.st_size, strerror(errno));
 	} else {
 		if (memmem(data, finfo.st_size, str_to_match, len_to_match)) {
 			LOGINFO("has_legacy_properties: Found legacy property match!\n");
diff --git a/twrp-functions.cpp b/twrp-functions.cpp
index c9aef20..40205c9 100644
--- a/twrp-functions.cpp
+++ b/twrp-functions.cpp
@@ -152,7 +152,6 @@
 	if (retpid == 0 && timeout == 0) {
 		LOGERR("%s took too long, killing process\n", Child_Name.c_str());
 		kill(pid, SIGKILL);
-		int died = 0;
 		for (timeout = 5; retpid == 0 && timeout; --timeout) {
 			sleep(1);
 			retpid = waitpid(pid, status, WNOHANG);
diff --git a/twrpAdbBuFifo.cpp b/twrpAdbBuFifo.cpp
index 1147d63..f98eb05 100644
--- a/twrpAdbBuFifo.cpp
+++ b/twrpAdbBuFifo.cpp
@@ -41,7 +41,6 @@
 
 void twrpAdbBuFifo::Check_Adb_Fifo_For_Events(void) {
 	char cmd[512];
-	int ret;
 
 	memset(&cmd, 0, sizeof(cmd));
 
@@ -169,10 +168,9 @@
 bool twrpAdbBuFifo::Restore_ADB_Backup(void) {
 	int partition_count = 0;
 	std::string Restore_Name;
-	std::size_t pos = 0;
 	struct AdbBackupFileTrailer adbmd5;
 	struct PartitionSettings part_settings;
-	int adb_control_twrp_fd, adb_write_fd, systemro;
+	int adb_control_twrp_fd;
 	int adb_control_bu_fd, ret = 0;
 	char cmd[512];
 
diff --git a/twrpTar.hpp b/twrpTar.hpp
index b07b06f..eb82fa8 100644
--- a/twrpTar.hpp
+++ b/twrpTar.hpp
@@ -106,6 +106,5 @@
 
 	std::vector<TarListStruct> *ItemList;
 	int output_fd;                                                                  // this stores the output fd that gzip will read from
-	int adb_control_twrp_fd, adb_control_bu_fd;                                     // fds for twrp to twrp bu and bu to twrp control fifos
 	unsigned thread_id;
 };
diff --git a/ui.cpp b/ui.cpp
index e80d7ed..3c9ded7 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -48,12 +48,16 @@
 static constexpr int UI_WAIT_KEY_TIMEOUT_SEC = 120;
 static constexpr const char* BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/brightness";
 static constexpr const char* MAX_BRIGHTNESS_FILE = "/sys/class/leds/lcd-backlight/max_brightness";
+static constexpr const char* BRIGHTNESS_FILE_SDM =
+    "/sys/class/backlight/panel0-backlight/brightness";
+static constexpr const char* MAX_BRIGHTNESS_FILE_SDM =
+    "/sys/class/backlight/panel0-backlight/max_brightness";
 
 RecoveryUI::RecoveryUI()
-    : locale_(""),
-      rtl_locale_(false),
-      brightness_normal_(50),
+    : brightness_normal_(50),
       brightness_dimmed_(25),
+      brightness_file_(BRIGHTNESS_FILE),
+      max_brightness_file_(MAX_BRIGHTNESS_FILE),
       touch_screen_allowed_(false),
       kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD),
       kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD),
@@ -103,12 +107,17 @@
   if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) {
     return false;
   }
-
+  if (access(brightness_file_.c_str(), R_OK | W_OK)) {
+    brightness_file_ = BRIGHTNESS_FILE_SDM;
+  }
+  if (access(max_brightness_file_.c_str(), R_OK)) {
+    max_brightness_file_ = MAX_BRIGHTNESS_FILE_SDM;
+  }
   // Set the initial brightness level based on the max brightness. Note that reading the initial
   // value from BRIGHTNESS_FILE doesn't give the actual brightness value (bullhead, sailfish), so
   // we don't have a good way to query the default value.
   std::string content;
-  if (!android::base::ReadFileToString(MAX_BRIGHTNESS_FILE, &content)) {
+  if (!android::base::ReadFileToString(max_brightness_file_, &content)) {
     PLOG(WARNING) << "Failed to read max brightness";
     return false;
   }
@@ -122,7 +131,7 @@
   brightness_normal_value_ = max_value * brightness_normal_ / 100.0;
   brightness_dimmed_value_ = max_value * brightness_dimmed_ / 100.0;
   if (!android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
-                                        BRIGHTNESS_FILE)) {
+                                        brightness_file_)) {
     PLOG(WARNING) << "Failed to set brightness";
     return false;
   }
@@ -132,10 +141,7 @@
   return true;
 }
 
-bool RecoveryUI::Init(const std::string& locale) {
-  // Set up the locale info.
-  SetLocale(locale);
-
+bool RecoveryUI::Init(const std::string& /* locale */) {
   ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2),
           touch_screen_allowed_);
 
@@ -435,13 +441,13 @@
         // Lower the brightness level: NORMAL -> DIMMED; DIMMED -> OFF.
         if (screensaver_state_ == ScreensaverState::NORMAL) {
           if (android::base::WriteStringToFile(std::to_string(brightness_dimmed_value_),
-                                               BRIGHTNESS_FILE)) {
+                                               brightness_file_)) {
             LOG(INFO) << "Brightness: " << brightness_dimmed_value_ << " (" << brightness_dimmed_
                       << "%)";
             screensaver_state_ = ScreensaverState::DIMMED;
           }
         } else if (screensaver_state_ == ScreensaverState::DIMMED) {
-          if (android::base::WriteStringToFile("0", BRIGHTNESS_FILE)) {
+          if (android::base::WriteStringToFile("0", brightness_file_)) {
             LOG(INFO) << "Brightness: 0 (off)";
             screensaver_state_ = ScreensaverState::OFF;
           }
@@ -456,7 +462,7 @@
 
         // Reset the brightness to normal.
         if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
-                                             BRIGHTNESS_FILE)) {
+                                             brightness_file_)) {
           screensaver_state_ = ScreensaverState::NORMAL;
           LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
                     << "%)";
@@ -574,23 +580,3 @@
   enable_reboot = enabled;
   pthread_mutex_unlock(&key_queue_mutex);
 }
-
-void RecoveryUI::SetLocale(const std::string& new_locale) {
-  this->locale_ = new_locale;
-  this->rtl_locale_ = false;
-
-  if (!new_locale.empty()) {
-    size_t underscore = new_locale.find('_');
-    // lang has the language prefix prior to '_', or full string if '_' doesn't exist.
-    std::string lang = new_locale.substr(0, underscore);
-
-    // A bit cheesy: keep an explicit list of supported RTL languages.
-    if (lang == "ar" ||  // Arabic
-        lang == "fa" ||  // Persian (Farsi)
-        lang == "he" ||  // Hebrew (new language code)
-        lang == "iw" ||  // Hebrew (old language code)
-        lang == "ur") {  // Urdu
-      rtl_locale_ = true;
-    }
-  }
-}
diff --git a/ui.h b/ui.h
index 3d9afec..4c54d69 100644
--- a/ui.h
+++ b/ui.h
@@ -26,6 +26,27 @@
 // Abstract class for controlling the user interface during recovery.
 class RecoveryUI {
  public:
+  enum Icon {
+    NONE,
+    INSTALLING_UPDATE,
+    ERASING,
+    NO_COMMAND,
+    ERROR
+  };
+
+  enum ProgressType {
+    EMPTY,
+    INDETERMINATE,
+    DETERMINATE
+  };
+
+  enum KeyAction {
+    ENQUEUE,
+    TOGGLE,
+    REBOOT,
+    IGNORE
+  };
+
   RecoveryUI();
 
   virtual ~RecoveryUI() {}
@@ -38,12 +59,10 @@
   virtual void SetStage(int current, int max) = 0;
 
   // Sets the overall recovery state ("background image").
-  enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR };
   virtual void SetBackground(Icon icon) = 0;
   virtual void SetSystemUpdateText(bool security_update) = 0;
 
   // --- progress indicator ---
-  enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE };
   virtual void SetProgressType(ProgressType determinate) = 0;
 
   // Shows a progress bar and define the scope of the next operation:
@@ -94,7 +113,6 @@
   // Called on each key press, even while operations are in progress. Return value indicates whether
   // an immediate operation should be triggered (toggling the display, rebooting the device), or if
   // the key should be enqueued for use by the main thread.
-  enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE };
   virtual KeyAction CheckKey(int key, bool is_long_press);
 
   // Called when a key is held down long enough to have been a long-press (but before the key is
@@ -125,24 +143,47 @@
  protected:
   void EnqueueKey(int key_code);
 
-  // The locale that's used to show the rendered texts.
-  std::string locale_;
-  bool rtl_locale_;
-
   // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% of
   // the max_brightness). Because the absolute values may vary across devices. These two values can
   // be configured via subclassing. Setting brightness_normal_ to 0 to disable screensaver.
   unsigned int brightness_normal_;
   unsigned int brightness_dimmed_;
+  std::string brightness_file_;
+  std::string max_brightness_file_;
 
   // Whether we should listen for touch inputs (default: false).
   bool touch_screen_allowed_;
 
  private:
+  enum class ScreensaverState {
+    DISABLED,
+    NORMAL,
+    DIMMED,
+    OFF
+  };
+
+  struct key_timer_t {
+    RecoveryUI* ui;
+    int key_code;
+    int count;
+  };
+
   // The sensitivity when detecting a swipe.
   const int kTouchLowThreshold;
   const int kTouchHighThreshold;
 
+  void OnKeyDetected(int key_code);
+  void OnTouchDetected(int dx, int dy);
+  int OnInputEvent(int fd, uint32_t epevents);
+  void ProcessKey(int key_code, int updown);
+
+  bool IsUsbConnected();
+
+  static void* time_key_helper(void* cookie);
+  void time_key(int key_code, int count);
+
+  bool InitScreensaver();
+
   // Key event input queue
   pthread_mutex_t key_queue_mutex;
   pthread_cond_t key_queue_cond;
@@ -172,33 +213,14 @@
   bool touch_swiping_;
   bool is_bootreason_recovery_ui_;
 
-  struct key_timer_t {
-    RecoveryUI* ui;
-    int key_code;
-    int count;
-  };
-
   pthread_t input_thread_;
 
-  void OnKeyDetected(int key_code);
-  void OnTouchDetected(int dx, int dy);
-  int OnInputEvent(int fd, uint32_t epevents);
-  void ProcessKey(int key_code, int updown);
-
-  bool IsUsbConnected();
-
-  static void* time_key_helper(void* cookie);
-  void time_key(int key_code, int count);
-
-  void SetLocale(const std::string&);
-
-  enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF };
   ScreensaverState screensaver_state_;
+
   // The following two contain the absolute values computed from brightness_normal_ and
   // brightness_dimmed_ respectively.
   unsigned int brightness_normal_value_;
   unsigned int brightness_dimmed_value_;
-  bool InitScreensaver();
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/bootloader_message/Android.NObp b/uncrypt/Android.bp
similarity index 70%
copy from bootloader_message/Android.NObp
copy to uncrypt/Android.bp
index f0d76e7..aa56d2f 100644
--- a/bootloader_message/Android.NObp
+++ b/uncrypt/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2017 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,15 +11,29 @@
 // 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: "libbootloader_message",
-    srcs: ["bootloader_message.cpp"],
-    cppflags: ["-Werror"],
-    static_libs: [
-        "libbase",
-        "libfs_mgr",
+cc_binary {
+    name: "uncrypt",
+
+    srcs: [
+        "uncrypt.cpp",
     ],
-    export_include_dirs: ["include"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    static_libs: [
+        "libbootloader_message",
+        "libotautil",
+        "libfs_mgr",
+        "libbase",
+        "libcutils",
+        "liblog",
+    ],
+
+    init_rc: [
+        "uncrypt.rc",
+    ],
 }
diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp
index ad3bdce..bb43c2c 100644
--- a/uncrypt/uncrypt.cpp
+++ b/uncrypt/uncrypt.cpp
@@ -116,7 +116,7 @@
 #include <cutils/sockets.h>
 #include <fs_mgr.h>
 
-#include "error_code.h"
+#include "otautil/error_code.h"
 
 static constexpr int WINDOW_SIZE = 5;
 static constexpr int FIBMAP_RETRY_LIMIT = 3;
@@ -172,10 +172,14 @@
     return fstab;
 }
 
-static const char* find_block_device(const char* path, bool* encryptable, bool* encrypted) {
+static const char* find_block_device(const char* path, bool* encryptable, bool* encrypted, bool *f2fs_fs) {
     // Look for a volume whose mount point is the prefix of path and
     // return its block device.  Set encrypted if it's currently
     // encrypted.
+
+    // ensure f2fs_fs is set to 0 first.
+    if (f2fs_fs)
+        *f2fs_fs = false;
     for (int i = 0; i < fstab->num_entries; ++i) {
         struct fstab_rec* v = &fstab->recs[i];
         if (!v->mount_point) {
@@ -192,6 +196,8 @@
                     *encrypted = true;
                 }
             }
+            if (f2fs_fs && strcmp(v->fs_type, "f2fs") == 0)
+                *f2fs_fs = true;
             return v->blk_device;
         }
     }
@@ -244,7 +250,7 @@
 }
 
 static int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
-                             bool encrypted, int socket) {
+                             bool encrypted, bool f2fs_fs, int socket) {
     std::string err;
     if (!android::base::RemoveFileIfExists(map_file, &err)) {
         LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err;
@@ -307,6 +313,17 @@
         }
     }
 
+#ifndef F2FS_IOC_SET_DONTMOVE
+#ifndef F2FS_IOCTL_MAGIC
+#define F2FS_IOCTL_MAGIC		0xf5
+#endif
+#define F2FS_IOC_SET_DONTMOVE		_IO(F2FS_IOCTL_MAGIC, 13)
+#endif
+    if (f2fs_fs && ioctl(fd, F2FS_IOC_SET_DONTMOVE) < 0) {
+        PLOG(ERROR) << "Failed to set non-movable file for f2fs: " << path << " on " << blk_dev;
+        return kUncryptIoctlError;
+    }
+
     off64_t pos = 0;
     int last_progress = 0;
     while (pos < sb.st_size) {
@@ -448,20 +465,21 @@
 static int uncrypt(const char* input_path, const char* map_file, const int socket) {
     LOG(INFO) << "update package is \"" << input_path << "\"";
 
-    // Turn the name of the file we're supposed to convert into an
-    // absolute path, so we can find what filesystem it's on.
+    // Turn the name of the file we're supposed to convert into an absolute path, so we can find
+    // what filesystem it's on.
     char path[PATH_MAX+1];
-    if (realpath(input_path, path) == NULL) {
+    if (realpath(input_path, path) == nullptr) {
         PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path";
-        return 1;
+        return kUncryptRealpathFindError;
     }
 
     bool encryptable;
     bool encrypted;
-    const char* blk_dev = find_block_device(path, &encryptable, &encrypted);
-    if (blk_dev == NULL) {
+    bool f2fs_fs;
+    const char* blk_dev = find_block_device(path, &encryptable, &encrypted, &f2fs_fs);
+    if (blk_dev == nullptr) {
         LOG(ERROR) << "failed to find block device for " << path;
-        return 1;
+        return kUncryptBlockDeviceFindError;
     }
 
     // If the filesystem it's on isn't encrypted, we only produce the
@@ -479,7 +497,7 @@
     // and /sdcard we leave the file alone.
     if (strncmp(path, "/data/", 6) == 0) {
         LOG(INFO) << "writing block map " << map_file;
-        return produce_block_map(path, map_file, blk_dev, encrypted, socket);
+        return produce_block_map(path, map_file, blk_dev, encrypted, f2fs_fs, socket);
     }
 
     return 0;
diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk
index 33c5fe9..0ff8854 100644
--- a/update_verifier/Android.mk
+++ b/update_verifier/Android.mk
@@ -22,6 +22,10 @@
     update_verifier.cpp
 
 LOCAL_MODULE := libupdate_verifier
+
+LOCAL_STATIC_LIBRARIES := \
+    libotautil
+
 LOCAL_SHARED_LIBRARIES := \
     libbase \
     libcutils \
@@ -54,7 +58,9 @@
 
 LOCAL_MODULE := update_verifier
 LOCAL_STATIC_LIBRARIES := \
-    libupdate_verifier
+    libupdate_verifier \
+    libotautil
+
 LOCAL_SHARED_LIBRARIES := \
     libbase \
     libcutils \
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index ba7b7ae..92d9313 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -58,6 +58,8 @@
 #include <android/hardware/boot/1.0/IBootControl.h>
 #include <cutils/android_reboot.h>
 
+#include "otautil/rangeset.h"
+
 using android::sp;
 using android::hardware::boot::V1_0::IBootControl;
 using android::hardware::boot::V1_0::BoolResult;
@@ -72,14 +74,13 @@
 }
 
 static bool read_blocks(const std::string& partition, const std::string& range_str) {
-  if (partition != "system" && partition != "vendor") {
-    LOG(ERROR) << "partition name must be system or vendor: " << partition;
+  if (partition != "system" && partition != "vendor" && partition != "product") {
+    LOG(ERROR) << "Invalid partition name \"" << partition << "\"";
     return false;
   }
-  // Iterate the content of "/sys/block/dm-X/dm/name". If it matches "system"
-  // (or "vendor"), then dm-X is a dm-wrapped system/vendor partition.
-  // Afterwards, update_verifier will read every block on the care_map_file of
-  // "/dev/block/dm-X" to ensure the partition's integrity.
+  // Iterate the content of "/sys/block/dm-X/dm/name". If it matches one of "system", "vendor" or
+  // "product", then dm-X is a dm-wrapped device for that target. We will later read all the
+  // ("cared") blocks from "/dev/block/dm-X" to ensure the target partition's integrity.
   static constexpr auto DM_PATH_PREFIX = "/sys/block/";
   dirent** namelist;
   int n = scandir(DM_PATH_PREFIX, &namelist, dm_name_filter, alphasort);
@@ -129,42 +130,33 @@
   // followed by 'count' number comma separated integers. Every two integers reprensent a
   // block range with the first number included in range but second number not included.
   // For example '4,64536,65343,74149,74150' represents: [64536,65343) and [74149,74150).
-  std::vector<std::string> ranges = android::base::Split(range_str, ",");
-  size_t range_count;
-  bool status = android::base::ParseUint(ranges[0], &range_count);
-  if (!status || (range_count == 0) || (range_count % 2 != 0) ||
-      (range_count != ranges.size() - 1)) {
-    LOG(ERROR) << "Error in parsing range string.";
+  RangeSet ranges = RangeSet::Parse(range_str);
+  if (!ranges) {
+    LOG(ERROR) << "Error parsing RangeSet string " << range_str;
     return false;
   }
-  range_count /= 2;
+
+  // RangeSet::Split() splits the ranges into multiple groups with same number of blocks (except for
+  // the last group).
+  size_t thread_num = std::thread::hardware_concurrency() ?: 4;
+  std::vector<RangeSet> groups = ranges.Split(thread_num);
 
   std::vector<std::future<bool>> threads;
-  size_t thread_num = std::thread::hardware_concurrency() ?: 4;
-  thread_num = std::min(thread_num, range_count);
-  size_t group_range_count = (range_count + thread_num - 1) / thread_num;
-
-  for (size_t t = 0; t < thread_num; t++) {
-    auto thread_func = [t, group_range_count, &dm_block_device, &ranges, &partition]() {
-      size_t blk_count = 0;
-      static constexpr size_t kBlockSize = 4096;
-      std::vector<uint8_t> buf(1024 * kBlockSize);
+  for (const auto& group : groups) {
+    auto thread_func = [&group, &dm_block_device, &partition]() {
       android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
       if (fd.get() == -1) {
         PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition;
         return false;
       }
 
-      for (size_t i = group_range_count * 2 * t + 1;
-           i < std::min(group_range_count * 2 * (t + 1) + 1, ranges.size()); i += 2) {
-        unsigned int range_start, range_end;
-        bool parse_status = android::base::ParseUint(ranges[i], &range_start);
-        parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end);
-        if (!parse_status || range_start >= range_end) {
-          LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1];
-          return false;
-        }
+      static constexpr size_t kBlockSize = 4096;
+      std::vector<uint8_t> buf(1024 * kBlockSize);
 
+      size_t block_count = 0;
+      for (const auto& range : group) {
+        size_t range_start = range.first;
+        size_t range_end = range.second;
         if (lseek64(fd.get(), static_cast<off64_t>(range_start) * kBlockSize, SEEK_SET) == -1) {
           PLOG(ERROR) << "lseek to " << range_start << " failed";
           return false;
@@ -179,9 +171,9 @@
           }
           remain -= to_read;
         }
-        blk_count += (range_end - range_start);
+        block_count += (range_end - range_start);
       }
-      LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device;
+      LOG(INFO) << "Finished reading " << block_count << " blocks on " << dm_block_device;
       return true;
     };
 
@@ -213,10 +205,9 @@
     PLOG(WARNING) << "Failed to open " << care_map_name;
     return true;
   }
-  // Care map file has four lines (two lines if vendor partition is not present):
-  // First line has the block partition name (system/vendor).
-  // Second line holds all ranges of blocks to verify.
-  // The next two lines have the same format but for vendor partition.
+  // care_map file has up to six lines, where every two lines make a pair. Within each pair, the
+  // first line has the partition name (e.g. "system"), while the second line holds the ranges of
+  // all the blocks to verify.
   std::string file_content;
   if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) {
     LOG(ERROR) << "Error reading care map contents to string.";
@@ -225,9 +216,9 @@
 
   std::vector<std::string> lines;
   lines = android::base::Split(android::base::Trim(file_content), "\n");
-  if (lines.size() != 2 && lines.size() != 4) {
+  if (lines.size() != 2 && lines.size() != 4 && lines.size() != 6) {
     LOG(ERROR) << "Invalid lines in care_map: found " << lines.size()
-               << " lines, expecting 2 or 4 lines.";
+               << " lines, expecting 2 or 4 or 6 lines.";
     return false;
   }
 
diff --git a/update_verifier/update_verifier_main.cpp b/update_verifier/update_verifier_main.cpp
index 46e8bbb..a86203b 100644
--- a/update_verifier/update_verifier_main.cpp
+++ b/update_verifier/update_verifier_main.cpp
@@ -16,8 +16,23 @@
 
 // See the comments in update_verifier.cpp.
 
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+
 #include "update_verifier/update_verifier.h"
 
 int main(int argc, char** argv) {
+  std::string s = android::base::GetProperty("ro.boot.slot_suffix", "");
+
+  if (s.empty()) {
+    return 0;  // non-A/B update device, so we quit
+  }
+
+  // Set up update_verifier logging to be written to kmsg; because we may not have Logd during
+  // boot time.
+  android::base::InitLogging(argv, &android::base::KernelLogger);
+
   return update_verifier(argc, argv);
 }
diff --git a/updater/Android.mk b/updater/Android.mk
index cef6b96..3d25913 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -71,7 +71,7 @@
     external/e2fsprogs/misc
 
 LOCAL_CFLAGS := \
-    -Wno-unused-parameter \
+    -Wall \
     -Werror
 
 LOCAL_EXPORT_C_INCLUDE_DIRS := \
@@ -96,7 +96,7 @@
     $(LOCAL_PATH)/include
 
 LOCAL_CFLAGS := \
-    -Wno-unused-parameter \
+    -Wall \
     -Werror
 
 LOCAL_STATIC_LIBRARIES := \
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index a0b9ad2..e93196b 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -34,11 +34,13 @@
 #include <fec/io.h>
 
 #include <functional>
+#include <limits>
 #include <memory>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
@@ -50,11 +52,12 @@
 #include <ziparchive/zip_archive.h>
 
 #include "edify/expr.h"
-#include "error_code.h"
-#include "ota_io.h"
-#include "print_sha1.h"
+#include "otafault/ota_io.h"
+#include "otautil/cache_location.h"
+#include "otautil/error_code.h"
+#include "otautil/print_sha1.h"
+#include "otautil/rangeset.h"
 #include "updater/install.h"
-#include "updater/rangeset.h"
 #include "updater/updater.h"
 
 // Set this to 0 to interpret 'erase' transfers to mean do a
@@ -63,7 +66,6 @@
 #define DEBUG_ERASE  0
 
 static constexpr size_t BLOCKSIZE = 4096;
-static constexpr const char* STASH_DIRECTORY_BASE = "/cache/recovery";
 static constexpr mode_t STASH_DIRECTORY_MODE = 0700;
 static constexpr mode_t STASH_FILE_MODE = 0600;
 
@@ -71,6 +73,93 @@
 static bool is_retry = false;
 static std::unordered_map<std::string, RangeSet> stash_map;
 
+static void DeleteLastCommandFile() {
+  std::string last_command_file = CacheLocation::location().last_command_file();
+  if (unlink(last_command_file.c_str()) == -1 && errno != ENOENT) {
+    PLOG(ERROR) << "Failed to unlink: " << last_command_file;
+  }
+}
+
+// Parse the last command index of the last update and save the result to |last_command_index|.
+// Return true if we successfully read the index.
+static bool ParseLastCommandFile(int* last_command_index) {
+  std::string last_command_file = CacheLocation::location().last_command_file();
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(last_command_file.c_str(), O_RDONLY)));
+  if (fd == -1) {
+    if (errno != ENOENT) {
+      PLOG(ERROR) << "Failed to open " << last_command_file;
+      return false;
+    }
+
+    LOG(INFO) << last_command_file << " doesn't exist.";
+    return false;
+  }
+
+  // Now that the last_command file exists, parse the last command index of previous update.
+  std::string content;
+  if (!android::base::ReadFdToString(fd.get(), &content)) {
+    LOG(ERROR) << "Failed to read: " << last_command_file;
+    return false;
+  }
+
+  std::vector<std::string> lines = android::base::Split(android::base::Trim(content), "\n");
+  if (lines.size() != 2) {
+    LOG(ERROR) << "Unexpected line counts in last command file: " << content;
+    return false;
+  }
+
+  if (!android::base::ParseInt(lines[0], last_command_index)) {
+    LOG(ERROR) << "Failed to parse integer in: " << lines[0];
+    return false;
+  }
+
+  return true;
+}
+
+// Update the last command index in the last_command_file if the current command writes to the
+// stash either explicitly or implicitly.
+static bool UpdateLastCommandIndex(int command_index, const std::string& command_string) {
+  std::string last_command_file = CacheLocation::location().last_command_file();
+  std::string last_command_tmp = last_command_file + ".tmp";
+  std::string content = std::to_string(command_index) + "\n" + command_string;
+  android::base::unique_fd wfd(
+      TEMP_FAILURE_RETRY(open(last_command_tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0660)));
+  if (wfd == -1 || !android::base::WriteStringToFd(content, wfd)) {
+    PLOG(ERROR) << "Failed to update last command";
+    return false;
+  }
+
+  if (fsync(wfd) == -1) {
+    PLOG(ERROR) << "Failed to fsync " << last_command_tmp;
+    return false;
+  }
+
+  if (chown(last_command_tmp.c_str(), AID_SYSTEM, AID_SYSTEM) == -1) {
+    PLOG(ERROR) << "Failed to change owner for " << last_command_tmp;
+    return false;
+  }
+
+  if (rename(last_command_tmp.c_str(), last_command_file.c_str()) == -1) {
+    PLOG(ERROR) << "Failed to rename" << last_command_tmp;
+    return false;
+  }
+
+  std::string last_command_dir = android::base::Dirname(last_command_file);
+  android::base::unique_fd dfd(
+      TEMP_FAILURE_RETRY(ota_open(last_command_dir.c_str(), O_RDONLY | O_DIRECTORY)));
+  if (dfd == -1) {
+    PLOG(ERROR) << "Failed to open " << last_command_dir;
+    return false;
+  }
+
+  if (fsync(dfd) == -1) {
+    PLOG(ERROR) << "Failed to fsync " << last_command_dir;
+    return false;
+  }
+
+  return true;
+}
+
 static int read_all(int fd, uint8_t* data, size_t size) {
     size_t so_far = 0;
     while (so_far < size) {
@@ -281,6 +370,11 @@
     // Wait for nti->writer to be non-null, indicating some of this data is wanted.
     pthread_mutex_lock(&nti->mu);
     while (nti->writer == nullptr) {
+      // End the new data receiver if we encounter an error when performing block image update.
+      if (!nti->receiver_available) {
+        pthread_mutex_unlock(&nti->mu);
+        return false;
+      }
       pthread_cond_wait(&nti->cv, &nti->mu);
     }
     pthread_mutex_unlock(&nti->mu);
@@ -316,6 +410,11 @@
     // Wait for nti->writer to be non-null, indicating some of this data is wanted.
     pthread_mutex_lock(&nti->mu);
     while (nti->writer == nullptr) {
+      // End the receiver if we encounter an error when performing block image update.
+      if (!nti->receiver_available) {
+        pthread_mutex_unlock(&nti->mu);
+        return false;
+      }
       pthread_cond_wait(&nti->cv, &nti->mu);
     }
     pthread_mutex_unlock(&nti->mu);
@@ -429,6 +528,7 @@
 struct CommandParameters {
     std::vector<std::string> tokens;
     size_t cpos;
+    int cmdindex;
     const char* cmdname;
     const char* cmdline;
     std::string freestash;
@@ -445,6 +545,7 @@
     pthread_t thread;
     std::vector<uint8_t> buffer;
     uint8_t* patch_start;
+    bool target_verified;  // The target blocks have expected contents already.
 };
 
 // Print the hash in hex for corrupted source blocks (excluding the stashed blocks which is
@@ -482,6 +583,10 @@
   }
 
   RangeSet src = RangeSet::Parse(params.tokens[pos++]);
+  if (!src) {
+    LOG(ERROR) << "Failed to parse range in " << params.cmdline;
+    return;
+  }
 
   RangeSet locs;
   // If there's no stashed blocks, content in the buffer is consecutive and has the same
@@ -572,7 +677,7 @@
         return "";
     }
 
-    std::string fn(STASH_DIRECTORY_BASE);
+    std::string fn(CacheLocation::location().stash_directory_base());
     fn += "/" + base + "/" + id + postfix;
 
     return fn;
@@ -803,7 +908,7 @@
   size_t max_stash_size = maxblocks * BLOCKSIZE;
 
   if (res == -1 && errno != ENOENT) {
-    ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s\n", dirname.c_str(),
+    ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s", dirname.c_str(),
                strerror(errno));
     return -1;
   } else if (res != 0) {
@@ -811,19 +916,19 @@
     res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE);
 
     if (res != 0) {
-      ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s\n", dirname.c_str(),
+      ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s", dirname.c_str(),
                  strerror(errno));
       return -1;
     }
 
     if (chown(dirname.c_str(), AID_SYSTEM, AID_SYSTEM) != 0) {  // system user
-      ErrorAbort(state, kStashCreationFailure, "chown \"%s\" failed: %s\n", dirname.c_str(),
+      ErrorAbort(state, kStashCreationFailure, "chown \"%s\" failed: %s", dirname.c_str(),
                  strerror(errno));
       return -1;
     }
 
     if (CacheSizeCheck(max_stash_size) != 0) {
-      ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)\n",
+      ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)",
                  max_stash_size);
       return -1;
     }
@@ -855,7 +960,7 @@
   if (max_stash_size > existing) {
     size_t needed = max_stash_size - existing;
     if (CacheSizeCheck(needed) != 0) {
-      ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)\n",
+      ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)",
                  needed);
       return -1;
     }
@@ -926,6 +1031,7 @@
     params.cpos++;
   } else {
     RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]);
+    CHECK(static_cast<bool>(src));
     *overlap = src.Overlaps(tgt);
 
     if (ReadBlocks(src, params.buffer, params.fd) == -1) {
@@ -938,6 +1044,7 @@
     }
 
     RangeSet locs = RangeSet::Parse(params.tokens[params.cpos++]);
+    CHECK(static_cast<bool>(locs));
     MoveRange(params.buffer, locs, params.buffer);
   }
 
@@ -960,6 +1067,7 @@
     }
 
     RangeSet locs = RangeSet::Parse(tokens[1]);
+    CHECK(static_cast<bool>(locs));
     MoveRange(params.buffer, locs, stash);
   }
 
@@ -1024,6 +1132,7 @@
 
   // <tgt_range>
   tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+  CHECK(static_cast<bool>(tgt));
 
   std::vector<uint8_t> tgtbuffer(tgt.blocks() * BLOCKSIZE);
   if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) {
@@ -1054,6 +1163,10 @@
         return -1;
       }
 
+      if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) {
+        LOG(WARNING) << "Failed to update the last command file.";
+      }
+
       params.stashed += *src_blocks;
       // Can be deleted when the write has completed.
       if (!stash_exists) {
@@ -1094,8 +1207,11 @@
 
   if (status == 0) {
     params.foundwrites = true;
-  } else if (params.foundwrites) {
-    LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
+  } else {
+    params.target_verified = true;
+    if (params.foundwrites) {
+      LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
+    }
   }
 
   if (params.canwrite) {
@@ -1136,6 +1252,7 @@
   }
 
   RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]);
+  CHECK(static_cast<bool>(src));
 
   allocate(src.blocks() * BLOCKSIZE, params.buffer);
   if (ReadBlocks(src, params.buffer, params.fd) == -1) {
@@ -1158,8 +1275,15 @@
   }
 
   LOG(INFO) << "stashing " << blocks << " blocks to " << id;
-  params.stashed += blocks;
-  return WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr);
+  int result = WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr);
+  if (result == 0) {
+    if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) {
+      LOG(WARNING) << "Failed to update the last command file.";
+    }
+
+    params.stashed += blocks;
+  }
+  return result;
 }
 
 static int PerformCommandFree(CommandParameters& params) {
@@ -1186,6 +1310,7 @@
   }
 
   RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+  CHECK(static_cast<bool>(tgt));
 
   LOG(INFO) << "  zeroing " << tgt.blocks() << " blocks";
 
@@ -1228,6 +1353,7 @@
   }
 
   RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+  CHECK(static_cast<bool>(tgt));
 
   if (params.canwrite) {
     LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data";
@@ -1285,8 +1411,11 @@
 
   if (status == 0) {
     params.foundwrites = true;
-  } else if (params.foundwrites) {
-    LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
+  } else {
+    params.target_verified = true;
+    if (params.foundwrites) {
+      LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]";
+    }
   }
 
   if (params.canwrite) {
@@ -1297,7 +1426,7 @@
 
       RangeSinkWriter writer(params.fd, tgt);
       if (params.cmdname[0] == 'i') {  // imgdiff
-        if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value,
+        if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, patch_value,
                             std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1,
                                       std::placeholders::_2),
                             nullptr, nullptr) != 0) {
@@ -1306,7 +1435,7 @@
           return -1;
         }
       } else {
-        if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, 0,
+        if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, patch_value, 0,
                              std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1,
                                        std::placeholders::_2),
                              nullptr) != 0) {
@@ -1358,6 +1487,7 @@
   }
 
   RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+  CHECK(static_cast<bool>(tgt));
 
   if (params.canwrite) {
     LOG(INFO) << " erasing " << tgt.blocks() << " blocks";
@@ -1495,7 +1625,7 @@
 
   std::vector<std::string> lines = android::base::Split(transfer_list_value->data, "\n");
   if (lines.size() < 2) {
-    ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]\n",
+    ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zd]",
                lines.size());
     return StringValue("");
   }
@@ -1511,7 +1641,7 @@
   // Second line in transfer list is the total number of blocks we expect to write.
   size_t total_blocks;
   if (!android::base::ParseUint(lines[1], &total_blocks)) {
-    ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str());
+    ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]", lines[1].c_str());
     return StringValue("");
   }
 
@@ -1521,7 +1651,7 @@
 
   size_t start = 2;
   if (lines.size() < 4) {
-    ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n",
+    ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]",
                lines.size());
     return StringValue("");
   }
@@ -1532,7 +1662,7 @@
   // Fourth line is the maximum number of blocks that will be stashed simultaneously
   size_t stash_max_blocks;
   if (!android::base::ParseUint(lines[3], &stash_max_blocks)) {
-    ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n",
+    ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]",
                lines[3].c_str());
     return StringValue("");
   }
@@ -1544,6 +1674,23 @@
 
   params.createdstash = res;
 
+  // When performing an update, save the index and cmdline of the current command into
+  // the last_command_file if this command writes to the stash either explicitly of implicitly.
+  // Upon resuming an update, read the saved index first; then
+  //   1. In verification mode, check if the 'move' or 'diff' commands before the saved index has
+  //      the expected target blocks already. If not, these commands cannot be skipped and we need
+  //      to attempt to execute them again. Therefore, we will delete the last_command_file so that
+  //      the update will resume from the start of the transfer list.
+  //   2. In update mode, skip all commands before the saved index. Therefore, we can avoid deleting
+  //      stashes with duplicate id unintentionally (b/69858743); and also speed up the update.
+  // If an update succeeds or is unresumable, delete the last_command_file.
+  int saved_last_command_index;
+  if (!ParseLastCommandFile(&saved_last_command_index)) {
+    DeleteLastCommandFile();
+    // We failed to parse the last command, set it explicitly to -1.
+    saved_last_command_index = -1;
+  }
+
   start += 2;
 
   // Build a map of the available commands
@@ -1559,14 +1706,20 @@
   int rc = -1;
 
   // Subsequent lines are all individual transfer commands
-  for (auto it = lines.cbegin() + start; it != lines.cend(); it++) {
-    const std::string& line(*it);
+  for (size_t i = start; i < lines.size(); i++) {
+    const std::string& line = lines[i];
     if (line.empty()) continue;
 
     params.tokens = android::base::Split(line, " ");
     params.cpos = 0;
+    if (i - start > std::numeric_limits<int>::max()) {
+      params.cmdindex = -1;
+    } else {
+      params.cmdindex = i - start;
+    }
     params.cmdname = params.tokens[params.cpos++].c_str();
     params.cmdline = line.c_str();
+    params.target_verified = false;
 
     if (cmd_map.find(params.cmdname) == cmd_map.end()) {
       LOG(ERROR) << "unexpected command [" << params.cmdname << "]";
@@ -1575,11 +1728,40 @@
 
     const Command* cmd = cmd_map[params.cmdname];
 
-    if (cmd->f != nullptr && cmd->f(params) == -1) {
+    // Skip the command if we explicitly set the corresponding function pointer to nullptr, e.g.
+    // "erase" during block_image_verify.
+    if (cmd->f == nullptr) {
+      LOG(DEBUG) << "skip executing command [" << line << "]";
+      continue;
+    }
+
+    // Skip all commands before the saved last command index when resuming an update.
+    if (params.canwrite && params.cmdindex != -1 && params.cmdindex <= saved_last_command_index) {
+      LOG(INFO) << "Skipping already executed command: " << params.cmdindex
+                << ", last executed command for previous update: " << saved_last_command_index;
+      continue;
+    }
+
+    if (cmd->f(params) == -1) {
       LOG(ERROR) << "failed to execute command [" << line << "]";
       goto pbiudone;
     }
 
+    // In verify mode, check if the commands before the saved last_command_index have been
+    // executed correctly. If some target blocks have unexpected contents, delete the last command
+    // file so that we will resume the update from the first command in the transfer list.
+    if (!params.canwrite && saved_last_command_index != -1 && params.cmdindex != -1 &&
+        params.cmdindex <= saved_last_command_index) {
+      // TODO(xunchang) check that the cmdline of the saved index is correct.
+      std::string cmdname = std::string(params.cmdname);
+      if ((cmdname == "move" || cmdname == "bsdiff" || cmdname == "imgdiff") &&
+          !params.target_verified) {
+        LOG(WARNING) << "Previously executed command " << saved_last_command_index << ": "
+                     << params.cmdline << " doesn't produce expected target blocks.";
+        saved_last_command_index = -1;
+        DeleteLastCommandFile();
+      }
+    }
     if (params.canwrite) {
       if (ota_fsync(params.fd) == -1) {
         failure_type = kFsyncFailure;
@@ -1591,29 +1773,45 @@
     }
   }
 
-  if (params.canwrite) {
-    pthread_join(params.thread, nullptr);
-
-    LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks;
-    LOG(INFO) << "stashed " << params.stashed << " blocks";
-    LOG(INFO) << "max alloc needed was " << params.buffer.size();
-
-    const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
-    if (partition != nullptr && *(partition + 1) != 0) {
-      fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE);
-      fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE);
-      fflush(cmd_pipe);
-    }
-    // Delete stash only after successfully completing the update, as it may contain blocks needed
-    // to complete the update later.
-    DeleteStash(params.stashbase);
-  } else {
-    LOG(INFO) << "verified partition contents; update may be resumed";
-  }
-
   rc = 0;
 
 pbiudone:
+  if (params.canwrite) {
+    pthread_mutex_lock(&params.nti.mu);
+    if (params.nti.receiver_available) {
+      LOG(WARNING) << "new data receiver is still available after executing all commands.";
+    }
+    params.nti.receiver_available = false;
+    pthread_cond_broadcast(&params.nti.cv);
+    pthread_mutex_unlock(&params.nti.mu);
+    int ret = pthread_join(params.thread, nullptr);
+    if (ret != 0) {
+      LOG(WARNING) << "pthread join returned with " << strerror(ret);
+    }
+
+    if (rc == 0) {
+      LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks;
+      LOG(INFO) << "stashed " << params.stashed << " blocks";
+      LOG(INFO) << "max alloc needed was " << params.buffer.size();
+
+      const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
+      if (partition != nullptr && *(partition + 1) != 0) {
+        fprintf(cmd_pipe, "log bytes_written_%s: %zu\n", partition + 1, params.written * BLOCKSIZE);
+        fprintf(cmd_pipe, "log bytes_stashed_%s: %zu\n", partition + 1, params.stashed * BLOCKSIZE);
+        fflush(cmd_pipe);
+      }
+      // Delete stash only after successfully completing the update, as it may contain blocks needed
+      // to complete the update later.
+      DeleteStash(params.stashbase);
+      DeleteLastCommandFile();
+    }
+
+    pthread_mutex_destroy(&params.nti.mu);
+    pthread_cond_destroy(&params.nti.cv);
+  } else if (rc == 0) {
+    LOG(INFO) << "verified partition contents; update may be resumed";
+  }
+
   if (ota_fsync(params.fd) == -1) {
     failure_type = kFsyncFailure;
     PLOG(ERROR) << "fsync failed";
@@ -1624,6 +1822,11 @@
     BrotliDecoderDestroyInstance(params.nti.brotli_decoder_state);
   }
 
+  // Delete the last command file if the update cannot be resumed.
+  if (params.isunresumable) {
+    DeleteLastCommandFile();
+  }
+
   // Only delete the stash if the update cannot be resumed, or it's a verification run and we
   // created the stash.
   if (params.isunresumable || (!params.canwrite && params.createdstash)) {
@@ -1748,6 +1951,7 @@
   }
 
   RangeSet rs = RangeSet::Parse(ranges->data);
+  CHECK(static_cast<bool>(rs));
 
   SHA_CTX ctx;
   SHA1_Init(&ctx);
@@ -1859,6 +2063,11 @@
     ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name);
     return StringValue("");
   }
+  RangeSet rs = RangeSet::Parse(ranges->data);
+  if (!rs) {
+    ErrorAbort(state, kArgsParsingFailure, "failed to parse ranges: %s", ranges->data.c_str());
+    return StringValue("");
+  }
 
   // Output notice to log when recover is attempted
   LOG(INFO) << filename->data << " image corrupted, attempting to recover...";
@@ -1884,7 +2093,7 @@
   }
 
   uint8_t buffer[BLOCKSIZE];
-  for (const auto& range : RangeSet::Parse(ranges->data)) {
+  for (const auto& range : rs) {
     for (size_t j = range.first; j < range.second; ++j) {
       // Stay within the data area, libfec validates and corrects metadata
       if (status.data_size <= static_cast<uint64_t>(j) * BLOCKSIZE) {
diff --git a/updater/include/updater/blockimg.h b/updater/include/updater/blockimg.h
index 2f4ad3c..71733b3 100644
--- a/updater/include/updater/blockimg.h
+++ b/updater/include/updater/blockimg.h
@@ -17,6 +17,8 @@
 #ifndef _UPDATER_BLOCKIMG_H_
 #define _UPDATER_BLOCKIMG_H_
 
+#include <string>
+
 void RegisterBlockImageFunctions();
 
 #endif
diff --git a/updater/include/updater/rangeset.h b/updater/include/updater/rangeset.h
deleted file mode 100644
index fad0380..0000000
--- a/updater/include/updater/rangeset.h
+++ /dev/null
@@ -1,164 +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.
- */
-
-#pragma once
-
-#include <stddef.h>
-
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <android-base/logging.h>
-#include <android-base/parseint.h>
-#include <android-base/strings.h>
-
-using Range = std::pair<size_t, size_t>;
-
-class RangeSet {
- public:
-  RangeSet() : blocks_(0) {}
-
-  explicit RangeSet(std::vector<Range>&& pairs) {
-    CHECK_NE(pairs.size(), static_cast<size_t>(0)) << "Invalid number of tokens";
-
-    // Sanity check the input.
-    size_t result = 0;
-    for (const auto& range : pairs) {
-      CHECK_LT(range.first, range.second)
-          << "Empty or negative range: " << range.first << ", " << range.second;
-      size_t sz = range.second - range.first;
-      CHECK_LE(result, SIZE_MAX - sz) << "RangeSet size overflow";
-      result += sz;
-    }
-
-    ranges_ = pairs;
-    blocks_ = result;
-  }
-
-  static RangeSet Parse(const std::string& range_text) {
-    std::vector<std::string> pieces = android::base::Split(range_text, ",");
-    CHECK_GE(pieces.size(), static_cast<size_t>(3)) << "Invalid range text: " << range_text;
-
-    size_t num;
-    CHECK(android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX)))
-        << "Failed to parse the number of tokens: " << range_text;
-
-    CHECK_NE(num, static_cast<size_t>(0)) << "Invalid number of tokens: " << range_text;
-    CHECK_EQ(num % 2, static_cast<size_t>(0)) << "Number of tokens must be even: " << range_text;
-    CHECK_EQ(num, pieces.size() - 1) << "Mismatching number of tokens: " << range_text;
-
-    std::vector<Range> pairs;
-    for (size_t i = 0; i < num; i += 2) {
-      size_t first;
-      CHECK(android::base::ParseUint(pieces[i + 1], &first, static_cast<size_t>(INT_MAX)));
-      size_t second;
-      CHECK(android::base::ParseUint(pieces[i + 2], &second, static_cast<size_t>(INT_MAX)));
-
-      pairs.emplace_back(first, second);
-    }
-
-    return RangeSet(std::move(pairs));
-  }
-
-  // Get the block number for the i-th (starting from 0) block in the RangeSet.
-  size_t 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;
-      }
-      idx -= (range.second - range.first);
-    }
-
-    CHECK(false) << "Failed to find block number for index " << idx;
-    return 0;  // Unreachable, but to make compiler happy.
-  }
-
-  // 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 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)) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  // size() gives the number of Range's in this RangeSet.
-  size_t size() const {
-    return ranges_.size();
-  }
-
-  // blocks() gives the number of all blocks in this RangeSet.
-  size_t blocks() const {
-    return blocks_;
-  }
-
-  // We provide const iterators only.
-  std::vector<Range>::const_iterator cbegin() const {
-    return ranges_.cbegin();
-  }
-
-  std::vector<Range>::const_iterator cend() const {
-    return ranges_.cend();
-  }
-
-  // Need to provide begin()/end() since range-based loop expects begin()/end().
-  std::vector<Range>::const_iterator begin() const {
-    return ranges_.cbegin();
-  }
-
-  std::vector<Range>::const_iterator end() const {
-    return ranges_.cend();
-  }
-
-  // Reverse const iterators for MoveRange().
-  std::vector<Range>::const_reverse_iterator crbegin() const {
-    return ranges_.crbegin();
-  }
-
-  std::vector<Range>::const_reverse_iterator crend() const {
-    return ranges_.crend();
-  }
-
-  const Range& operator[](size_t i) const {
-    return ranges_[i];
-  }
-
-  bool operator==(const RangeSet& other) const {
-    // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5".
-    return (ranges_ == other.ranges_);
-  }
-
-  bool operator!=(const RangeSet& other) const {
-    return ranges_ != other.ranges_;
-  }
-
- private:
-  // Actual limit for each value and the total number are both INT_MAX.
-  std::vector<Range> ranges_;
-  size_t blocks_;
-};
diff --git a/updater/install.cpp b/updater/install.cpp
index a8ee4bf..741d970 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -49,17 +49,15 @@
 #include <applypatch/applypatch.h>
 #include <bootloader_message/bootloader_message.h>
 #include <cutils/android_reboot.h>
-#include <ext4_utils/make_ext4fs.h>
 #include <ext4_utils/wipe.h>
 #include <openssl/sha.h>
 #include <selinux/label.h>
 #include <selinux/selinux.h>
+#include <tune2fs.h>
 #include <ziparchive/zip_archive.h>
 
 #include "edify/expr.h"
-#include "error_code.h"
 #include "mounts.h"
-#include "ota_io.h"
 
 #include "applypatch/applypatch.h"
 #include "flashutils/flashutils.h"
@@ -73,10 +71,11 @@
 #include "wipe.h"
 #endif
 
-#include "otautil/DirUtil.h"
 #include "otautil/ZipUtil.h"
-#include "print_sha1.h"
-#include "tune2fs.h"
+#include "otafault/ota_io.h"
+#include "otautil/DirUtil.h"
+#include "otautil/error_code.h"
+#include "otautil/print_sha1.h"
 #include "updater/updater.h"
 
 // Send over the buffer to recovery though the command pipe.
@@ -109,32 +108,242 @@
   uiPrint(state, error_msg);
 }
 
-static bool is_dir(const std::string& dirpath) {
-  struct stat st;
-  return stat(dirpath.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
+// This is the updater side handler for ui_print() in edify script. Contents will be sent over to
+// the recovery side for on-screen display.
+Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
+  }
+
+  std::string buffer = android::base::Join(args, "");
+  uiPrint(state, buffer);
+  return StringValue(buffer);
 }
 
-// Create all parent directories of name, if necessary.
-static bool make_parents(const std::string& name) {
-  size_t prev_end = 0;
-  while (prev_end < name.size()) {
-    size_t next_end = name.find('/', prev_end + 1);
-    if (next_end == std::string::npos) {
-      break;
-    }
-    std::string dir_path = name.substr(0, next_end);
-    if (!is_dir(dir_path)) {
-      int result = mkdir(dir_path.c_str(), 0700);
-      if (result != 0) {
-        PLOG(ERROR) << "failed to mkdir " << dir_path << " when make parents for " << name;
-        return false;
-      }
-
-      LOG(INFO) << "created [" << dir_path << "]";
-    }
-    prev_end = next_end;
+// package_extract_file(package_file[, dest_file])
+//   Extracts a single package_file from the update package and writes it to dest_file,
+//   overwriting existing files if necessary. Without the dest_file argument, returns the
+//   contents of the package file as a binary blob.
+Value* PackageExtractFileFn(const char* name, State* state,
+                            const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1 || argv.size() > 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name,
+                      argv.size());
   }
-  return true;
+
+  if (argv.size() == 2) {
+    // The two-argument version extracts to a file.
+
+    std::vector<std::string> args;
+    if (!ReadArgs(state, argv, &args)) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
+                        argv.size());
+    }
+    const std::string& zip_path = args[0];
+    const std::string& dest_path = args[1];
+
+    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+    ZipString zip_string_path(zip_path.c_str());
+    ZipEntry entry;
+    if (FindEntry(za, zip_string_path, &entry) != 0) {
+      LOG(ERROR) << name << ": no " << zip_path << " in package";
+      return StringValue("");
+    }
+
+    unique_fd fd(TEMP_FAILURE_RETRY(
+        ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)));
+    if (fd == -1) {
+      PLOG(ERROR) << name << ": can't open " << dest_path << " for write";
+      return StringValue("");
+    }
+
+    bool success = true;
+    int32_t ret = ExtractEntryToFile(za, &entry, fd);
+    if (ret != 0) {
+      LOG(ERROR) << name << ": Failed to extract entry \"" << zip_path << "\" ("
+                 << entry.uncompressed_length << " bytes) to \"" << dest_path
+                 << "\": " << ErrorCodeString(ret);
+      success = false;
+    }
+    if (ota_fsync(fd) == -1) {
+      PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed";
+      success = false;
+    }
+    if (ota_close(fd) == -1) {
+      PLOG(ERROR) << "close of \"" << dest_path << "\" failed";
+      success = false;
+    }
+
+    return StringValue(success ? "t" : "");
+  } else {
+    // The one-argument version returns the contents of the file as the result.
+
+    std::vector<std::string> args;
+    if (!ReadArgs(state, argv, &args)) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
+                        argv.size());
+    }
+    const std::string& zip_path = args[0];
+
+    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+    ZipString zip_string_path(zip_path.c_str());
+    ZipEntry entry;
+    if (FindEntry(za, zip_string_path, &entry) != 0) {
+      return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
+                        zip_path.c_str());
+    }
+
+    std::string buffer;
+    buffer.resize(entry.uncompressed_length);
+
+    int32_t ret =
+        ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&buffer[0]), buffer.size());
+    if (ret != 0) {
+      return ErrorAbort(state, kPackageExtractFileFailure,
+                        "%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name,
+                        zip_path.c_str(), buffer.size(), ErrorCodeString(ret));
+    }
+
+    return new Value(VAL_BLOB, buffer);
+  }
+}
+
+// apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...])
+//   Applies a binary patch to the src_file to produce the tgt_file. If the desired target is the
+//   same as the source, pass "-" for tgt_file. tgt_sha1 and tgt_size are the expected final SHA1
+//   hash and size of the target file. The remaining arguments must come in pairs: a SHA1 hash (a
+//   40-character hex string) and a blob. The blob is the patch to be applied when the source
+//   file's current contents have the given SHA1.
+//
+//   The patching is done in a safe manner that guarantees the target file either has the desired
+//   SHA1 hash and size, or it is untouched -- it will not be left in an unrecoverable intermediate
+//   state. If the process is interrupted during patching, the target file may be in an intermediate
+//   state; a copy exists in the cache partition so restarting the update can successfully update
+//   the file.
+Value* ApplyPatchFn(const char* name, State* state,
+                    const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 6 || (argv.size() % 2) == 1) {
+    return ErrorAbort(state, kArgsParsingFailure,
+                      "%s(): expected at least 6 args and an "
+                      "even number, got %zu",
+                      name, argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args, 0, 4)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& source_filename = args[0];
+  const std::string& target_filename = args[1];
+  const std::string& target_sha1 = args[2];
+  const std::string& target_size_str = args[3];
+
+  size_t target_size;
+  if (!android::base::ParseUint(target_size_str.c_str(), &target_size)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", name,
+                      target_size_str.c_str());
+  }
+
+  int patchcount = (argv.size() - 4) / 2;
+  std::vector<std::unique_ptr<Value>> arg_values;
+  if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) {
+    return nullptr;
+  }
+
+  for (int i = 0; i < patchcount; ++i) {
+    if (arg_values[i * 2]->type != VAL_STRING) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name, i * 2);
+    }
+    if (arg_values[i * 2 + 1]->type != VAL_BLOB) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name, i * 2 + 1);
+    }
+  }
+
+  std::vector<std::string> patch_sha_str;
+  std::vector<std::unique_ptr<Value>> patches;
+  for (int i = 0; i < patchcount; ++i) {
+    patch_sha_str.push_back(arg_values[i * 2]->data);
+    patches.push_back(std::move(arg_values[i * 2 + 1]));
+  }
+
+  int result = applypatch(source_filename.c_str(), target_filename.c_str(), target_sha1.c_str(),
+                          target_size, patch_sha_str, patches, nullptr);
+
+  return StringValue(result == 0 ? "t" : "");
+}
+
+// apply_patch_check(filename, [sha1, ...])
+//   Returns true if the contents of filename or the temporary copy in the cache partition (if
+//   present) have a SHA-1 checksum equal to one of the given sha1 values. sha1 values are
+//   specified as 40 hex digits. This function differs from sha1_check(read_file(filename),
+//   sha1 [, ...]) in that it knows to check the cache partition copy, so apply_patch_check() will
+//   succeed even if the file was corrupted by an interrupted apply_patch() update.
+Value* ApplyPatchCheckFn(const char* name, State* state,
+                         const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name,
+                      argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args, 0, 1)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& filename = args[0];
+
+  std::vector<std::string> sha1s;
+  if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  int result = applypatch_check(filename.c_str(), sha1s);
+
+  return StringValue(result == 0 ? "t" : "");
+}
+
+// sha1_check(data)
+//    to return the sha1 of the data (given in the format returned by
+//    read_file).
+//
+// sha1_check(data, sha1_hex, [sha1_hex, ...])
+//    returns the sha1 of the file if it matches any of the hex
+//    strings passed, or "" if it does not equal any of them.
+//
+Value* Sha1CheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
+  }
+
+  std::vector<std::unique_ptr<Value>> args;
+  if (!ReadValueArgs(state, argv, &args)) {
+    return nullptr;
+  }
+
+  if (args[0]->type == VAL_INVALID) {
+    return StringValue("");
+  }
+  uint8_t digest[SHA_DIGEST_LENGTH];
+  SHA1(reinterpret_cast<const uint8_t*>(args[0]->data.c_str()), args[0]->data.size(), digest);
+
+  if (argv.size() == 1) {
+    return StringValue(print_sha1(digest));
+  }
+
+  for (size_t i = 1; i < argv.size(); ++i) {
+    uint8_t arg_digest[SHA_DIGEST_LENGTH];
+    if (args[i]->type != VAL_STRING) {
+      LOG(ERROR) << name << "(): arg " << i << " is not a string; skipping";
+    } else if (ParseSha1(args[i]->data.c_str(), arg_digest) != 0) {
+      // Warn about bad args and skip them.
+      LOG(ERROR) << name << "(): error parsing \"" << args[i]->data << "\" as sha-1; skipping";
+    } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) {
+      // Found a match.
+      return args[i].release();
+    }
+  }
+
+  // Didn't match any of the hex strings; return false.
+  return StringValue("");
 }
 
 // mount(fs_type, partition_type, location, mount_point)
@@ -311,7 +520,7 @@
 
   int64_t size;
   if (!android::base::ParseInt(fs_size, &size)) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s\n", name,
+    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s", name,
                       fs_size.c_str());
   }
 
@@ -326,14 +535,8 @@
 
     int status = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv));
     if (status != 0) {
-      LOG(WARNING) << name << ": mke2fs failed (" << status << ") on " << location
-                   << ", falling back to make_ext4fs";
-      status = make_ext4fs(location.c_str(), size, mount_point.c_str(), sehandle);
-      if (status != 0) {
-        LOG(ERROR) << name << ": make_ext4fs failed (" << status << ") on " << location;
-        return StringValue("");
-      }
-      return StringValue(location);
+      LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location;
+      return StringValue("");
     }
 
     const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static", "-e",   "-a", mount_point.c_str(),
@@ -352,15 +555,30 @@
     std::string num_sectors = std::to_string(size / 512);
 
     const char* f2fs_path = "/sbin/mkfs.f2fs";
-    const char* f2fs_argv[] = {
-      "mkfs.f2fs", "-t", "-d1", location.c_str(), (size < 512) ? nullptr : num_sectors.c_str(),
-      nullptr
-    };
+    const char* f2fs_argv[] = { "mkfs.f2fs",
+                                "-d1",
+                                "-f",
+                                "-O", "encrypt",
+                                "-O", "quota",
+                                "-O", "verity",
+                                "-w", "512",
+                                location.c_str(),
+                                (size < 512) ? nullptr : num_sectors.c_str(),
+                                nullptr };
     int status = exec_cmd(f2fs_path, const_cast<char**>(f2fs_argv));
     if (status != 0) {
       LOG(ERROR) << name << ": mkfs.f2fs failed (" << status << ") on " << location;
       return StringValue("");
     }
+
+    const char* sload_argv[] = { "/sbin/sload.f2fs", "-t", mount_point.c_str(), location.c_str(),
+                                 nullptr };
+    status = exec_cmd(sload_argv[0], const_cast<char**>(sload_argv));
+    if (status != 0) {
+      LOG(ERROR) << name << ": sload.f2fs failed (" << status << ") on " << location;
+      return StringValue("");
+    }
+
     return StringValue(location);
   } else {
     LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \""
@@ -447,12 +665,12 @@
 
   double frac;
   if (!android::base::ParseDouble(frac_str.c_str(), &frac)) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s\n", name,
+    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s", name,
                       frac_str.c_str());
   }
   int sec;
   if (!android::base::ParseInt(sec_str.c_str(), &sec)) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s\n", name,
+    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s", name,
                       sec_str.c_str());
   }
 
@@ -462,7 +680,8 @@
   return StringValue(frac_str);
 }
 
-Value* SetProgressFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+Value* SetProgressFn(const char* name, State* state,
+                     const std::vector<std::unique_ptr<Expr>>& argv) {
   if (argv.size() != 1) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
   }
@@ -475,7 +694,7 @@
 
   double frac;
   if (!android::base::ParseDouble(frac_str.c_str(), &frac)) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s\n", name,
+    return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s", name,
                       frac_str.c_str());
   }
 
@@ -902,7 +1121,8 @@
 //   interprets 'file' as a getprop-style file (key=value pairs, one
 //   per line. # comment lines, blank lines, lines without '=' ignored),
 //   and returns the value for 'key' (or "" if it isn't defined).
-Value* FileGetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+Value* FileGetPropFn(const char* name, State* state,
+                     const std::vector<std::unique_ptr<Expr>>& argv) {
   if (argv.size() != 2) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
                       argv.size());
@@ -968,7 +1188,8 @@
 }
 
 // apply_patch_space(bytes)
-Value* ApplyPatchSpaceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+Value* ApplyPatchSpaceFn(const char* name, State* state,
+                         const std::vector<std::unique_ptr<Expr>>& argv) {
   if (argv.size() != 1) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 args, got %zu", name,
                       argv.size());
@@ -981,115 +1202,15 @@
 
   size_t bytes;
   if (!android::base::ParseUint(bytes_str.c_str(), &bytes)) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count\n\n",
-                      name, bytes_str.c_str());
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", name,
+                      bytes_str.c_str());
   }
 
-  return StringValue(CacheSizeCheck(bytes) ? "" : "t");
-}
-
-// apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...])
-//   Applies a binary patch to the src_file to produce the tgt_file. If the desired target is the
-//   same as the source, pass "-" for tgt_file. tgt_sha1 and tgt_size are the expected final SHA1
-//   hash and size of the target file. The remaining arguments must come in pairs: a SHA1 hash (a
-//   40-character hex string) and a blob. The blob is the patch to be applied when the source
-//   file's current contents have the given SHA1.
-//
-//   The patching is done in a safe manner that guarantees the target file either has the desired
-//   SHA1 hash and size, or it is untouched -- it will not be left in an unrecoverable intermediate
-//   state. If the process is interrupted during patching, the target file may be in an intermediate
-//   state; a copy exists in the cache partition so restarting the update can successfully update
-//   the file.
-Value* ApplyPatchFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
-    if (argv.size() < 6 || (argv.size() % 2) == 1) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 6 args and an "
-                          "even number, got %zu", name, argv.size());
-    }
-
-    std::vector<std::string> args;
-    if (!ReadArgs(state, argv, &args, 0, 4)) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
-    }
-    const std::string& source_filename = args[0];
-    const std::string& target_filename = args[1];
-    const std::string& target_sha1 = args[2];
-    const std::string& target_size_str = args[3];
-
-    size_t target_size;
-    if (!android::base::ParseUint(target_size_str.c_str(), &target_size)) {
-        return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count",
-                          name, target_size_str.c_str());
-    }
-
-    int patchcount = (argv.size()-4) / 2;
-    std::vector<std::unique_ptr<Value>> arg_values;
-    if (!ReadValueArgs(state, argv, &arg_values, 4, argv.size() - 4)) {
-        return nullptr;
-    }
-
-    for (int i = 0; i < patchcount; ++i) {
-        if (arg_values[i * 2]->type != VAL_STRING) {
-            return ErrorAbort(state, kArgsParsingFailure, "%s(): sha-1 #%d is not string", name,
-                              i * 2);
-        }
-        if (arg_values[i * 2 + 1]->type != VAL_BLOB) {
-            return ErrorAbort(state, kArgsParsingFailure, "%s(): patch #%d is not blob", name,
-                              i * 2 + 1);
-        }
-    }
-
-    std::vector<std::string> patch_sha_str;
-    std::vector<std::unique_ptr<Value>> patches;
-    for (int i = 0; i < patchcount; ++i) {
-        patch_sha_str.push_back(arg_values[i * 2]->data);
-        patches.push_back(std::move(arg_values[i * 2 + 1]));
-    }
-
-    int result = applypatch(source_filename.c_str(), target_filename.c_str(),
-                            target_sha1.c_str(), target_size,
-                            patch_sha_str, patches, nullptr);
-
-    return StringValue(result == 0 ? "t" : "");
-}
-
-// apply_patch_check(filename, [sha1, ...])
-//   Returns true if the contents of filename or the temporary copy in the cache partition (if
-//   present) have a SHA-1 checksum equal to one of the given sha1 values. sha1 values are
-//   specified as 40 hex digits. This function differs from sha1_check(read_file(filename),
-//   sha1 [, ...]) in that it knows to check the cache partition copy, so apply_patch_check() will
-//   succeed even if the file was corrupted by an interrupted apply_patch() update.
-Value* ApplyPatchCheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
-  if (argv.size() < 1) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s(): expected at least 1 arg, got %zu", name,
-                      argv.size());
+  // Skip the cache size check if the update is a retry.
+  if (state->is_retry || CacheSizeCheck(bytes) == 0) {
+    return StringValue("t");
   }
-
-  std::vector<std::string> args;
-  if (!ReadArgs(state, argv, &args, 0, 1)) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
-  }
-  const std::string& filename = args[0];
-
-  std::vector<std::string> sha1s;
-  if (argv.size() > 1 && !ReadArgs(state, argv, &sha1s, 1, argv.size() - 1)) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
-  }
-  int result = applypatch_check(filename.c_str(), sha1s);
-
-  return StringValue(result == 0 ? "t" : "");
-}
-
-// This is the updater side handler for ui_print() in edify script. Contents will be sent over to
-// the recovery side for on-screen display.
-Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
-  std::vector<std::string> args;
-  if (!ReadArgs(state, argv, &args)) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
-  }
-
-  std::string buffer = android::base::Join(args, "");
-  uiPrint(state, buffer);
-  return StringValue(buffer);
+  return StringValue("");
 }
 
 Value* WipeCacheFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -1139,51 +1260,6 @@
   return StringValue(std::to_string(status));
 }
 
-// sha1_check(data)
-//    to return the sha1 of the data (given in the format returned by
-//    read_file).
-//
-// sha1_check(data, sha1_hex, [sha1_hex, ...])
-//    returns the sha1 of the file if it matches any of the hex
-//    strings passed, or "" if it does not equal any of them.
-//
-Value* Sha1CheckFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
-  if (argv.size() < 1) {
-    return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
-  }
-
-  std::vector<std::unique_ptr<Value>> args;
-  if (!ReadValueArgs(state, argv, &args)) {
-    return nullptr;
-  }
-
-  if (args[0]->type == VAL_INVALID) {
-    return StringValue("");
-  }
-  uint8_t digest[SHA_DIGEST_LENGTH];
-  SHA1(reinterpret_cast<const uint8_t*>(args[0]->data.c_str()), args[0]->data.size(), digest);
-
-  if (argv.size() == 1) {
-    return StringValue(print_sha1(digest));
-  }
-
-  for (size_t i = 1; i < argv.size(); ++i) {
-    uint8_t arg_digest[SHA_DIGEST_LENGTH];
-    if (args[i]->type != VAL_STRING) {
-      LOG(ERROR) << name << "(): arg " << i << " is not a string; skipping";
-    } else if (ParseSha1(args[i]->data.c_str(), arg_digest) != 0) {
-      // Warn about bad args and skip them.
-      LOG(ERROR) << name << "(): error parsing \"" << args[i]->data << "\" as sha-1; skipping";
-    } else if (memcmp(digest, arg_digest, SHA_DIGEST_LENGTH) == 0) {
-      // Found a match.
-      return args[i].release();
-    }
-  }
-
-  // Didn't match any of the hex strings; return false.
-  return StringValue("");
-}
-
 // Read a local file and return its contents (the Value* returned
 // is actually a FileContents*).
 Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 25bd541..bd3b6fe 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -31,10 +31,12 @@
 #include <selinux/selinux.h>
 #include <ziparchive/zip_archive.h>
 
-#include "config.h"
 #include "edify/expr.h"
+#include "otafault/config.h"
 #include "otautil/DirUtil.h"
 #include "otautil/SysUtil.h"
+#include "otautil/cache_location.h"
+#include "otautil/error_code.h"
 #include "updater/blockimg.h"
 #include "updater/install.h"
 
diff --git a/verifier.cpp b/verifier.cpp
index 18437fb..283e043 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -32,7 +32,7 @@
 #include <openssl/obj_mac.h>
 
 #include "asn1_decoder.h"
-#include "print_sha1.h"
+#include "otautil/print_sha1.h"
 
 static constexpr size_t MiB = 1024 * 1024;
 
diff --git a/vr_ui.cpp b/vr_ui.cpp
index 1251672..a58c99e 100644
--- a/vr_ui.cpp
+++ b/vr_ui.cpp
@@ -20,16 +20,46 @@
 
 VrRecoveryUI::VrRecoveryUI() : kStereoOffset(RECOVERY_UI_VR_STEREO_OFFSET) {}
 
-bool VrRecoveryUI::InitTextParams() {
-  if (!ScreenRecoveryUI::InitTextParams()) return false;
-  int mid_divide = gr_fb_width() / 2;
-  text_cols_ = (mid_divide - kMarginWidth - kStereoOffset) / char_width_;
-  return true;
+int VrRecoveryUI::ScreenWidth() const {
+  return gr_fb_width() / 2;
+}
+
+int VrRecoveryUI::ScreenHeight() const {
+  return gr_fb_height();
+}
+
+void VrRecoveryUI::DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx,
+                               int dy) const {
+  gr_blit(surface, sx, sy, w, h, dx + kStereoOffset, dy);
+  gr_blit(surface, sx, sy, w, h, dx - kStereoOffset + ScreenWidth(), dy);
+}
+
+void VrRecoveryUI::DrawTextIcon(int x, int y, GRSurface* surface) const {
+  gr_texticon(x + kStereoOffset, y, surface);
+  gr_texticon(x - kStereoOffset + ScreenWidth(), y, surface);
 }
 
 int VrRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const {
-  int mid_divide = gr_fb_width() / 2;
   gr_text(gr_sys_font(), x + kStereoOffset, y, line, bold);
-  gr_text(gr_sys_font(), x - kStereoOffset + mid_divide, y, line, bold);
+  gr_text(gr_sys_font(), x - kStereoOffset + ScreenWidth(), y, line, bold);
   return char_height_ + 4;
 }
+
+int VrRecoveryUI::DrawHorizontalRule(int y) const {
+  y += 4;
+  gr_fill(kMarginWidth + kStereoOffset, y, ScreenWidth() - kMarginWidth + kStereoOffset, y + 2);
+  gr_fill(ScreenWidth() + kMarginWidth - kStereoOffset, y,
+          gr_fb_width() - kMarginWidth - kStereoOffset, y + 2);
+  return y + 4;
+}
+
+void VrRecoveryUI::DrawHighlightBar(int /* x */, int y, int /* width */, int height) const {
+  gr_fill(kMarginWidth + kStereoOffset, y, ScreenWidth() - kMarginWidth + kStereoOffset, y + height);
+  gr_fill(ScreenWidth() + kMarginWidth - kStereoOffset, y,
+          gr_fb_width() - kMarginWidth - kStereoOffset, y + height);
+}
+
+void VrRecoveryUI::DrawFill(int x, int y, int w, int h) const {
+  gr_fill(x + kStereoOffset, y, w, h);
+  gr_fill(x - kStereoOffset + ScreenWidth(), y, w, h);
+}
diff --git a/vr_ui.h b/vr_ui.h
index d996c14..eeb4589 100644
--- a/vr_ui.h
+++ b/vr_ui.h
@@ -28,8 +28,14 @@
   // Can vary per device depending on screen size and lens distortion.
   const int kStereoOffset;
 
-  bool InitTextParams() override;
+  int ScreenWidth() const override;
+  int ScreenHeight() const override;
 
+  void DrawSurface(GRSurface* surface, int sx, int sy, int w, int h, int dx, int dy) const override;
+  int DrawHorizontalRule(int y) const override;
+  void DrawHighlightBar(int x, int y, int width, int height) const override;
+  void DrawFill(int x, int y, int w, int h) const override;
+  void DrawTextIcon(int x, int y, GRSurface* surface) const override;
   int DrawTextLine(int x, int y, const char* line, bool bold) const override;
 };
 
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 624116c..ca6b1b1 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -16,40 +16,16 @@
 
 #include "wear_ui.h"
 
-#include <errno.h>
-#include <fcntl.h>
-#include <stdarg.h>
-#include <stdlib.h>
+#include <pthread.h>
+#include <stdio.h>  // TODO: Remove after killing the call to sprintf().
 #include <string.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
 
 #include <string>
-#include <vector>
 
 #include <android-base/properties.h>
 #include <android-base/strings.h>
-#include <android-base/stringprintf.h>
 #include <minui/minui.h>
 
-#include "common.h"
-#include "device.h"
-
-// There's only (at most) one of these objects, and global callbacks
-// (for pthread_create, and the input event system) need to find it,
-// so use a global variable.
-static WearRecoveryUI* self = NULL;
-
-// Return the current time as a double (including fractions of a second).
-static double now() {
-  struct timeval tv;
-  gettimeofday(&tv, NULL);
-  return tv.tv_sec + tv.tv_usec / 1000000.0;
-}
-
 WearRecoveryUI::WearRecoveryUI()
     : kProgressBarBaseline(RECOVERY_UI_PROGRESS_BAR_BASELINE),
       kMenuUnusableRows(RECOVERY_UI_MENU_UNUSABLE_ROWS) {
@@ -61,10 +37,6 @@
   loop_frames = 60;
 
   touch_screen_allowed_ = true;
-
-  for (size_t i = 0; i < 5; i++) backgroundIcon[i] = NULL;
-
-  self = this;
 }
 
 int WearRecoveryUI::GetProgressBaseline() const {
@@ -80,24 +52,12 @@
   gr_fill(0, 0, gr_fb_width(), gr_fb_height());
 
   if (currentIcon != NONE) {
-    GRSurface* surface;
-    if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
-      if (!intro_done) {
-        surface = introFrames[current_frame];
-      } else {
-        surface = loopFrames[current_frame];
-      }
-    } else {
-      surface = backgroundIcon[currentIcon];
-    }
-
-    int width = gr_get_width(surface);
-    int height = gr_get_height(surface);
-
-    int x = (gr_fb_width() - width) / 2;
-    int y = (gr_fb_height() - height) / 2;
-
-    gr_blit(surface, 0, 0, width, height, x, y);
+    GRSurface* frame = GetCurrentFrame();
+    int frame_width = gr_get_width(frame);
+    int frame_height = gr_get_height(frame);
+    int frame_x = (gr_fb_width() - frame_width) / 2;
+    int frame_y = (gr_fb_height() - frame_height) / 2;
+    gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
   }
 }
 
@@ -173,8 +133,7 @@
     // display from the bottom up, until we hit the top of the
     // screen, the bottom of the menu, or we've displayed the
     // entire text buffer.
-    int ty;
-    int row = (text_top_ + text_rows_ - 1) % text_rows_;
+    int row = text_row_;
     size_t count = 0;
     for (int ty = gr_fb_height() - char_height_ - kMarginHeight; ty > y + 2 && count < text_rows_;
          ty -= char_height_, ++count) {
@@ -191,64 +150,7 @@
   gr_flip();
 }
 
-bool WearRecoveryUI::InitTextParams() {
-  if (!ScreenRecoveryUI::InitTextParams()) {
-    return false;
-  }
-
-  text_cols_ = (gr_fb_width() - (kMarginWidth * 2)) / char_width_;
-
-  if (text_rows_ > kMaxRows) text_rows_ = kMaxRows;
-  if (text_cols_ > kMaxCols) text_cols_ = kMaxCols;
-
-  visible_text_rows = (gr_fb_height() - (kMarginHeight * 2)) / char_height_;
-  return true;
-}
-
-bool WearRecoveryUI::Init(const std::string& locale) {
-  if (!ScreenRecoveryUI::Init(locale)) {
-    return false;
-  }
-
-  LoadBitmap("icon_error", &backgroundIcon[ERROR]);
-  backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
-
-  // This leaves backgroundIcon[INSTALLING_UPDATE] and backgroundIcon[ERASING]
-  // as NULL which is fine since draw_background_locked() doesn't use them.
-
-  return true;
-}
-
-void WearRecoveryUI::SetStage(int current, int max) {
-}
-
-void WearRecoveryUI::Print(const char* fmt, ...) {
-  char buf[256];
-  va_list ap;
-  va_start(ap, fmt);
-  vsnprintf(buf, 256, fmt, ap);
-  va_end(ap);
-
-  fputs(buf, stdout);
-
-  // This can get called before ui_init(), so be careful.
-  pthread_mutex_lock(&updateMutex);
-  if (text_rows_ > 0 && text_cols_ > 0) {
-    char* ptr;
-    for (ptr = buf; *ptr != '\0'; ++ptr) {
-      if (*ptr == '\n' || text_col_ >= text_cols_) {
-        text_[text_row_][text_col_] = '\0';
-        text_col_ = 0;
-        text_row_ = (text_row_ + 1) % text_rows_;
-        if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
-      }
-      if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
-    }
-    text_[text_row_][text_col_] = '\0';
-    update_screen_locked();
-  }
-  pthread_mutex_unlock(&updateMutex);
-}
+void WearRecoveryUI::SetStage(int /* current */, int /* max */) {}
 
 void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* items,
                                int initial_selection) {
@@ -268,7 +170,7 @@
     show_menu = true;
     menu_sel = initial_selection;
     menu_start = 0;
-    menu_end = visible_text_rows - 1 - kMenuUnusableRows;
+    menu_end = text_rows_ - 1 - kMenuUnusableRows;
     if (menu_items <= menu_end) menu_end = menu_items;
     update_screen_locked();
   }
@@ -296,106 +198,3 @@
   pthread_mutex_unlock(&updateMutex);
   return sel;
 }
-
-void WearRecoveryUI::ShowFile(FILE* fp) {
-  std::vector<off_t> offsets;
-  offsets.push_back(ftello(fp));
-  ClearText();
-
-  struct stat sb;
-  fstat(fileno(fp), &sb);
-
-  bool show_prompt = false;
-  while (true) {
-    if (show_prompt) {
-      Print("--(%d%% of %d bytes)--",
-            static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))),
-            static_cast<int>(sb.st_size));
-      Redraw();
-      while (show_prompt) {
-        show_prompt = false;
-        int key = WaitKey();
-        if (key == KEY_POWER || key == KEY_ENTER) {
-          return;
-        } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
-          if (offsets.size() <= 1) {
-            show_prompt = true;
-          } else {
-            offsets.pop_back();
-            fseek(fp, offsets.back(), SEEK_SET);
-          }
-        } else {
-          if (feof(fp)) {
-            return;
-          }
-          offsets.push_back(ftello(fp));
-        }
-      }
-      ClearText();
-    }
-
-    int ch = getc(fp);
-    if (ch == EOF) {
-      text_row_ = text_top_ = text_rows_ - 2;
-      show_prompt = true;
-    } else {
-      PutChar(ch);
-      if (text_col_ == 0 && text_row_ >= text_rows_ - 2) {
-        text_top_ = text_row_;
-        show_prompt = true;
-      }
-    }
-  }
-}
-
-void WearRecoveryUI::PutChar(char ch) {
-  pthread_mutex_lock(&updateMutex);
-  if (ch != '\n') text_[text_row_][text_col_++] = ch;
-  if (ch == '\n' || text_col_ >= text_cols_) {
-    text_col_ = 0;
-    ++text_row_;
-  }
-  pthread_mutex_unlock(&updateMutex);
-}
-
-void WearRecoveryUI::ShowFile(const char* filename) {
-  FILE* fp = fopen_path(filename, "re");
-  if (fp == nullptr) {
-    Print("  Unable to open %s: %s\n", filename, strerror(errno));
-    return;
-  }
-  ShowFile(fp);
-  fclose(fp);
-}
-
-void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  PrintV(fmt, false, ap);
-  va_end(ap);
-}
-
-void WearRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
-  std::string str;
-  android::base::StringAppendV(&str, fmt, ap);
-
-  if (copy_to_stdout) {
-    fputs(str.c_str(), stdout);
-  }
-
-  pthread_mutex_lock(&updateMutex);
-  if (text_rows_ > 0 && text_cols_ > 0) {
-    for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
-      if (*ptr == '\n' || text_col_ >= text_cols_) {
-        text_[text_row_][text_col_] = '\0';
-        text_col_ = 0;
-        text_row_ = (text_row_ + 1) % text_rows_;
-        if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
-      }
-      if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
-    }
-    text_[text_row_][text_col_] = '\0';
-    update_screen_locked();
-  }
-  pthread_mutex_unlock(&updateMutex);
-}
diff --git a/wear_ui.h b/wear_ui.h
index 3bd90b6..739b4cb 100644
--- a/wear_ui.h
+++ b/wear_ui.h
@@ -19,22 +19,12 @@
 
 #include "screen_ui.h"
 
-#include <string>
-
 class WearRecoveryUI : public ScreenRecoveryUI {
  public:
   WearRecoveryUI();
 
-  bool Init(const std::string& locale) override;
-
   void SetStage(int current, int max) override;
 
-  // printing messages
-  void Print(const char* fmt, ...) override;
-  void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3);
-  void ShowFile(const char* filename) override;
-  void ShowFile(FILE* fp) override;
-
   // menu display
   void StartMenu(const char* const* headers, const char* const* items,
                  int initial_selection) override;
@@ -50,30 +40,13 @@
 
   int GetProgressBaseline() const override;
 
-  bool InitTextParams() override;
-
   void update_progress_locked() override;
 
-  void PrintV(const char*, bool, va_list) override;
-
  private:
-  GRSurface* backgroundIcon[5];
-
-  static const int kMaxCols = 96;
-  static const int kMaxRows = 96;
-
-  // Number of text rows seen on screen
-  int visible_text_rows;
-
-  const char* const* menu_headers_;
-  int menu_start, menu_end;
-
-  pthread_t progress_t;
-
   void draw_background_locked() override;
   void draw_screen_locked() override;
 
-  void PutChar(char);
+  int menu_start, menu_end;
 };
 
 #endif  // RECOVERY_WEAR_UI_H