Merge "Adding support for quiescent reboot to recovery"
diff --git a/Android.mk b/Android.mk
index 9e374de..5ce5cd7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -22,11 +22,10 @@
 # ===============================
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := fuse_sideload.cpp
-LOCAL_CLANG := true
-LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter -Werror
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
 LOCAL_MODULE := libfusesideload
-LOCAL_STATIC_LIBRARIES := libcutils libc libcrypto
+LOCAL_STATIC_LIBRARIES := libcrypto
 include $(BUILD_STATIC_LIBRARY)
 
 # libmounts (static library)
@@ -45,7 +44,7 @@
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := \
     install.cpp
-LOCAL_CFLAGS := -Wno-unused-parameter -Werror
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
 
 ifeq ($(AB_OTA_UPDATER),true)
@@ -55,6 +54,7 @@
 LOCAL_MODULE := librecovery
 LOCAL_STATIC_LIBRARIES := \
     libminui \
+    libvintf_recovery \
     libcrypto_utils \
     libcrypto \
     libbase
@@ -91,11 +91,9 @@
 
 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
 LOCAL_CFLAGS += -Wno-unused-parameter -Werror
-LOCAL_CLANG := true
 
 LOCAL_C_INCLUDES += \
     system/vold \
-    system/core/adb \
 
 LOCAL_STATIC_LIBRARIES := \
     librecovery \
@@ -114,6 +112,9 @@
     libfs_mgr \
     libcrypto_utils \
     libcrypto \
+    libvintf_recovery \
+    libvintf \
+    libtinyxml2 \
     libbase \
     libcutils \
     libutils \
diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp
index 7b191a8..aa32d57 100644
--- a/applypatch/applypatch_modes.cpp
+++ b/applypatch/applypatch_modes.cpp
@@ -44,19 +44,6 @@
     return applypatch_check(argv[2], sha1);
 }
 
-static int SpaceMode(int argc, const char** argv) {
-    if (argc != 3) {
-        return 2;
-    }
-
-    size_t bytes;
-    if (!android::base::ParseUint(argv[2], &bytes) || bytes == 0) {
-        printf("can't parse \"%s\" as byte count\n\n", argv[2]);
-        return 1;
-    }
-    return CacheSizeCheck(bytes);
-}
-
 // Parse arguments (which should be of the form "<sha1>:<filename>" into the
 // new parallel arrays *sha1s and *files. Returns true on success.
 static bool ParsePatchArgs(int argc, const char** argv, std::vector<std::string>* sha1s,
@@ -175,13 +162,12 @@
             "usage: %s [-b <bonus-file>] <src-file> <tgt-file> <tgt-sha1> <tgt-size> "
             "[<src-sha1>:<patch> ...]\n"
             "   or  %s -c <file> [<sha1> ...]\n"
-            "   or  %s -s <bytes>\n"
             "   or  %s -l\n"
             "\n"
             "Filenames may be of the form\n"
             "  EMMC:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n"
             "to specify reading from or writing to an EMMC partition.\n\n",
-            argv[0], argv[0], argv[0], argv[0]);
+            argv[0], argv[0], argv[0]);
         return 2;
     }
 
@@ -191,8 +177,6 @@
         result = ShowLicenses();
     } else if (strncmp(argv[1], "-c", 3) == 0) {
         result = CheckMode(argc, argv);
-    } else if (strncmp(argv[1], "-s", 3) == 0) {
-        result = SpaceMode(argc, argv);
     } else {
         result = PatchMode(argc, argv);
     }
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index 7d8b736..3d905f7 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -100,7 +100,10 @@
         printf("source data too short\n");
         return -1;
       }
-      ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx);
+      if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx) != 0) {
+        printf("Failed to apply bsdiff patch.\n");
+        return -1;
+      }
     } else if (type == CHUNK_RAW) {
       const char* raw_header = &patch->data[pos];
       pos += 4;
diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp
index 1725e88..279a976 100644
--- a/fuse_sideload.cpp
+++ b/fuse_sideload.cpp
@@ -226,11 +226,13 @@
     return NO_STATUS;
 }
 
-static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+static int handle_flush(void* /* data */, struct fuse_data* /* fd */,
+                        const struct fuse_in_header* /* hdr */) {
     return 0;
 }
 
-static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+static int handle_release(void* /* data */, struct fuse_data* /* fd */,
+                          const struct fuse_in_header* /* hdr */) {
     return 0;
 }
 
diff --git a/install.cpp b/install.cpp
index 6dcd356..e945d13 100644
--- a/install.cpp
+++ b/install.cpp
@@ -44,11 +44,11 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <vintf/VintfObjectRecovery.h>
 #include <ziparchive/zip_archive.h>
 
 #include "common.h"
 #include "error_code.h"
-#include "minui/minui.h"
 #include "otautil/SysUtil.h"
 #include "otautil/ThermalUtil.h"
 #include "roots.h"
@@ -57,18 +57,9 @@
 
 using namespace std::chrono_literals;
 
-#define ASSUMED_UPDATE_BINARY_NAME  "META-INF/com/google/android/update-binary"
-static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
-static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
-#define PUBLIC_KEYS_FILE "/res/keys"
-static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
-static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
-
 // Default allocation of progress bar segments to operations
 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;
 
 static std::condition_variable finish_log_temperature;
 
@@ -87,62 +78,58 @@
     return -1;
 }
 
-bool read_metadata_from_package(ZipArchiveHandle zip, std::string* meta_data) {
-    ZipString metadata_path(METADATA_PATH);
-    ZipEntry meta_entry;
-    if (meta_data == nullptr) {
-        LOG(ERROR) << "string* meta_data can't be nullptr";
-        return false;
-    }
-    if (FindEntry(zip, metadata_path, &meta_entry) != 0) {
-        LOG(ERROR) << "Failed to find " << METADATA_PATH << " in update package";
-        return false;
-    }
+bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) {
+  CHECK(metadata != nullptr);
 
-    meta_data->resize(meta_entry.uncompressed_length, '\0');
-    if (ExtractToMemory(zip, &meta_entry, reinterpret_cast<uint8_t*>(&(*meta_data)[0]),
-                        meta_entry.uncompressed_length) != 0) {
-        LOG(ERROR) << "Failed to read metadata in update package";
-        return false;
-    }
-    return true;
+  static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
+  ZipString path(METADATA_PATH);
+  ZipEntry entry;
+  if (FindEntry(zip, path, &entry) != 0) {
+    LOG(ERROR) << "Failed to find " << METADATA_PATH;
+    return false;
+  }
+
+  uint32_t length = entry.uncompressed_length;
+  metadata->resize(length, '\0');
+  int32_t err = ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&(*metadata)[0]), length);
+  if (err != 0) {
+    LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err);
+    return false;
+  }
+  return true;
 }
 
 // Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
 static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>& log_buffer) {
-    std::string meta_data;
-    if (!read_metadata_from_package(zip, &meta_data)) {
-        return;
+  std::string metadata;
+  if (!read_metadata_from_package(zip, &metadata)) {
+    return;
+  }
+  // Examples of the pre-build and post-build strings in metadata:
+  //   pre-build-incremental=2943039
+  //   post-build-incremental=2951741
+  std::vector<std::string> lines = android::base::Split(metadata, "\n");
+  for (const std::string& line : lines) {
+    std::string str = android::base::Trim(line);
+    if (android::base::StartsWith(str, "pre-build-incremental")) {
+      int source_build = parse_build_number(str);
+      if (source_build != -1) {
+        log_buffer.push_back(android::base::StringPrintf("source_build: %d", source_build));
+      }
+    } else if (android::base::StartsWith(str, "post-build-incremental")) {
+      int target_build = parse_build_number(str);
+      if (target_build != -1) {
+        log_buffer.push_back(android::base::StringPrintf("target_build: %d", target_build));
+      }
     }
-    // Examples of the pre-build and post-build strings in metadata:
-    // pre-build-incremental=2943039
-    // post-build-incremental=2951741
-    std::vector<std::string> lines = android::base::Split(meta_data, "\n");
-    for (const std::string& line : lines) {
-        std::string str = android::base::Trim(line);
-        if (android::base::StartsWith(str, "pre-build-incremental")){
-            int source_build = parse_build_number(str);
-            if (source_build != -1) {
-                log_buffer.push_back(android::base::StringPrintf("source_build: %d",
-                        source_build));
-            }
-        } else if (android::base::StartsWith(str, "post-build-incremental")) {
-            int target_build = parse_build_number(str);
-            if (target_build != -1) {
-                log_buffer.push_back(android::base::StringPrintf("target_build: %d",
-                        target_build));
-            }
-        }
-    }
+  }
 }
 
-// Extract the update binary from the open zip archive |zip| located at |path|
-// and store into |cmd| the command line that should be called. The |status_fd|
-// is the file descriptor the child process should use to report back the
-// progress of the update.
-static int
-update_binary_command(const char* path, ZipArchiveHandle zip, int retry_count,
-                      int status_fd, std::vector<std::string>* cmd);
+// Extract the update binary from the open zip archive |zip| located at |path| and store into |cmd|
+// the command line that should be called. The |status_fd| is the file descriptor the child process
+// should use to report back the progress of the update.
+int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
+                          int status_fd, std::vector<std::string>* cmd);
 
 #ifdef AB_OTA_UPDATER
 
@@ -224,87 +211,90 @@
   return 0;
 }
 
-static int
-update_binary_command(const char* path, ZipArchiveHandle zip, int retry_count,
-                      int status_fd, std::vector<std::string>* cmd)
-{
-    int ret = check_newer_ab_build(zip);
-    if (ret) {
-        return ret;
-    }
+int update_binary_command(const std::string& path, ZipArchiveHandle zip, int /* retry_count */,
+                          int status_fd, std::vector<std::string>* cmd) {
+  CHECK(cmd != nullptr);
+  int ret = check_newer_ab_build(zip);
+  if (ret != 0) {
+    return ret;
+  }
 
-    // For A/B updates we extract the payload properties to a buffer and obtain
-    // the RAW payload offset in the zip file.
-    ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES);
-    ZipEntry properties_entry;
-    if (FindEntry(zip, property_name, &properties_entry) != 0) {
-        LOG(ERROR) << "Can't find " << AB_OTA_PAYLOAD_PROPERTIES;
-        return INSTALL_CORRUPT;
-    }
-    std::vector<uint8_t> payload_properties(
-            properties_entry.uncompressed_length);
-    if (ExtractToMemory(zip, &properties_entry, payload_properties.data(),
-                        properties_entry.uncompressed_length) != 0) {
-        LOG(ERROR) << "Can't extract " << AB_OTA_PAYLOAD_PROPERTIES;
-        return INSTALL_CORRUPT;
-    }
+  // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset
+  // in the zip file.
+  static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
+  ZipString property_name(AB_OTA_PAYLOAD_PROPERTIES);
+  ZipEntry properties_entry;
+  if (FindEntry(zip, property_name, &properties_entry) != 0) {
+    LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES;
+    return INSTALL_CORRUPT;
+  }
+  uint32_t properties_entry_length = properties_entry.uncompressed_length;
+  std::vector<uint8_t> payload_properties(properties_entry_length);
+  int32_t err =
+      ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length);
+  if (err != 0) {
+    LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err);
+    return INSTALL_CORRUPT;
+  }
 
-    ZipString payload_name(AB_OTA_PAYLOAD);
-    ZipEntry payload_entry;
-    if (FindEntry(zip, payload_name, &payload_entry) != 0) {
-        LOG(ERROR) << "Can't find " << AB_OTA_PAYLOAD;
-        return INSTALL_CORRUPT;
-    }
-    long payload_offset = payload_entry.offset;
-    *cmd = {
-        "/sbin/update_engine_sideload",
-        android::base::StringPrintf("--payload=file://%s", path),
-        android::base::StringPrintf("--offset=%ld", payload_offset),
-        "--headers=" + std::string(payload_properties.begin(),
-                                   payload_properties.end()),
-        android::base::StringPrintf("--status_fd=%d", status_fd),
-    };
-    return 0;
+  static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
+  ZipString payload_name(AB_OTA_PAYLOAD);
+  ZipEntry payload_entry;
+  if (FindEntry(zip, payload_name, &payload_entry) != 0) {
+    LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD;
+    return INSTALL_CORRUPT;
+  }
+  long payload_offset = payload_entry.offset;
+  *cmd = {
+    "/sbin/update_engine_sideload",
+    "--payload=file://" + path,
+    android::base::StringPrintf("--offset=%ld", payload_offset),
+    "--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
+    android::base::StringPrintf("--status_fd=%d", status_fd),
+  };
+  return 0;
 }
 
 #else  // !AB_OTA_UPDATER
 
-static int
-update_binary_command(const char* path, ZipArchiveHandle zip, int retry_count,
-                      int status_fd, std::vector<std::string>* cmd)
-{
-    // On traditional updates we extract the update binary from the package.
-    ZipString binary_name(ASSUMED_UPDATE_BINARY_NAME);
-    ZipEntry binary_entry;
-    if (FindEntry(zip, binary_name, &binary_entry) != 0) {
-        return INSTALL_CORRUPT;
-    }
+int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
+                          int status_fd, std::vector<std::string>* cmd) {
+  CHECK(cmd != nullptr);
 
-    const char* binary = "/tmp/update_binary";
-    unlink(binary);
-    int fd = creat(binary, 0755);
-    if (fd < 0) {
-        PLOG(ERROR) << "Can't make " << binary;
-        return INSTALL_ERROR;
-    }
-    int error = ExtractEntryToFile(zip, &binary_entry, fd);
-    close(fd);
+  // On traditional updates we extract the update binary from the package.
+  static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
+  ZipString binary_name(UPDATE_BINARY_NAME);
+  ZipEntry binary_entry;
+  if (FindEntry(zip, binary_name, &binary_entry) != 0) {
+    LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
+    return INSTALL_CORRUPT;
+  }
 
-    if (error != 0) {
-        LOG(ERROR) << "Can't copy " << ASSUMED_UPDATE_BINARY_NAME
-                   << " : " << ErrorCodeString(error);
-        return INSTALL_ERROR;
-    }
+  const char* binary = "/tmp/update_binary";
+  unlink(binary);
+  int fd = creat(binary, 0755);
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to create " << binary;
+    return INSTALL_ERROR;
+  }
 
-    *cmd = {
-        binary,
-        EXPAND(RECOVERY_API_VERSION),   // defined in Android.mk
-        std::to_string(status_fd),
-        path,
-    };
-    if (retry_count > 0)
-        cmd->push_back("retry");
-    return 0;
+  int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
+  close(fd);
+  if (error != 0) {
+    LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
+    return INSTALL_ERROR;
+  }
+
+  *cmd = {
+    binary,
+    EXPAND(RECOVERY_API_VERSION),  // defined in Android.mk
+    std::to_string(status_fd),
+    path,
+  };
+  if (retry_count > 0) {
+    cmd->push_back("retry");
+  }
+  return 0;
 }
 #endif  // !AB_OTA_UPDATER
 
@@ -546,10 +536,15 @@
   }
   CloseArchive(zip_handle);
 
-  // TODO(b/36814503): Enable the actual verification when VintfObject::CheckCompatibility() lands.
-  // VintfObject::CheckCompatibility returns zero on success.
-  // return (android::vintf::VintfObject::CheckCompatibility(compatibility_info, true) == 0);
-  return true;
+  // VintfObjectRecovery::CheckCompatibility returns zero on success.
+  std::string err;
+  int result = android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err);
+  if (result == 0) {
+    return true;
+  }
+
+  LOG(ERROR) << "Failed to verify package compatibility (result " << result << "): " << err;
+  return false;
 }
 
 static int
@@ -601,7 +596,6 @@
 
     // Additionally verify the compatibility of the package.
     if (!verify_package_compatibility(zip)) {
-      LOG(ERROR) << "Failed to verify package compatibility";
       log_buffer.push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
       sysReleaseMap(&map);
       CloseArchive(zip);
@@ -623,80 +617,80 @@
     return result;
 }
 
-int
-install_package(const char* path, bool* wipe_cache, const char* install_file,
-                bool needs_mount, int retry_count)
-{
-    modified_flash = true;
-    auto start = std::chrono::system_clock::now();
+int install_package(const char* path, bool* wipe_cache, const char* install_file, bool needs_mount,
+                    int retry_count) {
+  modified_flash = true;
+  auto start = std::chrono::system_clock::now();
 
-    int start_temperature = GetMaxValueFromThermalZone();
-    int max_temperature = start_temperature;
+  int start_temperature = GetMaxValueFromThermalZone();
+  int max_temperature = start_temperature;
 
-    int result;
-    std::vector<std::string> log_buffer;
-    if (setup_install_mounts() != 0) {
-        LOG(ERROR) << "failed to set up expected mounts for install; aborting";
-        result = INSTALL_ERROR;
+  int result;
+  std::vector<std::string> log_buffer;
+  if (setup_install_mounts() != 0) {
+    LOG(ERROR) << "failed to set up expected mounts for install; aborting";
+    result = INSTALL_ERROR;
+  } else {
+    result = really_install_package(path, wipe_cache, needs_mount, log_buffer, retry_count,
+                                    &max_temperature);
+  }
+
+  // Measure the time spent to apply OTA update in seconds.
+  std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
+  int time_total = static_cast<int>(duration.count());
+
+  bool has_cache = volume_for_path("/cache") != nullptr;
+  // Skip logging the uncrypt_status on devices without /cache.
+  if (has_cache) {
+    static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
+    if (ensure_path_mounted(UNCRYPT_STATUS) != 0) {
+      LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS;
     } else {
-        result = really_install_package(path, wipe_cache, needs_mount, log_buffer, retry_count,
-                                        &max_temperature);
-    }
-
-    // Measure the time spent to apply OTA update in seconds.
-    std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
-    int time_total = static_cast<int>(duration.count());
-
-    bool has_cache = volume_for_path("/cache") != nullptr;
-    // Skip logging the uncrypt_status on devices without /cache.
-    if (has_cache) {
-      if (ensure_path_mounted(UNCRYPT_STATUS) != 0) {
-        LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS;
+      std::string uncrypt_status;
+      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;
       } else {
-        std::string uncrypt_status;
-        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;
-        } else {
-          log_buffer.push_back(android::base::Trim(uncrypt_status));
-        }
+        log_buffer.push_back(android::base::Trim(uncrypt_status));
       }
     }
+  }
 
-    // The first two lines need to be the package name and install result.
-    std::vector<std::string> log_header = {
-        path,
-        result == INSTALL_SUCCESS ? "1" : "0",
-        "time_total: " + std::to_string(time_total),
-        "retry: " + std::to_string(retry_count),
-    };
+  // The first two lines need to be the package name and install result.
+  std::vector<std::string> log_header = {
+    path,
+    result == INSTALL_SUCCESS ? "1" : "0",
+    "time_total: " + std::to_string(time_total),
+    "retry: " + std::to_string(retry_count),
+  };
 
-    int end_temperature = GetMaxValueFromThermalZone();
-    max_temperature = std::max(end_temperature, max_temperature);
-    if (start_temperature > 0) {
-      log_buffer.push_back("temperature_start: " + std::to_string(start_temperature));
-    }
-    if (end_temperature > 0) {
-      log_buffer.push_back("temperature_end: " + std::to_string(end_temperature));
-    }
-    if (max_temperature > 0) {
-      log_buffer.push_back("temperature_max: " + std::to_string(max_temperature));
-    }
+  int end_temperature = GetMaxValueFromThermalZone();
+  max_temperature = std::max(end_temperature, max_temperature);
+  if (start_temperature > 0) {
+    log_buffer.push_back("temperature_start: " + std::to_string(start_temperature));
+  }
+  if (end_temperature > 0) {
+    log_buffer.push_back("temperature_end: " + std::to_string(end_temperature));
+  }
+  if (max_temperature > 0) {
+    log_buffer.push_back("temperature_max: " + std::to_string(max_temperature));
+  }
 
-    std::string log_content = android::base::Join(log_header, "\n") + "\n" +
-            android::base::Join(log_buffer, "\n") + "\n";
-    if (!android::base::WriteStringToFile(log_content, install_file)) {
-        PLOG(ERROR) << "failed to write " << install_file;
-    }
+  std::string log_content =
+      android::base::Join(log_header, "\n") + "\n" + android::base::Join(log_buffer, "\n") + "\n";
+  if (!android::base::WriteStringToFile(log_content, install_file)) {
+    PLOG(ERROR) << "failed to write " << install_file;
+  }
 
-    // Write a copy into last_log.
-    LOG(INFO) << log_content;
+  // Write a copy into last_log.
+  LOG(INFO) << log_content;
 
-    return result;
+  return result;
 }
 
 bool verify_package(const unsigned char* package_data, size_t package_size) {
+  static constexpr const char* PUBLIC_KEYS_FILE = "/res/keys";
   std::vector<Certificate> loadedKeys;
   if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
     LOG(ERROR) << "Failed to load keys";
diff --git a/minadbd/fuse_adb_provider_test.cpp b/minadbd/fuse_adb_provider_test.cpp
index 0f2e881..31be2a6 100644
--- a/minadbd/fuse_adb_provider_test.cpp
+++ b/minadbd/fuse_adb_provider_test.cpp
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-#include "fuse_adb_provider.h"
-
-#include <gtest/gtest.h>
-
 #include <errno.h>
 #include <fcntl.h>
+#include <signal.h>
 #include <sys/socket.h>
 
 #include <string>
 
+#include <gtest/gtest.h>
+
 #include "adb_io.h"
+#include "fuse_adb_provider.h"
 
 TEST(fuse_adb_provider, read_block_adb) {
   adb_data data = {};
@@ -46,9 +46,8 @@
 
   uint32_t block = 1234U;
   const char expected_block[] = "00001234";
-  ASSERT_EQ(0, read_block_adb(reinterpret_cast<void*>(&data), block,
-                              reinterpret_cast<uint8_t*>(block_data),
-                              sizeof(expected_data) - 1));
+  ASSERT_EQ(0, read_block_adb(static_cast<void*>(&data), block,
+                              reinterpret_cast<uint8_t*>(block_data), sizeof(expected_data) - 1));
 
   // Check that read_block_adb requested the right block.
   char block_req[sizeof(expected_block)] = {};
@@ -80,9 +79,12 @@
 
   ASSERT_EQ(0, close(sockets[1]));
 
+  // write(2) raises SIGPIPE since the reading end has been closed. Ignore the signal to avoid
+  // failing the test.
+  signal(SIGPIPE, SIG_IGN);
+
   char buf[1];
-  ASSERT_EQ(-EIO, read_block_adb(reinterpret_cast<void*>(&data), 0,
-                                 reinterpret_cast<uint8_t*>(buf), 1));
+  ASSERT_EQ(-EIO, read_block_adb(static_cast<void*>(&data), 0, reinterpret_cast<uint8_t*>(buf), 1));
 
   close(sockets[0]);
 }
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
index a6aa321..61c06cc 100644
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -21,6 +21,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <string>
 #include <thread>
 
 #include "adb.h"
@@ -28,33 +29,30 @@
 #include "fuse_adb_provider.h"
 #include "sysdeps.h"
 
-static void sideload_host_service(int sfd, void* data) {
-    char* args = reinterpret_cast<char*>(data);
+static void sideload_host_service(int sfd, const std::string& args) {
     int file_size;
     int block_size;
-    if (sscanf(args, "%d:%d", &file_size, &block_size) != 2) {
-        printf("bad sideload-host arguments: %s\n", args);
+    if (sscanf(args.c_str(), "%d:%d", &file_size, &block_size) != 2) {
+        printf("bad sideload-host arguments: %s\n", args.c_str());
         exit(1);
     }
-    free(args);
 
     printf("sideload-host file size %d block size %d\n", file_size, block_size);
 
     int result = run_adb_fuse(sfd, file_size, block_size);
 
     printf("sideload_host finished\n");
-    sleep(1);
     exit(result == 0 ? 0 : 1);
 }
 
-static int create_service_thread(void (*func)(int, void *), void *cookie) {
+static int create_service_thread(void (*func)(int, const std::string&), const std::string& args) {
     int s[2];
     if (adb_socketpair(s)) {
         printf("cannot create service socket pair\n");
         return -1;
     }
 
-    std::thread([s, func, cookie]() { func(s[1], cookie); }).detach();
+    std::thread([s, func, args]() { func(s[1], args); }).detach();
 
     VLOG(SERVICES) << "service thread started, " << s[0] << ":" << s[1];
     return s[0];
@@ -69,7 +67,7 @@
         // sideload-host).
         exit(3);
     } else if (!strncmp(name, "sideload-host:", 14)) {
-        char* arg = strdup(name + 14);
+        std::string arg(name + 14);
         ret = create_service_thread(sideload_host_service, arg);
     }
     if (ret >= 0) {
diff --git a/private/install.h b/private/install.h
new file mode 100644
index 0000000..12d303b
--- /dev/null
+++ b/private/install.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Private headers exposed for testing purpose only.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
+                          int status_fd, std::vector<std::string>* cmd);
diff --git a/recovery.cpp b/recovery.cpp
index 173baac..3041d6c 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -40,7 +40,6 @@
 #include <string>
 #include <vector>
 
-#include <adb.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
@@ -1607,14 +1606,22 @@
         }
     }
 
-    if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {
-        copy_logs();
+    if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
         ui->SetBackground(RecoveryUI::ERROR);
+        if (!ui->IsTextVisible()) {
+            sleep(5);
+        }
     }
 
     Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
-    if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) ||
-            ui->IsTextVisible()) {
+    // 1. If the recovery menu is visible, prompt and wait for commands.
+    // 2. If the state is INSTALL_NONE, wait for commands. (i.e. In user build, manually reboot into
+    //    recovery to sideload a package.)
+    // 3. sideload_auto_reboot is an option only available in user-debug build, reboot the device
+    //    without waiting.
+    // 4. In all other cases, reboot the device. Therefore, normal users will observe the device
+    //    reboot after it shows the "error" screen for 5s.
+    if ((status == INSTALL_NONE && !sideload_auto_reboot) || ui->IsTextVisible()) {
         Device::BuiltinAction temp = prompt_and_wait(device, status);
         if (temp != Device::NO_ACTION) {
             after = temp;
diff --git a/tests/Android.mk b/tests/Android.mk
index a1f0d48..4e125cc 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -85,6 +85,14 @@
     -Werror \
     -D_FILE_OFFSET_BITS=64
 
+ifeq ($(AB_OTA_UPDATER),true)
+LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+endif
+
+ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
+LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
+endif
+
 LOCAL_MODULE := recovery_component_test
 LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_C_INCLUDES := bootable/recovery
@@ -97,6 +105,7 @@
     component/sideload_test.cpp \
     component/uncrypt_test.cpp \
     component/updater_test.cpp \
+    component/update_verifier_test.cpp \
     component/verifier_test.cpp
 
 LOCAL_FORCE_STATIC_EXECUTABLE := true
@@ -124,10 +133,14 @@
     libverifier \
     libotautil \
     libmounts \
+    libupdate_verifier \
     libdivsufsort \
     libdivsufsort64 \
     libfs_mgr \
     liblog \
+    libvintf_recovery \
+    libvintf \
+    libtinyxml2 \
     libselinux \
     libext4_utils \
     libsparse \
diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
index 5cba68f..016fed9 100644
--- a/tests/component/applypatch_test.cpp
+++ b/tests/component/applypatch_test.cpp
@@ -105,9 +105,6 @@
   static size_t new_size;
 };
 
-std::string ApplyPatchTest::old_file;
-std::string ApplyPatchTest::new_file;
-
 static void cp(const std::string& src, const std::string& tgt) {
   std::string cmd = "cp " + src + " " + tgt;
   system(cmd.c_str());
@@ -132,48 +129,8 @@
   }
 };
 
-class ApplyPatchFullTest : public ApplyPatchCacheTest {
- public:
-  static void SetUpTestCase() {
-    ApplyPatchTest::SetUpTestCase();
-
-    output_f = new TemporaryFile();
-    output_loc = std::string(output_f->path);
-
-    struct FileContents fc;
-
-    ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc));
-    patches.push_back(
-        std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end())));
-
-    ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc));
-    patches.push_back(
-        std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end())));
-  }
-
-  static void TearDownTestCase() {
-    delete output_f;
-    patches.clear();
-  }
-
-  static std::vector<std::unique_ptr<Value>> patches;
-  static TemporaryFile* output_f;
-  static std::string output_loc;
-};
-
-class ApplyPatchDoubleCacheTest : public ApplyPatchFullTest {
- public:
-  virtual void SetUp() {
-    ApplyPatchCacheTest::SetUp();
-    cp(cache_file, "/cache/reallysaved.file");
-  }
-
-  virtual void TearDown() {
-    cp("/cache/reallysaved.file", cache_file);
-    ApplyPatchCacheTest::TearDown();
-  }
-};
-
+std::string ApplyPatchTest::old_file;
+std::string ApplyPatchTest::new_file;
 std::string ApplyPatchTest::rand_file;
 std::string ApplyPatchTest::patch_file;
 std::string ApplyPatchTest::cache_file;
@@ -184,10 +141,6 @@
 size_t ApplyPatchTest::old_size;
 size_t ApplyPatchTest::new_size;
 
-std::vector<std::unique_ptr<Value>> ApplyPatchFullTest::patches;
-TemporaryFile* ApplyPatchFullTest::output_f;
-std::string ApplyPatchFullTest::output_loc;
-
 TEST_F(ApplyPatchTest, CheckModeSkip) {
   std::vector<std::string> sha1s;
   ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
@@ -424,20 +377,6 @@
   ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
 }
 
-TEST(ApplyPatchModesTest, SpaceModeInvalidArgs) {
-  // Insufficient args.
-  ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-s" }));
-
-  // Invalid bytes arg.
-  ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "x" }));
-
-  // 0 is invalid.
-  ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0" }));
-
-  // 0x10 is fine.
-  ASSERT_EQ(0, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0x10" }));
-}
-
 TEST(ApplyPatchModesTest, ShowLicenses) {
   ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
 }
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
index 3b6fbc3..40201d7 100644
--- a/tests/component/install_test.cpp
+++ b/tests/component/install_test.cpp
@@ -15,13 +15,22 @@
  */
 
 #include <stdio.h>
+#include <unistd.h>
 
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <android-base/test_utils.h>
 #include <gtest/gtest.h>
+#include <vintf/VintfObjectRecovery.h>
 #include <ziparchive/zip_archive.h>
 #include <ziparchive/zip_writer.h>
 
 #include "install.h"
+#include "private/install.h"
 
 TEST(InstallTest, verify_package_compatibility_no_entry) {
   TemporaryFile temp_file;
@@ -55,3 +64,215 @@
   ASSERT_FALSE(verify_package_compatibility(zip));
   CloseArchive(zip);
 }
+
+TEST(InstallTest, read_metadata_from_package_smoke) {
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+  const std::string content("abcdefg");
+  ASSERT_EQ(0, writer.WriteBytes(content.data(), content.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  std::string metadata;
+  ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
+  ASSERT_EQ(content, metadata);
+  CloseArchive(zip);
+
+  TemporaryFile temp_file2;
+  FILE* zip_file2 = fdopen(temp_file2.fd, "w");
+  ZipWriter writer2(zip_file2);
+  ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated));
+  ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size()));
+  ASSERT_EQ(0, writer2.FinishEntry());
+  ASSERT_EQ(0, writer2.Finish());
+  ASSERT_EQ(0, fclose(zip_file2));
+
+  ASSERT_EQ(0, OpenArchive(temp_file2.path, &zip));
+  metadata.clear();
+  ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
+  ASSERT_EQ(content, metadata);
+  CloseArchive(zip);
+}
+
+TEST(InstallTest, read_metadata_from_package_no_entry) {
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  std::string metadata;
+  ASSERT_FALSE(read_metadata_from_package(zip, &metadata));
+  CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
+  TemporaryFile compatibility_zip_file;
+  FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+  ZipWriter compatibility_zip_writer(compatibility_zip);
+  ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
+  std::string malformed_xml = "malformed";
+  ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(malformed_xml.data(), malformed_xml.size()));
+  ASSERT_EQ(0, compatibility_zip_writer.FinishEntry());
+  ASSERT_EQ(0, compatibility_zip_writer.Finish());
+  ASSERT_EQ(0, fclose(compatibility_zip));
+
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
+  std::string compatibility_zip_content;
+  ASSERT_TRUE(
+      android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
+  ASSERT_EQ(0,
+            writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  std::vector<std::string> compatibility_info;
+  compatibility_info.push_back(malformed_xml);
+  // Malformed compatibility zip is expected to be rejected by libvintf. But we defer that to
+  // libvintf.
+  std::string err;
+  bool result =
+      android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
+  ASSERT_EQ(result, verify_package_compatibility(zip));
+  CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml) {
+  static constexpr const char* system_manifest_xml_path = "/system/manifest.xml";
+  if (access(system_manifest_xml_path, R_OK) == -1) {
+    GTEST_LOG_(INFO) << "Test skipped on devices w/o /system/manifest.xml.";
+    return;
+  }
+  std::string system_manifest_xml_content;
+  ASSERT_TRUE(
+      android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
+  TemporaryFile compatibility_zip_file;
+  FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+  ZipWriter compatibility_zip_writer(compatibility_zip);
+  ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
+  ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(),
+                                                   system_manifest_xml_content.size()));
+  ASSERT_EQ(0, compatibility_zip_writer.FinishEntry());
+  ASSERT_EQ(0, compatibility_zip_writer.Finish());
+  ASSERT_EQ(0, fclose(compatibility_zip));
+
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
+  std::string compatibility_zip_content;
+  ASSERT_TRUE(
+      android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
+  ASSERT_EQ(0,
+            writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  std::vector<std::string> compatibility_info;
+  compatibility_info.push_back(system_manifest_xml_content);
+  std::string err;
+  bool result =
+      android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
+  // Make sure the result is consistent with libvintf library.
+  ASSERT_EQ(result, verify_package_compatibility(zip));
+  CloseArchive(zip);
+}
+
+TEST(InstallTest, update_binary_command_smoke) {
+#ifdef AB_OTA_UPDATER
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.StartEntry("payload_properties.txt", kCompressStored));
+  const std::string properties = "some_properties";
+  ASSERT_EQ(0, writer.WriteBytes(properties.data(), properties.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  // A metadata entry is mandatory.
+  ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+  std::string device = android::base::GetProperty("ro.product.device", "");
+  ASSERT_NE("", device);
+  std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
+  ASSERT_NE("", timestamp);
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
+      },
+      "\n");
+  ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  int status_fd = 10;
+  std::string path = "/path/to/update.zip";
+  std::vector<std::string> cmd;
+  ASSERT_EQ(0, update_binary_command(path, zip, 0, status_fd, &cmd));
+  ASSERT_EQ("/sbin/update_engine_sideload", cmd[0]);
+  ASSERT_EQ("--payload=file://" + path, cmd[1]);
+  ASSERT_EQ("--headers=" + properties, cmd[3]);
+  ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+  CloseArchive(zip);
+#else
+  // Cannot test update_binary_command() because it tries to extract update-binary to /tmp.
+  GTEST_LOG_(INFO) << "Test skipped on non-A/B device.";
+#endif  // AB_OTA_UPDATER
+}
+
+TEST(InstallTest, update_binary_command_invalid) {
+#ifdef AB_OTA_UPDATER
+  TemporaryFile temp_file;
+  FILE* zip_file = fdopen(temp_file.fd, "w");
+  ZipWriter writer(zip_file);
+  // Missing payload_properties.txt.
+  ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
+  ASSERT_EQ(0, writer.FinishEntry());
+  // A metadata entry is mandatory.
+  ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+  std::string device = android::base::GetProperty("ro.product.device", "");
+  ASSERT_NE("", device);
+  std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
+  ASSERT_NE("", timestamp);
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
+      },
+      "\n");
+  ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
+  ASSERT_EQ(0, writer.FinishEntry());
+  ASSERT_EQ(0, writer.Finish());
+  ASSERT_EQ(0, fclose(zip_file));
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+  int status_fd = 10;
+  std::string path = "/path/to/update.zip";
+  std::vector<std::string> cmd;
+  ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(path, zip, 0, status_fd, &cmd));
+  CloseArchive(zip);
+#else
+  // Cannot test update_binary_command() because it tries to extract update-binary to /tmp.
+  GTEST_LOG_(INFO) << "Test skipped on non-A/B device.";
+#endif  // AB_OTA_UPDATER
+}
diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp
new file mode 100644
index 0000000..73b4478
--- /dev/null
+++ b/tests/component/update_verifier_test.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <update_verifier/update_verifier.h>
+
+class UpdateVerifierTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+#ifdef PRODUCT_SUPPORTS_VERITY
+    verity_supported = true;
+#else
+    verity_supported = false;
+#endif
+  }
+
+  bool verity_supported;
+};
+
+TEST_F(UpdateVerifierTest, verify_image_no_care_map) {
+  // Non-existing care_map is allowed.
+  ASSERT_TRUE(verify_image("/doesntexist"));
+}
+
+TEST_F(UpdateVerifierTest, verify_image_smoke) {
+  // This test relies on dm-verity support.
+  if (!verity_supported) {
+    GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+    return;
+  }
+
+  // The care map file can have only two or four lines.
+  TemporaryFile temp_file;
+  std::string content = "system\n2,0,1";
+  ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+  ASSERT_TRUE(verify_image(temp_file.path));
+
+  // Leading and trailing newlines should be accepted.
+  ASSERT_TRUE(android::base::WriteStringToFile("\n" + content + "\n\n", temp_file.path));
+  ASSERT_TRUE(verify_image(temp_file.path));
+}
+
+TEST_F(UpdateVerifierTest, verify_image_wrong_lines) {
+  // The care map file can have only two or four lines.
+  TemporaryFile temp_file;
+  ASSERT_FALSE(verify_image(temp_file.path));
+
+  ASSERT_TRUE(android::base::WriteStringToFile("line1", temp_file.path));
+  ASSERT_FALSE(verify_image(temp_file.path));
+
+  ASSERT_TRUE(android::base::WriteStringToFile("line1\nline2\nline3", temp_file.path));
+  ASSERT_FALSE(verify_image(temp_file.path));
+}
+
+TEST_F(UpdateVerifierTest, verify_image_malformed_care_map) {
+  // This test relies on dm-verity support.
+  if (!verity_supported) {
+    GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+    return;
+  }
+
+  TemporaryFile temp_file;
+  std::string content = "system\n2,1,0";
+  ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+  ASSERT_FALSE(verify_image(temp_file.path));
+}
diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk
index 1acd5ec..37d9bfe 100644
--- a/update_verifier/Android.mk
+++ b/update_verifier/Android.mk
@@ -14,12 +14,43 @@
 
 LOCAL_PATH := $(call my-dir)
 
+# libupdate_verifier (static library)
+# ===============================
 include $(CLEAR_VARS)
 
-LOCAL_CLANG := true
-LOCAL_SRC_FILES := update_verifier.cpp
+LOCAL_SRC_FILES := \
+    update_verifier.cpp
+
+LOCAL_MODULE := libupdate_verifier
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    libcutils \
+    android.hardware.boot@1.0
+
+LOCAL_CFLAGS := -Wall -Werror
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+    $(LOCAL_PATH)/include
+
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+
+ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
+LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
+endif
+
+include $(BUILD_STATIC_LIBRARY)
+
+# update_verifier (executable)
+# ===============================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    update_verifier_main.cpp
 
 LOCAL_MODULE := update_verifier
+LOCAL_STATIC_LIBRARIES := \
+    libupdate_verifier
 LOCAL_SHARED_LIBRARIES := \
     libbase \
     libcutils \
@@ -29,13 +60,8 @@
     libhidlbase \
     android.hardware.boot@1.0
 
-LOCAL_CFLAGS := -Werror
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
+LOCAL_CFLAGS := -Wall -Werror
 
 LOCAL_INIT_RC := update_verifier.rc
 
-ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
-    LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
-endif
-
 include $(BUILD_EXECUTABLE)
diff --git a/update_verifier/include/update_verifier/update_verifier.h b/update_verifier/include/update_verifier/update_verifier.h
new file mode 100644
index 0000000..16b394e
--- /dev/null
+++ b/update_verifier/include/update_verifier/update_verifier.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+int update_verifier(int argc, char** argv);
+
+// Exposed for testing purpose.
+bool verify_image(const std::string& care_map_name);
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index 350020f..1950cbd 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -35,6 +35,8 @@
  * verifier reaches the end after the verification.
  */
 
+#include "update_verifier/update_verifier.h"
+
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -59,12 +61,6 @@
 using android::hardware::boot::V1_0::BoolResult;
 using android::hardware::boot::V1_0::CommandResult;
 
-constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt";
-constexpr auto DM_PATH_PREFIX = "/sys/block/";
-constexpr auto DM_PATH_SUFFIX = "/dm/name";
-constexpr auto DEV_PATH = "/dev/block/";
-constexpr int BLOCKSIZE = 4096;
-
 // Find directories in format of "/sys/block/dm-X".
 static int dm_name_filter(const dirent* de) {
   if (android::base::StartsWith(de->d_name, "dm-")) {
@@ -82,6 +78,7 @@
   // (or "vendor"), then dm-X is a dm-wrapped system/vendor partition.
   // Afterwards, update_verifier will read every block on the care_map_file of
   // "/dev/block/dm-X" to ensure the partition's integrity.
+  static constexpr auto DM_PATH_PREFIX = "/sys/block/";
   dirent** namelist;
   int n = scandir(DM_PATH_PREFIX, &namelist, dm_name_filter, alphasort);
   if (n == -1) {
@@ -93,6 +90,8 @@
     return false;
   }
 
+  static constexpr auto DM_PATH_SUFFIX = "/dm/name";
+  static constexpr auto DEV_PATH = "/dev/block/";
   std::string dm_block_device;
   while (n--) {
     std::string path = DM_PATH_PREFIX + std::string(namelist[n]->d_name) + DM_PATH_SUFFIX;
@@ -143,6 +142,7 @@
       return false;
     }
 
+    static constexpr int BLOCKSIZE = 4096;
     if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) {
       PLOG(ERROR) << "lseek to " << range_start << " failed";
       return false;
@@ -161,7 +161,7 @@
   return true;
 }
 
-static bool verify_image(const std::string& care_map_name) {
+bool verify_image(const std::string& care_map_name) {
     android::base::unique_fd care_map_fd(TEMP_FAILURE_RETRY(open(care_map_name.c_str(), O_RDONLY)));
     // If the device is flashed before the current boot, it may not have care_map.txt
     // in /data/ota_package. To allow the device to continue booting in this situation,
@@ -205,7 +205,7 @@
   while (true) pause();
 }
 
-int main(int argc, char** argv) {
+int update_verifier(int argc, char** argv) {
   for (int i = 1; i < argc; i++) {
     LOG(INFO) << "Started with arg " << i << ": " << argv[i];
   }
@@ -238,6 +238,7 @@
       return reboot_device();
     }
 
+    static constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt";
     if (!verify_image(CARE_MAP_FILE)) {
       LOG(ERROR) << "Failed to verify all blocks in care map file.";
       return reboot_device();
diff --git a/update_verifier/update_verifier_main.cpp b/update_verifier/update_verifier_main.cpp
new file mode 100644
index 0000000..46e8bbb
--- /dev/null
+++ b/update_verifier/update_verifier_main.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// See the comments in update_verifier.cpp.
+
+#include "update_verifier/update_verifier.h"
+
+int main(int argc, char** argv) {
+  return update_verifier(argc, argv);
+}