Move parts of roots.cpp to libfs_mgr
am: 0f339e27bb

Change-Id: I8e933cf6fb5361735d51a387102417c2885ee816
diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp
index e487865..3868ef2 100644
--- a/applypatch/freecache.cpp
+++ b/applypatch/freecache.cpp
@@ -141,8 +141,9 @@
     return -1;
   }
 
-  int64_t free_space = static_cast<int64_t>(sf.f_bsize) * sf.f_bavail;
-  if (sf.f_bsize == 0 || free_space / sf.f_bsize != sf.f_bavail) {
+  auto f_bsize = static_cast<int64_t>(sf.f_bsize);
+  auto free_space = sf.f_bsize * sf.f_bavail;
+  if (f_bsize == 0 || free_space / f_bsize != static_cast<int64_t>(sf.f_bavail)) {
     LOG(ERROR) << "Invalid block size or overflow (sf.f_bsize " << sf.f_bsize << ", sf.f_bavail "
                << sf.f_bavail << ")";
     return -1;
@@ -170,6 +171,13 @@
 
 bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
                             const std::function<int64_t(const std::string&)>& space_checker) {
+  // The requested size cannot exceed max int64_t.
+  if (static_cast<uint64_t>(bytes_needed) >
+      static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
+    LOG(ERROR) << "Invalid arg of bytes_needed: " << bytes_needed;
+    return false;
+  }
+
   struct stat st;
   if (stat(dirname.c_str(), &st) == -1) {
     PLOG(ERROR) << "Failed to stat " << dirname;
@@ -187,7 +195,7 @@
   }
   LOG(INFO) << free_now << " bytes free on " << dirname << " (" << bytes_needed << " needed)";
 
-  if (free_now >= bytes_needed) {
+  if (free_now >= static_cast<int64_t>(bytes_needed)) {
     return true;
   }
 
@@ -230,7 +238,7 @@
       return false;
     }
     LOG(INFO) << "Deleted " << file << "; now " << free_now << " bytes free";
-    if (free_now >= bytes_needed) {
+    if (free_now >= static_cast<int64_t>(bytes_needed)) {
       return true;
     }
   }
diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp
index e6be39a..f4c33e5 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -54,7 +54,7 @@
                                             const Value& patch, size_t patch_offset,
                                             const char* deflate_header, SinkFn sink) {
   size_t expected_target_length = static_cast<size_t>(Read8(deflate_header + 32));
-  CHECK_GT(expected_target_length, 0);
+  CHECK_GT(expected_target_length, static_cast<size_t>(0));
   int level = Read4(deflate_header + 40);
   int method = Read4(deflate_header + 44);
   int window_bits = Read4(deflate_header + 48);
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index aaeffdc..b933cbf 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -27,21 +27,22 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
-#include <fs_mgr.h>
+#include <fstab/fstab.h>
 
 static std::string get_misc_blk_device(std::string* err) {
-  std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
-                                                             fs_mgr_free_fstab);
-  if (!fstab) {
+  Fstab fstab;
+  if (!ReadDefaultFstab(&fstab)) {
     *err = "failed to read default fstab";
     return "";
   }
-  fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc");
-  if (record == nullptr) {
-    *err = "failed to find /misc partition";
-    return "";
+  for (const auto& entry : fstab) {
+    if (entry.mount_point == "/misc") {
+      return entry.blk_device;
+    }
   }
-  return record->blk_device;
+
+  *err = "failed to find /misc partition";
+  return "";
 }
 
 // In recovery mode, recovery can get started and try to access the misc
diff --git a/minui/events.cpp b/minui/events.cpp
index 2894c3b..d94e977 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -55,7 +55,7 @@
 }
 
 int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
-  g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
+  g_epoll_fd = epoll_create1(EPOLL_CLOEXEC);
   if (g_epoll_fd == -1) {
     return -1;
   }
diff --git a/recovery.cpp b/recovery.cpp
index 7e1fa43..de916c6 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -369,7 +369,14 @@
 }
 
 static bool ask_to_wipe_data(Device* device) {
-  return yes_no(device, "Wipe all user data?", "  THIS CAN NOT BE UNDONE!");
+  std::vector<std::string> headers{ "Wipe all user data?", "  THIS CAN NOT BE UNDONE!" };
+  std::vector<std::string> items{ " Cancel", " Factory data reset" };
+
+  size_t chosen_item = ui->ShowPromptWipeDataConfirmationMenu(
+      headers, items,
+      std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+  return (chosen_item == 1);
 }
 
 // Return true on success.
@@ -420,7 +427,6 @@
       return INSTALL_SUCCESS;  // Just reboot, no wipe; not a failure, user asked for it
     }
 
-    // TODO(xunchang) localize the confirmation texts also.
     if (ask_to_wipe_data(device)) {
       if (wipe_data(device)) {
         return INSTALL_SUCCESS;
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 765d2fe..5756054 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -844,9 +844,13 @@
   return true;
 }
 
-// TODO(xunchang) load localized text icons for the menu. (Init for screenRecoveryUI but
-// not wearRecoveryUI).
 bool ScreenRecoveryUI::LoadWipeDataMenuText() {
+  // Ignores the errors since the member variables will stay as nullptr.
+  cancel_wipe_data_text_ = LoadLocalizedBitmap("cancel_wipe_data_text");
+  factory_data_reset_text_ = LoadLocalizedBitmap("factory_data_reset_text");
+  try_again_text_ = LoadLocalizedBitmap("try_again_text");
+  wipe_data_confirmation_text_ = LoadLocalizedBitmap("wipe_data_confirmation_text");
+  wipe_data_menu_header_text_ = LoadLocalizedBitmap("wipe_data_menu_header_text");
   return true;
 }
 
@@ -1250,6 +1254,20 @@
   return ShowMenu(std::move(wipe_data_menu), true, key_handler);
 }
 
+size_t ScreenRecoveryUI::ShowPromptWipeDataConfirmationMenu(
+    const std::vector<std::string>& backup_headers, const std::vector<std::string>& backup_items,
+    const std::function<int(int, bool)>& key_handler) {
+  auto confirmation_menu =
+      CreateMenu(wipe_data_confirmation_text_.get(),
+                 { cancel_wipe_data_text_.get(), factory_data_reset_text_.get() }, backup_headers,
+                 backup_items, 0);
+  if (confirmation_menu == nullptr) {
+    return 0;
+  }
+
+  return ShowMenu(std::move(confirmation_menu), true, key_handler);
+}
+
 bool ScreenRecoveryUI::IsTextVisible() {
   std::lock_guard<std::mutex> lg(updateMutex);
   int visible = show_text;
diff --git a/screen_ui.h b/screen_ui.h
index ff245a2..acd44c8 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -240,6 +240,11 @@
                                 const std::vector<std::string>& backup_items,
                                 const std::function<int(int, bool)>& key_handler) override;
 
+  // Displays the localized wipe data confirmation menu.
+  size_t ShowPromptWipeDataConfirmationMenu(
+      const std::vector<std::string>& backup_headers, const std::vector<std::string>& backup_items,
+      const std::function<int(int, bool)>& key_handler) override;
+
  protected:
   static constexpr int kMenuIndent = 4;
 
@@ -334,9 +339,11 @@
   std::unique_ptr<GRSurface> no_command_text_;
 
   // Localized text images for the wipe data menu.
-  std::unique_ptr<GRSurface> wipe_data_menu_header_text_;
-  std::unique_ptr<GRSurface> try_again_text_;
+  std::unique_ptr<GRSurface> cancel_wipe_data_text_;
   std::unique_ptr<GRSurface> factory_data_reset_text_;
+  std::unique_ptr<GRSurface> try_again_text_;
+  std::unique_ptr<GRSurface> wipe_data_confirmation_text_;
+  std::unique_ptr<GRSurface> wipe_data_menu_header_text_;
 
   // current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by
   // current_frame_, or error_icon_.
diff --git a/stub_ui.h b/stub_ui.h
index ca137df..fb1d8c7 100644
--- a/stub_ui.h
+++ b/stub_ui.h
@@ -74,6 +74,13 @@
     return 0;
   }
 
+  size_t ShowPromptWipeDataConfirmationMenu(
+      const std::vector<std::string>& /* backup_headers */,
+      const std::vector<std::string>& /* backup_items */,
+      const std::function<int(int, bool)>& /* key_handle */) override {
+    return 0;
+  }
+
   void SetTitle(const std::vector<std::string>& /* lines */) override {}
 };
 
diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp
index 61a0925..647c7b2 100644
--- a/tests/unit/screen_ui_test.cpp
+++ b/tests/unit/screen_ui_test.cpp
@@ -308,11 +308,11 @@
   int KeyHandler(int key, bool visible) const;
 
  private:
-  FRIEND_TEST(ScreenRecoveryUITest, Init);
-  FRIEND_TEST(ScreenRecoveryUITest, RtlLocale);
-  FRIEND_TEST(ScreenRecoveryUITest, RtlLocaleWithSuffix);
-  FRIEND_TEST(ScreenRecoveryUITest, LoadAnimation);
-  FRIEND_TEST(ScreenRecoveryUITest, LoadAnimation_MissingAnimation);
+  FRIEND_TEST(DISABLED_ScreenRecoveryUITest, Init);
+  FRIEND_TEST(DISABLED_ScreenRecoveryUITest, RtlLocale);
+  FRIEND_TEST(DISABLED_ScreenRecoveryUITest, RtlLocaleWithSuffix);
+  FRIEND_TEST(DISABLED_ScreenRecoveryUITest, LoadAnimation);
+  FRIEND_TEST(DISABLED_ScreenRecoveryUITest, LoadAnimation_MissingAnimation);
 
   std::vector<KeyCode> key_buffer_;
   size_t key_buffer_index_;
@@ -340,7 +340,7 @@
   return static_cast<int>(key_buffer_[key_buffer_index_++]);
 }
 
-class ScreenRecoveryUITest : public ::testing::Test {
+class DISABLED_ScreenRecoveryUITest : public ::testing::Test {
  protected:
   const std::string kTestLocale = "en-US";
   const std::string kTestRtlLocale = "ar";
@@ -372,7 +372,7 @@
     }                                                                         \
   } while (false)
 
-TEST_F(ScreenRecoveryUITest, Init) {
+TEST_F(DISABLED_ScreenRecoveryUITest, Init) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -382,12 +382,12 @@
   ASSERT_FALSE(ui_->WasTextEverVisible());
 }
 
-TEST_F(ScreenRecoveryUITest, dtor_NotCallingInit) {
+TEST_F(DISABLED_ScreenRecoveryUITest, dtor_NotCallingInit) {
   ui_.reset();
   ASSERT_FALSE(ui_);
 }
 
-TEST_F(ScreenRecoveryUITest, ShowText) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowText) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -401,21 +401,21 @@
   ASSERT_TRUE(ui_->WasTextEverVisible());
 }
 
-TEST_F(ScreenRecoveryUITest, RtlLocale) {
+TEST_F(DISABLED_ScreenRecoveryUITest, RtlLocale) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestRtlLocale));
   ASSERT_TRUE(ui_->rtl_locale_);
 }
 
-TEST_F(ScreenRecoveryUITest, RtlLocaleWithSuffix) {
+TEST_F(DISABLED_ScreenRecoveryUITest, RtlLocaleWithSuffix) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix));
   ASSERT_TRUE(ui_->rtl_locale_);
 }
 
-TEST_F(ScreenRecoveryUITest, ShowMenu) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -443,7 +443,7 @@
                                         std::placeholders::_1, std::placeholders::_2)));
 }
 
-TEST_F(ScreenRecoveryUITest, ShowMenu_NotMenuOnly) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_NotMenuOnly) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -456,7 +456,7 @@
                                     std::placeholders::_1, std::placeholders::_2)));
 }
 
-TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_TimedOut) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -467,7 +467,7 @@
             ui_->ShowMenu(HEADERS, ITEMS, 3, true, nullptr));
 }
 
-TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -485,7 +485,7 @@
                                         std::placeholders::_1, std::placeholders::_2)));
 }
 
-TEST_F(ScreenRecoveryUITest, ShowMenuWithInterrupt) {
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenuWithInterrupt) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -517,7 +517,7 @@
                                     std::placeholders::_1, std::placeholders::_2)));
 }
 
-TEST_F(ScreenRecoveryUITest, LoadAnimation) {
+TEST_F(DISABLED_ScreenRecoveryUITest, LoadAnimation) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestLocale));
@@ -547,7 +547,7 @@
   }
 }
 
-TEST_F(ScreenRecoveryUITest, LoadAnimation_MissingAnimation) {
+TEST_F(DISABLED_ScreenRecoveryUITest, LoadAnimation_MissingAnimation) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestLocale));
diff --git a/ui.h b/ui.h
index 1e6186a..4924fec 100644
--- a/ui.h
+++ b/ui.h
@@ -169,6 +169,13 @@
                                         const std::vector<std::string>& backup_items,
                                         const std::function<int(int, bool)>& key_handler) = 0;
 
+  // Displays the localized wipe data confirmation menu with pre-generated images. Falls back to
+  // the text strings upon failures. The initial selection is the 0th item, which returns to the
+  // upper level menu.
+  virtual size_t ShowPromptWipeDataConfirmationMenu(
+      const std::vector<std::string>& backup_headers, const std::vector<std::string>& backup_items,
+      const std::function<int(int, bool)>& key_handler) = 0;
+
   // Resets the key interrupt status.
   void ResetKeyInterruptStatus() {
     key_interrupted_ = false;
diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp
index d1970e5..75595ac 100644
--- a/uncrypt/uncrypt.cpp
+++ b/uncrypt/uncrypt.cpp
@@ -89,7 +89,6 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
-#include <libgen.h>
 #include <linux/fs.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -103,6 +102,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include <android-base/file.h>
@@ -115,6 +115,7 @@
 #include <cutils/android_reboot.h>
 #include <cutils/sockets.h>
 #include <fs_mgr.h>
+#include <fstab/fstab.h>
 
 #include "otautil/error_code.h"
 
@@ -136,7 +137,7 @@
 static const std::string UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
 static const std::string UNCRYPT_SOCKET = "uncrypt";
 
-static struct fstab* fstab = nullptr;
+static Fstab fstab;
 
 static int write_at_offset(unsigned char* buffer, size_t size, int wfd, off64_t offset) {
     if (TEMP_FAILURE_RETRY(lseek64(wfd, offset, SEEK_SET)) == -1) {
@@ -162,49 +163,34 @@
     }
 }
 
-static struct fstab* read_fstab() {
-    fstab = fs_mgr_read_fstab_default();
-    if (!fstab) {
-        LOG(ERROR) << "failed to read default fstab";
-        return NULL;
+// Looks for a volume whose mount point is the prefix of path and returns its block device or an
+// empty string. Sets encryption flags accordingly.
+static std::string FindBlockDevice(const std::string& path, bool* encryptable, bool* encrypted,
+                                   bool* f2fs_fs) {
+  // Ensure f2fs_fs is set to false first.
+  *f2fs_fs = false;
+
+  for (const auto& entry : fstab) {
+    if (entry.mount_point.empty()) {
+      continue;
     }
-
-    return fstab;
-}
-
-static const char* find_block_device(const char* path, bool* encryptable,
-                                     bool* encrypted, bool* f2fs_fs) {
-    // Look for a volume whose mount point is the prefix of path and
-    // return its block device.  Set encrypted if it's currently
-    // encrypted.
-
-    // ensure f2fs_fs is set to false first.
-    *f2fs_fs = false;
-
-    for (int i = 0; i < fstab->num_entries; ++i) {
-        struct fstab_rec* v = &fstab->recs[i];
-        if (!v->mount_point) {
-            continue;
+    if (android::base::StartsWith(path, entry.mount_point + "/")) {
+      *encrypted = false;
+      *encryptable = false;
+      if (entry.is_encryptable() || entry.fs_mgr_flags.file_encryption) {
+        *encryptable = true;
+        if (android::base::GetProperty("ro.crypto.state", "") == "encrypted") {
+          *encrypted = true;
         }
-        int len = strlen(v->mount_point);
-        if (strncmp(path, v->mount_point, len) == 0 &&
-            (path[len] == '/' || path[len] == 0)) {
-            *encrypted = false;
-            *encryptable = false;
-            if (fs_mgr_is_encryptable(v) || fs_mgr_is_file_encrypted(v)) {
-                *encryptable = true;
-                if (android::base::GetProperty("ro.crypto.state", "") == "encrypted") {
-                    *encrypted = true;
-                }
-            }
-            if (strcmp(v->fs_type, "f2fs") == 0) {
-                *f2fs_fs = true;
-            }
-            return v->blk_device;
-        }
+      }
+      if (entry.fs_type == "f2fs") {
+        *f2fs_fs = true;
+      }
+      return entry.blk_device;
     }
+  }
 
-    return NULL;
+  return "";
 }
 
 static bool write_status_to_socket(int status, int socket) {
@@ -217,103 +203,102 @@
     return android::base::WriteFully(socket, &status_out, sizeof(int));
 }
 
-// Parse uncrypt_file to find the update package name.
-static bool find_uncrypt_package(const std::string& uncrypt_path_file, std::string* package_name) {
-    CHECK(package_name != nullptr);
-    std::string uncrypt_path;
-    if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) {
-        PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\"";
-        return false;
-    }
+// Parses the given path file to find the update package name.
+static bool FindUncryptPackage(const std::string& uncrypt_path_file, std::string* package_name) {
+  CHECK(package_name != nullptr);
+  std::string uncrypt_path;
+  if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) {
+    PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\"";
+    return false;
+  }
 
-    // Remove the trailing '\n' if present.
-    *package_name = android::base::Trim(uncrypt_path);
-    return true;
+  // Remove the trailing '\n' if present.
+  *package_name = android::base::Trim(uncrypt_path);
+  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);
+static int RetryFibmap(int fd, const std::string& 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;
     }
-    LOG(ERROR) << "fibmap of " << head_block << "always returns 0";
-    return kUncryptIoctlError;
+    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, bool f2fs_fs, int socket) {
-    std::string err;
-    if (!android::base::RemoveFileIfExists(map_file, &err)) {
-        LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err;
-        return kUncryptFileRemoveError;
+static int ProductBlockMap(const std::string& path, const std::string& map_file,
+                           const std::string& blk_dev, bool encrypted, bool f2fs_fs, int socket) {
+  std::string err;
+  if (!android::base::RemoveFileIfExists(map_file, &err)) {
+    LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err;
+    return kUncryptFileRemoveError;
+  }
+  std::string tmp_map_file = map_file + ".tmp";
+  android::base::unique_fd mapfd(open(tmp_map_file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR));
+  if (mapfd == -1) {
+    PLOG(ERROR) << "failed to open " << tmp_map_file;
+    return kUncryptFileOpenError;
+  }
+
+  // Make sure we can write to the socket.
+  if (!write_status_to_socket(0, socket)) {
+    LOG(ERROR) << "failed to write to socket " << socket;
+    return kUncryptSocketWriteError;
+  }
+
+  struct stat sb;
+  if (stat(path.c_str(), &sb) != 0) {
+    PLOG(ERROR) << "failed to stat " << path;
+    return kUncryptFileStatError;
+  }
+
+  LOG(INFO) << " block size: " << sb.st_blksize << " bytes";
+
+  int blocks = ((sb.st_size - 1) / sb.st_blksize) + 1;
+  LOG(INFO) << "  file size: " << sb.st_size << " bytes, " << blocks << " blocks";
+
+  std::vector<int> ranges;
+
+  std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n", blk_dev.c_str(),
+                                              static_cast<int64_t>(sb.st_size),
+                                              static_cast<int64_t>(sb.st_blksize));
+  if (!android::base::WriteStringToFd(s, mapfd)) {
+    PLOG(ERROR) << "failed to write " << tmp_map_file;
+    return kUncryptWriteError;
+  }
+
+  std::vector<std::vector<unsigned char>> buffers;
+  if (encrypted) {
+    buffers.resize(WINDOW_SIZE, std::vector<unsigned char>(sb.st_blksize));
+  }
+  int head_block = 0;
+  int head = 0, tail = 0;
+
+  android::base::unique_fd fd(open(path.c_str(), O_RDWR));
+  if (fd == -1) {
+    PLOG(ERROR) << "failed to open " << path << " for reading";
+    return kUncryptFileOpenError;
+  }
+
+  android::base::unique_fd wfd;
+  if (encrypted) {
+    wfd.reset(open(blk_dev.c_str(), O_WRONLY));
+    if (wfd == -1) {
+      PLOG(ERROR) << "failed to open " << blk_dev << " for writing";
+      return kUncryptBlockOpenError;
     }
-    std::string tmp_map_file = std::string(map_file) + ".tmp";
-    android::base::unique_fd mapfd(open(tmp_map_file.c_str(),
-                                        O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR));
-    if (mapfd == -1) {
-        PLOG(ERROR) << "failed to open " << tmp_map_file;
-        return kUncryptFileOpenError;
-    }
-
-    // Make sure we can write to the socket.
-    if (!write_status_to_socket(0, socket)) {
-        LOG(ERROR) << "failed to write to socket " << socket;
-        return kUncryptSocketWriteError;
-    }
-
-    struct stat sb;
-    if (stat(path, &sb) != 0) {
-        LOG(ERROR) << "failed to stat " << path;
-        return kUncryptFileStatError;
-    }
-
-    LOG(INFO) << " block size: " << sb.st_blksize << " bytes";
-
-    int blocks = ((sb.st_size-1) / sb.st_blksize) + 1;
-    LOG(INFO) << "  file size: " << sb.st_size << " bytes, " << blocks << " blocks";
-
-    std::vector<int> ranges;
-
-    std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n",
-                       blk_dev, static_cast<int64_t>(sb.st_size),
-                       static_cast<int64_t>(sb.st_blksize));
-    if (!android::base::WriteStringToFd(s, mapfd)) {
-        PLOG(ERROR) << "failed to write " << tmp_map_file;
-        return kUncryptWriteError;
-    }
-
-    std::vector<std::vector<unsigned char>> buffers;
-    if (encrypted) {
-        buffers.resize(WINDOW_SIZE, std::vector<unsigned char>(sb.st_blksize));
-    }
-    int head_block = 0;
-    int head = 0, tail = 0;
-
-    android::base::unique_fd fd(open(path, O_RDWR));
-    if (fd == -1) {
-        PLOG(ERROR) << "failed to open " << path << " for reading";
-        return kUncryptFileOpenError;
-    }
-
-    android::base::unique_fd wfd;
-    if (encrypted) {
-        wfd.reset(open(blk_dev, O_WRONLY));
-        if (wfd == -1) {
-            PLOG(ERROR) << "failed to open " << blk_dev << " for writing";
-            return kUncryptBlockOpenError;
-        }
-    }
+  }
 
 // F2FS-specific ioctl
 // It requires the below kernel commit merged in v4.16-rc1.
@@ -361,7 +346,7 @@
 
             if (block == 0) {
                 LOG(ERROR) << "failed to find block " << head_block << ", retrying";
-                int error = retry_fibmap(fd, path, &block, head_block);
+                int error = RetryFibmap(fd, path, &block, head_block);
                 if (error != kUncryptNoError) {
                     return error;
                 }
@@ -406,7 +391,7 @@
 
         if (block == 0) {
             LOG(ERROR) << "failed to find block " << head_block << ", retrying";
-            int error = retry_fibmap(fd, path, &block, head_block);
+            int error = RetryFibmap(fd, path, &block, head_block);
             if (error != kUncryptNoError) {
                 return error;
             }
@@ -456,13 +441,12 @@
         }
     }
 
-    if (rename(tmp_map_file.c_str(), map_file) == -1) {
-        PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file;
-        return kUncryptFileRenameError;
+    if (rename(tmp_map_file.c_str(), map_file.c_str()) == -1) {
+      PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file;
+      return kUncryptFileRenameError;
     }
     // Sync dir to make rename() result written to disk.
-    std::string file_name = map_file;
-    std::string dir_name = dirname(&file_name[0]);
+    std::string dir_name = android::base::Dirname(map_file);
     android::base::unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY));
     if (dfd == -1) {
         PLOG(ERROR) << "failed to open dir " << dir_name;
@@ -479,45 +463,42 @@
     return 0;
 }
 
-static int uncrypt(const char* input_path, const char* map_file, const int socket) {
-    LOG(INFO) << "update package is \"" << input_path << "\"";
+static int Uncrypt(const std::string& input_path, const std::string& map_file, int socket) {
+  LOG(INFO) << "update package is \"" << input_path << "\"";
 
-    // Turn the name of the file we're supposed to convert into an absolute path, so we can find
-    // what filesystem it's on.
-    char path[PATH_MAX+1];
-    if (realpath(input_path, path) == nullptr) {
-        PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path";
-        return kUncryptRealpathFindError;
-    }
+  // Turn the name of the file we're supposed to convert into an absolute path, so we can find what
+  // filesystem it's on.
+  std::string path;
+  if (!android::base::Realpath(input_path, &path)) {
+    PLOG(ERROR) << "Failed to convert \"" << input_path << "\" to absolute path";
+    return kUncryptRealpathFindError;
+  }
 
-    bool encryptable;
-    bool encrypted;
-    bool f2fs_fs;
-    const char* blk_dev = find_block_device(path, &encryptable, &encrypted, &f2fs_fs);
-    if (blk_dev == nullptr) {
-        LOG(ERROR) << "failed to find block device for " << path;
-        return kUncryptBlockDeviceFindError;
-    }
+  bool encryptable;
+  bool encrypted;
+  bool f2fs_fs;
+  const std::string blk_dev = FindBlockDevice(path, &encryptable, &encrypted, &f2fs_fs);
+  if (blk_dev.empty()) {
+    LOG(ERROR) << "Failed to find block device for " << path;
+    return kUncryptBlockDeviceFindError;
+  }
 
-    // If the filesystem it's on isn't encrypted, we only produce the
-    // block map, we don't rewrite the file contents (it would be
-    // pointless to do so).
-    LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no");
-    LOG(INFO) << "  encrypted: " << (encrypted ? "yes" : "no");
+  // If the filesystem it's on isn't encrypted, we only produce the block map, we don't rewrite the
+  // file contents (it would be pointless to do so).
+  LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no");
+  LOG(INFO) << "  encrypted: " << (encrypted ? "yes" : "no");
 
-    // Recovery supports installing packages from 3 paths: /cache,
-    // /data, and /sdcard.  (On a particular device, other locations
-    // may work, but those are three we actually expect.)
-    //
-    // On /data we want to convert the file to a block map so that we
-    // can read the package without mounting the partition.  On /cache
-    // and /sdcard we leave the file alone.
-    if (strncmp(path, "/data/", 6) == 0) {
-        LOG(INFO) << "writing block map " << map_file;
-        return produce_block_map(path, map_file, blk_dev, encrypted, f2fs_fs, socket);
-    }
+  // Recovery supports installing packages from 3 paths: /cache, /data, and /sdcard. (On a
+  // particular device, other locations may work, but those are three we actually expect.)
+  //
+  // On /data we want to convert the file to a block map so that we can read the package without
+  // mounting the partition. On /cache and /sdcard we leave the file alone.
+  if (android::base::StartsWith(path, "/data/")) {
+    LOG(INFO) << "writing block map " << map_file;
+    return ProductBlockMap(path, map_file, blk_dev, encrypted, f2fs_fs, socket);
+  }
 
-    return 0;
+  return 0;
 }
 
 static void log_uncrypt_error_code(UncryptErrorCode error_code) {
@@ -533,18 +514,18 @@
 
     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.
-            log_uncrypt_error_code(kUncryptPackageMissingError);
-            return false;
-        }
-        input_path = package.c_str();
+      if (!FindUncryptPackage(UNCRYPT_PATH_FILE, &package)) {
+        write_status_to_socket(-1, socket);
+        // Overwrite the error message.
+        log_uncrypt_error_code(kUncryptPackageMissingError);
+        return false;
+      }
+      input_path = package.c_str();
     }
     CHECK(map_file != nullptr);
 
     auto start = std::chrono::system_clock::now();
-    int status = uncrypt(input_path, map_file, socket);
+    int status = Uncrypt(input_path, map_file, socket);
     std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
     int count = static_cast<int>(duration.count());
 
@@ -654,7 +635,8 @@
         return 2;
     }
 
-    if ((fstab = read_fstab()) == nullptr) {
+    if (!ReadDefaultFstab(&fstab)) {
+        LOG(ERROR) << "failed to read default fstab";
         log_uncrypt_error_code(kUncryptFstabReadError);
         return 1;
     }
diff --git a/updater_sample/AndroidManifest.xml b/updater_sample/AndroidManifest.xml
index 18d8425..0a25116 100644
--- a/updater_sample/AndroidManifest.xml
+++ b/updater_sample/AndroidManifest.xml
@@ -33,7 +33,7 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <service android:name=".services.PrepareStreamingService"/>
+        <service android:name=".services.PrepareUpdateService"/>
     </application>
 
 </manifest>
diff --git a/updater_sample/README.md b/updater_sample/README.md
index f9c3fb8..bc66a9b 100644
--- a/updater_sample/README.md
+++ b/updater_sample/README.md
@@ -235,8 +235,8 @@
 5. Run a test file
    ```
    adb shell am instrument \
-     -w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner \
-     -c com.example.android.systemupdatersample.util.PayloadSpecsTest
+     -w -e class com.example.android.systemupdatersample.UpdateManagerTest#applyUpdate_appliesPayloadToUpdateEngine \
+     com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner
    ```
 
 
diff --git a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
index 12a8f3f..c02e608 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/UpdateManager.java
@@ -17,19 +17,18 @@
 package com.example.android.systemupdatersample;
 
 import android.content.Context;
+import android.os.Handler;
 import android.os.UpdateEngine;
 import android.os.UpdateEngineCallback;
 import android.util.Log;
 
-import com.example.android.systemupdatersample.services.PrepareStreamingService;
-import com.example.android.systemupdatersample.util.PayloadSpecs;
+import com.example.android.systemupdatersample.services.PrepareUpdateService;
 import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
 import com.example.android.systemupdatersample.util.UpdateEngineProperties;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.AtomicDouble;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -50,11 +49,10 @@
     private static final String TAG = "UpdateManager";
 
     /** HTTP Header: User-Agent; it will be sent to the server when streaming the payload. */
-    private static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
+    static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
             + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";
 
     private final UpdateEngine mUpdateEngine;
-    private final PayloadSpecs mPayloadSpecs;
 
     private AtomicInteger mUpdateEngineStatus =
             new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE);
@@ -84,9 +82,15 @@
     private final UpdateManager.UpdateEngineCallbackImpl
             mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl();
 
-    public UpdateManager(UpdateEngine updateEngine, PayloadSpecs payloadSpecs) {
+    private final Handler mHandler;
+
+    /**
+     * @param updateEngine UpdateEngine instance.
+     * @param handler      Handler for {@link PrepareUpdateService} intent service.
+     */
+    public UpdateManager(UpdateEngine updateEngine, Handler handler) {
         this.mUpdateEngine = updateEngine;
-        this.mPayloadSpecs = payloadSpecs;
+        this.mHandler = handler;
     }
 
     /**
@@ -293,45 +297,17 @@
             mManualSwitchSlotRequired.set(false);
         }
 
-        if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
-            applyAbNonStreamingUpdate(config);
-        } else {
-            applyAbStreamingUpdate(context, config);
-        }
-    }
-
-    private void applyAbNonStreamingUpdate(UpdateConfig config)
-            throws UpdaterState.InvalidTransitionException {
-        UpdateData.Builder builder = UpdateData.builder()
-                .setExtraProperties(prepareExtraProperties(config));
-
-        try {
-            builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile()));
-        } catch (IOException e) {
-            Log.e(TAG, "Error creating payload spec", e);
-            setUpdaterState(UpdaterState.ERROR);
-            return;
-        }
-        updateEngineApplyPayload(builder.build());
-    }
-
-    private void applyAbStreamingUpdate(Context context, UpdateConfig config) {
-        UpdateData.Builder builder = UpdateData.builder()
-                .setExtraProperties(prepareExtraProperties(config));
-
-        Log.d(TAG, "Starting PrepareStreamingService");
-        PrepareStreamingService.startService(context, config, (code, payloadSpec) -> {
-            if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) {
-                builder.setPayload(payloadSpec);
-                builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT);
-                config.getAbConfig()
-                        .getAuthorization()
-                        .ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s));
-                updateEngineApplyPayload(builder.build());
-            } else {
-                Log.e(TAG, "PrepareStreamingService failed, result code is " + code);
+        Log.d(TAG, "Starting PrepareUpdateService");
+        PrepareUpdateService.startService(context, config, mHandler, (code, payloadSpec) -> {
+            if (code != PrepareUpdateService.RESULT_CODE_SUCCESS) {
+                Log.e(TAG, "PrepareUpdateService failed, result code is " + code);
                 setUpdaterStateSilent(UpdaterState.ERROR);
+                return;
             }
+            updateEngineApplyPayload(UpdateData.builder()
+                    .setExtraProperties(prepareExtraProperties(config))
+                    .setPayload(payloadSpec)
+                    .build());
         });
     }
 
@@ -343,6 +319,12 @@
             // User will enable it manually by clicking "Switch Slot" button on the screen.
             extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT);
         }
+        if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_STREAMING) {
+            extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT);
+            config.getAbConfig()
+                    .getAuthorization()
+                    .ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s));
+        }
         return extraProperties;
     }
 
@@ -497,14 +479,14 @@
      * system/update_engine/binder_service_android.cc in
      * function BinderUpdateEngineAndroidService::bind).
      *
-     * @param status one of {@link UpdateEngine.UpdateStatusConstants}.
+     * @param status   one of {@link UpdateEngine.UpdateStatusConstants}.
      * @param progress a number from 0.0 to 1.0.
      */
     private void onStatusUpdate(int status, float progress) {
         Log.d(TAG, String.format(
-                        "onStatusUpdate invoked, status=%s, progress=%.2f",
-                        status,
-                        progress));
+                "onStatusUpdate invoked, status=%s, progress=%.2f",
+                status,
+                progress));
 
         int previousStatus = mUpdateEngineStatus.get();
         mUpdateEngineStatus.set(status);
@@ -555,7 +537,6 @@
     }
 
     /**
-     *
      * Contains update data - PayloadSpec and extra properties list.
      *
      * <p>{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}.
diff --git a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java
similarity index 69%
rename from updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java
rename to updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java
index 9314048..29eb13d 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/services/PrepareStreamingService.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/services/PrepareUpdateService.java
@@ -28,6 +28,7 @@
 import android.os.Handler;
 import android.os.RecoverySystem;
 import android.os.ResultReceiver;
+import android.os.UpdateEngine;
 import android.util.Log;
 
 import com.example.android.systemupdatersample.PayloadSpec;
@@ -41,7 +42,9 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.Optional;
 
 /**
@@ -49,10 +52,10 @@
  * without downloading the whole package. And it constructs {@link PayloadSpec}.
  * All this work required to install streaming A/B updates.
  *
- * PrepareStreamingService runs on it's own thread. It will notify activity
+ * PrepareUpdateService runs on it's own thread. It will notify activity
  * using interface {@link UpdateResultCallback} when update is ready to install.
  */
-public class PrepareStreamingService extends IntentService {
+public class PrepareUpdateService extends IntentService {
 
     /**
      * UpdateResultCallback result codes.
@@ -61,62 +64,63 @@
     public static final int RESULT_CODE_ERROR = 1;
 
     /**
-     * This interface is used to send results from {@link PrepareStreamingService} to
+     * Extra params that will be sent to IntentService.
+     */
+    public static final String EXTRA_PARAM_CONFIG = "config";
+    public static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
+
+    /**
+     * This interface is used to send results from {@link PrepareUpdateService} to
      * {@code MainActivity}.
      */
     public interface UpdateResultCallback {
-
         /**
          * Invoked when files are downloaded and payload spec is constructed.
          *
-         * @param resultCode result code, values are defined in {@link PrepareStreamingService}
+         * @param resultCode  result code, values are defined in {@link PrepareUpdateService}
          * @param payloadSpec prepared payload spec for streaming update
          */
         void onReceiveResult(int resultCode, PayloadSpec payloadSpec);
     }
 
     /**
-     * Starts PrepareStreamingService.
+     * Starts PrepareUpdateService.
      *
-     * @param context application context
-     * @param config update config
+     * @param context        application context
+     * @param config         update config
      * @param resultCallback callback that will be called when the update is ready to be installed
      */
     public static void startService(Context context,
             UpdateConfig config,
+            Handler handler,
             UpdateResultCallback resultCallback) {
-        Log.d(TAG, "Starting PrepareStreamingService");
-        ResultReceiver receiver = new CallbackResultReceiver(new Handler(), resultCallback);
-        Intent intent = new Intent(context, PrepareStreamingService.class);
+        Log.d(TAG, "Starting PrepareUpdateService");
+        ResultReceiver receiver = new CallbackResultReceiver(handler, resultCallback);
+        Intent intent = new Intent(context, PrepareUpdateService.class);
         intent.putExtra(EXTRA_PARAM_CONFIG, config);
         intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver);
         context.startService(intent);
     }
 
-    public PrepareStreamingService() {
+    public PrepareUpdateService() {
         super(TAG);
     }
 
-    private static final String TAG = "PrepareStreamingService";
-
-    /**
-     * Extra params that will be sent from Activity to IntentService.
-     */
-    private static final String EXTRA_PARAM_CONFIG = "config";
-    private static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
+    private static final String TAG = "PrepareUpdateService";
 
     /**
      * The files that should be downloaded before streaming.
      */
     private static final ImmutableSet<String> PRE_STREAMING_FILES_SET =
             ImmutableSet.of(
-                PackageFiles.CARE_MAP_FILE_NAME,
-                PackageFiles.COMPATIBILITY_ZIP_FILE_NAME,
-                PackageFiles.METADATA_FILE_NAME,
-                PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME
+                    PackageFiles.CARE_MAP_FILE_NAME,
+                    PackageFiles.COMPATIBILITY_ZIP_FILE_NAME,
+                    PackageFiles.METADATA_FILE_NAME,
+                    PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME
             );
 
     private final PayloadSpecs mPayloadSpecs = new PayloadSpecs();
+    private final UpdateEngine mUpdateEngine = new UpdateEngine();
 
     @Override
     protected void onHandleIntent(Intent intent) {
@@ -142,6 +146,17 @@
     private PayloadSpec execute(UpdateConfig config)
             throws IOException, PreparationFailedException {
 
+        if (config.getAbConfig().getVerifyPayloadMetadata()) {
+            Log.i(TAG, "Verifying payload metadata with UpdateEngine.");
+            if (!verifyPayloadMetadata(config)) {
+                throw new PreparationFailedException("Payload metadata is not compatible");
+            }
+        }
+
+        if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
+            return mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile());
+        }
+
         downloadPreStreamingFiles(config, OTA_PACKAGE_DIR);
 
         Optional<UpdateConfig.PackageFile> payloadBinary =
@@ -173,9 +188,52 @@
     }
 
     /**
+     * Downloads only payload_metadata.bin and verifies with
+     * {@link UpdateEngine#verifyPayloadMetadata}.
+     * Returns {@code true} if the payload is verified or the result is unknown because of
+     * exception from UpdateEngine.
+     * By downloading only small portion of the package, it allows to verify if UpdateEngine
+     * will install the update.
+     */
+    private boolean verifyPayloadMetadata(UpdateConfig config) {
+        Optional<UpdateConfig.PackageFile> metadataPackageFile =
+                Arrays.stream(config.getAbConfig().getPropertyFiles())
+                        .filter(p -> p.getFilename().equals(
+                                PackageFiles.PAYLOAD_METADATA_FILE_NAME))
+                        .findFirst();
+        if (!metadataPackageFile.isPresent()) {
+            Log.w(TAG, String.format("ab_config.property_files doesn't contain %s",
+                    PackageFiles.PAYLOAD_METADATA_FILE_NAME));
+            return true;
+        }
+        Path metadataPath = Paths.get(OTA_PACKAGE_DIR, PackageFiles.PAYLOAD_METADATA_FILE_NAME);
+        try {
+            Files.deleteIfExists(metadataPath);
+            FileDownloader d = new FileDownloader(
+                    config.getUrl(),
+                    metadataPackageFile.get().getOffset(),
+                    metadataPackageFile.get().getSize(),
+                    metadataPath.toFile());
+            d.download();
+        } catch (IOException e) {
+            Log.w(TAG, String.format("Downloading %s from %s failed",
+                    PackageFiles.PAYLOAD_METADATA_FILE_NAME,
+                    config.getUrl()), e);
+            return true;
+        }
+        try {
+            return mUpdateEngine.verifyPayloadMetadata(metadataPath.toAbsolutePath().toString());
+        } catch (Exception e) {
+            Log.w(TAG, "UpdateEngine#verifyPayloadMetadata failed", e);
+            return true;
+        }
+    }
+
+    /**
      * Downloads files defined in {@link UpdateConfig#getAbConfig()}
      * and exists in {@code PRE_STREAMING_FILES_SET}, and put them
      * in directory {@code dir}.
+     *
      * @throws IOException when can't download a file
      */
     private void downloadPreStreamingFiles(UpdateConfig config, String dir)
@@ -212,7 +270,7 @@
     }
 
     /**
-     * Used by {@link PrepareStreamingService} to pass {@link PayloadSpec}
+     * Used by {@link PrepareUpdateService} to pass {@link PayloadSpec}
      * to {@link UpdateResultCallback#onReceiveResult}.
      */
     private static class CallbackResultReceiver extends ResultReceiver {
diff --git a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java
index fc9fddd..6d1e4c3 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/ui/MainActivity.java
@@ -21,6 +21,7 @@
 import android.graphics.Color;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.UpdateEngine;
 import android.util.Log;
 import android.view.View;
@@ -34,7 +35,6 @@
 import com.example.android.systemupdatersample.UpdateConfig;
 import com.example.android.systemupdatersample.UpdateManager;
 import com.example.android.systemupdatersample.UpdaterState;
-import com.example.android.systemupdatersample.util.PayloadSpecs;
 import com.example.android.systemupdatersample.util.UpdateConfigs;
 import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
 import com.example.android.systemupdatersample.util.UpdateEngineStatuses;
@@ -67,7 +67,7 @@
     private List<UpdateConfig> mConfigs;
 
     private final UpdateManager mUpdateManager =
-            new UpdateManager(new UpdateEngine(), new PayloadSpecs());
+            new UpdateManager(new UpdateEngine(), new Handler());
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
diff --git a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java
index ddd0919..0f9083d 100644
--- a/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java
+++ b/updater_sample/src/com/example/android/systemupdatersample/util/FileDownloader.java
@@ -30,7 +30,7 @@
  * Downloads chunk of a file from given url using {@code offset} and {@code size},
  * and saves to a given location.
  *
- * In real-life application this helper class should download from HTTP Server,
+ * In a real-life application this helper class should download from HTTP Server,
  * but in this sample app it will only download from a local file.
  */
 public final class FileDownloader {
diff --git a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java
index e05ad29..5ad16d4 100644
--- a/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java
+++ b/updater_sample/tests/src/com/example/android/systemupdatersample/UpdateManagerTest.java
@@ -18,20 +18,25 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
 import android.os.UpdateEngine;
 import android.os.UpdateEngineCallback;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.example.android.systemupdatersample.services.PrepareUpdateService;
 import com.example.android.systemupdatersample.tests.R;
-import com.example.android.systemupdatersample.util.PayloadSpecs;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.CharStreams;
 
@@ -43,7 +48,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 
@@ -60,49 +64,39 @@
     @Mock
     private UpdateEngine mUpdateEngine;
     @Mock
-    private PayloadSpecs mPayloadSpecs;
+    private Context mMockContext;
     private UpdateManager mSubject;
-    private Context mContext;
-    private UpdateConfig mNonStreamingUpdate003;
+    private Context mTestContext;
+    private UpdateConfig mStreamingUpdate002;
 
     @Before
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getContext();
-        mSubject = new UpdateManager(mUpdateEngine, mPayloadSpecs);
-        mNonStreamingUpdate003 =
-                UpdateConfig.fromJson(readResource(R.raw.update_config_003_nonstream));
+        mTestContext = InstrumentationRegistry.getContext();
+        mSubject = new UpdateManager(mUpdateEngine, null);
+        mStreamingUpdate002 =
+                UpdateConfig.fromJson(readResource(R.raw.update_config_002_stream));
     }
 
     @Test
     public void applyUpdate_appliesPayloadToUpdateEngine() throws Exception {
-        PayloadSpec payload = buildMockPayloadSpec();
-        when(mPayloadSpecs.forNonStreaming(any(File.class))).thenReturn(payload);
-        when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> {
-            // When UpdateManager is bound to update_engine, it passes
-            // UpdateEngineCallback as a callback to update_engine.
-            UpdateEngineCallback callback = answer.getArgument(0);
-            callback.onStatusUpdate(
-                    UpdateEngine.UpdateStatusConstants.IDLE,
-                    /*engineProgress*/ 0.0f);
-            return null;
-        });
-
-        mSubject.bind();
-        mSubject.applyUpdate(null, mNonStreamingUpdate003);
+        mockContextStartServiceAnswer(buildMockPayloadSpec());
+        mSubject.applyUpdate(mMockContext, mStreamingUpdate002);
 
         verify(mUpdateEngine).applyPayload(
                 "file://blah",
                 120,
                 340,
-                new String[] {
-                        "SWITCH_SLOT_ON_REBOOT=0" // ab_config.force_switch_slot = false
+                new String[]{
+                        "SWITCH_SLOT_ON_REBOOT=0", // ab_config.force_switch_slot = false
+                        "USER_AGENT=" + UpdateManager.HTTP_USER_AGENT
                 });
     }
 
     @Test
-    public void stateIsRunningAndEngineStatusIsIdle_reApplyLastUpdate() throws Exception {
-        PayloadSpec payload = buildMockPayloadSpec();
-        when(mPayloadSpecs.forNonStreaming(any(File.class))).thenReturn(payload);
+    @UiThreadTest
+    public void stateIsRunningAndEngineStatusIsIdle_reApplyLastUpdate() throws Throwable {
+        mockContextStartServiceAnswer(buildMockPayloadSpec());
+        // UpdateEngine always returns IDLE status.
         when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> {
             // When UpdateManager is bound to update_engine, it passes
             // UpdateEngineCallback as a callback to update_engine.
@@ -114,21 +108,36 @@
         });
 
         mSubject.bind();
-        mSubject.applyUpdate(null, mNonStreamingUpdate003);
+        mSubject.applyUpdate(mMockContext, mStreamingUpdate002);
         mSubject.unbind();
         mSubject.bind(); // re-bind - now it should re-apply last update
 
         assertEquals(mSubject.getUpdaterState(), UpdaterState.RUNNING);
-        // it should be called 2 times
         verify(mUpdateEngine, times(2)).applyPayload(
                 "file://blah",
                 120,
                 340,
-                new String[] {
-                        "SWITCH_SLOT_ON_REBOOT=0" // ab_config.force_switch_slot = false
+                new String[]{
+                        "SWITCH_SLOT_ON_REBOOT=0", // ab_config.force_switch_slot = false
+                        "USER_AGENT=" + UpdateManager.HTTP_USER_AGENT
                 });
     }
 
+    private void mockContextStartServiceAnswer(PayloadSpec payloadSpec) {
+        doAnswer(args -> {
+            Intent intent = args.getArgument(0);
+            ResultReceiver resultReceiver = intent.getParcelableExtra(
+                    PrepareUpdateService.EXTRA_PARAM_RESULT_RECEIVER);
+            Bundle b = new Bundle();
+            b.putSerializable(
+                    /* PrepareUpdateService.CallbackResultReceiver.BUNDLE_PARAM_PAYLOAD_SPEC */
+                    "payload-spec",
+                    payloadSpec);
+            resultReceiver.send(PrepareUpdateService.RESULT_CODE_SUCCESS, b);
+            return null;
+        }).when(mMockContext).startService(any(Intent.class));
+    }
+
     private PayloadSpec buildMockPayloadSpec() {
         PayloadSpec payload = mock(PayloadSpec.class);
         when(payload.getUrl()).thenReturn("file://blah");
@@ -140,7 +149,7 @@
 
     private String readResource(int id) throws IOException {
         return CharStreams.toString(new InputStreamReader(
-                mContext.getResources().openRawResource(id)));
+                mTestContext.getResources().openRawResource(id)));
     }
 
 }