Merge "bootable/recovery: cleanup compiler warnings (potential leak of memory)"
diff --git a/.clang-format b/.clang-format
index b8c6428..5322788 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,6 +1,7 @@
 BasedOnStyle: Google
 AllowShortBlocksOnASingleLine: false
-AllowShortFunctionsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: true
 
 ColumnLimit: 100
 CommentPragmas: NOLINT:.*
diff --git a/Android.mk b/Android.mk
index 4a7afb7..f8e5ac2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -76,8 +76,8 @@
 LOCAL_STATIC_LIBRARIES := \
     libbatterymonitor \
     libbootloader_message \
-    libext4_utils_static \
-    libsparse_static \
+    libext4_utils \
+    libsparse \
     libziparchive \
     libotautil \
     libmounts \
@@ -151,7 +151,11 @@
     asn1_decoder.cpp \
     verifier.cpp \
     ui.cpp
-LOCAL_STATIC_LIBRARIES := libcrypto_utils libcrypto libbase
+LOCAL_STATIC_LIBRARIES := \
+    libminui \
+    libcrypto_utils \
+    libcrypto \
+    libbase
 LOCAL_CFLAGS := -Werror
 include $(BUILD_STATIC_LIBRARY)
 
@@ -159,8 +163,8 @@
     $(LOCAL_PATH)/applypatch/Android.mk \
     $(LOCAL_PATH)/bootloader_message/Android.mk \
     $(LOCAL_PATH)/edify/Android.mk \
-    $(LOCAL_PATH)/minui/Android.mk \
     $(LOCAL_PATH)/minadbd/Android.mk \
+    $(LOCAL_PATH)/minui/Android.mk \
     $(LOCAL_PATH)/otafault/Android.mk \
     $(LOCAL_PATH)/otautil/Android.mk \
     $(LOCAL_PATH)/tests/Android.mk \
diff --git a/README.md b/README.md
index dc3d44e..8e20b5a 100644
--- a/README.md
+++ b/README.md
@@ -40,3 +40,10 @@
 - Reboot the device immediately and run the test again. The test should save the
   contents of pmsg buffer into /data/misc/recovery/inject.txt. Test will pass if
   this file has expected contents.
+
+`ResourceTest` validates whether the png files are qualified as background text
+image under recovery.
+
+    1. `adb sync data` to make sure the test-dir has the images to test.
+    2. The test will automatically pickup and verify all `_text.png` files in
+       the test dir.
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
index fa0fe8a..bdaef1b 100644
--- a/applypatch/Android.mk
+++ b/applypatch/Android.mk
@@ -17,7 +17,6 @@
 # libapplypatch (static library)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_SRC_FILES := \
     applypatch.cpp \
     bspatch.cpp \
@@ -26,52 +25,67 @@
     utils.cpp
 LOCAL_MODULE := libapplypatch
 LOCAL_MODULE_TAGS := eng
-LOCAL_C_INCLUDES += \
+LOCAL_C_INCLUDES := \
     $(LOCAL_PATH)/include \
     bootable/recovery
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES += \
+LOCAL_STATIC_LIBRARIES := \
     libotafault \
     libbase \
     libcrypto \
     libbz \
     libz
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := \
+    -DZLIB_CONST \
+    -Werror
 include $(BUILD_STATIC_LIBRARY)
 
 # libimgpatch (static library)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.cpp
+LOCAL_SRC_FILES := \
+    bspatch.cpp \
+    imgpatch.cpp \
+    utils.cpp
 LOCAL_MODULE := libimgpatch
-LOCAL_C_INCLUDES += \
+LOCAL_C_INCLUDES := \
     $(LOCAL_PATH)/include \
     bootable/recovery
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES += libcrypto libbz libz
-LOCAL_CFLAGS := -Werror
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto \
+    libbz \
+    libz
+LOCAL_CFLAGS := \
+    -DZLIB_CONST \
+    -Werror
 include $(BUILD_STATIC_LIBRARY)
 
 # libimgpatch (host static library)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := bspatch.cpp imgpatch.cpp utils.cpp
+LOCAL_SRC_FILES := \
+    bspatch.cpp \
+    imgpatch.cpp \
+    utils.cpp
 LOCAL_MODULE := libimgpatch
 LOCAL_MODULE_HOST_OS := linux
-LOCAL_C_INCLUDES += \
+LOCAL_C_INCLUDES := \
     $(LOCAL_PATH)/include \
     bootable/recovery
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_STATIC_LIBRARIES += libcrypto libbz libz
-LOCAL_CFLAGS := -Werror
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto \
+    libbz \
+    libz
+LOCAL_CFLAGS := \
+    -DZLIB_CONST \
+    -Werror
 include $(BUILD_HOST_STATIC_LIBRARY)
 
 # libapplypatch_modes (static library)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_SRC_FILES := \
     applypatch_modes.cpp
 LOCAL_MODULE := libapplypatch_modes
@@ -87,7 +101,6 @@
 # applypatch (target executable)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_SRC_FILES := applypatch_main.cpp
 LOCAL_MODULE := applypatch
 LOCAL_C_INCLUDES := bootable/recovery
@@ -106,18 +119,60 @@
 LOCAL_CFLAGS := -Werror
 include $(BUILD_EXECUTABLE)
 
+libimgdiff_src_files := \
+    imgdiff.cpp \
+    utils.cpp
+
+# libbsdiff is compiled with -D_FILE_OFFSET_BITS=64.
+libimgdiff_cflags := \
+    -Werror \
+    -D_FILE_OFFSET_BITS=64
+
+libimgdiff_static_libraries := \
+    libbsdiff \
+    libdivsufsort \
+    libdivsufsort64 \
+    libbase \
+    libz
+
+# libimgdiff (static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    $(libimgdiff_src_files)
+LOCAL_MODULE := libimgdiff
+LOCAL_CFLAGS := \
+    $(libimgdiff_cflags)
+LOCAL_STATIC_LIBRARIES := \
+    $(libimgdiff_static_libraries)
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+include $(BUILD_STATIC_LIBRARY)
+
+# libimgdiff (host static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    $(libimgdiff_src_files)
+LOCAL_MODULE := libimgdiff
+LOCAL_CFLAGS := \
+    $(libimgdiff_cflags)
+LOCAL_STATIC_LIBRARIES := \
+    $(libimgdiff_static_libraries)
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+include $(BUILD_HOST_STATIC_LIBRARY)
+
 # imgdiff (host static executable)
 # ===============================
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := imgdiff.cpp utils.cpp
+LOCAL_SRC_FILES := imgdiff_main.cpp
 LOCAL_MODULE := imgdiff
-LOCAL_STATIC_LIBRARIES += \
-    libbsdiff \
-    libbz \
-    libdivsufsort64 \
-    libdivsufsort \
-    libz
 LOCAL_CFLAGS := -Werror
-LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_STATIC_LIBRARIES := \
+    libimgdiff \
+    $(libimgdiff_static_libraries) \
+    libbz
 include $(BUILD_HOST_EXECUTABLE)
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
index 95389da..5006631 100644
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -332,6 +332,17 @@
       success = true;
       break;
     }
+
+    if (ota_close(fd) != 0) {
+      printf("failed to close %s: %s\n", partition, strerror(errno));
+      return -1;
+    }
+
+    fd.reset(ota_open(partition, O_RDWR));
+    if (fd == -1) {
+      printf("failed to reopen %s for retry write && verify: %s\n", partition, strerror(errno));
+      return -1;
+    }
   }
 
   if (!success) {
@@ -380,7 +391,7 @@
 // Search an array of sha1 strings for one matching the given sha1.
 // Return the index of the match on success, or -1 if no match is
 // found.
-int FindMatchingPatch(uint8_t* sha1, const std::vector<std::string>& patch_sha1_str) {
+static int FindMatchingPatch(uint8_t* sha1, const std::vector<std::string>& patch_sha1_str) {
   for (size_t i = 0; i < patch_sha1_str.size(); ++i) {
     uint8_t patch_sha1[SHA_DIGEST_LENGTH];
     if (ParseSha1(patch_sha1_str[i].c_str(), patch_sha1) == 0 &&
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index 7d6ebab..2f0e165 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -121,19 +121,23 @@
  * information that is stored on the system partition.
  */
 
+#include "applypatch/imgdiff.h"
+
 #include <errno.h>
-#include <inttypes.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
-#include <unistd.h>
 #include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/unique_fd.h>
 
 #include <bsdiff.h>
+#include <zlib.h>
 
-#include "zlib.h"
-#include "imgdiff.h"
 #include "utils.h"
 
 typedef struct {
@@ -375,8 +379,7 @@
  * return value when done with all the chunks.  Returns NULL on
  * failure.
  */
-unsigned char* ReadImage(const char* filename,
-                         int* num_chunks, ImageChunk** chunks) {
+unsigned char* ReadImage(const char* filename, int* num_chunks, ImageChunk** chunks) {
   struct stat st;
   if (stat(filename, &st) != 0) {
     printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
@@ -384,19 +387,12 @@
   }
 
   size_t sz = static_cast<size_t>(st.st_size);
-  unsigned char* img = static_cast<unsigned char*>(malloc(sz + 4));
-  FILE* f = fopen(filename, "rb");
-  if (fread(img, 1, sz, f) != sz) {
+  unsigned char* img = static_cast<unsigned char*>(malloc(sz));
+  android::base::unique_fd fd(open(filename, O_RDONLY));
+  if (!android::base::ReadFully(fd, img, sz)) {
     printf("failed to read \"%s\" %s\n", filename, strerror(errno));
-    fclose(f);
-    return NULL;
+    return nullptr;
   }
-  fclose(f);
-
-  // append 4 zero bytes to the data so we can always search for the
-  // four-byte string 1f8b0800 starting at any point in the actual
-  // file data, without special-casing the end of the data.
-  memset(img+sz, 0, 4);
 
   size_t pos = 0;
 
@@ -404,7 +400,7 @@
   *chunks = NULL;
 
   while (pos < sz) {
-    unsigned char* p = img+pos;
+    unsigned char* p = img + pos;
 
     if (sz - pos >= 4 &&
         p[0] == 0x1f && p[1] == 0x8b &&
@@ -414,8 +410,7 @@
       size_t chunk_offset = pos;
 
       *num_chunks += 3;
-      *chunks = static_cast<ImageChunk*>(realloc(*chunks,
-          *num_chunks * sizeof(ImageChunk)));
+      *chunks = static_cast<ImageChunk*>(realloc(*chunks, *num_chunks * sizeof(ImageChunk)));
       ImageChunk* curr = *chunks + (*num_chunks-3);
 
       // create a normal chunk for the header.
@@ -503,8 +498,7 @@
       // the decompression.
       size_t footer_size = Read4(p-4);
       if (footer_size != curr[-2].len) {
-        printf("Error: footer size %zu != decompressed size %zu\n",
-            footer_size, curr[-2].len);
+        printf("Error: footer size %zu != decompressed size %zu\n", footer_size, curr[-2].len);
         free(img);
         return NULL;
       }
@@ -522,10 +516,8 @@
       curr->data = p;
 
       for (curr->len = 0; curr->len < (sz - pos); ++curr->len) {
-        if (p[curr->len] == 0x1f &&
-            p[curr->len+1] == 0x8b &&
-            p[curr->len+2] == 0x08 &&
-            p[curr->len+3] == 0x00) {
+        if (sz - pos >= 4 && p[curr->len] == 0x1f && p[curr->len + 1] == 0x8b &&
+            p[curr->len + 2] == 0x08 && p[curr->len + 3] == 0x00) {
           break;
         }
       }
@@ -624,12 +616,12 @@
 }
 
 /*
- * Given source and target chunks, compute a bsdiff patch between them
- * by running bsdiff in a subprocess.  Return the patch data, placing
- * its length in *size.  Return NULL on failure.  We expect the bsdiff
- * program to be in the path.
+ * Given source and target chunks, compute a bsdiff patch between them.
+ * Return the patch data, placing its length in *size. Return NULL on failure.
+ * |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk
+ * is used repeatedly, pass nullptr if not needed.
  */
-unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) {
+unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size, saidx_t** bsdiff_cache) {
   if (tgt->type == CHUNK_NORMAL) {
     if (tgt->len <= 160) {
       tgt->type = CHUNK_RAW;
@@ -638,7 +630,11 @@
     }
   }
 
+#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) {
@@ -649,7 +645,7 @@
   close(fd); // temporary file is created and we don't need its file
              // descriptor
 
-  int r = bsdiff::bsdiff(src->data, src->len, tgt->data, tgt->len, ptemp);
+  int r = bsdiff::bsdiff(src->data, src->len, tgt->data, tgt->len, ptemp, bsdiff_cache);
   if (r != 0) {
     printf("bsdiff() failed: %d\n", r);
     return NULL;
@@ -794,10 +790,8 @@
   *num_chunks = out;
 }
 
-ImageChunk* FindChunkByName(const char* name,
-                            ImageChunk* chunks, int num_chunks) {
-  int i;
-  for (i = 0; i < num_chunks; ++i) {
+ImageChunk* FindChunkByName(const char* name, ImageChunk* chunks, int num_chunks) {
+  for (int i = 0; i < num_chunks; ++i) {
     if (chunks[i].type == CHUNK_DEFLATE && chunks[i].filename &&
         strcmp(name, chunks[i].filename) == 0) {
       return chunks+i;
@@ -813,11 +807,11 @@
     }
 }
 
-int main(int argc, char** argv) {
-  int zip_mode = 0;
+int imgdiff(int argc, const char** argv) {
+  bool zip_mode = false;
 
   if (argc >= 2 && strcmp(argv[1], "-z") == 0) {
-    zip_mode = 1;
+    zip_mode = true;
     --argc;
     ++argv;
   }
@@ -881,12 +875,10 @@
     // Verify that the source and target images have the same chunk
     // structure (ie, the same sequence of deflate and normal chunks).
 
-    if (!zip_mode) {
-        // Merge the gzip header and footer in with any adjacent
-        // normal chunks.
-        MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
-        MergeAdjacentNormalChunks(src_chunks, &num_src_chunks);
-    }
+    // Merge the gzip header and footer in with any adjacent
+    // normal chunks.
+    MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
+    MergeAdjacentNormalChunks(src_chunks, &num_src_chunks);
 
     if (num_src_chunks != num_tgt_chunks) {
       printf("source and target don't have same number of chunks!\n");
@@ -898,8 +890,7 @@
     }
     for (i = 0; i < num_src_chunks; ++i) {
       if (src_chunks[i].type != tgt_chunks[i].type) {
-        printf("source and target don't have same chunk "
-                "structure! (chunk %d)\n", i);
+        printf("source and target don't have same chunk structure! (chunk %d)\n", i);
         printf("source chunks:\n");
         DumpChunks(src_chunks, num_src_chunks);
         printf("target chunks:\n");
@@ -980,30 +971,31 @@
   unsigned char** patch_data = static_cast<unsigned char**>(malloc(
       num_tgt_chunks * sizeof(unsigned char*)));
   size_t* patch_size = static_cast<size_t*>(malloc(num_tgt_chunks * sizeof(size_t)));
+  saidx_t* bsdiff_cache = nullptr;
   for (i = 0; i < num_tgt_chunks; ++i) {
     if (zip_mode) {
       ImageChunk* src;
       if (tgt_chunks[i].type == CHUNK_DEFLATE &&
-          (src = FindChunkByName(tgt_chunks[i].filename, src_chunks,
-                                 num_src_chunks))) {
-        patch_data[i] = MakePatch(src, tgt_chunks+i, patch_size+i);
+          (src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks))) {
+        patch_data[i] = MakePatch(src, tgt_chunks + i, patch_size + i, nullptr);
       } else {
-        patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i);
+        patch_data[i] = MakePatch(src_chunks, tgt_chunks + i, patch_size + i, &bsdiff_cache);
       }
     } else {
       if (i == 1 && bonus_data) {
         printf("  using %zu bytes of bonus data for chunk %d\n", bonus_size, i);
-        src_chunks[i].data = static_cast<unsigned char*>(realloc(src_chunks[i].data,
-            src_chunks[i].len + bonus_size));
-        memcpy(src_chunks[i].data+src_chunks[i].len, bonus_data, bonus_size);
+        src_chunks[i].data =
+            static_cast<unsigned char*>(realloc(src_chunks[i].data, src_chunks[i].len + bonus_size));
+        memcpy(src_chunks[i].data + src_chunks[i].len, bonus_data, bonus_size);
         src_chunks[i].len += bonus_size;
-     }
+      }
 
-      patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i);
+      patch_data[i] = MakePatch(src_chunks + i, tgt_chunks + i, patch_size + i, nullptr);
     }
-    printf("patch %3d is %zu bytes (of %zu)\n",
-           i, patch_size[i], tgt_chunks[i].source_len);
+    printf("patch %3d is %zu bytes (of %zu)\n", i, patch_size[i], tgt_chunks[i].source_len);
   }
+  free(bsdiff_cache);
+  free(src_chunks);
 
   // Figure out how big the imgdiff file header is going to be, so
   // that we can correctly compute the offset of each bsdiff patch
@@ -1080,6 +1072,7 @@
     }
   }
 
+  free(tgt_chunks);
   free(patch_data);
   free(patch_size);
 
diff --git a/applypatch/imgdiff_main.cpp b/applypatch/imgdiff_main.cpp
new file mode 100644
index 0000000..7d5bdf9
--- /dev/null
+++ b/applypatch/imgdiff_main.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "applypatch/imgdiff.h"
+
+int main(int argc, char** argv) {
+  return imgdiff(argc, const_cast<const char**>(argv));
+}
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index 1c4409e..00ea90e 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -14,32 +14,34 @@
  * limitations under the License.
  */
 
-// See imgdiff.c in this directory for a description of the patch file
+// See imgdiff.cpp in this directory for a description of the patch file
 // format.
 
+#include <applypatch/imgpatch.h>
+
+#include <errno.h>
 #include <stdio.h>
+#include <string.h>
 #include <sys/cdefs.h>
 #include <sys/stat.h>
-#include <errno.h>
 #include <unistd.h>
-#include <string.h>
 
 #include <string>
 #include <vector>
 
-#include "zlib.h"
-#include "openssl/sha.h"
-#include "applypatch/applypatch.h"
-#include "imgdiff.h"
+#include <applypatch/applypatch.h>
+#include <applypatch/imgdiff.h>
+#include <openssl/sha.h>
+#include <zlib.h>
+
 #include "utils.h"
 
 int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
                     const unsigned char* patch_data, ssize_t patch_size,
                     SinkFn sink, void* token) {
-    Value patch(VAL_BLOB, std::string(
-            reinterpret_cast<const char*>(patch_data), patch_size));
+  Value patch(VAL_BLOB, std::string(reinterpret_cast<const char*>(patch_data), patch_size));
 
-    return ApplyImagePatch(old_data, old_size, &patch, sink, token, nullptr, nullptr);
+  return ApplyImagePatch(old_data, old_size, &patch, sink, token, nullptr, nullptr);
 }
 
 /*
@@ -48,208 +50,201 @@
  * file, and update the SHA context with the output data as well.
  * Return 0 on success.
  */
-int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
-                    const Value* patch,
-                    SinkFn sink, void* token, SHA_CTX* ctx,
-                    const Value* bonus_data) {
-    if (patch->data.size() < 12) {
-        printf("patch too short to contain header\n");
+int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value* patch,
+                    SinkFn sink, void* token, SHA_CTX* ctx, const Value* bonus_data) {
+  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) {
+    printf("corrupt patch file header (magic number)\n");
+    return -1;
+  }
+
+  int num_chunks = Read4(header + 8);
+
+  for (int i = 0; i < num_chunks; ++i) {
+    // each chunk's header record starts with 4 bytes.
+    if (pos + 4 > patch->data.size()) {
+      printf("failed to read chunk %d record\n", i);
+      return -1;
+    }
+    int type = Read4(&patch->data[pos]);
+    pos += 4;
+
+    if (type == CHUNK_NORMAL) {
+      const char* normal_header = &patch->data[pos];
+      pos += 24;
+      if (pos > patch->data.size()) {
+        printf("failed to read chunk %d normal header data\n", i);
         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) {
-        printf("corrupt patch file header (magic number)\n");
+      size_t src_start = Read8(normal_header);
+      size_t src_len = Read8(normal_header + 8);
+      size_t patch_offset = Read8(normal_header + 16);
+
+      if (src_start + src_len > static_cast<size_t>(old_size)) {
+        printf("source data too short\n");
         return -1;
-    }
+      }
+      ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, token, ctx);
+    } else if (type == CHUNK_RAW) {
+      const char* raw_header = &patch->data[pos];
+      pos += 4;
+      if (pos > patch->data.size()) {
+        printf("failed to read chunk %d raw header data\n", i);
+        return -1;
+      }
 
-    int num_chunks = Read4(header+8);
+      ssize_t data_len = Read4(raw_header);
 
-    for (int i = 0; i < num_chunks; ++i) {
-        // each chunk's header record starts with 4 bytes.
-        if (pos + 4 > patch->data.size()) {
-            printf("failed to read chunk %d record\n", i);
-            return -1;
+      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, token) !=
+          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];
+      pos += 60;
+      if (pos > patch->data.size()) {
+        printf("failed to read chunk %d deflate header data\n", i);
+        return -1;
+      }
+
+      size_t src_start = Read8(deflate_header);
+      size_t src_len = Read8(deflate_header + 8);
+      size_t patch_offset = Read8(deflate_header + 16);
+      size_t expanded_len = Read8(deflate_header + 24);
+      size_t target_len = Read8(deflate_header + 32);
+      int level = Read4(deflate_header + 40);
+      int method = Read4(deflate_header + 44);
+      int windowBits = Read4(deflate_header + 48);
+      int memLevel = Read4(deflate_header + 52);
+      int strategy = Read4(deflate_header + 56);
+
+      if (src_start + src_len > static_cast<size_t>(old_size)) {
+        printf("source data too short\n");
+        return -1;
+      }
+
+      // Decompress the source data; the chunk header tells us exactly
+      // how big we expect it to be when decompressed.
+
+      // Note: expanded_len will include the bonus data size if
+      // the patch was constructed with bonus data.  The
+      // deflation will come up 'bonus_size' bytes short; these
+      // must be appended from the bonus_data value.
+      size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->data.size() : 0;
+
+      std::vector<unsigned char> expanded_source(expanded_len);
+
+      // inflate() doesn't like strm.next_out being a nullptr even with
+      // avail_out being zero (Z_STREAM_ERROR).
+      if (expanded_len != 0) {
+        z_stream strm;
+        strm.zalloc = Z_NULL;
+        strm.zfree = Z_NULL;
+        strm.opaque = Z_NULL;
+        strm.avail_in = src_len;
+        strm.next_in = old_data + src_start;
+        strm.avail_out = expanded_len;
+        strm.next_out = expanded_source.data();
+
+        int ret = inflateInit2(&strm, -15);
+        if (ret != Z_OK) {
+          printf("failed to init source inflation: %d\n", ret);
+          return -1;
         }
-        int type = Read4(&patch->data[pos]);
-        pos += 4;
 
-        if (type == CHUNK_NORMAL) {
-            const char* normal_header = &patch->data[pos];
-            pos += 24;
-            if (pos > patch->data.size()) {
-                printf("failed to read chunk %d normal header data\n", i);
-                return -1;
-            }
-
-            size_t src_start = Read8(normal_header);
-            size_t src_len = Read8(normal_header+8);
-            size_t patch_offset = Read8(normal_header+16);
-
-            if (src_start + src_len > static_cast<size_t>(old_size)) {
-                printf("source data too short\n");
-                return -1;
-            }
-            ApplyBSDiffPatch(old_data + src_start, src_len,
-                             patch, patch_offset, sink, token, ctx);
-        } else if (type == CHUNK_RAW) {
-            const char* raw_header = &patch->data[pos];
-            pos += 4;
-            if (pos > patch->data.size()) {
-                printf("failed to read chunk %d raw header data\n", i);
-                return -1;
-            }
-
-            ssize_t data_len = Read4(raw_header);
-
-            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, token) != 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];
-            pos += 60;
-            if (pos > patch->data.size()) {
-                printf("failed to read chunk %d deflate header data\n", i);
-                return -1;
-            }
-
-            size_t src_start = Read8(deflate_header);
-            size_t src_len = Read8(deflate_header+8);
-            size_t patch_offset = Read8(deflate_header+16);
-            size_t expanded_len = Read8(deflate_header+24);
-            size_t target_len = Read8(deflate_header+32);
-            int level = Read4(deflate_header+40);
-            int method = Read4(deflate_header+44);
-            int windowBits = Read4(deflate_header+48);
-            int memLevel = Read4(deflate_header+52);
-            int strategy = Read4(deflate_header+56);
-
-            if (src_start + src_len > static_cast<size_t>(old_size)) {
-                printf("source data too short\n");
-                return -1;
-            }
-
-            // Decompress the source data; the chunk header tells us exactly
-            // how big we expect it to be when decompressed.
-
-            // Note: expanded_len will include the bonus data size if
-            // the patch was constructed with bonus data.  The
-            // deflation will come up 'bonus_size' bytes short; these
-            // must be appended from the bonus_data value.
-            size_t bonus_size = (i == 1 && bonus_data != NULL) ? bonus_data->data.size() : 0;
-
-            std::vector<unsigned char> expanded_source(expanded_len);
-
-            // inflate() doesn't like strm.next_out being a nullptr even with
-            // avail_out being zero (Z_STREAM_ERROR).
-            if (expanded_len != 0) {
-                z_stream strm;
-                strm.zalloc = Z_NULL;
-                strm.zfree = Z_NULL;
-                strm.opaque = Z_NULL;
-                strm.avail_in = src_len;
-                strm.next_in = (unsigned char*)(old_data + src_start);
-                strm.avail_out = expanded_len;
-                strm.next_out = expanded_source.data();
-
-                int ret;
-                ret = inflateInit2(&strm, -15);
-                if (ret != Z_OK) {
-                    printf("failed to init source inflation: %d\n", ret);
-                    return -1;
-                }
-
-                // Because we've provided enough room to accommodate the output
-                // data, we expect one call to inflate() to suffice.
-                ret = inflate(&strm, Z_SYNC_FLUSH);
-                if (ret != Z_STREAM_END) {
-                    printf("source inflation returned %d\n", ret);
-                    return -1;
-                }
-                // We should have filled the output buffer exactly, except
-                // for the bonus_size.
-                if (strm.avail_out != bonus_size) {
-                    printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size);
-                    return -1;
-                }
-                inflateEnd(&strm);
-
-                if (bonus_size) {
-                    memcpy(expanded_source.data() + (expanded_len - bonus_size),
-                           &bonus_data->data[0], bonus_size);
-                }
-            }
-
-            // Next, apply the bsdiff patch (in memory) to the uncompressed
-            // data.
-            std::vector<unsigned char> uncompressed_target_data;
-            if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len,
-                                    patch, patch_offset,
-                                    &uncompressed_target_data) != 0) {
-                return -1;
-            }
-            if (uncompressed_target_data.size() != target_len) {
-                printf("expected target len to be %zu, but it's %zu\n",
-                       target_len, uncompressed_target_data.size());
-                return -1;
-            }
-
-            // Now compress the target data and append it to the output.
-
-            // we're done with the expanded_source data buffer, so we'll
-            // reuse that memory to receive the output of deflate.
-            if (expanded_source.size() < 32768U) {
-                expanded_source.resize(32768U);
-            }
-
-            {
-                std::vector<unsigned char>& temp_data = expanded_source;
-
-                // now the deflate stream
-                z_stream strm;
-                strm.zalloc = Z_NULL;
-                strm.zfree = Z_NULL;
-                strm.opaque = Z_NULL;
-                strm.avail_in = uncompressed_target_data.size();
-                strm.next_in = uncompressed_target_data.data();
-                int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy);
-                if (ret != Z_OK) {
-                    printf("failed to init uncompressed data deflation: %d\n", ret);
-                    return -1;
-                }
-                do {
-                    strm.avail_out = temp_data.size();
-                    strm.next_out = temp_data.data();
-                    ret = deflate(&strm, Z_FINISH);
-                    ssize_t have = temp_data.size() - strm.avail_out;
-
-                    if (sink(temp_data.data(), have, token) != have) {
-                        printf("failed to write %zd compressed bytes to output\n",
-                               have);
-                        return -1;
-                    }
-                    if (ctx) SHA1_Update(ctx, temp_data.data(), have);
-                } while (ret != Z_STREAM_END);
-                deflateEnd(&strm);
-            }
-        } else {
-            printf("patch chunk %d is unknown type %d\n", i, type);
-            return -1;
+        // Because we've provided enough room to accommodate the output
+        // data, we expect one call to inflate() to suffice.
+        ret = inflate(&strm, Z_SYNC_FLUSH);
+        if (ret != Z_STREAM_END) {
+          printf("source inflation returned %d\n", ret);
+          return -1;
         }
-    }
+        // We should have filled the output buffer exactly, except
+        // for the bonus_size.
+        if (strm.avail_out != bonus_size) {
+          printf("source inflation short by %zu bytes\n", strm.avail_out - bonus_size);
+          return -1;
+        }
+        inflateEnd(&strm);
 
-    return 0;
+        if (bonus_size) {
+          memcpy(expanded_source.data() + (expanded_len - bonus_size), &bonus_data->data[0],
+                 bonus_size);
+        }
+      }
+
+      // Next, apply the bsdiff patch (in memory) to the uncompressed data.
+      std::vector<unsigned char> uncompressed_target_data;
+      if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len, patch, patch_offset,
+                              &uncompressed_target_data) != 0) {
+        return -1;
+      }
+      if (uncompressed_target_data.size() != target_len) {
+        printf("expected target len to be %zu, but it's %zu\n", target_len,
+               uncompressed_target_data.size());
+        return -1;
+      }
+
+      // Now compress the target data and append it to the output.
+
+      // we're done with the expanded_source data buffer, so we'll
+      // reuse that memory to receive the output of deflate.
+      if (expanded_source.size() < 32768U) {
+        expanded_source.resize(32768U);
+      }
+
+      {
+        std::vector<unsigned char>& temp_data = expanded_source;
+
+        // now the deflate stream
+        z_stream strm;
+        strm.zalloc = Z_NULL;
+        strm.zfree = Z_NULL;
+        strm.opaque = Z_NULL;
+        strm.avail_in = uncompressed_target_data.size();
+        strm.next_in = uncompressed_target_data.data();
+        int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy);
+        if (ret != Z_OK) {
+          printf("failed to init uncompressed data deflation: %d\n", ret);
+          return -1;
+        }
+        do {
+          strm.avail_out = temp_data.size();
+          strm.next_out = temp_data.data();
+          ret = deflate(&strm, Z_FINISH);
+          ssize_t have = temp_data.size() - strm.avail_out;
+
+          if (sink(temp_data.data(), have, token) != have) {
+            printf("failed to write %zd compressed bytes to output\n", have);
+            return -1;
+          }
+          if (ctx) SHA1_Update(ctx, temp_data.data(), have);
+        } while (ret != Z_STREAM_END);
+        deflateEnd(&strm);
+      }
+    } else {
+      printf("patch chunk %d is unknown type %d\n", i, type);
+      return -1;
+    }
+  }
+
+  return 0;
 }
diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h
index ca3dafb..4489dec 100644
--- a/applypatch/include/applypatch/applypatch.h
+++ b/applypatch/include/applypatch/applypatch.h
@@ -63,9 +63,8 @@
 
 int LoadFileContents(const char* filename, FileContents* file);
 int SaveFileContents(const char* filename, const FileContents* file);
-int FindMatchingPatch(uint8_t* sha1, const std::vector<std::string>& patch_sha1_str);
 
-// bsdiff.cpp
+// bspatch.cpp
 void ShowBSDiffLicense();
 int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size,
                      const Value* patch, ssize_t patch_offset,
diff --git a/applypatch/imgdiff.h b/applypatch/include/applypatch/imgdiff.h
similarity index 69%
rename from applypatch/imgdiff.h
rename to applypatch/include/applypatch/imgdiff.h
index f2069b4..22cbd4f 100644
--- a/applypatch/imgdiff.h
+++ b/applypatch/include/applypatch/imgdiff.h
@@ -14,17 +14,26 @@
  * limitations under the License.
  */
 
+#ifndef _APPLYPATCH_IMGDIFF_H
+#define _APPLYPATCH_IMGDIFF_H
+
+#include <stddef.h>
+
 // Image patch chunk types
-#define CHUNK_NORMAL   0
-#define CHUNK_GZIP     1   // version 1 only
-#define CHUNK_DEFLATE  2   // version 2 only
-#define CHUNK_RAW      3   // version 2 only
+#define CHUNK_NORMAL 0
+#define CHUNK_GZIP 1     // version 1 only
+#define CHUNK_DEFLATE 2  // version 2 only
+#define CHUNK_RAW 3      // version 2 only
 
 // The gzip header size is actually variable, but we currently don't
 // support gzipped data with any of the optional fields, so for now it
 // will always be ten bytes.  See RFC 1952 for the definition of the
 // gzip format.
-#define GZIP_HEADER_LEN   10
+static constexpr size_t GZIP_HEADER_LEN = 10;
 
 // The gzip footer size really is fixed.
-#define GZIP_FOOTER_LEN   8
+static constexpr size_t GZIP_FOOTER_LEN = 8;
+
+int imgdiff(int argc, const char** argv);
+
+#endif  // _APPLYPATCH_IMGDIFF_H
diff --git a/applypatch/include/applypatch/imgpatch.h b/applypatch/include/applypatch/imgpatch.h
index 64d9aa9..6549f79 100644
--- a/applypatch/include/applypatch/imgpatch.h
+++ b/applypatch/include/applypatch/imgpatch.h
@@ -14,13 +14,15 @@
  * limitations under the License.
  */
 
-#ifndef _IMGPATCH_H
-#define _IMGPATCH_H
+#ifndef _APPLYPATCH_IMGPATCH_H
+#define _APPLYPATCH_IMGPATCH_H
 
-typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*);
+#include <sys/types.h>
+
+using SinkFn = ssize_t (*)(const unsigned char*, ssize_t, void*);
 
 int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
                     const unsigned char* patch_data, ssize_t patch_size,
                     SinkFn sink, void* token);
 
-#endif  //_IMGPATCH_H
+#endif  // _APPLYPATCH_IMGPATCH_H
diff --git a/bootloader_message/Android.mk b/bootloader_message/Android.mk
index 815ac67..a8c5081 100644
--- a/bootloader_message/Android.mk
+++ b/bootloader_message/Android.mk
@@ -19,6 +19,7 @@
 LOCAL_SRC_FILES := bootloader_message.cpp
 LOCAL_MODULE := libbootloader_message
 LOCAL_STATIC_LIBRARIES := libbase libfs_mgr
+LOCAL_CFLAGS := -Werror
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 include $(BUILD_STATIC_LIBRARY)
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index 9a56718..b873d3d 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -176,6 +176,27 @@
   return write_bootloader_message(boot, err);
 }
 
+bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) {
+  bootloader_message boot;
+  if (!read_bootloader_message(&boot, err)) {
+    return false;
+  }
+
+  // Zero out the entire 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));
+    if (s.back() != '\n') {
+      strlcat(boot.recovery, "\n", sizeof(boot.recovery));
+    }
+  }
+  return write_bootloader_message(boot, err);
+}
+
 bool write_reboot_bootloader(std::string* err) {
   bootloader_message boot;
   if (!read_bootloader_message(&boot, err)) {
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index e45f424..bc5104d 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -22,7 +22,7 @@
 #include <stdint.h>
 
 // Spaces used by misc partition are as below:
-// 0   - 2K     Bootloader Message
+// 0   - 2K     For bootloader_message
 // 2K  - 16K    Used by Vendor's bootloader (the 2K - 4K range may be optionally used
 //              as bootloader_message_ab struct)
 // 16K - 64K    Used by uncrypt and recovery to store wipe_package for A/B devices
@@ -42,8 +42,9 @@
  * It is also updated by the bootloader when firmware update
  * is complete (to boot into recovery for any final cleanup)
  *
- * The status field is written by the bootloader after the
- * completion of an "update-radio" or "update-hboot" command.
+ * The status field was used by the bootloader after the completion
+ * of an "update-radio" or "update-hboot" command, which has been
+ * deprecated since Froyo.
  *
  * The recovery field is only written by linux and used
  * for the system to send a message to recovery or the
@@ -173,6 +174,7 @@
               sizeof(((struct bootloader_message_ab *)0)->slot_suffix),
               "struct bootloader_control has wrong size");
 #endif
+
 #ifdef __cplusplus
 
 #include <string>
@@ -192,9 +194,14 @@
 bool write_bootloader_message_to(const bootloader_message& boot,
                                  const std::string& misc_blk_device, std::string* err);
 
-// Write bootloader message (boots into recovery with the options) to BCB.
+// Write bootloader message (boots into recovery with the options) to BCB. Will
+// set the command and recovery fields, and reset the rest.
 bool write_bootloader_message(const std::vector<std::string>& options, std::string* err);
 
+// Update bootloader message (boots into recovery with the options) to BCB. Will
+// only update the command and recovery fields.
+bool update_bootloader_message(const std::vector<std::string>& options, std::string* err);
+
 // Clear BCB.
 bool clear_bootloader_message(std::string* err);
 
diff --git a/error_code.h b/error_code.h
index 92b1574..5dad6b2 100644
--- a/error_code.h
+++ b/error_code.h
@@ -47,7 +47,7 @@
 
 enum UncryptErrorCode {
   kUncryptNoError = -1,
-  kUncryptErrorHolder = 50,
+  kUncryptErrorPlaceholder = 50,
   kUncryptTimeoutError = 100,
   kUncryptFileRemoveError,
   kUncryptFileOpenError,
diff --git a/etc/init.rc b/etc/init.rc
index b1473ba..477e13d 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -30,6 +30,7 @@
     write /proc/sys/vm/max_map_count 1000000
 
 on fs
+    write /sys/class/android_usb/android0/f_ffs/aliases adb
     mkdir /dev/usb-ffs 0770 shell shell
     mkdir /dev/usb-ffs/adb 0770 shell shell
     mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000
@@ -37,7 +38,6 @@
     write /sys/class/android_usb/android0/enable 0
     write /sys/class/android_usb/android0/idVendor 18D1
     write /sys/class/android_usb/android0/idProduct D001
-    write /sys/class/android_usb/android0/f_ffs/aliases adb
     write /sys/class/android_usb/android0/functions adb
     write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer}
     write /sys/class/android_usb/android0/iProduct ${ro.product.model}
diff --git a/install.cpp b/install.cpp
index f124a26..959a742 100644
--- a/install.cpp
+++ b/install.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "install.h"
+
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -32,15 +34,15 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/parsedouble.h>
 #include <android-base/parseint.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <cutils/properties.h>
 #include <ziparchive/zip_archive.h>
 
 #include "common.h"
 #include "error_code.h"
-#include "install.h"
 #include "minui/minui.h"
 #include "otautil/SysUtil.h"
 #include "roots.h"
@@ -55,10 +57,10 @@
 static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
 
 // Default allocation of progress bar segments to operations
-static const int VERIFICATION_PROGRESS_TIME = 60;
-static const float VERIFICATION_PROGRESS_FRACTION = 0.25;
-static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4;
-static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1;
+static constexpr int VERIFICATION_PROGRESS_TIME = 60;
+static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
+static constexpr float DEFAULT_FILES_PROGRESS_FRACTION = 0.4;
+static constexpr float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1;
 
 // This function parses and returns the build.version.incremental
 static int parse_build_number(const std::string& str) {
@@ -137,81 +139,79 @@
 // Parses the metadata of the OTA package in |zip| and checks whether we are
 // allowed to accept this A/B package. Downgrading is not allowed unless
 // explicitly enabled in the package and only for incremental packages.
-static int check_newer_ab_build(ZipArchiveHandle zip)
-{
-    std::string metadata_str;
-    if (!read_metadata_from_package(zip, &metadata_str)) {
-        return INSTALL_CORRUPT;
+static int check_newer_ab_build(ZipArchiveHandle zip) {
+  std::string metadata_str;
+  if (!read_metadata_from_package(zip, &metadata_str)) {
+    return INSTALL_CORRUPT;
+  }
+  std::map<std::string, std::string> metadata;
+  for (const std::string& line : android::base::Split(metadata_str, "\n")) {
+    size_t eq = line.find('=');
+    if (eq != std::string::npos) {
+      metadata[line.substr(0, eq)] = line.substr(eq + 1);
     }
-    std::map<std::string, std::string> metadata;
-    for (const std::string& line : android::base::Split(metadata_str, "\n")) {
-        size_t eq = line.find('=');
-        if (eq != std::string::npos) {
-            metadata[line.substr(0, eq)] = line.substr(eq + 1);
-        }
-    }
-    char value[PROPERTY_VALUE_MAX];
+  }
 
-    property_get("ro.product.device", value, "");
-    const std::string& pkg_device = metadata["pre-device"];
-    if (pkg_device != value || pkg_device.empty()) {
-        LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value;
-        return INSTALL_ERROR;
-    }
+  std::string value = android::base::GetProperty("ro.product.device", "");
+  const std::string& pkg_device = metadata["pre-device"];
+  if (pkg_device != value || pkg_device.empty()) {
+    LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value;
+    return INSTALL_ERROR;
+  }
 
-    // We allow the package to not have any serialno, but if it has a non-empty
-    // value it should match.
-    property_get("ro.serialno", value, "");
-    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;
-    }
+  // We allow the package to not have any serialno, but if it has a non-empty
+  // value it should match.
+  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 (metadata["ota-type"] != "AB") {
-        LOG(ERROR) << "Package is not A/B";
-        return INSTALL_ERROR;
-    }
+  if (metadata["ota-type"] != "AB") {
+    LOG(ERROR) << "Package is not A/B";
+    return INSTALL_ERROR;
+  }
 
-    // Incremental updates should match the current build.
-    property_get("ro.build.version.incremental", value, "");
-    const std::string& pkg_pre_build = metadata["pre-build-incremental"];
-    if (!pkg_pre_build.empty() && pkg_pre_build != value) {
-        LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value;
-        return INSTALL_ERROR;
-    }
-    property_get("ro.build.fingerprint", value, "");
-    const std::string& pkg_pre_build_fingerprint = metadata["pre-build"];
-    if (!pkg_pre_build_fingerprint.empty() &&
-        pkg_pre_build_fingerprint != value) {
-        LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint
-                   << " but expected " << value;
-        return INSTALL_ERROR;
-    }
+  // Incremental updates should match the current build.
+  value = android::base::GetProperty("ro.build.version.incremental", "");
+  const std::string& pkg_pre_build = metadata["pre-build-incremental"];
+  if (!pkg_pre_build.empty() && pkg_pre_build != value) {
+    LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value;
+    return INSTALL_ERROR;
+  }
 
-    // Check for downgrade version.
-    int64_t build_timestamp = property_get_int64(
-            "ro.build.date.utc", std::numeric_limits<int64_t>::max());
-    int64_t pkg_post_timestamp = 0;
-    // We allow to full update to the same version we are running, in case there
-    // is a problem with the current copy of that version.
-    if (metadata["post-timestamp"].empty() ||
-        !android::base::ParseInt(metadata["post-timestamp"].c_str(),
-                                 &pkg_post_timestamp) ||
-        pkg_post_timestamp < build_timestamp) {
-        if (metadata["ota-downgrade"] != "yes") {
-            LOG(ERROR) << "Update package is older than the current build, expected a build "
-                       "newer than timestamp " << build_timestamp << " but package has "
-                       "timestamp " << pkg_post_timestamp << " and downgrade not allowed.";
-            return INSTALL_ERROR;
-        }
-        if (pkg_pre_build_fingerprint.empty()) {
-            LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
-            return INSTALL_ERROR;
-        }
-    }
+  value = android::base::GetProperty("ro.build.fingerprint", "");
+  const std::string& pkg_pre_build_fingerprint = metadata["pre-build"];
+  if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != value) {
+    LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected "
+               << value;
+    return INSTALL_ERROR;
+  }
 
-    return 0;
+  // Check for downgrade version.
+  int64_t build_timestamp =
+      android::base::GetIntProperty("ro.build.date.utc", std::numeric_limits<int64_t>::max());
+  int64_t pkg_post_timestamp = 0;
+  // We allow to full update to the same version we are running, in case there
+  // is a problem with the current copy of that version.
+  if (metadata["post-timestamp"].empty() ||
+      !android::base::ParseInt(metadata["post-timestamp"].c_str(), &pkg_post_timestamp) ||
+      pkg_post_timestamp < build_timestamp) {
+    if (metadata["ota-downgrade"] != "yes") {
+      LOG(ERROR) << "Update package is older than the current build, expected a build "
+                    "newer than timestamp "
+                 << build_timestamp << " but package has timestamp " << pkg_post_timestamp
+                 << " and downgrade not allowed.";
+      return INSTALL_ERROR;
+    }
+    if (pkg_pre_build_fingerprint.empty()) {
+      LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
+      return INSTALL_ERROR;
+    }
+  }
+
+  return 0;
 }
 
 static int
@@ -299,158 +299,164 @@
 #endif  // !AB_OTA_UPDATER
 
 // If the package contains an update binary, extract it and run it.
-static int
-try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache,
-                  std::vector<std::string>& log_buffer, int retry_count)
-{
-    read_source_target_build(zip, log_buffer);
+static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache,
+                             std::vector<std::string>& log_buffer, int retry_count) {
+  read_source_target_build(zip, log_buffer);
 
-    int pipefd[2];
-    pipe(pipefd);
+  int pipefd[2];
+  pipe(pipefd);
 
-    std::vector<std::string> args;
-    int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
-    if (ret) {
-        close(pipefd[0]);
-        close(pipefd[1]);
-        return ret;
-    }
-
-    // When executing the update binary contained in the package, the
-    // arguments passed are:
-    //
-    //   - the version number for this interface
-    //
-    //   - an fd to which the program can write in order to update the
-    //     progress bar.  The program can write single-line commands:
-    //
-    //        progress <frac> <secs>
-    //            fill up the next <frac> part of of the progress bar
-    //            over <secs> seconds.  If <secs> is zero, use
-    //            set_progress commands to manually control the
-    //            progress of this segment of the bar.
-    //
-    //        set_progress <frac>
-    //            <frac> should be between 0.0 and 1.0; sets the
-    //            progress bar within the segment defined by the most
-    //            recent progress command.
-    //
-    //        firmware <"hboot"|"radio"> <filename>
-    //            arrange to install the contents of <filename> in the
-    //            given partition on reboot.
-    //
-    //            (API v2: <filename> may start with "PACKAGE:" to
-    //            indicate taking a file from the OTA package.)
-    //
-    //            (API v3: this command no longer exists.)
-    //
-    //        ui_print <string>
-    //            display <string> on the screen.
-    //
-    //        wipe_cache
-    //            a wipe of cache will be performed following a successful
-    //            installation.
-    //
-    //        clear_display
-    //            turn off the text display.
-    //
-    //        enable_reboot
-    //            packages can explicitly request that they want the user
-    //            to be able to reboot during installation (useful for
-    //            debugging packages that don't exit).
-    //
-    //   - the name of the package zip file.
-    //
-    //   - an optional argument "retry" if this update is a retry of a failed
-    //   update attempt.
-    //
-
-    // Convert the vector to a NULL-terminated char* array suitable for execv.
-    const char* chr_args[args.size() + 1];
-    chr_args[args.size()] = NULL;
-    for (size_t i = 0; i < args.size(); i++) {
-        chr_args[i] = args[i].c_str();
-    }
-
-    pid_t pid = fork();
-
-    if (pid == -1) {
-        close(pipefd[0]);
-        close(pipefd[1]);
-        PLOG(ERROR) << "Failed to fork update binary";
-        return INSTALL_ERROR;
-    }
-
-    if (pid == 0) {
-        umask(022);
-        close(pipefd[0]);
-        execv(chr_args[0], const_cast<char**>(chr_args));
-        fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
-        _exit(-1);
-    }
+  std::vector<std::string> args;
+  int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
+  if (ret) {
+    close(pipefd[0]);
     close(pipefd[1]);
+    return ret;
+  }
 
-    *wipe_cache = false;
-    bool retry_update = false;
+  // When executing the update binary contained in the package, the
+  // arguments passed are:
+  //
+  //   - the version number for this interface
+  //
+  //   - an FD to which the program can write in order to update the
+  //     progress bar.  The program can write single-line commands:
+  //
+  //        progress <frac> <secs>
+  //            fill up the next <frac> part of of the progress bar
+  //            over <secs> seconds.  If <secs> is zero, use
+  //            set_progress commands to manually control the
+  //            progress of this segment of the bar.
+  //
+  //        set_progress <frac>
+  //            <frac> should be between 0.0 and 1.0; sets the
+  //            progress bar within the segment defined by the most
+  //            recent progress command.
+  //
+  //        ui_print <string>
+  //            display <string> on the screen.
+  //
+  //        wipe_cache
+  //            a wipe of cache will be performed following a successful
+  //            installation.
+  //
+  //        clear_display
+  //            turn off the text display.
+  //
+  //        enable_reboot
+  //            packages can explicitly request that they want the user
+  //            to be able to reboot during installation (useful for
+  //            debugging packages that don't exit).
+  //
+  //        retry_update
+  //            updater encounters some issue during the update. It requests
+  //            a reboot to retry the same package automatically.
+  //
+  //        log <string>
+  //            updater requests logging the string (e.g. cause of the
+  //            failure).
+  //
+  //   - the name of the package zip file.
+  //
+  //   - an optional argument "retry" if this update is a retry of a failed
+  //   update attempt.
+  //
 
-    char buffer[1024];
-    FILE* from_child = fdopen(pipefd[0], "r");
-    while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
-        char* command = strtok(buffer, " \n");
-        if (command == NULL) {
-            continue;
-        } else if (strcmp(command, "progress") == 0) {
-            char* fraction_s = strtok(NULL, " \n");
-            char* seconds_s = strtok(NULL, " \n");
+  // Convert the vector to a NULL-terminated char* array suitable for execv.
+  const char* chr_args[args.size() + 1];
+  chr_args[args.size()] = nullptr;
+  for (size_t i = 0; i < args.size(); i++) {
+    chr_args[i] = args[i].c_str();
+  }
 
-            float fraction = strtof(fraction_s, NULL);
-            int seconds = strtol(seconds_s, NULL, 10);
+  pid_t pid = fork();
 
-            ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);
-        } else if (strcmp(command, "set_progress") == 0) {
-            char* fraction_s = strtok(NULL, " \n");
-            float fraction = strtof(fraction_s, NULL);
-            ui->SetProgress(fraction);
-        } else if (strcmp(command, "ui_print") == 0) {
-            char* str = strtok(NULL, "\n");
-            if (str) {
-                ui->PrintOnScreenOnly("%s", str);
-            } else {
-                ui->PrintOnScreenOnly("\n");
-            }
-            fflush(stdout);
-        } else if (strcmp(command, "wipe_cache") == 0) {
-            *wipe_cache = true;
-        } else if (strcmp(command, "clear_display") == 0) {
-            ui->SetBackground(RecoveryUI::NONE);
-        } else if (strcmp(command, "enable_reboot") == 0) {
-            // packages can explicitly request that they want the user
-            // to be able to reboot during installation (useful for
-            // debugging packages that don't exit).
-            ui->SetEnableReboot(true);
-        } else if (strcmp(command, "retry_update") == 0) {
-            retry_update = true;
-        } else if (strcmp(command, "log") == 0) {
-            // Save the logging request from updater and write to
-            // last_install later.
-            log_buffer.push_back(std::string(strtok(NULL, "\n")));
-        } else {
-            LOG(ERROR) << "unknown command [" << command << "]";
-        }
+  if (pid == -1) {
+    close(pipefd[0]);
+    close(pipefd[1]);
+    PLOG(ERROR) << "Failed to fork update binary";
+    return INSTALL_ERROR;
+  }
+
+  if (pid == 0) {
+    umask(022);
+    close(pipefd[0]);
+    execv(chr_args[0], const_cast<char**>(chr_args));
+    PLOG(ERROR) << "Can't run " << chr_args[0];
+    _exit(-1);
+  }
+  close(pipefd[1]);
+
+  *wipe_cache = false;
+  bool retry_update = false;
+
+  char buffer[1024];
+  FILE* from_child = fdopen(pipefd[0], "r");
+  while (fgets(buffer, sizeof(buffer), from_child) != nullptr) {
+    std::string line(buffer);
+    size_t space = line.find_first_of(" \n");
+    std::string command(line.substr(0, space));
+    if (command.empty()) continue;
+
+    // Get rid of the leading and trailing space and/or newline.
+    std::string args = space == std::string::npos ? "" : android::base::Trim(line.substr(space));
+
+    if (command == "progress") {
+      std::vector<std::string> tokens = android::base::Split(args, " ");
+      double fraction;
+      int seconds;
+      if (tokens.size() == 2 && android::base::ParseDouble(tokens[0].c_str(), &fraction) &&
+          android::base::ParseInt(tokens[1], &seconds)) {
+        ui->ShowProgress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), seconds);
+      } else {
+        LOG(ERROR) << "invalid \"progress\" parameters: " << line;
+      }
+    } else if (command == "set_progress") {
+      std::vector<std::string> tokens = android::base::Split(args, " ");
+      double fraction;
+      if (tokens.size() == 1 && android::base::ParseDouble(tokens[0].c_str(), &fraction)) {
+        ui->SetProgress(fraction);
+      } else {
+        LOG(ERROR) << "invalid \"set_progress\" parameters: " << line;
+      }
+    } else if (command == "ui_print") {
+      ui->PrintOnScreenOnly("%s\n", args.c_str());
+      fflush(stdout);
+    } else if (command == "wipe_cache") {
+      *wipe_cache = true;
+    } else if (command == "clear_display") {
+      ui->SetBackground(RecoveryUI::NONE);
+    } else if (command == "enable_reboot") {
+      // packages can explicitly request that they want the user
+      // to be able to reboot during installation (useful for
+      // debugging packages that don't exit).
+      ui->SetEnableReboot(true);
+    } else if (command == "retry_update") {
+      retry_update = true;
+    } else if (command == "log") {
+      if (!args.empty()) {
+        // Save the logging request from updater and write to last_install later.
+        log_buffer.push_back(args);
+      } else {
+        LOG(ERROR) << "invalid \"log\" parameters: " << line;
+      }
+    } else {
+      LOG(ERROR) << "unknown command [" << command << "]";
     }
-    fclose(from_child);
+  }
+  fclose(from_child);
 
-    int status;
-    waitpid(pid, &status, 0);
-    if (retry_update) {
-        return INSTALL_RETRY;
-    }
-    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-        LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")";
-        return INSTALL_ERROR;
-    }
+  int status;
+  waitpid(pid, &status, 0);
+  if (retry_update) {
+    return INSTALL_RETRY;
+  }
+  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+    LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")";
+    return INSTALL_ERROR;
+  }
 
-    return INSTALL_SUCCESS;
+  return INSTALL_SUCCESS;
 }
 
 static int
@@ -542,7 +548,7 @@
         if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) {
             PLOG(WARNING) << "failed to read uncrypt status";
         } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) {
-            LOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status;
+            PLOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status;
         } else {
             log_buffer.push_back(android::base::Trim(uncrypt_status));
         }
diff --git a/minadbd/README.txt b/minadbd/README.md
similarity index 79%
rename from minadbd/README.txt
rename to minadbd/README.md
index e69dc87..5a0a067 100644
--- a/minadbd/README.txt
+++ b/minadbd/README.md
@@ -3,6 +3,6 @@
 
   - all services removed
   - all host mode support removed
-  - sideload_service() added; this is the only service supported.  It
+  - `sideload_service()` added; this is the only service supported. It
     receives a single blob of data, writes it to a fixed filename, and
     makes the process exit.
diff --git a/minui/Android.mk b/minui/Android.mk
index 67b81fc..281f649 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -1,3 +1,17 @@
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
@@ -9,16 +23,18 @@
     graphics_fbdev.cpp \
     resources.cpp \
 
-LOCAL_WHOLE_STATIC_LIBRARIES += libadf
-LOCAL_WHOLE_STATIC_LIBRARIES += libdrm
-LOCAL_WHOLE_STATIC_LIBRARIES += libsync_recovery
-LOCAL_STATIC_LIBRARIES += libpng
+LOCAL_WHOLE_STATIC_LIBRARIES := \
+    libadf \
+    libdrm \
+    libsync_recovery
+
+LOCAL_STATIC_LIBRARIES := libpng
 LOCAL_CFLAGS := -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 
 LOCAL_MODULE := libminui
 
-LOCAL_CLANG := true
-
 # This used to compare against values in double-quotes (which are just
 # ordinary characters in this context).  Strip double-quotes from the
 # value so that either will work.
@@ -43,9 +59,10 @@
 
 # Used by OEMs for factory test images.
 include $(CLEAR_VARS)
-LOCAL_CLANG := true
 LOCAL_MODULE := libminui
 LOCAL_WHOLE_STATIC_LIBRARIES += libminui
 LOCAL_SHARED_LIBRARIES := libpng
 LOCAL_CFLAGS := -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 include $(BUILD_SHARED_LIBRARY)
diff --git a/minui/events.cpp b/minui/events.cpp
index e6e7bd2..6dd60fe 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -15,17 +15,15 @@
  */
 
 #include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
+#include <linux/input.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/epoll.h>
+#include <sys/ioctl.h>
 #include <unistd.h>
 
-#include <linux/input.h>
-
-#include "minui.h"
+#include "minui/minui.h"
 
 #define MAX_DEVICES 16
 #define MAX_MISC_FDS 16
@@ -34,9 +32,8 @@
 #define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG)
 
 struct fd_info {
-    int fd;
-    ev_callback cb;
-    void* data;
+  int fd;
+  ev_callback cb;
 };
 
 static int g_epoll_fd;
@@ -53,89 +50,87 @@
     return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
 }
 
-int ev_init(ev_callback input_cb, void* data) {
-    bool epollctlfail = false;
+int ev_init(ev_callback input_cb) {
+  bool epollctlfail = false;
 
-    g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
-    if (g_epoll_fd == -1) {
-        return -1;
+  g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
+  if (g_epoll_fd == -1) {
+    return -1;
+  }
+
+  DIR* dir = opendir("/dev/input");
+  if (dir != NULL) {
+    dirent* de;
+    while ((de = readdir(dir))) {
+      // Use unsigned long to match ioctl's parameter type.
+      unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];  // NOLINT
+
+      //            fprintf(stderr,"/dev/input/%s\n", de->d_name);
+      if (strncmp(de->d_name, "event", 5)) continue;
+      int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
+      if (fd == -1) continue;
+
+      // Read the evbits of the input device.
+      if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+        close(fd);
+        continue;
+      }
+
+      // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed.
+      if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
+        close(fd);
+        continue;
+      }
+
+      epoll_event ev;
+      ev.events = EPOLLIN | EPOLLWAKEUP;
+      ev.data.ptr = &ev_fdinfo[ev_count];
+      if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
+        close(fd);
+        epollctlfail = true;
+        continue;
+      }
+
+      ev_fdinfo[ev_count].fd = fd;
+      ev_fdinfo[ev_count].cb = std::move(input_cb);
+      ev_count++;
+      ev_dev_count++;
+      if (ev_dev_count == MAX_DEVICES) break;
     }
 
-    DIR* dir = opendir("/dev/input");
-    if (dir != NULL) {
-        dirent* de;
-        while ((de = readdir(dir))) {
-            // Use unsigned long to match ioctl's parameter type.
-            unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
+    closedir(dir);
+  }
 
-//            fprintf(stderr,"/dev/input/%s\n", de->d_name);
-            if (strncmp(de->d_name, "event", 5)) continue;
-            int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
-            if (fd == -1) continue;
+  if (epollctlfail && !ev_count) {
+    close(g_epoll_fd);
+    g_epoll_fd = -1;
+    return -1;
+  }
 
-            // Read the evbits of the input device.
-            if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
-                close(fd);
-                continue;
-            }
-
-            // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed.
-            if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
-                close(fd);
-                continue;
-            }
-
-            epoll_event ev;
-            ev.events = EPOLLIN | EPOLLWAKEUP;
-            ev.data.ptr = &ev_fdinfo[ev_count];
-            if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
-                close(fd);
-                epollctlfail = true;
-                continue;
-            }
-
-            ev_fdinfo[ev_count].fd = fd;
-            ev_fdinfo[ev_count].cb = input_cb;
-            ev_fdinfo[ev_count].data = data;
-            ev_count++;
-            ev_dev_count++;
-            if (ev_dev_count == MAX_DEVICES) break;
-        }
-
-        closedir(dir);
-    }
-
-    if (epollctlfail && !ev_count) {
-        close(g_epoll_fd);
-        g_epoll_fd = -1;
-        return -1;
-    }
-
-    return 0;
+  return 0;
 }
 
 int ev_get_epollfd(void) {
     return g_epoll_fd;
 }
 
-int ev_add_fd(int fd, ev_callback cb, void* data) {
-    if (ev_misc_count == MAX_MISC_FDS || cb == NULL) {
-        return -1;
-    }
+int ev_add_fd(int fd, ev_callback cb) {
+  if (ev_misc_count == MAX_MISC_FDS || cb == NULL) {
+    return -1;
+  }
 
-    epoll_event ev;
-    ev.events = EPOLLIN | EPOLLWAKEUP;
-    ev.data.ptr = (void *)&ev_fdinfo[ev_count];
-    int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev);
-    if (!ret) {
-        ev_fdinfo[ev_count].fd = fd;
-        ev_fdinfo[ev_count].cb = cb;
-        ev_fdinfo[ev_count].data = data;
-        ev_count++;
-        ev_misc_count++;
-    }
+  epoll_event ev;
+  ev.events = EPOLLIN | EPOLLWAKEUP;
+  ev.data.ptr = static_cast<void*>(&ev_fdinfo[ev_count]);
+  int ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev);
+  if (!ret) {
+    ev_fdinfo[ev_count].fd = fd;
+    ev_fdinfo[ev_count].cb = std::move(cb);
+    ev_count++;
+    ev_misc_count++;
+  }
 
-    return ret;
+  return ret;
 }
 
 void ev_exit(void) {
@@ -156,13 +151,13 @@
 }
 
 void ev_dispatch(void) {
-    for (int n = 0; n < npolledevents; n++) {
-        fd_info* fdi = reinterpret_cast<fd_info*>(polledevents[n].data.ptr);
-        ev_callback cb = fdi->cb;
-        if (cb) {
-            cb(fdi->fd, polledevents[n].events, fdi->data);
-        }
+  for (int n = 0; n < npolledevents; n++) {
+    fd_info* fdi = static_cast<fd_info*>(polledevents[n].data.ptr);
+    const ev_callback& cb = fdi->cb;
+    if (cb) {
+      cb(fdi->fd, polledevents[n].events);
     }
+  }
 }
 
 int ev_get_input(int fd, uint32_t epevents, input_event* ev) {
@@ -175,33 +170,33 @@
     return -1;
 }
 
-int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data) {
-    // Use unsigned long to match ioctl's parameter type.
-    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
-    unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT
+int ev_sync_key_state(const ev_set_key_callback& set_key_cb) {
+  // Use unsigned long to match ioctl's parameter type.
+  unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];    // NOLINT
+  unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];  // NOLINT
 
-    for (size_t i = 0; i < ev_dev_count; ++i) {
-        memset(ev_bits, 0, sizeof(ev_bits));
-        memset(key_bits, 0, sizeof(key_bits));
+  for (size_t i = 0; i < ev_dev_count; ++i) {
+    memset(ev_bits, 0, sizeof(ev_bits));
+    memset(key_bits, 0, sizeof(key_bits));
 
-        if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
-            continue;
-        }
-        if (!test_bit(EV_KEY, ev_bits)) {
-            continue;
-        }
-        if (ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits) == -1) {
-            continue;
-        }
-
-        for (int code = 0; code <= KEY_MAX; code++) {
-            if (test_bit(code, key_bits)) {
-                set_key_cb(code, 1, data);
-            }
-        }
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+      continue;
+    }
+    if (!test_bit(EV_KEY, ev_bits)) {
+      continue;
+    }
+    if (ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits) == -1) {
+      continue;
     }
 
-    return 0;
+    for (int code = 0; code <= KEY_MAX; code++) {
+      if (test_bit(code, key_bits)) {
+        set_key_cb(code, 1);
+      }
+    }
+  }
+
+  return 0;
 }
 
 void ev_iterate_available_keys(const std::function<void(int)>& f) {
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index dcca3ec..34ea81c 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-#include <stdbool.h>
+#include "graphics.h"
+
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
@@ -32,8 +33,7 @@
 #include <time.h>
 
 #include "font_10x18.h"
-#include "minui.h"
-#include "graphics.h"
+#include "minui/minui.h"
 
 static GRFont* gr_font = NULL;
 static minui_backend* gr_backend = NULL;
diff --git a/minui/graphics.h b/minui/graphics.h
index 52968eb..1eaafc7 100644
--- a/minui/graphics.h
+++ b/minui/graphics.h
@@ -17,7 +17,7 @@
 #ifndef _GRAPHICS_H_
 #define _GRAPHICS_H_
 
-#include "minui.h"
+#include "minui/minui.h"
 
 // TODO: lose the function pointers.
 struct minui_backend {
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
index 03e33b7..199f4d8 100644
--- a/minui/graphics_drm.cpp
+++ b/minui/graphics_drm.cpp
@@ -16,7 +16,6 @@
 
 #include <drm_fourcc.h>
 #include <fcntl.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -28,7 +27,7 @@
 #include <xf86drm.h>
 #include <xf86drmMode.h>
 
-#include "minui.h"
+#include "minui/minui.h"
 #include "graphics.h"
 
 #define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp
index 631ef4e..2d70249 100644
--- a/minui/graphics_fbdev.cpp
+++ b/minui/graphics_fbdev.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
@@ -30,7 +29,7 @@
 #include <linux/fb.h>
 #include <linux/kd.h>
 
-#include "minui.h"
+#include "minui/minui.h"
 #include "graphics.h"
 
 static GRSurface* fbdev_init(minui_backend*);
diff --git a/minui/minui.h b/minui/include/minui/minui.h
similarity index 91%
rename from minui/minui.h
rename to minui/include/minui/minui.h
index 78890b8..a1749df 100644
--- a/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -70,15 +70,14 @@
 
 struct input_event;
 
-// TODO: move these over to std::function.
-typedef int (*ev_callback)(int fd, uint32_t epevents, void* data);
-typedef int (*ev_set_key_callback)(int code, int value, void* data);
+using ev_callback = std::function<int(int fd, uint32_t epevents)>;
+using ev_set_key_callback = std::function<int(int code, int value)>;
 
-int ev_init(ev_callback input_cb, void* data);
+int ev_init(ev_callback input_cb);
 void ev_exit();
-int ev_add_fd(int fd, ev_callback cb, void* data);
+int ev_add_fd(int fd, ev_callback cb);
 void ev_iterate_available_keys(const std::function<void(int)>& f);
-int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data);
+int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
 
 // 'timeout' has the same semantics as poll(2).
 //    0 : don't block
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 9ccbf4b..c0f9c5c 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -14,24 +14,22 @@
  * limitations under the License.
  */
 
+#include <fcntl.h>
+#include <linux/fb.h>
+#include <linux/kd.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
-
-#include <fcntl.h>
-#include <stdio.h>
-
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/types.h>
-
-#include <linux/fb.h>
-#include <linux/kd.h>
+#include <unistd.h>
 
 #include <vector>
+
 #include <png.h>
 
-#include "minui.h"
+#include "minui/minui.h"
 
 #define SURFACE_DATA_ALIGNMENT 8
 
@@ -374,7 +372,9 @@
 // This function tests if a locale string stored in PNG (prefix) matches
 // the locale string provided by the system (locale).
 bool matches_locale(const char* prefix, const char* locale) {
-    if (locale == NULL) return false;
+    if (locale == nullptr) {
+        return false;
+    }
 
     // Return true if the whole string of prefix matches the top part of
     // locale. For instance, prefix == "en" matches locale == "en_US";
diff --git a/otafault/config.cpp b/otafault/config.cpp
index ee4ef89..8590833 100644
--- a/otafault/config.cpp
+++ b/otafault/config.cpp
@@ -29,21 +29,23 @@
 #define OTAIO_MAX_FNAME_SIZE 128
 
 static ZipArchiveHandle archive;
+static bool is_retry = false;
 static std::map<std::string, bool> should_inject_cache;
 
 static std::string get_type_path(const char* io_type) {
     return android::base::StringPrintf("%s/%s", OTAIO_BASE_DIR, io_type);
 }
 
-void ota_io_init(ZipArchiveHandle za) {
+void ota_io_init(ZipArchiveHandle za, bool retry) {
     archive = za;
+    is_retry = retry;
     ota_set_fault_files();
 }
 
 bool should_fault_inject(const char* io_type) {
     // archive will be NULL if we used an entry point other
     // than updater/updater.cpp:main
-    if (archive == NULL) {
+    if (archive == nullptr || is_retry) {
         return false;
     }
     const std::string type_path = get_type_path(io_type);
diff --git a/otafault/config.h b/otafault/config.h
index c048617..4adbdd1 100644
--- a/otafault/config.h
+++ b/otafault/config.h
@@ -52,7 +52,7 @@
 /*
  * Initialize libotafault by providing a reference to the OTA package.
  */
-void ota_io_init(ZipArchiveHandle zip);
+void ota_io_init(ZipArchiveHandle zip, bool retry);
 
 /*
  * Return true if a config file is present for the given IO type.
diff --git a/recovery.cpp b/recovery.cpp
index 5f16075..25d3546 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -68,6 +68,7 @@
 #include "roots.h"
 #include "rotate_logs.h"
 #include "screen_ui.h"
+#include "stub_ui.h"
 #include "ui.h"
 
 static const struct option OPTIONS[] = {
@@ -86,6 +87,7 @@
   { "security", no_argument, NULL, 'e'},
   { "wipe_ab", no_argument, NULL, 0 },
   { "wipe_package_size", required_argument, NULL, 0 },
+  { "prompt_and_wipe_data", no_argument, NULL, 0 },
   { NULL, 0, NULL, 0 },
 };
 
@@ -137,6 +139,8 @@
  * The arguments which may be supplied in the recovery.command file:
  *   --update_package=path - verify install an OTA package file
  *   --wipe_data - erase user data (and cache), then reboot
+ *   --prompt_and_wipe_data - prompt the user that data is corrupt,
+ *       with their consent erase user data (and cache), then reboot
  *   --wipe_cache - wipe cache (but not user data), then reboot
  *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
  *   --just_exit - do nothing; exit and reboot
@@ -169,21 +173,7 @@
  *    -- after this, rebooting will (try to) restart the main system --
  * 7. ** if install failed **
  *    7a. prompt_and_wait() shows an error icon and waits for the user
- *    7b; the user reboots (pulling the battery, etc) into the main system
- * 8. main() calls maybe_install_firmware_update()
- *    ** if the update contained radio/hboot firmware **:
- *    8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
- *        -- after this, rebooting will reformat cache & restart main system --
- *    8b. m_i_f_u() writes firmware image into raw cache partition
- *    8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
- *        -- after this, rebooting will attempt to reinstall firmware --
- *    8d. bootloader tries to flash firmware
- *    8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
- *        -- after this, rebooting will reformat cache & restart main system --
- *    8f. erase_volume() reformats /cache
- *    8g. finish_recovery() erases BCB
- *        -- after this, rebooting will (try to) restart the main system --
- * 9. main() calls reboot() to boot main system
+ *    7b. the user reboots (pulling the battery, etc) into the main system
  */
 
 // open a given path, mounting partitions as necessary
@@ -332,7 +322,7 @@
   std::vector<std::string> args(argv, argv + argc);
 
   // --- if arguments weren't supplied, look in the bootloader control block
-  if (argc == 1) {
+  if (args.size() == 1) {
     boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
     std::string boot_recovery(boot.recovery);
     std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");
@@ -348,11 +338,14 @@
   }
 
   // --- if that doesn't work, try the command file (if we have /cache).
-  if (argc == 1 && has_cache) {
+  if (args.size() == 1 && has_cache) {
     std::string content;
-    if (android::base::ReadFileToString(COMMAND_FILE, &content)) {
+    if (ensure_path_mounted(COMMAND_FILE) == 0 &&
+        android::base::ReadFileToString(COMMAND_FILE, &content)) {
       std::vector<std::string> tokens = android::base::Split(content, "\n");
-      for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {
+      // All the arguments in COMMAND_FILE are needed (unlike the BCB message,
+      // COMMAND_FILE doesn't use filename as the first argument).
+      for (auto it = tokens.begin(); it != tokens.end(); it++) {
         // Skip empty and '\0'-filled tokens.
         if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
       }
@@ -364,20 +357,21 @@
   // bootloader control block. So the device will always boot into recovery to
   // finish the pending work, until finish_recovery() is called.
   std::vector<std::string> options(args.cbegin() + 1, args.cend());
-  if (!write_bootloader_message(options, &err)) {
-    LOG(ERROR) << err;
+  if (!update_bootloader_message(options, &err)) {
+    LOG(ERROR) << "Failed to set BCB message: " << err;
   }
 
   return args;
 }
 
-static void
-set_sdcard_update_bootloader_message() {
-    std::vector<std::string> options;
-    std::string err;
-    if (!write_bootloader_message(options, &err)) {
-        LOG(ERROR) << err;
-    }
+// Set the BCB to reboot back into recovery (it won't resume the install from
+// sdcard though).
+static void set_sdcard_update_bootloader_message() {
+  std::vector<std::string> options;
+  std::string err;
+  if (!update_bootloader_message(options, &err)) {
+    LOG(ERROR) << "Failed to set BCB message: " << err;
+  }
 }
 
 // Read from kernel log into buffer and write out to file.
@@ -495,7 +489,7 @@
     // Reset to normal system boot so recovery won't cycle indefinitely.
     std::string err;
     if (!clear_bootloader_message(&err)) {
-        LOG(ERROR) << err;
+        LOG(ERROR) << "Failed to clear BCB message: " << err;
     }
 
     // Remove the command file, so recovery won't repeat indefinitely.
@@ -509,117 +503,107 @@
     sync();  // For good measure.
 }
 
-typedef struct _saved_log_file {
-    char* name;
-    struct stat st;
-    unsigned char* data;
-    struct _saved_log_file* next;
-} saved_log_file;
+struct saved_log_file {
+  std::string name;
+  struct stat sb;
+  std::string data;
+};
 
 static bool erase_volume(const char* volume) {
-    bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
-    bool is_data = (strcmp(volume, DATA_ROOT) == 0);
+  bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
+  bool is_data = (strcmp(volume, DATA_ROOT) == 0);
 
-    ui->SetBackground(RecoveryUI::ERASING);
-    ui->SetProgressType(RecoveryUI::INDETERMINATE);
+  ui->SetBackground(RecoveryUI::ERASING);
+  ui->SetProgressType(RecoveryUI::INDETERMINATE);
 
-    saved_log_file* head = NULL;
+  std::vector<saved_log_file> log_files;
 
-    if (is_cache) {
-        // If we're reformatting /cache, we load any past logs
-        // (i.e. "/cache/recovery/last_*") and the current log
-        // ("/cache/recovery/log") into memory, so we can restore them after
-        // the reformat.
+  if (is_cache) {
+    // If we're reformatting /cache, we load any past logs
+    // (i.e. "/cache/recovery/last_*") and the current log
+    // ("/cache/recovery/log") into memory, so we can restore them after
+    // the reformat.
 
-        ensure_path_mounted(volume);
+    ensure_path_mounted(volume);
 
-        DIR* d;
-        struct dirent* de;
-        d = opendir(CACHE_LOG_DIR);
-        if (d) {
-            char path[PATH_MAX];
-            strcpy(path, CACHE_LOG_DIR);
-            strcat(path, "/");
-            int path_len = strlen(path);
-            while ((de = readdir(d)) != NULL) {
-                if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) {
-                    saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file));
-                    strcpy(path+path_len, de->d_name);
-                    p->name = strdup(path);
-                    if (stat(path, &(p->st)) == 0) {
-                        // truncate files to 512kb
-                        if (p->st.st_size > (1 << 19)) {
-                            p->st.st_size = 1 << 19;
-                        }
-                        p->data = (unsigned char*) malloc(p->st.st_size);
-                        FILE* f = fopen(path, "rb");
-                        fread(p->data, 1, p->st.st_size, f);
-                        fclose(f);
-                        p->next = head;
-                        head = p;
-                    } else {
-                        free(p);
-                    }
-                }
+    struct dirent* de;
+    std::unique_ptr<DIR, decltype(&closedir)> d(opendir(CACHE_LOG_DIR), closedir);
+    if (d) {
+      while ((de = readdir(d.get())) != nullptr) {
+        if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) {
+          std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name);
+
+          struct stat sb;
+          if (stat(path.c_str(), &sb) == 0) {
+            // truncate files to 512kb
+            if (sb.st_size > (1 << 19)) {
+              sb.st_size = 1 << 19;
             }
-            closedir(d);
-        } else {
-            if (errno != ENOENT) {
-                printf("opendir failed: %s\n", strerror(errno));
-            }
+
+            std::string data(sb.st_size, '\0');
+            FILE* f = fopen(path.c_str(), "rb");
+            fread(&data[0], 1, data.size(), f);
+            fclose(f);
+
+            log_files.emplace_back(saved_log_file{ path, sb, data });
+          }
         }
-    }
-
-    ui->Print("Formatting %s...\n", volume);
-
-    ensure_path_unmounted(volume);
-
-    int result;
-
-    if (is_data && reason && strcmp(reason, "convert_fbe") == 0) {
-        // Create convert_fbe breadcrumb file to signal to init
-        // to convert to file based encryption, not full disk encryption
-        if (mkdir(CONVERT_FBE_DIR, 0700) != 0) {
-            ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno));
-            return true;
-        }
-        FILE* f = fopen(CONVERT_FBE_FILE, "wb");
-        if (!f) {
-            ui->Print("Failed to convert to file encryption %s\n", strerror(errno));
-            return true;
-        }
-        fclose(f);
-        result = format_volume(volume, CONVERT_FBE_DIR);
-        remove(CONVERT_FBE_FILE);
-        rmdir(CONVERT_FBE_DIR);
+      }
     } else {
-        result = format_volume(volume);
+      if (errno != ENOENT) {
+        PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR;
+      }
     }
+  }
 
-    if (is_cache) {
-        while (head) {
-            FILE* f = fopen_path(head->name, "wb");
-            if (f) {
-                fwrite(head->data, 1, head->st.st_size, f);
-                fclose(f);
-                chmod(head->name, head->st.st_mode);
-                chown(head->name, head->st.st_uid, head->st.st_gid);
-            }
-            free(head->name);
-            free(head->data);
-            saved_log_file* temp = head->next;
-            free(head);
-            head = temp;
+  ui->Print("Formatting %s...\n", volume);
+
+  ensure_path_unmounted(volume);
+
+  int result;
+
+  if (is_data && reason && strcmp(reason, "convert_fbe") == 0) {
+    // Create convert_fbe breadcrumb file to signal to init
+    // to convert to file based encryption, not full disk encryption
+    if (mkdir(CONVERT_FBE_DIR, 0700) != 0) {
+      ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno));
+      return true;
+    }
+    FILE* f = fopen(CONVERT_FBE_FILE, "wb");
+    if (!f) {
+      ui->Print("Failed to convert to file encryption %s\n", strerror(errno));
+      return true;
+    }
+    fclose(f);
+    result = format_volume(volume, CONVERT_FBE_DIR);
+    remove(CONVERT_FBE_FILE);
+    rmdir(CONVERT_FBE_DIR);
+  } else {
+    result = format_volume(volume);
+  }
+
+  if (is_cache) {
+    // 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) {
+      for (const auto& log : log_files) {
+        if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid,
+                                              log.sb.st_gid)) {
+          PLOG(ERROR) << "Failed to write to " << log.name;
         }
-
-        // Any part of the log we'd copied to cache is now gone.
-        // Reset the pointer so we copy from the beginning of the temp
-        // log.
-        tmplog_offset = 0;
-        copy_logs();
+      }
+    } else {
+      PLOG(ERROR) << "Failed to mount / create " << CACHE_LOG_DIR;
     }
 
-    return (result == 0);
+    // Any part of the log we'd copied to cache is now gone.
+    // Reset the pointer so we copy from the beginning of the temp
+    // log.
+    tmplog_offset = 0;
+    copy_logs();
+  }
+
+  return (result == 0);
 }
 
 static int
@@ -784,12 +768,12 @@
     return (chosen_item == 1);
 }
 
-// Return true on success.
-static bool wipe_data(int should_confirm, Device* device) {
-    if (should_confirm && !yes_no(device, "Wipe all user data?", "  THIS CAN NOT BE UNDONE!")) {
-        return false;
-    }
+static bool ask_to_wipe_data(Device* device) {
+    return yes_no(device, "Wipe all user data?", "  THIS CAN NOT BE UNDONE!");
+}
 
+// Return true on success.
+static bool wipe_data(Device* device) {
     modified_flash = true;
 
     ui->Print("\n-- Wiping data...\n");
@@ -802,6 +786,28 @@
     return success;
 }
 
+static bool prompt_and_wipe_data(Device* device) {
+    const char* const headers[] = {
+        "Boot halted, user data is corrupt",
+        "Wipe all user data to recover",
+        NULL
+    };
+    const char* const items[] = {
+        "Retry boot",
+        "Wipe user data",
+        NULL
+    };
+    for (;;) {
+        int chosen_item = get_menu_selection(headers, items, 1, 0, device);
+        if (chosen_item != 1) {
+            return true; // Just reboot, no wipe; not a failure, user asked for it
+        }
+        if (ask_to_wipe_data(device)) {
+            return wipe_data(device);
+        }
+    }
+}
+
 // Return true on success.
 static bool wipe_cache(bool should_confirm, Device* device) {
     if (!has_cache) {
@@ -886,8 +892,8 @@
 
     // Extract metadata
     ZipArchiveHandle zip;
-    int err = OpenArchiveFromMemory(reinterpret_cast<void*>(&wipe_package[0]),
-                                    wipe_package.size(), "wipe_package", &zip);
+    int err = OpenArchiveFromMemory(static_cast<void*>(&wipe_package[0]), wipe_package.size(),
+                                    "wipe_package", &zip);
     if (err != 0) {
         LOG(ERROR) << "Can't open wipe package : " << ErrorCodeString(err);
         return false;
@@ -910,13 +916,11 @@
             ota_type_matched = true;
         } else if (android::base::StartsWith(line, "pre-device=")) {
             std::string device_type = line.substr(strlen("pre-device="));
-            char real_device_type[PROPERTY_VALUE_MAX];
-            property_get("ro.build.product", real_device_type, "");
+            std::string real_device_type = android::base::GetProperty("ro.build.product", "");
             device_type_matched = (device_type == real_device_type);
         } else if (android::base::StartsWith(line, "serialno=")) {
             std::string serial_no = line.substr(strlen("serialno="));
-            char real_serial_no[PROPERTY_VALUE_MAX];
-            property_get("ro.serialno", real_serial_no, "");
+            std::string real_serial_no = android::base::GetProperty("ro.serialno", "");
             has_serial_number = true;
             serial_number_matched = (serial_no == real_serial_no);
         }
@@ -1166,8 +1170,14 @@
                 return chosen_action;
 
             case Device::WIPE_DATA:
-                wipe_data(ui->IsTextVisible(), device);
-                if (!ui->IsTextVisible()) return Device::NO_ACTION;
+                if (ui->IsTextVisible()) {
+                    if (ask_to_wipe_data(device)) {
+                        wipe_data(device);
+                    }
+                } else {
+                    wipe_data(device);
+                    return Device::NO_ACTION;
+                }
                 break;
 
             case Device::WIPE_CACHE:
@@ -1343,21 +1353,21 @@
     // Increment the retry counter by 1.
     options.push_back(android::base::StringPrintf("--retry_count=%d", retry_count+1));
     std::string err;
-    if (!write_bootloader_message(options, &err)) {
+    if (!update_bootloader_message(options, &err)) {
         LOG(ERROR) << err;
     }
 }
 
 static bool bootreason_in_blacklist() {
-    char bootreason[PROPERTY_VALUE_MAX];
-    if (property_get("ro.boot.bootreason", bootreason, nullptr) > 0) {
-        for (const auto& str : bootreason_blacklist) {
-            if (strcasecmp(str.c_str(), bootreason) == 0) {
-                return true;
-            }
-        }
+  std::string bootreason = android::base::GetProperty("ro.boot.bootreason", "");
+  if (!bootreason.empty()) {
+    for (const auto& str : bootreason_blacklist) {
+      if (strcasecmp(str.c_str(), bootreason.c_str()) == 0) {
+        return true;
+      }
     }
-    return false;
+  }
+  return false;
 }
 
 static void log_failure_code(ErrorCode code, const char *update_package) {
@@ -1423,6 +1433,7 @@
 
     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;
@@ -1460,12 +1471,13 @@
         case 'r': reason = optarg; break;
         case 'e': security_update = true; break;
         case 0: {
-            if (strcmp(OPTIONS[option_index].name, "wipe_ab") == 0) {
+            std::string option = OPTIONS[option_index].name;
+            if (option == "wipe_ab") {
                 should_wipe_ab = true;
-                break;
-            } else if (strcmp(OPTIONS[option_index].name, "wipe_package_size") == 0) {
+            } else if (option == "wipe_package_size") {
                 android::base::ParseUint(optarg, &wipe_package_size);
-                break;
+            } else if (option == "prompt_and_wipe_data") {
+                should_prompt_and_wipe_data = true;
             }
             break;
         }
@@ -1492,8 +1504,10 @@
     Device* device = make_device();
     ui = device->GetUI();
 
-    ui->SetLocale(locale.c_str());
-    ui->Init();
+    if (!ui->Init(locale)) {
+      printf("Failed to initialize UI, use stub UI instead.");
+      ui = new StubRecoveryUI();
+    }
     // Set background string to "installing security update" for security update,
     // otherwise set it to "installing system update".
     ui->SetSystemUpdateText(security_update);
@@ -1522,27 +1536,7 @@
     for (const auto& arg : args) {
         printf(" \"%s\"", arg.c_str());
     }
-    printf("\n");
-
-    if (update_package) {
-        // For backwards compatibility on the cache partition only, if
-        // we're given an old 'root' path "CACHE:foo", change it to
-        // "/cache/foo".
-        if (strncmp(update_package, "CACHE:", 6) == 0) {
-            int len = strlen(update_package) + 10;
-            char* modified_path = (char*)malloc(len);
-            if (modified_path) {
-                strlcpy(modified_path, "/cache/", len);
-                strlcat(modified_path, update_package+6, len);
-                printf("(replacing path \"%s\" with \"%s\")\n",
-                       update_package, modified_path);
-                update_package = modified_path;
-            }
-            else
-                printf("modified_path allocation failed\n");
-        }
-    }
-    printf("\n");
+    printf("\n\n");
 
     property_list(print_property, NULL);
     printf("\n");
@@ -1602,9 +1596,16 @@
             }
         }
     } else if (should_wipe_data) {
-        if (!wipe_data(false, device)) {
+        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;
diff --git a/res-hdpi/images/erasing_text.png b/res-hdpi/images/erasing_text.png
index 2186c19..684fc7c 100644
--- a/res-hdpi/images/erasing_text.png
+++ 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
index 9700f45..00c485d 100644
--- a/res-hdpi/images/error_text.png
+++ 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
index 0f60595..dadcfc3 100644
--- a/res-hdpi/images/installing_security_text.png
+++ 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
index ec8b875..abe73b4 100644
--- a/res-hdpi/images/installing_text.png
+++ 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
index 3eddcbb..958e106 100644
--- a/res-hdpi/images/no_command_text.png
+++ 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
index b0dd3c6..10e3178 100644
--- a/res-mdpi/images/erasing_text.png
+++ 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
index 6a47a59..0022d10 100644
--- a/res-mdpi/images/error_text.png
+++ 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
index 1499398..7a4cd41 100644
--- a/res-mdpi/images/installing_security_text.png
+++ 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
index 01e9bfe..ee95e56 100644
--- a/res-mdpi/images/installing_text.png
+++ 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
index d340df5..af76609 100644
--- a/res-mdpi/images/no_command_text.png
+++ 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
index 2f8b469..91cc358 100644
--- a/res-xhdpi/images/erasing_text.png
+++ 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
index ad18851..772b139 100644
--- a/res-xhdpi/images/error_text.png
+++ 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
index acc6a7c..a7113a0 100644
--- a/res-xhdpi/images/installing_security_text.png
+++ 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
index 32897d0..566eb06 100644
--- a/res-xhdpi/images/installing_text.png
+++ 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
index eb43c59..b8da125 100644
--- a/res-xhdpi/images/no_command_text.png
+++ 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
index 8ff2b2f..86693f4 100644
--- a/res-xxhdpi/images/erasing_text.png
+++ 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
index 658d4ea..9c4bcab 100644
--- a/res-xxhdpi/images/error_text.png
+++ 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
index 23fcaa4..f5ec698 100644
--- a/res-xxhdpi/images/installing_security_text.png
+++ 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
index fd8e584..100a5b3 100644
--- a/res-xxhdpi/images/installing_text.png
+++ 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
index 23932d6..590030c 100644
--- a/res-xxhdpi/images/no_command_text.png
+++ 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
index 0315293..4cf5d76 100644
--- a/res-xxxhdpi/images/erasing_text.png
+++ 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
index dba127f..8dd6f12 100644
--- a/res-xxxhdpi/images/error_text.png
+++ 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
index 6cdbef4..fa06f31 100644
--- a/res-xxxhdpi/images/installing_security_text.png
+++ 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
index 32511a9..d0f9301 100644
--- a/res-xxxhdpi/images/installing_text.png
+++ 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
index b6cdd77..233aec4 100644
--- a/res-xxxhdpi/images/no_command_text.png
+++ b/res-xxxhdpi/images/no_command_text.png
Binary files differ
diff --git a/roots.cpp b/roots.cpp
index 14018fc..376fcbd 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -215,7 +215,12 @@
         }
         int result;
         if (strcmp(v->fs_type, "ext4") == 0) {
-            result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory);
+            if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
+                result = make_ext4fs_directory_align(v->blk_device, length, volume, sehandle,
+                        directory, v->erase_blk_size, v->logical_blk_size);
+            } else {
+                result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory);
+            }
         } else {   /* Has to be f2fs because we checked earlier. */
             if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) {
                 LOG(ERROR) << "format_volume: crypt footer + negative length (" << length
diff --git a/screen_ui.cpp b/screen_ui.cpp
index fab3489..706877b 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -29,6 +29,7 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <string>
 #include <vector>
 
 #include <android-base/logging.h>
@@ -51,37 +52,34 @@
     return tv.tv_sec + tv.tv_usec / 1000000.0;
 }
 
-ScreenRecoveryUI::ScreenRecoveryUI() :
-    currentIcon(NONE),
-    locale(nullptr),
-    intro_done(false),
-    current_frame(0),
-    progressBarType(EMPTY),
-    progressScopeStart(0),
-    progressScopeSize(0),
-    progress(0),
-    pagesIdentical(false),
-    text_cols_(0),
-    text_rows_(0),
-    text_(nullptr),
-    text_col_(0),
-    text_row_(0),
-    text_top_(0),
-    show_text(false),
-    show_text_ever(false),
-    menu_(nullptr),
-    show_menu(false),
-    menu_items(0),
-    menu_sel(0),
-    file_viewer_text_(nullptr),
-    intro_frames(0),
-    loop_frames(0),
-    animation_fps(30), // TODO: there's currently no way to infer this.
-    stage(-1),
-    max_stage(-1),
-    updateMutex(PTHREAD_MUTEX_INITIALIZER),
-    rtl_locale(false) {
-}
+ScreenRecoveryUI::ScreenRecoveryUI()
+    : currentIcon(NONE),
+      progressBarType(EMPTY),
+      progressScopeStart(0),
+      progressScopeSize(0),
+      progress(0),
+      pagesIdentical(false),
+      text_cols_(0),
+      text_rows_(0),
+      text_(nullptr),
+      text_col_(0),
+      text_row_(0),
+      text_top_(0),
+      show_text(false),
+      show_text_ever(false),
+      menu_(nullptr),
+      show_menu(false),
+      menu_items(0),
+      menu_sel(0),
+      file_viewer_text_(nullptr),
+      intro_frames(0),
+      loop_frames(0),
+      current_frame(0),
+      intro_done(false),
+      animation_fps(30),  // TODO: there's currently no way to infer this.
+      stage(-1),
+      max_stage(-1),
+      updateMutex(PTHREAD_MUTEX_INITIALIZER) {}
 
 GRSurface* ScreenRecoveryUI::GetCurrentFrame() {
     if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
@@ -106,29 +104,41 @@
 
 // Here's the intended layout:
 
-//          | regular     large
-// ---------+--------------------
-//          |   220dp     366dp
-// icon     |  (200dp)   (200dp)
-//          |    68dp      68dp
-// text     |   (14sp)    (14sp)
-//          |    32dp      32dp
-// progress |    (2dp)     (2dp)
-//          |   194dp     340dp
+//          | portrait    large        landscape      large
+// ---------+-------------------------------------------------
+//      gap |   220dp     366dp            142dp      284dp
+// icon     |                   (200dp)
+//      gap |    68dp      68dp             56dp      112dp
+// text     |                    (14sp)
+//      gap |    32dp      32dp             26dp       52dp
+// progress |                     (2dp)
+//      gap |   194dp     340dp            131dp      262dp
 
 // Note that "baseline" is actually the *top* of each icon (because that's how our drawing
 // routines work), so that's the more useful measurement for calling code.
 
+enum Layout { PORTRAIT = 0, PORTRAIT_LARGE = 1, LANDSCAPE = 2, LANDSCAPE_LARGE = 3, LAYOUT_MAX };
+enum Dimension { PROGRESS = 0, TEXT = 1, ICON = 2, DIMENSION_MAX };
+static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = {
+    { 194,  32,  68, }, // PORTRAIT
+    { 340,  32,  68, }, // PORTRAIT_LARGE
+    { 131,  26,  56, }, // LANDSCAPE
+    { 262,  52, 112, }, // LANDSCAPE_LARGE
+};
+
 int ScreenRecoveryUI::GetAnimationBaseline() {
-    return GetTextBaseline() - PixelsFromDp(68) - gr_get_height(loopFrames[0]);
+    return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) -
+            gr_get_height(loopFrames[0]);
 }
 
 int ScreenRecoveryUI::GetTextBaseline() {
-    return GetProgressBaseline() - PixelsFromDp(32) - gr_get_height(installing_text);
+    return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
+            gr_get_height(installing_text);
 }
 
 int ScreenRecoveryUI::GetProgressBaseline() {
-    return gr_fb_height() - PixelsFromDp(is_large_ ? 340 : 194) - gr_get_height(progressBarFill);
+    return gr_fb_height() - PixelsFromDp(kLayouts[layout_][PROGRESS]) -
+            gr_get_height(progressBarFill);
 }
 
 // Clear the screen and draw the currently selected background icon (if any).
@@ -163,51 +173,50 @@
 // Does not flip pages.
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::draw_foreground_locked() {
-    if (currentIcon != NONE) {
-        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 = GetAnimationBaseline();
-        gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
-    }
+  if (currentIcon != NONE) {
+    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 = GetAnimationBaseline();
+    gr_blit(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);
+  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_y = GetProgressBaseline();
+    int progress_x = (gr_fb_width() - 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);
+    // 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);
 
-        if (progressBarType == DETERMINATE) {
-            float p = progressScopeStart + progress * progressScopeSize;
-            int pos = (int) (p * width);
+    if (progressBarType == DETERMINATE) {
+      float p = progressScopeStart + progress * progressScopeSize;
+      int pos = static_cast<int>(p * width);
 
-            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);
-                }
-                if (pos < width-1) {
-                    gr_blit(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);
-                }
-                if (pos < width-1) {
-                    gr_blit(progressBarEmpty, pos, 0, width-pos, height,
-                            progress_x+pos, progress_y);
-                }
-            }
+      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);
         }
+        if (pos < width - 1) {
+          gr_blit(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);
+        }
+        if (pos < width - 1) {
+          gr_blit(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y);
+        }
+      }
     }
+  }
 }
 
 void ScreenRecoveryUI::SetColor(UIElement e) {
@@ -411,10 +420,10 @@
 }
 
 void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
-    int result = res_create_localized_alpha_surface(filename, locale, surface);
-    if (result < 0) {
-        LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
-    }
+  int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface);
+  if (result < 0) {
+    LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
+  }
 }
 
 static char** Alloc2d(size_t rows, size_t cols) {
@@ -436,98 +445,94 @@
     Redraw();
 }
 
-void ScreenRecoveryUI::Init() {
-    gr_init();
-
-    density_ = static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f;
-    is_large_ = gr_fb_height() > PixelsFromDp(800);
+bool ScreenRecoveryUI::InitTextParams() {
+    if (gr_init() < 0) {
+      return false;
+    }
 
     gr_font_size(gr_sys_font(), &char_width_, &char_height_);
     text_rows_ = gr_fb_height() / char_height_;
     text_cols_ = gr_fb_width() / char_width_;
+    return true;
+}
 
-    text_ = Alloc2d(text_rows_, text_cols_ + 1);
-    file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
-    menu_ = Alloc2d(text_rows_, text_cols_ + 1);
+bool ScreenRecoveryUI::Init(const std::string& locale) {
+  RecoveryUI::Init(locale);
+  if (!InitTextParams()) {
+    return false;
+  }
 
-    text_col_ = text_row_ = 0;
-    text_top_ = 1;
+  density_ = static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f;
 
-    LoadBitmap("icon_error", &error_icon);
+  // Are we portrait or landscape?
+  layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
+  // Are we the large variant of our base layout?
+  if (gr_fb_height() > PixelsFromDp(800)) ++layout_;
 
-    LoadBitmap("progress_empty", &progressBarEmpty);
-    LoadBitmap("progress_fill", &progressBarFill);
+  text_ = Alloc2d(text_rows_, text_cols_ + 1);
+  file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
+  menu_ = Alloc2d(text_rows_, text_cols_ + 1);
 
-    LoadBitmap("stage_empty", &stageMarkerEmpty);
-    LoadBitmap("stage_fill", &stageMarkerFill);
+  text_col_ = text_row_ = 0;
+  text_top_ = 1;
 
-    // Background text for "installing_update" could be "installing update"
-    // or "installing security update". It will be set after UI init according
-    // to commands in BCB.
-    installing_text = nullptr;
-    LoadLocalizedBitmap("erasing_text", &erasing_text);
-    LoadLocalizedBitmap("no_command_text", &no_command_text);
-    LoadLocalizedBitmap("error_text", &error_text);
+  LoadBitmap("icon_error", &error_icon);
 
-    LoadAnimation();
+  LoadBitmap("progress_empty", &progressBarEmpty);
+  LoadBitmap("progress_fill", &progressBarFill);
 
-    pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this);
+  LoadBitmap("stage_empty", &stageMarkerEmpty);
+  LoadBitmap("stage_fill", &stageMarkerFill);
 
-    RecoveryUI::Init();
+  // Background text for "installing_update" could be "installing update"
+  // or "installing security update". It will be set after UI init according
+  // to commands in BCB.
+  installing_text = nullptr;
+  LoadLocalizedBitmap("erasing_text", &erasing_text);
+  LoadLocalizedBitmap("no_command_text", &no_command_text);
+  LoadLocalizedBitmap("error_text", &error_text);
+
+  LoadAnimation();
+
+  pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this);
+
+  return true;
 }
 
 void ScreenRecoveryUI::LoadAnimation() {
-    // How many frames of intro and loop do we have?
     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir);
     dirent* de;
+    std::vector<std::string> intro_frame_names;
+    std::vector<std::string> loop_frame_names;
+
     while ((de = readdir(dir.get())) != nullptr) {
-        int value;
-        if (sscanf(de->d_name, "intro%d", &value) == 1 && intro_frames < (value + 1)) {
-            intro_frames = value + 1;
-        } else if (sscanf(de->d_name, "loop%d", &value) == 1 && loop_frames < (value + 1)) {
-            loop_frames = value + 1;
+        int value, num_chars;
+        if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) {
+            intro_frame_names.emplace_back(de->d_name, num_chars);
+        } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) {
+            loop_frame_names.emplace_back(de->d_name, num_chars);
         }
     }
 
+    intro_frames = intro_frame_names.size();
+    loop_frames = loop_frame_names.size();
+
     // It's okay to not have an intro.
     if (intro_frames == 0) intro_done = true;
     // But you must have an animation.
     if (loop_frames == 0) abort();
 
+    std::sort(intro_frame_names.begin(), intro_frame_names.end());
+    std::sort(loop_frame_names.begin(), loop_frame_names.end());
+
     introFrames = new GRSurface*[intro_frames];
-    for (int i = 0; i < intro_frames; ++i) {
-        // TODO: remember the names above, so we don't have to hard-code the number of 0s.
-        LoadBitmap(android::base::StringPrintf("intro%05d", i).c_str(), &introFrames[i]);
+    for (size_t i = 0; i < intro_frames; i++) {
+        LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]);
     }
 
     loopFrames = new GRSurface*[loop_frames];
-    for (int i = 0; i < loop_frames; ++i) {
-        LoadBitmap(android::base::StringPrintf("loop%05d", i).c_str(), &loopFrames[i]);
-    }
-}
-
-void ScreenRecoveryUI::SetLocale(const char* new_locale) {
-    this->locale = new_locale;
-    this->rtl_locale = false;
-
-    if (locale) {
-        char* lang = strdup(locale);
-        for (char* p = lang; *p; ++p) {
-            if (*p == '_') {
-                *p = '\0';
-                break;
-            }
-        }
-
-        // A bit cheesy: keep an explicit list of supported RTL languages.
-        if (strcmp(lang, "ar") == 0 ||   // Arabic
-            strcmp(lang, "fa") == 0 ||   // Persian (Farsi)
-            strcmp(lang, "he") == 0 ||   // Hebrew (new language code)
-            strcmp(lang, "iw") == 0 ||   // Hebrew (old language code)
-            strcmp(lang, "ur") == 0) {   // Urdu
-            rtl_locale = true;
-        }
-        free(lang);
+    for (size_t i = 0; i < loop_frames; i++) {
+        LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]);
     }
 }
 
diff --git a/screen_ui.h b/screen_ui.h
index 4319b76..b2dcf4a 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -20,8 +20,12 @@
 #include <pthread.h>
 #include <stdio.h>
 
+#include <string>
+
 #include "ui.h"
-#include "minui/minui.h"
+
+// From minui/minui.h.
+struct GRSurface;
 
 // Implementation of RecoveryUI appropriate for devices with a screen
 // (shows an icon + a progress bar, text logging, menu, etc.)
@@ -29,24 +33,23 @@
   public:
     ScreenRecoveryUI();
 
-    void Init();
-    void SetLocale(const char* locale);
+    bool Init(const std::string& locale) override;
 
     // overall recovery state ("background image")
     void SetBackground(Icon icon);
     void SetSystemUpdateText(bool security_update);
 
     // progress indicator
-    void SetProgressType(ProgressType type);
-    void ShowProgress(float portion, float seconds);
-    void SetProgress(float fraction);
+    void SetProgressType(ProgressType type) override;
+    void ShowProgress(float portion, float seconds) override;
+    void SetProgress(float fraction) override;
 
-    void SetStage(int current, int max);
+    void SetStage(int current, int max) override;
 
     // text log
-    void ShowText(bool visible);
-    bool IsTextVisible();
-    bool WasTextEverVisible();
+    void ShowText(bool visible) override;
+    bool IsTextVisible() override;
+    bool WasTextEverVisible() override;
 
     // printing messages
     void Print(const char* fmt, ...) __printflike(2, 3);
@@ -71,14 +74,10 @@
   protected:
     Icon currentIcon;
 
-    const char* locale;
-    bool intro_done;
-    int current_frame;
-
     // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
     float density_;
-    // True if we should use the large layout.
-    bool is_large_;
+    // The layout to use.
+    int layout_;
 
     GRSurface* error_icon;
 
@@ -123,8 +122,11 @@
     pthread_t progress_thread_;
 
     // Number of intro frames and loop frames in the animation.
-    int intro_frames;
-    int loop_frames;
+    size_t intro_frames;
+    size_t loop_frames;
+
+    size_t current_frame;
+    bool intro_done;
 
     // Number of frames per sec (default: 30) for both parts of the animation.
     int animation_fps;
@@ -134,13 +136,14 @@
     int char_width_;
     int char_height_;
     pthread_mutex_t updateMutex;
-    bool rtl_locale;
 
-    void draw_background_locked();
-    void draw_foreground_locked();
-    void draw_screen_locked();
-    void update_screen_locked();
-    void update_progress_locked();
+    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();
     GRSurface* GetCurrentText();
@@ -148,8 +151,8 @@
     static void* ProgressThreadStartRoutine(void* data);
     void ProgressThreadLoop();
 
-    void ShowFile(FILE*);
-    void PrintV(const char*, bool, va_list);
+    virtual void ShowFile(FILE*);
+    virtual void PrintV(const char*, bool, va_list);
     void PutChar(char);
     void ClearText();
 
@@ -158,9 +161,9 @@
     void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
 
     int PixelsFromDp(int dp);
-    int GetAnimationBaseline();
-    int GetProgressBaseline();
-    int GetTextBaseline();
+    virtual int GetAnimationBaseline();
+    virtual int GetProgressBaseline();
+    virtual int GetTextBaseline();
 
     void DrawHorizontalRule(int* y);
     void DrawTextLine(int x, int* y, const char* line, bool bold);
diff --git a/stub_ui.h b/stub_ui.h
new file mode 100644
index 0000000..85dbcfd
--- /dev/null
+++ b/stub_ui.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RECOVERY_STUB_UI_H
+#define RECOVERY_STUB_UI_H
+
+#include "ui.h"
+
+// Stub implementation of RecoveryUI for devices without screen.
+class StubRecoveryUI : public RecoveryUI {
+ public:
+  StubRecoveryUI() = default;
+
+  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 SetStage(int current, int max) override {}
+
+  // text log
+  void ShowText(bool visible) override {}
+  bool IsTextVisible() override {
+    return false;
+  }
+  bool WasTextEverVisible() override {
+    return false;
+  }
+
+  // printing messages
+  void Print(const char* fmt, ...) override {
+    va_list ap;
+    va_start(ap, fmt);
+    vprintf(fmt, ap);
+    va_end(ap);
+  }
+  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 {}
+  int SelectMenu(int sel) override {
+    return sel;
+  }
+  void EndMenu() override {}
+};
+
+#endif  // RECOVERY_STUB_UI_H
diff --git a/tests/Android.mk b/tests/Android.mk
index 5f6a7ce..a7e7b3c 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -33,9 +33,11 @@
 
 LOCAL_SRC_FILES := \
     unit/asn1_decoder_test.cpp \
+    unit/dirutil_test.cpp \
     unit/locale_test.cpp \
     unit/sysutil_test.cpp \
-    unit/zip_test.cpp
+    unit/zip_test.cpp \
+    unit/ziputil_test.cpp
 
 LOCAL_C_INCLUDES := bootable/recovery
 LOCAL_SHARED_LIBRARIES := liblog
@@ -47,10 +49,34 @@
 LOCAL_CFLAGS := -Werror
 LOCAL_MODULE := recovery_manual_test
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-LOCAL_STATIC_LIBRARIES := libbase
+LOCAL_STATIC_LIBRARIES := \
+    libminui \
+    libbase
 
 LOCAL_SRC_FILES := manual/recovery_test.cpp
-LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_SHARED_LIBRARIES := \
+    liblog \
+    libpng
+
+resource_files := $(call find-files-in-subdirs, bootable/recovery, \
+    "*_text.png", \
+    res-mdpi/images \
+    res-hdpi/images \
+    res-xhdpi/images \
+    res-xxhdpi/images \
+    res-xxxhdpi/images \
+    )
+
+# The resource image files that will go to $OUT/data/nativetest/recovery.
+testimage_out_path := $(TARGET_OUT_DATA)/nativetest/recovery
+GEN := $(addprefix $(testimage_out_path)/, $(resource_files))
+
+$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+$(GEN): $(testimage_out_path)/% : bootable/recovery/%
+	$(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(GEN)
+
 include $(BUILD_NATIVE_TEST)
 
 # Component tests
@@ -61,16 +87,20 @@
 LOCAL_C_INCLUDES := bootable/recovery
 LOCAL_SRC_FILES := \
     component/applypatch_test.cpp \
+    component/bootloader_message_test.cpp \
     component/edify_test.cpp \
+    component/imgdiff_test.cpp \
+    component/uncrypt_test.cpp \
     component/updater_test.cpp \
     component/verifier_test.cpp
+
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
 tune2fs_static_libraries := \
     libext2_com_err \
     libext2_blkid \
     libext2_quota \
-    libext2_uuid_static \
+    libext2_uuid \
     libext2_e2p \
     libext2fs
 
@@ -78,6 +108,9 @@
     libapplypatch_modes \
     libapplypatch \
     libedify \
+    libimgdiff \
+    libimgpatch \
+    libbsdiff \
     libotafault \
     libupdater \
     libbootloader_message \
@@ -85,11 +118,13 @@
     libminui \
     libotautil \
     libmounts \
+    libdivsufsort \
+    libdivsufsort64 \
     libfs_mgr \
     liblog \
     libselinux \
-    libext4_utils_static \
-    libsparse_static \
+    libext4_utils \
+    libsparse \
     libcrypto_utils \
     libcrypto \
     libcutils \
@@ -125,3 +160,26 @@
 LOCAL_PICKUP_FILES := $(testdata_continuous_zip_prefix)
 
 include $(BUILD_NATIVE_TEST)
+
+# Host tests
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := -Werror
+LOCAL_MODULE := recovery_host_test
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_C_INCLUDES := bootable/recovery
+LOCAL_SRC_FILES := \
+    component/imgdiff_test.cpp
+LOCAL_STATIC_LIBRARIES := \
+    libimgdiff \
+    libimgpatch \
+    libbsdiff \
+    libziparchive \
+    libbase \
+    libcrypto \
+    libbz \
+    libdivsufsort64 \
+    libdivsufsort \
+    libz
+LOCAL_SHARED_LIBRARIES := \
+    liblog
+include $(BUILD_HOST_NATIVE_TEST)
diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp
new file mode 100644
index 0000000..dbcaf61
--- /dev/null
+++ b/tests/component/bootloader_message_test.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/strings.h>
+#include <bootloader_message/bootloader_message.h>
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+class BootloaderMessageTest : public ::testing::Test {
+ protected:
+  virtual void TearDown() override {
+    // Clear the BCB.
+    std::string err;
+    ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+  }
+};
+
+TEST_F(BootloaderMessageTest, clear_bootloader_message) {
+  // 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) {
+  // Write the BCB.
+  bootloader_message boot = {};
+  strlcpy(boot.command, "command", sizeof(boot.command));
+  strlcpy(boot.recovery, "message1\nmessage2\n", sizeof(boot.recovery));
+  strlcpy(boot.status, "status1", sizeof(boot.status));
+
+  std::string err;
+  ASSERT_TRUE(write_bootloader_message(boot, &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_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) {
+  // 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;
+  strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
+  strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
+
+  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+  // 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)));
+}
+
+TEST_F(BootloaderMessageTest, write_bootloader_message_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;
+
+  // 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.
+  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) {
+  // 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;
+
+  // Make sure it's long enough.
+  std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
+  ASSERT_GE(expected.size(), sizeof(boot.recovery));
+
+  // command and recovery fields should be set.
+  ASSERT_EQ("boot-recovery", std::string(boot.command));
+  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.
+  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) {
+  // 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
new file mode 100644
index 0000000..7ad3307
--- /dev/null
+++ b/tests/component/imgdiff_test.cpp
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <applypatch/imgdiff.h>
+#include <applypatch/imgpatch.h>
+#include <gtest/gtest.h>
+#include <ziparchive/zip_writer.h>
+
+#include "applypatch/utils.h"
+
+static ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) {
+  std::string* s = static_cast<std::string*>(token);
+  s->append(reinterpret_cast<const char*>(data), len);
+  return len;
+}
+
+// Sanity check for the given imgdiff patch header.
+static void verify_patch_header(const std::string& patch, size_t* num_normal, size_t* num_raw,
+                                size_t* num_deflate) {
+  const size_t size = patch.size();
+  const char* data = patch.data();
+
+  ASSERT_GE(size, 12U);
+  ASSERT_EQ("IMGDIFF2", std::string(data, 8));
+
+  const int num_chunks = Read4(data + 8);
+  ASSERT_GE(num_chunks, 0);
+
+  size_t normal = 0;
+  size_t raw = 0;
+  size_t deflate = 0;
+
+  size_t pos = 12;
+  for (int i = 0; i < num_chunks; ++i) {
+    ASSERT_LE(pos + 4, size);
+    int type = Read4(data + pos);
+    pos += 4;
+    if (type == CHUNK_NORMAL) {
+      pos += 24;
+      ASSERT_LE(pos, size);
+      normal++;
+    } else if (type == CHUNK_RAW) {
+      ASSERT_LE(pos + 4, size);
+      ssize_t data_len = Read4(data + pos);
+      ASSERT_GT(data_len, 0);
+      pos += 4 + data_len;
+      ASSERT_LE(pos, size);
+      raw++;
+    } else if (type == CHUNK_DEFLATE) {
+      pos += 60;
+      ASSERT_LE(pos, size);
+      deflate++;
+    } else {
+      FAIL() << "Invalid patch type: " << type;
+    }
+  }
+
+  if (num_normal != nullptr) *num_normal = normal;
+  if (num_raw != nullptr) *num_raw = raw;
+  if (num_deflate != nullptr) *num_deflate = deflate;
+}
+
+TEST(ImgdiffTest, invalid_args) {
+  // Insufficient inputs.
+  ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" }));
+  ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-z" }));
+  ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-b" }));
+  ASSERT_EQ(2, imgdiff(3, (const char* []){ "imgdiff", "-z", "-b" }));
+
+  // Failed to read bonus file.
+  ASSERT_EQ(1, imgdiff(3, (const char* []){ "imgdiff", "-b", "doesntexist" }));
+
+  // Failed to read input files.
+  ASSERT_EQ(1, imgdiff(4, (const char* []){ "imgdiff", "doesntexist", "doesntexist", "output" }));
+  ASSERT_EQ(
+      1, imgdiff(5, (const char* []){ "imgdiff", "-z", "doesntexist", "doesntexist", "output" }));
+}
+
+TEST(ImgdiffTest, image_mode_smoke) {
+  // Random bytes.
+  const std::string src("abcdefg");
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  const std::string tgt("abcdefgxyz");
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  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(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_store) {
+  // Construct src and tgt zip files.
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  ZipWriter src_writer(src_file_ptr);
+  ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0));  // Store mode.
+  const std::string src_content("abcdefg");
+  ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+  ASSERT_EQ(0, src_writer.FinishEntry());
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+  ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0));  // Store mode.
+  const std::string tgt_content("abcdefgxyz");
+  ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+  ASSERT_EQ(0, tgt_writer.FinishEntry());
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+  std::string src;
+  ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  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(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_compressed) {
+  // Construct src and tgt zip files.
+  TemporaryFile src_file;
+  FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+  ZipWriter src_writer(src_file_ptr);
+  ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+  const std::string src_content("abcdefg");
+  ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+  ASSERT_EQ(0, src_writer.FinishEntry());
+  ASSERT_EQ(0, src_writer.Finish());
+  ASSERT_EQ(0, fclose(src_file_ptr));
+
+  TemporaryFile tgt_file;
+  FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+  ZipWriter tgt_writer(tgt_file_ptr);
+  ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+  const std::string tgt_content("abcdefgxyz");
+  ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+  ASSERT_EQ(0, tgt_writer.FinishEntry());
+  ASSERT_EQ(0, tgt_writer.Finish());
+  ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+  // Compute patch.
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string tgt;
+  ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+  std::string src;
+  ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(1U, num_deflate);
+  ASSERT_EQ(2U, num_raw);
+
+  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(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_simple) {
+  // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+                                       '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+                                       '\x00', '\x00', '\x00' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz" + gzipped "xxyyzz".
+  const std::vector<char> tgt_data = {
+    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'x',    'y',    'z',    '\x1f', '\x8b',
+    '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+    '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
+  };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(1U, num_deflate);
+  ASSERT_EQ(2U, num_raw);
+
+  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(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_different_num_chunks) {
+  // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd) + gzipped "test".
+  const std::vector<char> src_data = {
+    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'h',    '\x1f', '\x8b', '\x08',
+    '\x00', '\xc4', '\x1e', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', '\x02',
+    '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', '\x00', '\x00', '\x00', '\x1f', '\x8b',
+    '\x08', '\x00', '\xb2', '\x3a', '\x53', '\x58', '\x00', '\x03', '\x2b', '\x49', '\x2d',
+    '\x2e', '\x01', '\x00', '\x0c', '\x7e', '\x7f', '\xd8', '\x04', '\x00', '\x00', '\x00'
+  };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz" + gzipped "xxyyzz".
+  const std::vector<char> tgt_data = {
+    'a',    'b',    'c',    'd',    'e',    'f',    'g',    'x',    'y',    'z',    '\x1f', '\x8b',
+    '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+    '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
+  };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(1, imgdiff(args.size(), args.data()));
+}
+
+TEST(ImgdiffTest, image_mode_merge_chunks) {
+  // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+                                       '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+                                       '\x00', '\x00', '\x00' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: gzipped "xyz" + "abcdefgh".
+  const std::vector<char> tgt_data = {
+    '\x1f', '\x8b', '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8',
+    '\xa8', '\xac', '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00',
+    '\x00', '\x00', 'a',    'b',    'c',    'd',    'e',    'f',    'g',    'x',    'y',    'z'
+  };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  // Since a gzipped entry will become CHUNK_RAW (header) + CHUNK_DEFLATE (data) +
+  // CHUNK_RAW (footer), they both should contain the same chunk types after merging.
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(1U, num_deflate);
+  ASSERT_EQ(2U, num_raw);
+
+  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(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_spurious_magic) {
+  // src: "abcdefgh" + '0x1f8b0b00' + some bytes.
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', 't',    'e',    's',    't' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz".
+  const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW (header) entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  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(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_short_input1) {
+  // src: "abcdefgh" + '0x1f8b0b'.
+  const std::vector<char> src_data = { 'a', 'b', 'c',    'd',    'e',   'f',
+                                       'g', 'h', '\x1f', '\x8b', '\x08' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz".
+  const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW (header) entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  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(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_short_input2) {
+  // src: "abcdefgh" + '0x1f8b0b00'.
+  const std::vector<char> src_data = { 'a', 'b', 'c',    'd',    'e',    'f',
+                                       'g', 'h', '\x1f', '\x8b', '\x08', '\x00' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz".
+  const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_RAW (header) entry.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(0U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(1U, num_raw);
+
+  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(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_single_entry_long) {
+  // src: "abcdefgh" + '0x1f8b0b00' + some bytes.
+  const std::vector<char> src_data = { 'a',    'b',    'c',    'd',    'e',    'f',    'g',
+                                       'h',    '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+                                       '\x53', '\x58', 't',    'e',    's',    't' };
+  const std::string src(src_data.cbegin(), src_data.cend());
+  TemporaryFile src_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+  // tgt: "abcdefgxyz" + 200 bytes.
+  std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+  tgt_data.resize(tgt_data.size() + 200);
+
+  const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+  TemporaryFile tgt_file;
+  ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+  TemporaryFile patch_file;
+  std::vector<const char*> args = {
+    "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+  };
+  ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+  // Verify.
+  std::string patch;
+  ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+  // Expect one CHUNK_NORMAL entry, since it's exceeding the 160-byte limit for RAW.
+  size_t num_normal;
+  size_t num_raw;
+  size_t num_deflate;
+  verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+  ASSERT_EQ(1U, num_normal);
+  ASSERT_EQ(0U, num_deflate);
+  ASSERT_EQ(0U, num_raw);
+
+  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(),
+                               MemorySink, &patched));
+  ASSERT_EQ(tgt, patched);
+}
diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp
new file mode 100644
index 0000000..a554c3e
--- /dev/null
+++ b/tests/component/uncrypt_test.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <bootloader_message/bootloader_message.h>
+#include <gtest/gtest.h>
+
+static const std::string UNCRYPT_SOCKET = "/dev/socket/uncrypt";
+static const std::string INIT_SVC_SETUP_BCB = "init.svc.setup-bcb";
+static const std::string INIT_SVC_CLEAR_BCB = "init.svc.clear-bcb";
+static const std::string INIT_SVC_UNCRYPT = "init.svc.uncrypt";
+static constexpr int SOCKET_CONNECTION_MAX_RETRY = 30;
+
+class UncryptTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    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.";
+  }
+};
+
+TEST_F(UncryptTest, setup_bcb) {
+  // Trigger the setup-bcb service.
+  ASSERT_TRUE(android::base::SetProperty("ctl.start", "setup-bcb"));
+
+  // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+  sleep(1);
+
+  struct sockaddr_un un = {};
+  un.sun_family = AF_UNIX;
+  strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
+
+  int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  ASSERT_NE(-1, sockfd);
+
+  // Connect to the uncrypt socket.
+  bool success = false;
+  for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+    if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
+      success = true;
+      break;
+    }
+    sleep(1);
+  }
+  ASSERT_TRUE(success);
+
+  // Send out the BCB message.
+  std::string message = "--update_message=abc value";
+  std::string message_in_bcb = "recovery\n--update_message=abc value\n";
+  int length = static_cast<int>(message.size());
+  int length_out = htonl(length);
+  ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int)))
+      << "Failed to write length: " << strerror(errno);
+  ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length))
+      << "Failed to write message: " << strerror(errno);
+
+  // Check the status code from uncrypt.
+  int status;
+  ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+  ASSERT_EQ(100U, ntohl(status));
+
+  // Ack having received the status code.
+  int code = 0;
+  ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+  ASSERT_EQ(0, close(sockfd));
+
+  ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+
+  // Verify the message by reading from BCB directly.
+  bootloader_message boot;
+  std::string err;
+  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+  ASSERT_EQ("boot-recovery", std::string(boot.command));
+  ASSERT_EQ(message_in_bcb, std::string(boot.recovery));
+
+  // The rest of the boot.recovery message should be zero'd out.
+  ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery));
+  size_t left = sizeof(boot.recovery) - message_in_bcb.size();
+  ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left));
+
+  // Clear the BCB.
+  ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+}
+
+TEST_F(UncryptTest, clear_bcb) {
+  // Trigger the clear-bcb service.
+  ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb"));
+
+  // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+  sleep(1);
+
+  struct sockaddr_un un = {};
+  un.sun_family = AF_UNIX;
+  strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
+
+  int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  ASSERT_NE(-1, sockfd);
+
+  // Connect to the uncrypt socket.
+  bool success = false;
+  for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+    if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
+      success = true;
+      break;
+    }
+    sleep(1);
+  }
+  ASSERT_TRUE(success);
+
+  // Check the status code from uncrypt.
+  int status;
+  ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+  ASSERT_EQ(100U, ntohl(status));
+
+  // Ack having received the status code.
+  int code = 0;
+  ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+  ASSERT_EQ(0, close(sockfd));
+
+  ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+
+  // Verify the content by reading from BCB directly.
+  bootloader_message boot;
+  std::string err;
+  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)));
+}
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index f31f1f8..fa5f031 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <stdio.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -22,6 +23,8 @@
 
 #include <android-base/file.h>
 #include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <android-base/test_utils.h>
 #include <bootloader_message/bootloader_message.h>
 #include <gtest/gtest.h>
@@ -510,3 +513,50 @@
   script = "set_stage(\"/dev/full\", \"1/3\")";
   expect("", script.c_str(), kNoCause);
 }
+
+TEST_F(UpdaterTest, set_progress) {
+  // set_progress() expects one argument.
+  expect(nullptr, "set_progress()", kArgsParsingFailure);
+  expect(nullptr, "set_progress(\"arg1\", \"arg2\")", kArgsParsingFailure);
+
+  // Invalid progress argument.
+  expect(nullptr, "set_progress(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "set_progress(\"3x+5\")", kArgsParsingFailure);
+  expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure);
+
+  TemporaryFile tf;
+  UpdaterInfo updater_info;
+  updater_info.cmd_pipe = fdopen(tf.fd, "w");
+  expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
+  fflush(updater_info.cmd_pipe);
+
+  std::string cmd;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
+  ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd);
+  // recovery-updater protocol expects 2 tokens ("set_progress <frac>").
+  ASSERT_EQ(2U, android::base::Split(cmd, " ").size());
+}
+
+TEST_F(UpdaterTest, show_progress) {
+  // show_progress() expects two arguments.
+  expect(nullptr, "show_progress()", kArgsParsingFailure);
+  expect(nullptr, "show_progress(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "show_progress(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  // Invalid progress arguments.
+  expect(nullptr, "show_progress(\"arg1\", \"arg2\")", kArgsParsingFailure);
+  expect(nullptr, "show_progress(\"3x+5\", \"10\")", kArgsParsingFailure);
+  expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure);
+
+  TemporaryFile tf;
+  UpdaterInfo updater_info;
+  updater_info.cmd_pipe = fdopen(tf.fd, "w");
+  expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
+  fflush(updater_info.cmd_pipe);
+
+  std::string cmd;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
+  ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd);
+  // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
+  ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
+}
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index 60a78f5..b740af9 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -40,38 +40,44 @@
 RecoveryUI* ui = NULL;
 
 class MockUI : public RecoveryUI {
-    void Init() { }
-    void SetStage(int, int) { }
-    void SetLocale(const char*) { }
-    void SetBackground(Icon /*icon*/) { }
-    void SetSystemUpdateText(bool /*security_update*/) { }
+  bool Init(const std::string&) override {
+    return true;
+  }
+  void SetStage(int, int) override {}
+  void SetBackground(Icon /*icon*/) override {}
+  void SetSystemUpdateText(bool /*security_update*/) override {}
 
-    void SetProgressType(ProgressType /*determinate*/) { }
-    void ShowProgress(float /*portion*/, float /*seconds*/) { }
-    void SetProgress(float /*fraction*/) { }
+  void SetProgressType(ProgressType /*determinate*/) override {}
+  void ShowProgress(float /*portion*/, float /*seconds*/) override {}
+  void SetProgress(float /*fraction*/) override {}
 
-    void ShowText(bool /*visible*/) { }
-    bool IsTextVisible() { return false; }
-    bool WasTextEverVisible() { return false; }
-    void Print(const char* fmt, ...) {
-        va_list ap;
-        va_start(ap, fmt);
-        vfprintf(stderr, fmt, ap);
-        va_end(ap);
-    }
-    void PrintOnScreenOnly(const char* fmt, ...) {
-        va_list ap;
-        va_start(ap, fmt);
-        vfprintf(stderr, fmt, ap);
-        va_end(ap);
-    }
-    void ShowFile(const char*) { }
+  void ShowText(bool /*visible*/) override {}
+  bool IsTextVisible() override {
+    return false;
+  }
+  bool WasTextEverVisible() override {
+    return false;
+  }
+  void Print(const char* fmt, ...) override {
+    va_list ap;
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+  }
+  void PrintOnScreenOnly(const char* fmt, ...) override {
+    va_list ap;
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+  }
+  void ShowFile(const char*) override {}
 
-    void StartMenu(const char* const* /*headers*/,
-                   const char* const* /*items*/,
-                   int /*initial_selection*/) { }
-    int SelectMenu(int /*sel*/) { return 0; }
-    void EndMenu() { }
+  void StartMenu(const char* const* /*headers*/, const char* const* /*items*/,
+                 int /*initial_selection*/) override {}
+  int SelectMenu(int /*sel*/) override {
+    return 0;
+  }
+  void EndMenu() override {}
 };
 
 void
diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp
index e838495..d36dd33 100644
--- a/tests/manual/recovery_test.cpp
+++ b/tests/manual/recovery_test.cpp
@@ -14,20 +14,27 @@
  * limitations under the License.
  */
 
-#include <fcntl.h>
+#include <dirent.h>
 #include <string.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #include <string>
+#include <vector>
 
 #include <android-base/file.h>
+#include <android-base/strings.h>
 #include <android/log.h>
 #include <gtest/gtest.h>
+#include <png.h>
 #include <private/android_logger.h>
 
+#include "minui/minui.h"
+
 static const std::string myFilename = "/data/misc/recovery/inject.txt";
 static const std::string myContent = "Hello World\nWelcome to my recovery\n";
+static const std::string kLocale = "zu";
+static const std::string kResourceTestDir = "/data/nativetest/recovery/";
 
 // Failure is expected on systems that do not deliver either the
 // recovery-persist or recovery-refresh executables. Tests also require
@@ -79,9 +86,139 @@
   std::string buf;
   EXPECT_TRUE(android::base::ReadFileToString(myFilename, &buf));
   EXPECT_EQ(myContent, buf);
-  if (access(myFilename.c_str(), O_RDONLY) == 0) {
+  if (access(myFilename.c_str(), F_OK) == 0) {
     fprintf(stderr, "Removing persistent test data, "
         "check if reconstructed on reboot\n");
   }
   EXPECT_EQ(0, unlink(myFilename.c_str()));
 }
+
+std::vector<std::string> image_dir {
+  "res-mdpi/images/",
+  "res-hdpi/images/",
+  "res-xhdpi/images/",
+  "res-xxhdpi/images/",
+  "res-xxxhdpi/images/"
+};
+
+static int png_filter(const dirent* de) {
+  if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
+    return 0;
+  }
+  return 1;
+}
+
+// Find out all png files to test under /data/nativetest/recovery/.
+static std::vector<std::string> add_files() {
+  std::vector<std::string> files;
+  for (const std::string& str : image_dir) {
+    std::string dir_path = kResourceTestDir + str;
+    dirent** namelist;
+    int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
+    if (n == -1) {
+      printf("Failed to scan dir %s: %s\n", kResourceTestDir.c_str(), strerror(errno));
+      return files;
+    }
+    if (n == 0) {
+      printf("No file is added for test in %s\n", kResourceTestDir.c_str());
+    }
+
+    while (n--) {
+      std::string file_path = dir_path + namelist[n]->d_name;
+      files.push_back(file_path);
+      free(namelist[n]);
+    }
+    free(namelist);
+  }
+  return files;
+}
+
+class ResourceTest : public testing::TestWithParam<std::string> {
+ public:
+  static std::vector<std::string> png_list;
+
+  // Parse a png file and test if it's qualified for the background text image
+  // under recovery.
+  void SetUp() override {
+    std::string file_path = GetParam();
+    fp = fopen(file_path.c_str(), "rb");
+    ASSERT_NE(nullptr, fp);
+
+    unsigned char header[8];
+    size_t bytesRead = fread(header, 1, sizeof(header), fp);
+    ASSERT_EQ(sizeof(header), bytesRead);
+    ASSERT_EQ(0, png_sig_cmp(header, 0, sizeof(header)));
+
+    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+    ASSERT_NE(nullptr, png_ptr);
+
+    info_ptr = png_create_info_struct(png_ptr);
+    ASSERT_NE(nullptr, info_ptr);
+
+    png_init_io(png_ptr, fp);
+    png_set_sig_bytes(png_ptr, sizeof(header));
+    png_read_info(png_ptr, info_ptr);
+
+    int color_type, bit_depth;
+    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr,
+                 nullptr);
+    ASSERT_EQ(PNG_COLOR_TYPE_GRAY, color_type) << "Recovery expects grayscale PNG file.";
+    ASSERT_LT(static_cast<png_uint_32>(5), width);
+    ASSERT_LT(static_cast<png_uint_32>(0), height);
+    if (bit_depth <= 8) {
+      // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
+      png_set_expand_gray_1_2_4_to_8(png_ptr);
+    }
+
+    png_byte channels = png_get_channels(png_ptr, info_ptr);
+    ASSERT_EQ(1, channels) << "Recovery background text images expects 1-channel PNG file.";
+  }
+
+  void TearDown() override {
+    if (png_ptr != nullptr && info_ptr != nullptr) {
+      png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
+    }
+
+    if (fp != nullptr) {
+      fclose(fp);
+    }
+  }
+
+ protected:
+  png_structp png_ptr;
+  png_infop info_ptr;
+  png_uint_32 width, height;
+
+  FILE* fp;
+};
+
+std::vector<std::string> ResourceTest::png_list = add_files();
+
+TEST_P(ResourceTest, ValidateLocale) {
+  std::vector<unsigned char> row(width);
+  for (png_uint_32 y = 0; y < height; ++y) {
+    png_read_row(png_ptr, row.data(), nullptr);
+    int w = (row[1] << 8) | row[0];
+    int h = (row[3] << 8) | row[2];
+    int len = row[4];
+    EXPECT_LT(0, w);
+    EXPECT_LT(0, h);
+    EXPECT_LT(0, len) << "Locale string should be non-empty.";
+    EXPECT_NE(0, row[5]) << "Locale string is missing.";
+
+    ASSERT_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()));
+      break;
+    } else {
+      for (int i = 0; i < h; ++i, ++y) {
+        png_read_row(png_ptr, row.data(), nullptr);
+      }
+    }
+  }
+}
+
+INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourceTest,
+                        ::testing::ValuesIn(ResourceTest::png_list.cbegin(),
+                                            ResourceTest::png_list.cend()));
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
new file mode 100644
index 0000000..5e2ae4f
--- /dev/null
+++ b/tests/unit/dirutil_test.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/DirUtil.h>
+
+TEST(DirUtilTest, create_invalid) {
+  // Requesting to create an empty dir is invalid.
+  ASSERT_EQ(-1, dirCreateHierarchy("", 0755, nullptr, 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(ENOENT, errno);
+
+  // Creating a dir that already exists.
+  TemporaryDir td;
+  ASSERT_EQ(0, dirCreateHierarchy(td.path, 0755, nullptr, false, nullptr));
+
+  // "///" is a valid dir.
+  ASSERT_EQ(0, dirCreateHierarchy("///", 0755, nullptr, 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(ENOTDIR, errno);
+}
+
+TEST(DirUtilTest, create_smoke) {
+  TemporaryDir td;
+  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));
+
+  // Verify.
+  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);
+
+  // Clean up.
+  ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str()));
+  ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
+}
+
+TEST(DirUtilTest, create_strip_filename) {
+  TemporaryDir td;
+  std::string prefix(td.path);
+  std::string path = prefix + "/a/b";
+  ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), 0755, nullptr, true, nullptr));
+
+  // Verify that "../a" exists but not "../a/b".
+  struct stat sb;
+  ASSERT_EQ(0, stat((prefix + "/a").c_str(), &sb)) << strerror(errno);
+  ASSERT_TRUE(S_ISDIR(sb.st_mode));
+
+  ASSERT_EQ(-1, stat(path.c_str(), &sb));
+  ASSERT_EQ(ENOENT, errno);
+
+  // Clean up.
+  ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
+}
+
+TEST(DirUtilTest, create_mode_and_timestamp) {
+  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));
+
+  // Verify the mode and timestamp 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.
+  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);
+
+  // Clean up.
+  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/locale_test.cpp b/tests/unit/locale_test.cpp
index 0e515f8..f732350 100644
--- a/tests/unit/locale_test.cpp
+++ b/tests/unit/locale_test.cpp
@@ -26,4 +26,7 @@
     EXPECT_TRUE(matches_locale("en", "en_GB"));
     EXPECT_FALSE(matches_locale("en_GB", "en"));
     EXPECT_FALSE(matches_locale("en_GB", "en_US"));
+    EXPECT_FALSE(matches_locale("en_US", ""));
+    // Empty locale prefix in the PNG file will match the input locale.
+    EXPECT_TRUE(matches_locale("", "en_US"));
 }
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index ef0ee4c..4a1a49b 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <errno.h>
-#include <fcntl.h>
 #include <unistd.h>
 
 #include <memory>
@@ -42,10 +41,10 @@
 
   // Make sure all the files are extracted correctly.
   std::string path(td.path);
-  ASSERT_EQ(0, access((path + "/a.txt").c_str(), O_RDONLY));
-  ASSERT_EQ(0, access((path + "/b.txt").c_str(), O_RDONLY));
-  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), O_RDONLY));
-  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), O_RDONLY));
+  ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
 
   // The content of the file is the same as expected.
   std::string content1;
@@ -54,7 +53,16 @@
 
   std::string content2;
   ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
-  ASSERT_EQ(kBTxtContents, content2);
+  ASSERT_EQ(kDTxtContents, content2);
+
+  CloseArchive(handle);
+
+  // Clean up.
+  ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+  ASSERT_EQ(0, rmdir((path + "/b").c_str()));
 }
 
 TEST(ZipTest, OpenFromMemory) {
@@ -76,6 +84,7 @@
   ASSERT_NE(-1, tmp_binary.fd);
   ASSERT_EQ(0, ExtractEntryToFile(handle, &binary_entry, tmp_binary.fd));
 
+  CloseArchive(handle);
   sysReleaseMap(&map);
 }
 
diff --git a/tests/unit/ziputil_test.cpp b/tests/unit/ziputil_test.cpp
new file mode 100644
index 0000000..14e5416
--- /dev/null
+++ b/tests/unit/ziputil_test.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/ZipUtil.h>
+#include <ziparchive/zip_archive.h>
+
+#include "common/test_constants.h"
+
+TEST(ZipUtilTest, invalid_args) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // zip_path must be a relative path.
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "/a/b", "/tmp", nullptr, nullptr));
+
+  // dest_path must be an absolute path.
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "tmp", nullptr, nullptr));
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "", nullptr, nullptr));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_all) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract the whole package into a temp directory.
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+  // Make sure all the files are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+  ASSERT_EQ(kATxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+  ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_with_slash) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract all the entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b/", td.path, nullptr, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+  // And the rest are not extracted.
+  ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+  ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+  ASSERT_EQ(kCTxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_without_slash) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract all the file entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b", td.path, nullptr, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+  // And the rest are not extracted.
+  ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+  ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+  ASSERT_EQ(kCTxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, set_timestamp) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Set the timestamp to 8/1/2008.
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
+
+  // Extract all the entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b", td.path, &timestamp, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  std::string file_c = path + "/c.txt";
+  std::string file_d = path + "/d.txt";
+  ASSERT_EQ(0, access(file_c.c_str(), F_OK));
+  ASSERT_EQ(0, access(file_d.c_str(), F_OK));
+
+  // Verify the timestamp.
+  timespec time;
+  time.tv_sec = 1217592000;
+  time.tv_nsec = 0;
+
+  struct stat sb;
+  ASSERT_EQ(0, stat(file_c.c_str(), &sb)) << strerror(errno);
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+  ASSERT_EQ(0, stat(file_d.c_str(), &sb)) << strerror(errno);
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  CloseArchive(handle);
+}
diff --git a/tools/recovery_l10n/README.md b/tools/recovery_l10n/README.md
index 1554f66..e9e85d6 100644
--- a/tools/recovery_l10n/README.md
+++ b/tools/recovery_l10n/README.md
@@ -8,6 +8,9 @@
 
     *   For example, we can use Nexus 5 to generate the text images under
         res-xxhdpi.
+    *   We can set up the maximum width of the final png image in res/layout/main.xml
+        Currently, the image width is 1200px for xxxhdpi, 900px for xxhdpi and
+        480px for xhdpi/hdpi/mdpi.
     *   When using the emulator, make sure the NDK version matches the current
         repository. Otherwise, the app may not work properly.
 
diff --git a/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
index ac94bde..30d45f6 100644
--- a/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
+++ b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
@@ -38,6 +38,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Locale;
 
@@ -148,11 +149,28 @@
         mText = (TextView) findViewById(R.id.text);
 
         String[] localeNames = getAssets().getLocales();
-        Arrays.sort(localeNames);
+        Arrays.sort(localeNames, new Comparator<String>() {
+        // Override the string comparator so that en is sorted behind en_US.
+        // As a result, en_US will be matched first in recovery.
+            @Override
+            public int compare(String s1, String s2) {
+                if (s1.equals(s2)) {
+                    return 0;
+                } else if (s1.startsWith(s2)) {
+                    return -1;
+                } else if (s2.startsWith(s1)) {
+                    return 1;
+                }
+                return s1.compareTo(s2);
+            }
+        });
+
         ArrayList<Locale> locales = new ArrayList<Locale>();
         for (String localeName : localeNames) {
             Log.i(TAG, "locale = " + localeName);
-            locales.add(Locale.forLanguageTag(localeName));
+            if (!localeName.isEmpty()) {
+                locales.add(Locale.forLanguageTag(localeName));
+            }
         }
 
         final Runnable seq = buildSequence(locales.toArray(new Locale[0]));
diff --git a/ui.cpp b/ui.cpp
index 78b6e4f..5efdc5a 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "ui.h"
+
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/input.h>
@@ -28,32 +30,43 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <cutils/android_reboot.h>
+#include <minui/minui.h>
 
 #include "common.h"
 #include "roots.h"
 #include "device.h"
-#include "minui/minui.h"
-#include "screen_ui.h"
-#include "ui.h"
 
-#define UI_WAIT_KEY_TIMEOUT_SEC    120
+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";
 
 RecoveryUI::RecoveryUI()
-        : key_queue_len(0),
-          key_last_down(-1),
-          key_long_press(false),
-          key_down_count(0),
-          enable_reboot(true),
-          consecutive_power_keys(0),
-          last_key(-1),
-          has_power_key(false),
-          has_up_key(false),
-          has_down_key(false) {
-    pthread_mutex_init(&key_queue_mutex, nullptr);
-    pthread_cond_init(&key_queue_cond, nullptr);
-    memset(key_pressed, 0, sizeof(key_pressed));
+    : locale_(""),
+      rtl_locale_(false),
+      brightness_normal_(50),
+      brightness_dimmed_(25),
+      key_queue_len(0),
+      key_last_down(-1),
+      key_long_press(false),
+      key_down_count(0),
+      enable_reboot(true),
+      consecutive_power_keys(0),
+      last_key(-1),
+      has_power_key(false),
+      has_up_key(false),
+      has_down_key(false),
+      screensaver_state_(ScreensaverState::DISABLED) {
+  pthread_mutex_init(&key_queue_mutex, nullptr);
+  pthread_cond_init(&key_queue_cond, nullptr);
+  memset(key_pressed, 0, sizeof(key_pressed));
 }
 
 void RecoveryUI::OnKeyDetected(int key_code) {
@@ -66,10 +79,6 @@
     }
 }
 
-int RecoveryUI::InputCallback(int fd, uint32_t epevents, void* data) {
-    return reinterpret_cast<RecoveryUI*>(data)->OnInputEvent(fd, epevents);
-}
-
 // Reads input events, handles special hot keys, and adds to the key queue.
 static void* InputThreadLoop(void*) {
     while (true) {
@@ -80,12 +89,54 @@
     return nullptr;
 }
 
-void RecoveryUI::Init() {
-    ev_init(InputCallback, this);
+bool RecoveryUI::InitScreensaver() {
+  // Disabled.
+  if (brightness_normal_ == 0 || brightness_dimmed_ > brightness_normal_) {
+    return false;
+  }
 
-    ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+  // 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)) {
+    LOG(WARNING) << "Failed to read max brightness: " << content;
+    return false;
+  }
 
-    pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);
+  unsigned int max_value;
+  if (!android::base::ParseUint(android::base::Trim(content), &max_value)) {
+    LOG(WARNING) << "Failed to parse max brightness: " << content;
+    return false;
+  }
+
+  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)) {
+    PLOG(WARNING) << "Failed to set brightness";
+    return false;
+  }
+
+  LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_ << "%)";
+  screensaver_state_ = ScreensaverState::NORMAL;
+  return true;
+}
+
+bool RecoveryUI::Init(const std::string& locale) {
+  // Set up the locale info.
+  SetLocale(locale);
+
+  ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2));
+
+  ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+
+  if (!InitScreensaver()) {
+    LOG(INFO) << "Screensaver disabled";
+  }
+
+  pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);
+  return true;
 }
 
 int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
@@ -216,31 +267,65 @@
 }
 
 int RecoveryUI::WaitKey() {
-    pthread_mutex_lock(&key_queue_mutex);
+  pthread_mutex_lock(&key_queue_mutex);
 
-    // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
-    // plugged in.
-    do {
-        struct timeval now;
-        struct timespec timeout;
-        gettimeofday(&now, nullptr);
-        timeout.tv_sec = now.tv_sec;
-        timeout.tv_nsec = now.tv_usec * 1000;
-        timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
+  // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
+  // plugged in.
+  do {
+    struct timeval now;
+    struct timespec timeout;
+    gettimeofday(&now, nullptr);
+    timeout.tv_sec = now.tv_sec;
+    timeout.tv_nsec = now.tv_usec * 1000;
+    timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
 
-        int rc = 0;
-        while (key_queue_len == 0 && rc != ETIMEDOUT) {
-            rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout);
-        }
-    } while (IsUsbConnected() && key_queue_len == 0);
-
-    int key = -1;
-    if (key_queue_len > 0) {
-        key = key_queue[0];
-        memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+    int rc = 0;
+    while (key_queue_len == 0 && rc != ETIMEDOUT) {
+      rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout);
     }
-    pthread_mutex_unlock(&key_queue_mutex);
-    return key;
+
+    if (screensaver_state_ != ScreensaverState::DISABLED) {
+      if (rc == ETIMEDOUT) {
+        // 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)) {
+            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)) {
+            LOG(INFO) << "Brightness: 0 (off)";
+            screensaver_state_ = ScreensaverState::OFF;
+          }
+        }
+      } else if (screensaver_state_ != ScreensaverState::NORMAL) {
+        // Drop the first key if it's changing from OFF to NORMAL.
+        if (screensaver_state_ == ScreensaverState::OFF) {
+          if (key_queue_len > 0) {
+            memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+          }
+        }
+
+        // Reset the brightness to normal.
+        if (android::base::WriteStringToFile(std::to_string(brightness_normal_value_),
+                                             BRIGHTNESS_FILE)) {
+          screensaver_state_ = ScreensaverState::NORMAL;
+          LOG(INFO) << "Brightness: " << brightness_normal_value_ << " (" << brightness_normal_
+                    << "%)";
+        }
+      }
+    }
+  } while (IsUsbConnected() && key_queue_len == 0);
+
+  int key = -1;
+  if (key_queue_len > 0) {
+    key = key_queue[0];
+    memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+  }
+  pthread_mutex_unlock(&key_queue_mutex);
+  return key;
 }
 
 bool RecoveryUI::IsUsbConnected() {
@@ -326,7 +411,7 @@
     }
 
     last_key = key;
-    return IsTextVisible() ? ENQUEUE : IGNORE;
+    return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE;
 }
 
 void RecoveryUI::KeyLongPress(int) {
@@ -337,3 +422,23 @@
     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 82d95a3..823eb65 100644
--- a/ui.h
+++ b/ui.h
@@ -21,6 +21,8 @@
 #include <pthread.h>
 #include <time.h>
 
+#include <string>
+
 // Abstract class for controlling the user interface during recovery.
 class RecoveryUI {
   public:
@@ -28,14 +30,13 @@
 
     virtual ~RecoveryUI() { }
 
-    // Initialize the object; called before anything else.
-    virtual void Init();
+    // Initialize the object; called before anything else. UI texts will be
+    // initialized according to the given locale. Returns true on success.
+    virtual bool Init(const std::string& locale);
+
     // Show a stage indicator.  Call immediately after Init().
     virtual void SetStage(int current, int max) = 0;
 
-    // After calling Init(), you can tell the UI what locale it is operating in.
-    virtual void SetLocale(const char* locale) = 0;
-
     // Set the overall recovery state ("background image").
     enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR };
     virtual void SetBackground(Icon icon) = 0;
@@ -122,10 +123,21 @@
     // statements will be displayed.
     virtual void EndMenu() = 0;
 
-protected:
+  protected:
     void EnqueueKey(int key_code);
 
-private:
+    // 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_;
+
+  private:
     // Key event input queue
     pthread_mutex_t key_queue_mutex;
     pthread_cond_t key_queue_cond;
@@ -153,8 +165,6 @@
     pthread_t input_thread_;
 
     void OnKeyDetected(int key_code);
-
-    static int InputCallback(int fd, uint32_t epevents, void* data);
     int OnInputEvent(int fd, uint32_t epevents);
     void ProcessKey(int key_code, int updown);
 
@@ -162,6 +172,16 @@
 
     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/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp
index e1b6a1c..a06384d 100644
--- a/uncrypt/uncrypt.cpp
+++ b/uncrypt/uncrypt.cpp
@@ -118,7 +118,8 @@
 
 #include "error_code.h"
 
-#define WINDOW_SIZE 5
+static constexpr int WINDOW_SIZE = 5;
+static constexpr int FIBMAP_RETRY_LIMIT = 3;
 
 // uncrypt provides three services: SETUP_BCB, CLEAR_BCB and UNCRYPT.
 //
@@ -233,6 +234,26 @@
     return true;
 }
 
+static int retry_fibmap(const int fd, const char* name, int* block, const int head_block) {
+    CHECK(block != nullptr);
+    for (size_t i = 0; i < FIBMAP_RETRY_LIMIT; i++) {
+        if (fsync(fd) == -1) {
+            PLOG(ERROR) << "failed to fsync \"" << name << "\"";
+            return kUncryptFileSyncError;
+        }
+        if (ioctl(fd, FIBMAP, block) != 0) {
+            PLOG(ERROR) << "failed to find block " << head_block;
+            return kUncryptIoctlError;
+        }
+        if (*block != 0) {
+            return kUncryptNoError;
+        }
+        sleep(1);
+    }
+    LOG(ERROR) << "fibmap of " << head_block << "always returns 0";
+    return kUncryptIoctlError;
+}
+
 static int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
                              bool encrypted, int socket) {
     std::string err;
@@ -314,6 +335,15 @@
                 PLOG(ERROR) << "failed to find block " << head_block;
                 return kUncryptIoctlError;
             }
+
+            if (block == 0) {
+                LOG(ERROR) << "failed to find block " << head_block << ", retrying";
+                int error = retry_fibmap(fd, path, &block, head_block);
+                if (error != kUncryptNoError) {
+                    return error;
+                }
+            }
+
             add_block_to_ranges(ranges, block);
             if (encrypted) {
                 if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd,
@@ -350,6 +380,15 @@
             PLOG(ERROR) << "failed to find block " << head_block;
             return kUncryptIoctlError;
         }
+
+        if (block == 0) {
+            LOG(ERROR) << "failed to find block " << head_block << ", retrying";
+            int error = retry_fibmap(fd, path, &block, head_block);
+            if (error != kUncryptNoError) {
+                return error;
+            }
+        }
+
         add_block_to_ranges(ranges, block);
         if (encrypted) {
             if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd,
@@ -457,22 +496,23 @@
     return 0;
 }
 
-static bool uncrypt_wrapper(const char* input_path, const char* map_file, const int socket) {
-    // Initialize the uncrypt error to kUncryptErrorHolder.
+static void log_uncrypt_error_code(UncryptErrorCode error_code) {
     if (!android::base::WriteStringToFile(android::base::StringPrintf(
-            "uncrypt_error: %d\n", kUncryptErrorHolder), UNCRYPT_STATUS)) {
+            "uncrypt_error: %d\n", error_code), UNCRYPT_STATUS)) {
         PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
     }
+}
+
+static bool uncrypt_wrapper(const char* input_path, const char* map_file, const int socket) {
+    // Initialize the uncrypt error to kUncryptErrorPlaceholder.
+    log_uncrypt_error_code(kUncryptErrorPlaceholder);
 
     std::string package;
     if (input_path == nullptr) {
         if (!find_uncrypt_package(UNCRYPT_PATH_FILE, &package)) {
             write_status_to_socket(-1, socket);
             // Overwrite the error message.
-            if (!android::base::WriteStringToFile(android::base::StringPrintf(
-                    "uncrypt_error: %d\n", kUncryptPackageMissingError), UNCRYPT_STATUS)) {
-                PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
-            }
+            log_uncrypt_error_code(kUncryptPackageMissingError);
             return false;
         }
         input_path = package.c_str();
@@ -529,7 +569,7 @@
     std::string content;
     content.resize(length);
     if (!android::base::ReadFully(socket, &content[0], length)) {
-        PLOG(ERROR) << "failed to read the length";
+        PLOG(ERROR) << "failed to read the message";
         return false;
     }
     LOG(INFO) << "  received command: [" << content << "] (" << content.size() << ")";
@@ -591,10 +631,7 @@
     }
 
     if ((fstab = read_fstab()) == nullptr) {
-        if (!android::base::WriteStringToFile(android::base::StringPrintf(
-                "uncrypt_error: %d\n", kUncryptFstabReadError), UNCRYPT_STATUS)) {
-            PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
-        }
+        log_uncrypt_error_code(kUncryptFstabReadError);
         return 1;
     }
 
@@ -614,30 +651,21 @@
     android::base::unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str()));
     if (service_socket == -1) {
         PLOG(ERROR) << "failed to open socket \"" << UNCRYPT_SOCKET << "\"";
-        if (!android::base::WriteStringToFile(android::base::StringPrintf(
-                "uncrypt_error: %d\n", kUncryptSocketOpenError), UNCRYPT_STATUS)) {
-            PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
-        }
+        log_uncrypt_error_code(kUncryptSocketOpenError);
         return 1;
     }
     fcntl(service_socket, F_SETFD, FD_CLOEXEC);
 
     if (listen(service_socket, 1) == -1) {
         PLOG(ERROR) << "failed to listen on socket " << service_socket.get();
-        if (!android::base::WriteStringToFile(android::base::StringPrintf(
-                "uncrypt_error: %d\n", kUncryptSocketListenError), UNCRYPT_STATUS)) {
-            PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
-        }
+        log_uncrypt_error_code(kUncryptSocketListenError);
         return 1;
     }
 
     android::base::unique_fd socket_fd(accept4(service_socket, nullptr, nullptr, SOCK_CLOEXEC));
     if (socket_fd == -1) {
         PLOG(ERROR) << "failed to accept on socket " << service_socket.get();
-        if (!android::base::WriteStringToFile(android::base::StringPrintf(
-                "uncrypt_error: %d\n", kUncryptSocketAcceptError), UNCRYPT_STATUS)) {
-            PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
-        }
+        log_uncrypt_error_code(kUncryptSocketAcceptError);
         return 1;
     }
 
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index e97a3ad..1c9be2d 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -28,7 +28,6 @@
  *
  * The current slot will be marked as having booted successfully if the
  * verifier reaches the end after the verification.
- *
  */
 
 #include <errno.h>
@@ -42,9 +41,9 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <cutils/properties.h>
 #include <android/hardware/boot/1.0/IBootControl.h>
 
 using android::sp;
@@ -56,54 +55,53 @@
 constexpr int BLOCKSIZE = 4096;
 
 static bool read_blocks(const std::string& blk_device_prefix, const std::string& range_str) {
-    char slot_suffix[PROPERTY_VALUE_MAX];
-    property_get("ro.boot.slot_suffix", slot_suffix, "");
-    std::string blk_device = blk_device_prefix + std::string(slot_suffix);
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(blk_device.c_str(), O_RDONLY)));
-    if (fd.get() == -1) {
-        PLOG(ERROR) << "Error reading partition " << blk_device;
-        return false;
+  std::string slot_suffix = android::base::GetProperty("ro.boot.slot_suffix", "");
+  std::string blk_device = blk_device_prefix + slot_suffix;
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(blk_device.c_str(), O_RDONLY)));
+  if (fd.get() == -1) {
+    PLOG(ERROR) << "Error reading partition " << blk_device;
+    return false;
+  }
+
+  // For block range string, first integer 'count' equals 2 * total number of valid ranges,
+  // 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.";
+    return false;
+  }
+
+  size_t blk_count = 0;
+  for (size_t i = 1; i < 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;
     }
 
-    // For block range string, first integer 'count' equals 2 * total number of valid ranges,
-    // 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].c_str(), &range_count);
-    if (!status || (range_count == 0) || (range_count % 2 != 0) ||
-            (range_count != ranges.size()-1)) {
-        LOG(ERROR) << "Error in parsing range string.";
-        return false;
+    if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) {
+      PLOG(ERROR) << "lseek to " << range_start << " failed";
+      return false;
     }
 
-    size_t blk_count = 0;
-    for (size_t i = 1; i < ranges.size(); i += 2) {
-        unsigned int range_start, range_end;
-        bool parse_status = android::base::ParseUint(ranges[i].c_str(), &range_start);
-        parse_status = parse_status && android::base::ParseUint(ranges[i+1].c_str(), &range_end);
-        if (!parse_status || range_start >= range_end) {
-            LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i+1];
-            return false;
-        }
-
-        if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) {
-            PLOG(ERROR) << "lseek to " << range_start << " failed";
-            return false;
-        }
-
-        size_t size = (range_end - range_start) * BLOCKSIZE;
-        std::vector<uint8_t> buf(size);
-        if (!android::base::ReadFully(fd.get(), buf.data(), size)) {
-            PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end;
-            return false;
-        }
-        blk_count += (range_end - range_start);
+    size_t size = (range_end - range_start) * BLOCKSIZE;
+    std::vector<uint8_t> buf(size);
+    if (!android::base::ReadFully(fd.get(), buf.data(), size)) {
+      PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end;
+      return false;
     }
+    blk_count += (range_end - range_start);
+  }
 
-    LOG(INFO) << "Finished reading " << blk_count << " blocks on " << blk_device;
-    return true;
+  LOG(INFO) << "Finished reading " << blk_count << " blocks on " << blk_device;
+  return true;
 }
 
 static bool verify_image(const std::string& care_map_name) {
@@ -147,7 +145,7 @@
     LOG(INFO) << "Started with arg " << i << ": " << argv[i];
   }
 
-  sp<IBootControl> module = IBootControl::getService("bootctrl");
+  sp<IBootControl> module = IBootControl::getService();
   if (module == nullptr) {
     LOG(ERROR) << "Error getting bootctrl module.";
     return -1;
@@ -160,16 +158,16 @@
 
   if (is_successful == BoolResult::FALSE) {
     // The current slot has not booted successfully.
-    char verity_mode[PROPERTY_VALUE_MAX];
-    if (property_get("ro.boot.veritymode", verity_mode, "") == -1) {
+    std::string verity_mode = android::base::GetProperty("ro.boot.veritymode", "");
+    if (verity_mode.empty()) {
       LOG(ERROR) << "Failed to get dm-verity mode.";
       return -1;
-    } else if (strcasecmp(verity_mode, "eio") == 0) {
+    } else if (android::base::EqualsIgnoreCase(verity_mode, "eio")) {
       // We shouldn't see verity in EIO mode if the current slot hasn't booted
       // successfully before. Therefore, fail the verification when veritymode=eio.
       LOG(ERROR) << "Found dm-verity in EIO mode, skip verification.";
       return -1;
-    } else if (strcmp(verity_mode, "enforcing") != 0) {
+    } else if (verity_mode != "enforcing") {
       LOG(ERROR) << "Unexpected dm-verity mode : " << verity_mode << ", expecting enforcing.";
       return -1;
     } else if (!verify_image(CARE_MAP_FILE)) {
diff --git a/updater/Android.mk b/updater/Android.mk
index 5d328a3..ac26bf4 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -18,7 +18,7 @@
     libext2_com_err \
     libext2_blkid \
     libext2_quota \
-    libext2_uuid_static \
+    libext2_uuid \
     libext2_e2p \
     libext2fs
 
@@ -31,13 +31,13 @@
     libutils \
     libmounts \
     libotafault \
-    libext4_utils_static \
+    libext4_utils \
     libfec \
     libfec_rs \
     libfs_mgr \
     liblog \
     libselinux \
-    libsparse_static \
+    libsparse \
     libsquashfs_utils \
     libbz \
     libz \
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 7257e23..6755d78 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -41,106 +41,104 @@
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <applypatch/applypatch.h>
+#include <openssl/sha.h>
 #include <ziparchive/zip_archive.h>
 
-#include "applypatch/applypatch.h"
 #include "edify/expr.h"
 #include "error_code.h"
 #include "updater/install.h"
-#include "openssl/sha.h"
 #include "ota_io.h"
 #include "print_sha1.h"
 #include "updater/updater.h"
 
-static constexpr size_t BLOCKSIZE = 4096;
-
 // Set this to 0 to interpret 'erase' transfers to mean do a
 // BLKDISCARD ioctl (the normal behavior).  Set to 1 to interpret
 // erase to mean fill the region with zeroes.
 #define DEBUG_ERASE  0
 
-#define STASH_DIRECTORY_BASE "/cache/recovery"
-#define STASH_DIRECTORY_MODE 0700
-#define STASH_FILE_MODE 0600
+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;
 
 struct RangeSet {
-    size_t count;             // Limit is INT_MAX.
-    size_t size;
-    std::vector<size_t> pos;  // Actual limit is INT_MAX.
+  size_t count;  // Limit is INT_MAX.
+  size_t size;
+  std::vector<size_t> pos;  // Actual limit is INT_MAX.
 };
 
 static CauseCode failure_type = kNoCause;
 static bool is_retry = false;
 static std::unordered_map<std::string, RangeSet> stash_map;
 
-static void parse_range(const std::string& range_text, RangeSet& rs) {
+static RangeSet parse_range(const std::string& range_text) {
+  RangeSet rs;
 
-    std::vector<std::string> pieces = android::base::Split(range_text, ",");
-    if (pieces.size() < 3) {
-        goto err;
+  std::vector<std::string> pieces = android::base::Split(range_text, ",");
+  if (pieces.size() < 3) {
+    goto err;
+  }
+
+  size_t num;
+  if (!android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX))) {
+    goto err;
+  }
+
+  if (num == 0 || num % 2) {
+    goto err;  // must be even
+  } else if (num != pieces.size() - 1) {
+    goto err;
+  }
+
+  rs.pos.resize(num);
+  rs.count = num / 2;
+  rs.size = 0;
+
+  for (size_t i = 0; i < num; i += 2) {
+    if (!android::base::ParseUint(pieces[i + 1], &rs.pos[i], static_cast<size_t>(INT_MAX))) {
+      goto err;
     }
 
-    size_t num;
-    if (!android::base::ParseUint(pieces[0].c_str(), &num, static_cast<size_t>(INT_MAX))) {
-        goto err;
+    if (!android::base::ParseUint(pieces[i + 2], &rs.pos[i + 1], static_cast<size_t>(INT_MAX))) {
+      goto err;
     }
 
-    if (num == 0 || num % 2) {
-        goto err; // must be even
-    } else if (num != pieces.size() - 1) {
-        goto err;
+    if (rs.pos[i] >= rs.pos[i + 1]) {
+      goto err;  // empty or negative range
     }
 
-    rs.pos.resize(num);
-    rs.count = num / 2;
-    rs.size = 0;
-
-    for (size_t i = 0; i < num; i += 2) {
-        if (!android::base::ParseUint(pieces[i+1].c_str(), &rs.pos[i],
-                                      static_cast<size_t>(INT_MAX))) {
-            goto err;
-        }
-
-        if (!android::base::ParseUint(pieces[i+2].c_str(), &rs.pos[i+1],
-                                      static_cast<size_t>(INT_MAX))) {
-            goto err;
-        }
-
-        if (rs.pos[i] >= rs.pos[i+1]) {
-            goto err; // empty or negative range
-        }
-
-        size_t sz = rs.pos[i+1] - rs.pos[i];
-        if (rs.size > SIZE_MAX - sz) {
-            goto err; // overflow
-        }
-
-        rs.size += sz;
+    size_t sz = rs.pos[i + 1] - rs.pos[i];
+    if (rs.size > SIZE_MAX - sz) {
+      goto err;  // overflow
     }
 
-    return;
+    rs.size += sz;
+  }
+
+  return rs;
 
 err:
-    LOG(ERROR) << "failed to parse range '" << range_text << "'";
-    exit(1);
+  LOG(ERROR) << "failed to parse range '" << range_text << "'";
+  exit(1);
 }
 
 static bool range_overlaps(const RangeSet& r1, const RangeSet& r2) {
-    for (size_t i = 0; i < r1.count; ++i) {
-        size_t r1_0 = r1.pos[i * 2];
-        size_t r1_1 = r1.pos[i * 2 + 1];
+  for (size_t i = 0; i < r1.count; ++i) {
+    size_t r1_0 = r1.pos[i * 2];
+    size_t r1_1 = r1.pos[i * 2 + 1];
 
-        for (size_t j = 0; j < r2.count; ++j) {
-            size_t r2_0 = r2.pos[j * 2];
-            size_t r2_1 = r2.pos[j * 2 + 1];
+    for (size_t j = 0; j < r2.count; ++j) {
+      size_t r2_0 = r2.pos[j * 2];
+      size_t r2_1 = r2.pos[j * 2 + 1];
 
-            if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) {
-                return true;
-            }
-        }
+      if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) {
+        return true;
+      }
     }
+  }
 
-    return false;
+  return false;
 }
 
 static int read_all(int fd, uint8_t* data, size_t size) {
@@ -431,11 +429,10 @@
     }
 
     // <src_range>
-    RangeSet src;
-    parse_range(params.tokens[params.cpos++], src);
+    RangeSet src = parse_range(params.tokens[params.cpos++]);
 
     // <tgt_range>
-    parse_range(params.tokens[params.cpos++], tgt);
+    tgt = parse_range(params.tokens[params.cpos++]);
 
     allocate(src.size * BLOCKSIZE, buffer);
     int rc = ReadBlocks(src, buffer, fd);
@@ -509,18 +506,18 @@
 }
 
 static void UpdateFileSize(const std::string& fn, void* data) {
-    if (fn.empty() || !data) {
-        return;
-    }
+  if (fn.empty() || !data) {
+    return;
+  }
 
-    struct stat sb;
-    if (stat(fn.c_str(), &sb) == -1) {
-        PLOG(ERROR) << "stat \"" << fn << "\" failed";
-        return;
-    }
+  struct stat sb;
+  if (stat(fn.c_str(), &sb) == -1) {
+    PLOG(ERROR) << "stat \"" << fn << "\" failed";
+    return;
+  }
 
-    int* size = reinterpret_cast<int*>(data);
-    *size += sb.st_size;
+  size_t* size = static_cast<size_t*>(data);
+  *size += sb.st_size;
 }
 
 // Deletes the stash directory and all files in it. Assumes that it only
@@ -710,63 +707,67 @@
 // hash enough space for the expected amount of blocks we need to store. Returns
 // >0 if we created the directory, zero if it existed already, and <0 of failure.
 
-static int CreateStash(State* state, int maxblocks, const char* blockdev, std::string& base) {
-    if (blockdev == nullptr) {
-        return -1;
+static int CreateStash(State* state, size_t maxblocks, const std::string& blockdev,
+                       std::string& base) {
+  if (blockdev.empty()) {
+    return -1;
+  }
+
+  // Stash directory should be different for each partition to avoid conflicts
+  // when updating multiple partitions at the same time, so we use the hash of
+  // the block device name as the base directory
+  uint8_t digest[SHA_DIGEST_LENGTH];
+  SHA1(reinterpret_cast<const uint8_t*>(blockdev.data()), blockdev.size(), digest);
+  base = print_sha1(digest);
+
+  std::string dirname = GetStashFileName(base, "", "");
+  struct stat sb;
+  int res = stat(dirname.c_str(), &sb);
+  size_t max_stash_size = maxblocks * BLOCKSIZE;
+
+  if (res == -1 && errno != ENOENT) {
+    ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s\n", dirname.c_str(),
+               strerror(errno));
+    return -1;
+  } else if (res != 0) {
+    LOG(INFO) << "creating stash " << dirname;
+    res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE);
+
+    if (res != 0) {
+      ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s\n", dirname.c_str(),
+                 strerror(errno));
+      return -1;
     }
 
-    // Stash directory should be different for each partition to avoid conflicts
-    // when updating multiple partitions at the same time, so we use the hash of
-    // the block device name as the base directory
-    uint8_t digest[SHA_DIGEST_LENGTH];
-    SHA1(reinterpret_cast<const uint8_t*>(blockdev), strlen(blockdev), digest);
-    base = print_sha1(digest);
-
-    std::string dirname = GetStashFileName(base, "", "");
-    struct stat sb;
-    int res = stat(dirname.c_str(), &sb);
-
-    if (res == -1 && errno != ENOENT) {
-        ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s\n",
-                   dirname.c_str(), strerror(errno));
-        return -1;
-    } else if (res != 0) {
-        LOG(INFO) << "creating stash " << dirname;
-        res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE);
-
-        if (res != 0) {
-            ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s\n",
-                       dirname.c_str(), strerror(errno));
-            return -1;
-        }
-
-        if (CacheSizeCheck(maxblocks * BLOCKSIZE) != 0) {
-            ErrorAbort(state, kStashCreationFailure, "not enough space for stash\n");
-            return -1;
-        }
-
-        return 1;  // Created directory
+    if (CacheSizeCheck(max_stash_size) != 0) {
+      ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)\n",
+                 max_stash_size);
+      return -1;
     }
 
-    LOG(INFO) << "using existing stash " << dirname;
+    return 1;  // Created directory
+  }
 
-    // If the directory already exists, calculate the space already allocated to
-    // stash files and check if there's enough for all required blocks. Delete any
-    // partially completed stash files first.
+  LOG(INFO) << "using existing stash " << dirname;
 
-    EnumerateStash(dirname, DeletePartial, nullptr);
-    int size = 0;
-    EnumerateStash(dirname, UpdateFileSize, &size);
+  // If the directory already exists, calculate the space already allocated to
+  // stash files and check if there's enough for all required blocks. Delete any
+  // partially completed stash files first.
 
-    size = maxblocks * BLOCKSIZE - size;
+  EnumerateStash(dirname, DeletePartial, nullptr);
+  size_t existing = 0;
+  EnumerateStash(dirname, UpdateFileSize, &existing);
 
-    if (size > 0 && CacheSizeCheck(size) != 0) {
-        ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%d more needed)\n",
-                   size);
-        return -1;
+  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",
+                 needed);
+      return -1;
     }
+  }
 
-    return 0; // Using existing directory
+  return 0;  // Using existing directory
 }
 
 static int SaveStash(CommandParameters& params, const std::string& base,
@@ -787,8 +788,7 @@
         return 0;
     }
 
-    RangeSet src;
-    parse_range(params.tokens[params.cpos++], src);
+    RangeSet src = parse_range(params.tokens[params.cpos++]);
 
     allocate(src.size * BLOCKSIZE, buffer);
     if (ReadBlocks(src, buffer, fd) == -1) {
@@ -872,7 +872,7 @@
     }
 
     // <tgt_range>
-    parse_range(params.tokens[params.cpos++], tgt);
+    tgt = parse_range(params.tokens[params.cpos++]);
 
     // <src_block_count>
     const std::string& token = params.tokens[params.cpos++];
@@ -888,8 +888,7 @@
         // no source ranges, only stashes
         params.cpos++;
     } else {
-        RangeSet src;
-        parse_range(params.tokens[params.cpos++], src);
+        RangeSet src = parse_range(params.tokens[params.cpos++]);
         int res = ReadBlocks(src, buffer, fd);
 
         if (overlap) {
@@ -905,8 +904,7 @@
             return 0;
         }
 
-        RangeSet locs;
-        parse_range(params.tokens[params.cpos++], locs);
+        RangeSet locs = parse_range(params.tokens[params.cpos++]);
         MoveRange(buffer, locs, buffer);
     }
 
@@ -931,8 +929,7 @@
             continue;
         }
 
-        RangeSet locs;
-        parse_range(tokens[1], locs);
+        RangeSet locs = parse_range(tokens[1]);
 
         MoveRange(buffer, locs, stash);
     }
@@ -1116,8 +1113,7 @@
         return -1;
     }
 
-    RangeSet tgt;
-    parse_range(params.tokens[params.cpos++], tgt);
+    RangeSet tgt = parse_range(params.tokens[params.cpos++]);
 
     LOG(INFO) << "  zeroing " << tgt.size << " blocks";
 
@@ -1160,8 +1156,7 @@
         return -1;
     }
 
-    RangeSet tgt;
-    parse_range(params.tokens[params.cpos++], tgt);
+    RangeSet tgt = parse_range(params.tokens[params.cpos++]);
 
     if (params.canwrite) {
         LOG(INFO) << " writing " << tgt.size << " blocks of new data";
@@ -1316,8 +1311,7 @@
         return -1;
     }
 
-    RangeSet tgt;
-    parse_range(params.tokens[params.cpos++], tgt);
+    RangeSet tgt = parse_range(params.tokens[params.cpos++]);
 
     if (params.canwrite) {
         LOG(INFO) << " erasing " << tgt.size << " blocks";
@@ -1358,7 +1352,7 @@
     CommandParameters params = {};
     params.canwrite = !dryrun;
 
-    LOG(INFO) << "performing " << dryrun ? "verification" : "update";
+    LOG(INFO) << "performing " << (dryrun ? "verification" : "update");
     if (state->is_retry) {
         is_retry = true;
         LOG(INFO) << "This update is a retry.";
@@ -1393,8 +1387,7 @@
         return StringValue("");
     }
 
-    UpdaterInfo* ui = reinterpret_cast<UpdaterInfo*>(state->cookie);
-
+    UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
     if (ui == nullptr) {
         return StringValue("");
     }
@@ -1452,7 +1445,7 @@
     }
 
     // First line in transfer list is the version number
-    if (!android::base::ParseInt(lines[0].c_str(), &params.version, 1, 4)) {
+    if (!android::base::ParseInt(lines[0], &params.version, 1, 4)) {
         LOG(ERROR) << "unexpected transfer list version [" << lines[0] << "]";
         return StringValue("");
     }
@@ -1460,8 +1453,8 @@
     LOG(INFO) << "blockimg version is " << params.version;
 
     // Second line in transfer list is the total number of blocks we expect to write
-    int total_blocks;
-    if (!android::base::ParseInt(lines[1].c_str(), &total_blocks, 0)) {
+    size_t total_blocks;
+    if (!android::base::ParseUint(lines[1], &total_blocks)) {
         ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]\n", lines[1].c_str());
         return StringValue("");
     }
@@ -1473,23 +1466,23 @@
     size_t start = 2;
     if (params.version >= 2) {
         if (lines.size() < 4) {
-            ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n",
-                       lines.size());
-            return StringValue("");
+          ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]\n",
+                     lines.size());
+          return StringValue("");
         }
 
         // Third line is how many stash entries are needed simultaneously
         LOG(INFO) << "maximum stash entries " << lines[2];
 
         // Fourth line is the maximum number of blocks that will be stashed simultaneously
-        int stash_max_blocks;
-        if (!android::base::ParseInt(lines[3].c_str(), &stash_max_blocks, 0)) {
+        size_t stash_max_blocks;
+        if (!android::base::ParseUint(lines[3], &stash_max_blocks)) {
             ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]\n",
                        lines[3].c_str());
             return StringValue("");
         }
 
-        int res = CreateStash(state, stash_max_blocks, blockdev_filename->data.c_str(), params.stashbase);
+        int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase);
         if (res == -1) {
             return StringValue("");
         }
@@ -1514,15 +1507,13 @@
 
     // Subsequent lines are all individual transfer commands
     for (auto it = lines.cbegin() + start; it != lines.cend(); it++) {
-        const std::string& line_str(*it);
-        if (line_str.empty()) {
-            continue;
-        }
+        const std::string& line(*it);
+        if (line.empty()) continue;
 
-        params.tokens = android::base::Split(line_str, " ");
+        params.tokens = android::base::Split(line, " ");
         params.cpos = 0;
         params.cmdname = params.tokens[params.cpos++].c_str();
-        params.cmdline = line_str.c_str();
+        params.cmdline = line.c_str();
 
         if (cmd_map.find(params.cmdname) == cmd_map.end()) {
             LOG(ERROR) << "unexpected command [" << params.cmdname << "]";
@@ -1532,7 +1523,7 @@
         const Command* cmd = cmd_map[params.cmdname];
 
         if (cmd->f != nullptr && cmd->f(params) == -1) {
-            LOG(ERROR) << "failed to execute command [" << line_str << "]";
+            LOG(ERROR) << "failed to execute command [" << line << "]";
             goto pbiudone;
         }
 
@@ -1542,7 +1533,8 @@
                 PLOG(ERROR) << "fsync failed";
                 goto pbiudone;
             }
-            fprintf(cmd_pipe, "set_progress %.4f\n", (double) params.written / total_blocks);
+            fprintf(cmd_pipe, "set_progress %.4f\n",
+                    static_cast<double>(params.written) / total_blocks);
             fflush(cmd_pipe);
         }
     }
@@ -1555,7 +1547,7 @@
         LOG(INFO) << "max alloc needed was " << params.buffer.size();
 
         const char* partition = strrchr(blockdev_filename->data.c_str(), '/');
-        if (partition != nullptr && *(partition+1) != 0) {
+        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,
@@ -1707,8 +1699,7 @@
         return StringValue("");
     }
 
-    RangeSet rs;
-    parse_range(ranges->data, rs);
+    RangeSet rs = parse_range(ranges->data);
 
     SHA_CTX ctx;
     SHA1_Init(&ctx);
@@ -1832,8 +1823,7 @@
         return StringValue("");
     }
 
-    RangeSet rs;
-    parse_range(ranges->data, rs);
+    RangeSet rs = parse_range(ranges->data);
 
     uint8_t buffer[BLOCKSIZE];
 
diff --git a/updater/install.cpp b/updater/install.cpp
index 3cf3877..7a8e92f 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -46,6 +46,8 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#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>
@@ -54,8 +56,6 @@
 #include <selinux/selinux.h>
 #include <ziparchive/zip_archive.h>
 
-#include "applypatch/applypatch.h"
-#include "bootloader.h"
 #include "edify/expr.h"
 #include "error_code.h"
 #include "mounts.h"
@@ -76,7 +76,6 @@
   for (auto& line : lines) {
     if (!line.empty()) {
       fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str());
-      fprintf(ui->cmd_pipe, "ui_print\n");
     }
   }
 
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 3e624da..22c060f 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -100,7 +100,6 @@
     CloseArchive(za);
     return 3;
   }
-  ota_io_init(za);
 
   ZipString script_name(SCRIPT_NAME);
   ZipEntry script_entry;
@@ -166,6 +165,7 @@
       printf("unexpected argument: %s", argv[4]);
     }
   }
+  ota_io_init(za, state.is_retry);
 
   std::string result;
   bool status = Evaluate(&state, root, &result);
@@ -191,7 +191,6 @@
         }
         fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
       }
-      fprintf(cmd_pipe, "ui_print\n");
     }
 
     if (state.error_code != kNoError) {
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 5433d11..6c02865 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "wear_ui.h"
+
 #include <errno.h>
 #include <fcntl.h>
 #include <stdarg.h>
@@ -25,14 +27,16 @@
 #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"
-#include "wear_ui.h"
-#include "android-base/properties.h"
-#include "android-base/strings.h"
-#include "android-base/stringprintf.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,
@@ -47,32 +51,13 @@
 }
 
 WearRecoveryUI::WearRecoveryUI() :
-    progress_bar_height(3),
-    progress_bar_width(200),
     progress_bar_y(259),
     outer_height(0),
     outer_width(0),
-    menu_unusable_rows(0),
-    intro_frames(22),
-    loop_frames(60),
-    animation_fps(30),
-    currentIcon(NONE),
-    intro_done(false),
-    current_frame(0),
-    progressBarType(EMPTY),
-    progressScopeStart(0),
-    progressScopeSize(0),
-    progress(0),
-    text_cols(0),
-    text_rows(0),
-    text_col(0),
-    text_row(0),
-    text_top(0),
-    show_text(false),
-    show_text_ever(false),
-    show_menu(false),
-    menu_items(0),
-    menu_sel(0) {
+    menu_unusable_rows(0) {
+    intro_frames = 22;
+    loop_frames = 60;
+    animation_fps = 30;
 
     for (size_t i = 0; i < 5; i++)
         backgroundIcon[i] = NULL;
@@ -80,16 +65,22 @@
     self = this;
 }
 
+int WearRecoveryUI::GetProgressBaseline() {
+    return progress_bar_y;
+}
+
 // Draw background frame on the screen.  Does not flip pages.
 // Should only be called with updateMutex locked.
-void WearRecoveryUI::draw_background_locked(Icon icon)
+// TODO merge drawing routines with screen_ui
+void WearRecoveryUI::draw_background_locked()
 {
+    pagesIdentical = false;
     gr_color(0, 0, 0, 255);
     gr_fill(0, 0, gr_fb_width(), gr_fb_height());
 
-    if (icon) {
+    if (currentIcon != NONE) {
         GRSurface* surface;
-        if (icon == INSTALLING_UPDATE || icon == ERASING) {
+        if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
             if (!intro_done) {
                 surface = introFrames[current_frame];
             } else {
@@ -97,7 +88,7 @@
             }
         }
         else {
-            surface = backgroundIcon[icon];
+            surface = backgroundIcon[currentIcon];
         }
 
         int width = gr_get_width(surface);
@@ -110,36 +101,6 @@
     }
 }
 
-// Draw the progress bar (if any) on the screen.  Does not flip pages.
-// Should only be called with updateMutex locked.
-void WearRecoveryUI::draw_progress_locked()
-{
-    if (currentIcon == ERROR) return;
-    if (progressBarType != DETERMINATE) return;
-
-    int width = progress_bar_width;
-    int height = progress_bar_height;
-    int dx = (gr_fb_width() - width)/2;
-    int dy = progress_bar_y;
-
-    float p = progressScopeStart + progress * progressScopeSize;
-    int pos = (int) (p * width);
-
-    gr_color(0x43, 0x43, 0x43, 0xff);
-    gr_fill(dx, dy, dx + width, dy + height);
-
-    if (pos > 0) {
-        gr_color(0x02, 0xa8, 0xf3, 255);
-        if (rtl_locale) {
-            // Fill the progress bar from right to left.
-            gr_fill(dx + width - pos, dy, dx + width, dy + height);
-        } else {
-            // Fill the progress bar from left to right.
-            gr_fill(dx, dy, dx + pos, dy + height);
-        }
-    }
-}
-
 static const char* HEADERS[] = {
     "Swipe up/down to move.",
     "Swipe left/right to select.",
@@ -147,13 +108,15 @@
     NULL
 };
 
+// TODO merge drawing routines with screen_ui
 void WearRecoveryUI::draw_screen_locked()
 {
-    draw_background_locked(currentIcon);
-    draw_progress_locked();
     char cur_selection_str[50];
 
-    if (show_text) {
+    draw_background_locked();
+    if (!show_text) {
+        draw_foreground_locked();
+    } else {
         SetColor(TEXT_FILL);
         gr_fill(0, 0, gr_fb_width(), gr_fb_height());
 
@@ -192,10 +155,12 @@
                     gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2);
                     // white text of selected item
                     SetColor(MENU_SEL_FG);
-                    if (menu[i][0]) gr_text(gr_sys_font(), x+4, y, menu[i], 1);
+                    if (menu_[i][0]) {
+                        gr_text(gr_sys_font(), x + 4, y, menu_[i], 1);
+                    }
                     SetColor(MENU);
-                } else if (menu[i][0]) {
-                    gr_text(gr_sys_font(), x+4, y, menu[i], 0);
+                } else if (menu_[i][0]) {
+                    gr_text(gr_sys_font(), x + 4, y, menu_[i], 0);
                 }
                 y += char_height_+4;
             }
@@ -211,215 +176,99 @@
         // 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_top_ + text_rows_ - 1) % text_rows_;
         size_t count = 0;
         for (int ty = gr_fb_height() - char_height_ - outer_height;
-             ty > y+2 && count < text_rows;
+             ty > y + 2 && count < text_rows_;
              ty -= char_height_, ++count) {
-            gr_text(gr_sys_font(), x+4, ty, text[row], 0);
+            gr_text(gr_sys_font(), x+4, ty, text_[row], 0);
             --row;
-            if (row < 0) row = text_rows-1;
+            if (row < 0) row = text_rows_ - 1;
         }
     }
 }
 
-void WearRecoveryUI::update_screen_locked()
-{
+// TODO merge drawing routines with screen_ui
+void WearRecoveryUI::update_progress_locked() {
     draw_screen_locked();
     gr_flip();
 }
 
-// Keeps the progress bar updated, even when the process is otherwise busy.
-void* WearRecoveryUI::progress_thread(void *cookie) {
-    self->progress_loop();
-    return NULL;
-}
-
-void WearRecoveryUI::progress_loop() {
-    double interval = 1.0 / animation_fps;
-    for (;;) {
-        double start = now();
-        pthread_mutex_lock(&updateMutex);
-        int redraw = 0;
-
-        if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING)
-                                                            && !show_text) {
-            if (!intro_done) {
-                if (current_frame >= intro_frames - 1) {
-                    intro_done = true;
-                    current_frame = 0;
-                } else {
-                    current_frame++;
-                }
-            } else {
-                current_frame = (current_frame + 1) % loop_frames;
-            }
-            redraw = 1;
-        }
-
-        // move the progress bar forward on timed intervals, if configured
-        int duration = progressScopeDuration;
-        if (progressBarType == DETERMINATE && duration > 0) {
-            double elapsed = now() - progressScopeTime;
-            float p = 1.0 * elapsed / duration;
-            if (p > 1.0) p = 1.0;
-            if (p > progress) {
-                progress = p;
-                redraw = 1;
-            }
-        }
-
-        if (redraw)
-            update_screen_locked();
-
-        pthread_mutex_unlock(&updateMutex);
-        double end = now();
-        // minimum of 20ms delay between frames
-        double delay = interval - (end-start);
-        if (delay < 0.02) delay = 0.02;
-        usleep(static_cast<useconds_t>(delay * 1000000));
+bool WearRecoveryUI::InitTextParams() {
+    if (!ScreenRecoveryUI::InitTextParams()) {
+        return false;
     }
-}
 
-void WearRecoveryUI::Init()
-{
-    gr_init();
+    text_cols_ = (gr_fb_width() - (outer_width * 2)) / char_width_;
 
-    gr_font_size(gr_sys_font(), &char_width_, &char_height_);
+    if (text_rows_ > kMaxRows) text_rows_ = kMaxRows;
+    if (text_cols_ > kMaxCols) text_cols_ = kMaxCols;
 
-    text_col = text_row = 0;
-    text_rows = (gr_fb_height()) / char_height_;
     visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_;
-    if (text_rows > kMaxRows) text_rows = kMaxRows;
-    text_top = 1;
-
-    text_cols = (gr_fb_width() - (outer_width * 2)) / char_width_;
-    if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1;
-
-    LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]);
-    backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
-    LoadBitmap("icon_error", &backgroundIcon[ERROR]);
-    backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
-
-    introFrames = (GRSurface**)malloc(intro_frames * sizeof(GRSurface*));
-    for (int i = 0; i < intro_frames; ++i) {
-        char filename[40];
-        sprintf(filename, "intro%02d", i);
-        LoadBitmap(filename, introFrames + i);
-    }
-
-    loopFrames = (GRSurface**)malloc(loop_frames * sizeof(GRSurface*));
-    for (int i = 0; i < loop_frames; ++i) {
-        char filename[40];
-        sprintf(filename, "loop%02d", i);
-        LoadBitmap(filename, loopFrames + i);
-    }
-
-    pthread_create(&progress_t, NULL, progress_thread, NULL);
-    RecoveryUI::Init();
+    return true;
 }
 
-void WearRecoveryUI::SetBackground(Icon icon)
-{
-    pthread_mutex_lock(&updateMutex);
-    currentIcon = icon;
+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::SetProgressType(ProgressType type)
-{
-    pthread_mutex_lock(&updateMutex);
-    if (progressBarType != type) {
-        progressBarType = type;
-    }
-    progressScopeStart = 0;
-    progressScopeSize = 0;
-    progress = 0;
-    update_screen_locked();
-    pthread_mutex_unlock(&updateMutex);
-}
-
-void WearRecoveryUI::ShowProgress(float portion, float seconds)
-{
-    pthread_mutex_lock(&updateMutex);
-    progressBarType = DETERMINATE;
-    progressScopeStart += progressScopeSize;
-    progressScopeSize = portion;
-    progressScopeTime = now();
-    progressScopeDuration = seconds;
-    progress = 0;
-    update_screen_locked();
-    pthread_mutex_unlock(&updateMutex);
-}
-
-void WearRecoveryUI::SetProgress(float fraction)
-{
-    pthread_mutex_lock(&updateMutex);
-    if (fraction < 0.0) fraction = 0.0;
-    if (fraction > 1.0) fraction = 1.0;
-    if (progressBarType == DETERMINATE && fraction > progress) {
-        // Skip updates that aren't visibly different.
-        int width = progress_bar_width;
-        float scale = width * progressScopeSize;
-        if ((int) (progress * scale) != (int) (fraction * scale)) {
-            progress = fraction;
-            update_screen_locked();
-        }
-    }
-    pthread_mutex_unlock(&updateMutex);
-}
-
-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);
+  }
+  pthread_mutex_unlock(&updateMutex);
 }
 
 void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
-                                 int initial_selection) {
+                               int initial_selection) {
     pthread_mutex_lock(&updateMutex);
-    if (text_rows > 0 && text_cols > 0) {
+    if (text_rows_ > 0 && text_cols_ > 0) {
         menu_headers_ = headers;
         size_t i = 0;
-        // "i < text_rows" is removed from the loop termination condition,
+        // "i < text_rows_" is removed from the loop termination condition,
         // which is different from the one in ScreenRecoveryUI::StartMenu().
         // Because WearRecoveryUI supports scrollable menu, it's fine to have
-        // more entries than text_rows. The menu may be truncated otherwise.
+        // more entries than text_rows_. The menu may be truncated otherwise.
         // Bug: 23752519
         for (; items[i] != nullptr; i++) {
-            strncpy(menu[i], items[i], text_cols - 1);
-            menu[i][text_cols - 1] = '\0';
+            strncpy(menu_[i], items[i], text_cols_ - 1);
+            menu_[i][text_cols_ - 1] = '\0';
         }
         menu_items = i;
-        show_menu = 1;
+        show_menu = true;
         menu_sel = initial_selection;
         menu_start = 0;
         menu_end = visible_text_rows - 1 - menu_unusable_rows;
@@ -433,7 +282,7 @@
 int WearRecoveryUI::SelectMenu(int sel) {
     int old_sel;
     pthread_mutex_lock(&updateMutex);
-    if (show_menu > 0) {
+    if (show_menu) {
         old_sel = menu_sel;
         menu_sel = sel;
         if (menu_sel < 0) menu_sel = 0;
@@ -452,53 +301,6 @@
     return sel;
 }
 
-void WearRecoveryUI::EndMenu() {
-    int i;
-    pthread_mutex_lock(&updateMutex);
-    if (show_menu > 0 && text_rows > 0 && text_cols > 0) {
-        show_menu = 0;
-        update_screen_locked();
-    }
-    pthread_mutex_unlock(&updateMutex);
-}
-
-bool WearRecoveryUI::IsTextVisible()
-{
-    pthread_mutex_lock(&updateMutex);
-    int visible = show_text;
-    pthread_mutex_unlock(&updateMutex);
-    return visible;
-}
-
-bool WearRecoveryUI::WasTextEverVisible()
-{
-    pthread_mutex_lock(&updateMutex);
-    int ever_visible = show_text_ever;
-    pthread_mutex_unlock(&updateMutex);
-    return ever_visible;
-}
-
-void WearRecoveryUI::ShowText(bool visible)
-{
-    pthread_mutex_lock(&updateMutex);
-    // Don't show text during ota install or factory reset
-    if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
-        pthread_mutex_unlock(&updateMutex);
-        return;
-    }
-    show_text = visible;
-    if (show_text) show_text_ever = 1;
-    update_screen_locked();
-    pthread_mutex_unlock(&updateMutex);
-}
-
-void WearRecoveryUI::Redraw()
-{
-    pthread_mutex_lock(&updateMutex);
-    update_screen_locked();
-    pthread_mutex_unlock(&updateMutex);
-}
-
 void WearRecoveryUI::ShowFile(FILE* fp) {
     std::vector<off_t> offsets;
     offsets.push_back(ftello(fp));
@@ -538,12 +340,12 @@
 
         int ch = getc(fp);
         if (ch == EOF) {
-            text_row = text_top = text_rows - 2;
+            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;
+            if (text_col_ == 0 && text_row_ >= text_rows_ - 2) {
+                text_top_ = text_row_;
                 show_prompt = true;
             }
         }
@@ -552,10 +354,10 @@
 
 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;
+    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);
 }
@@ -572,11 +374,11 @@
 
 void WearRecoveryUI::ClearText() {
     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);
+    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);
     }
     pthread_mutex_unlock(&updateMutex);
 }
@@ -597,17 +399,17 @@
     }
 
     pthread_mutex_lock(&updateMutex);
-    if (text_rows > 0 && text_cols > 0) {
+    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_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;
+            if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
         }
-        text[text_row][text_col] = '\0';
+        text_[text_row_][text_col_] = '\0';
         update_screen_locked();
     }
     pthread_mutex_unlock(&updateMutex);
diff --git a/wear_ui.h b/wear_ui.h
index e2d6fe0..4cd852f 100644
--- a/wear_ui.h
+++ b/wear_ui.h
@@ -19,43 +19,28 @@
 
 #include "screen_ui.h"
 
+#include <string>
+
 class WearRecoveryUI : public ScreenRecoveryUI {
   public:
     WearRecoveryUI();
 
-    void Init();
-    // overall recovery state ("background image")
-    void SetBackground(Icon icon);
+    bool Init(const std::string& locale) override;
 
-    // progress indicator
-    void SetProgressType(ProgressType type);
-    void ShowProgress(float portion, float seconds);
-    void SetProgress(float fraction);
-
-    void SetStage(int current, int max);
-
-    // text log
-    void ShowText(bool visible);
-    bool IsTextVisible();
-    bool WasTextEverVisible();
+    void SetStage(int current, int max) override;
 
     // printing messages
-    void Print(const char* fmt, ...);
-    void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3);
-    void ShowFile(const char* filename);
-    void ShowFile(FILE* fp);
+    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);
-    int SelectMenu(int sel);
-    void EndMenu();
-
-    void Redraw();
+                   int initial_selection) override;
+    int SelectMenu(int sel) override;
 
   protected:
-    int progress_bar_height, progress_bar_width;
-
     // progress bar vertical position, it's centered horizontally
     int progress_bar_y;
 
@@ -67,59 +52,34 @@
     // that may otherwise go out of the screen.
     int menu_unusable_rows;
 
-    // number of intro frames (default: 22) and loop frames (default: 60)
-    int intro_frames;
-    int loop_frames;
+    int GetProgressBaseline() override;
 
-    // Number of frames per sec (default: 30) for both of intro and loop.
-    int animation_fps;
+    bool InitTextParams() override;
+
+    void update_progress_locked() override;
+
+    void PrintV(const char*, bool, va_list) override;
 
   private:
-    Icon currentIcon;
-
-    bool intro_done;
-
-    int current_frame;
-
     GRSurface* backgroundIcon[5];
-    GRSurface* *introFrames;
-    GRSurface* *loopFrames;
-
-    ProgressType progressBarType;
-
-    float progressScopeStart, progressScopeSize, progress;
-    double progressScopeTime, progressScopeDuration;
 
     static const int kMaxCols = 96;
     static const int kMaxRows = 96;
 
-    // Log text overlay, displayed when a magic key is pressed
-    char text[kMaxRows][kMaxCols];
-    size_t text_cols, text_rows;
     // Number of text rows seen on screen
     int visible_text_rows;
-    size_t text_col, text_row, text_top;
-    bool show_text;
-    bool show_text_ever;   // has show_text ever been true?
 
-    char menu[kMaxRows][kMaxCols];
-    bool show_menu;
     const char* const* menu_headers_;
-    int menu_items, menu_sel;
     int menu_start, menu_end;
 
     pthread_t progress_t;
 
-  private:
-    void draw_background_locked(Icon icon);
+    void draw_background_locked() override;
+    void draw_screen_locked() override;
     void draw_progress_locked();
-    void draw_screen_locked();
-    void update_screen_locked();
-    static void* progress_thread(void* cookie);
-    void progress_loop();
+
     void PutChar(char);
     void ClearText();
-    void PrintV(const char*, bool, va_list);
 };
 
 #endif  // RECOVERY_WEAR_UI_H