Merge "Delete the dumpkey host tool"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 1084291..28aa06f 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -7,5 +7,4 @@
 
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
-                  -fw updater_sample/
-
+                  --file_whitelist tools/ updater_sample/
diff --git a/install.cpp b/install.cpp
index 42d2641..680937d 100644
--- a/install.cpp
+++ b/install.cpp
@@ -32,9 +32,7 @@
 #include <condition_variable>
 #include <functional>
 #include <limits>
-#include <map>
 #include <mutex>
-#include <string>
 #include <thread>
 #include <vector>
 
@@ -47,7 +45,6 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <vintf/VintfObjectRecovery.h>
-#include <ziparchive/zip_archive.h>
 
 #include "common.h"
 #include "otautil/error_code.h"
@@ -67,18 +64,7 @@
 
 static std::condition_variable finish_log_temperature;
 
-// This function parses and returns the build.version.incremental
-static std::string parse_build_number(const std::string& str) {
-    size_t pos = str.find('=');
-    if (pos != std::string::npos) {
-        return android::base::Trim(str.substr(pos+1));
-    }
-
-    LOG(ERROR) << "Failed to parse build number in " << str;
-    return "";
-}
-
-bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) {
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata) {
   CHECK(metadata != nullptr);
 
   static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
@@ -90,101 +76,79 @@
   }
 
   uint32_t length = entry.uncompressed_length;
-  metadata->resize(length, '\0');
-  int32_t err = ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&(*metadata)[0]), length);
+  std::string metadata_string(length, '\0');
+  int32_t err =
+      ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&metadata_string[0]), length);
   if (err != 0) {
     LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err);
     return false;
   }
+
+  for (const std::string& line : android::base::Split(metadata_string, "\n")) {
+    size_t eq = line.find('=');
+    if (eq != std::string::npos) {
+      metadata->emplace(android::base::Trim(line.substr(0, eq)),
+                        android::base::Trim(line.substr(eq + 1)));
+    }
+  }
+
   return true;
 }
 
-// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
-static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>* log_buffer) {
-  std::string metadata;
-  if (!read_metadata_from_package(zip, &metadata)) {
-    return;
-  }
-  // Examples of the pre-build and post-build strings in metadata:
-  //   pre-build-incremental=2943039
-  //   post-build-incremental=2951741
-  std::vector<std::string> lines = android::base::Split(metadata, "\n");
-  for (const std::string& line : lines) {
-    std::string str = android::base::Trim(line);
-    if (android::base::StartsWith(str, "pre-build-incremental")) {
-      std::string source_build = parse_build_number(str);
-      if (!source_build.empty()) {
-        log_buffer->push_back("source_build: " + source_build);
-      }
-    } else if (android::base::StartsWith(str, "post-build-incremental")) {
-      std::string target_build = parse_build_number(str);
-      if (!target_build.empty()) {
-        log_buffer->push_back("target_build: " + target_build);
-      }
-    }
+// Gets the value for the given key in |metadata|. Returns an emtpy string if the key isn't
+// present.
+static std::string get_value(const std::map<std::string, std::string>& metadata,
+                             const std::string& key) {
+  const auto& it = metadata.find(key);
+  return (it == metadata.end()) ? "" : it->second;
+}
+
+static std::string OtaTypeToString(OtaType type) {
+  switch (type) {
+    case OtaType::AB:
+      return "AB";
+    case OtaType::BLOCK:
+      return "BLOCK";
+    case OtaType::BRICK:
+      return "BRICK";
   }
 }
 
-// Parses the metadata of the OTA package in |zip| and checks whether we are allowed to accept this
-// A/B package. Downgrading is not allowed unless explicitly enabled in the package and only for
+// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
+static void ReadSourceTargetBuild(const std::map<std::string, std::string>& metadata,
+                                  std::vector<std::string>* log_buffer) {
+  // Examples of the pre-build and post-build strings in metadata:
+  //   pre-build-incremental=2943039
+  //   post-build-incremental=2951741
+  auto source_build = get_value(metadata, "pre-build-incremental");
+  if (!source_build.empty()) {
+    log_buffer->push_back("source_build: " + source_build);
+  }
+
+  auto target_build = get_value(metadata, "post-build-incremental");
+  if (!target_build.empty()) {
+    log_buffer->push_back("target_build: " + target_build);
+  }
+}
+
+// Checks the build version, fingerprint and timestamp in the metadata of the A/B package.
+// Downgrading is not allowed unless explicitly enabled in the package and only for
 // incremental packages.
-static int check_newer_ab_build(ZipArchiveHandle zip) {
-  std::string metadata_str;
-  if (!read_metadata_from_package(zip, &metadata_str)) {
-    return INSTALL_CORRUPT;
-  }
-  std::map<std::string, std::string> metadata;
-  for (const std::string& line : android::base::Split(metadata_str, "\n")) {
-    size_t eq = line.find('=');
-    if (eq != std::string::npos) {
-      metadata[line.substr(0, eq)] = line.substr(eq + 1);
-    }
-  }
-
-  std::string value = android::base::GetProperty("ro.product.device", "");
-  const std::string& pkg_device = metadata["pre-device"];
-  if (pkg_device != value || pkg_device.empty()) {
-    LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value;
-    return INSTALL_ERROR;
-  }
-
-  // We allow the package to not have any serialno; and we also allow it to carry multiple serial
-  // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
-  // verification if the device's serialno doesn't match any of these carried numbers.
-  value = android::base::GetProperty("ro.serialno", "");
-  const std::string& pkg_serial_no = metadata["serialno"];
-  if (!pkg_serial_no.empty()) {
-    bool match = false;
-    for (const std::string& number : android::base::Split(pkg_serial_no, "|")) {
-      if (value == android::base::Trim(number)) {
-        match = true;
-        break;
-      }
-    }
-    if (!match) {
-      LOG(ERROR) << "Package is for serial " << pkg_serial_no;
-      return INSTALL_ERROR;
-    }
-  }
-
-  if (metadata["ota-type"] != "AB") {
-    LOG(ERROR) << "Package is not A/B";
-    return INSTALL_ERROR;
-  }
-
+static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
   // Incremental updates should match the current build.
-  value = android::base::GetProperty("ro.build.version.incremental", "");
-  const std::string& pkg_pre_build = metadata["pre-build-incremental"];
-  if (!pkg_pre_build.empty() && pkg_pre_build != value) {
-    LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value;
+  auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", "");
+  auto pkg_pre_build = get_value(metadata, "pre-build-incremental");
+  if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) {
+    LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected "
+               << device_pre_build;
     return INSTALL_ERROR;
   }
 
-  value = android::base::GetProperty("ro.build.fingerprint", "");
-  const std::string& pkg_pre_build_fingerprint = metadata["pre-build"];
-  if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != value) {
+  auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", "");
+  auto pkg_pre_build_fingerprint = get_value(metadata, "pre-build");
+  if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) {
     LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected "
-               << value;
+               << device_fingerprint;
     return INSTALL_ERROR;
   }
 
@@ -194,10 +158,11 @@
   int64_t pkg_post_timestamp = 0;
   // We allow to full update to the same version we are running, in case there
   // is a problem with the current copy of that version.
-  if (metadata["post-timestamp"].empty() ||
-      !android::base::ParseInt(metadata["post-timestamp"].c_str(), &pkg_post_timestamp) ||
+  auto pkg_post_timestamp_string = get_value(metadata, "post-timestamp");
+  if (pkg_post_timestamp_string.empty() ||
+      !android::base::ParseInt(pkg_post_timestamp_string, &pkg_post_timestamp) ||
       pkg_post_timestamp < build_timestamp) {
-    if (metadata["ota-downgrade"] != "yes") {
+    if (get_value(metadata, "ota-downgrade") != "yes") {
       LOG(ERROR) << "Update package is older than the current build, expected a build "
                     "newer than timestamp "
                  << build_timestamp << " but package has timestamp " << pkg_post_timestamp
@@ -213,13 +178,55 @@
   return 0;
 }
 
+int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
+  auto package_ota_type = get_value(metadata, "ota-type");
+  auto expected_ota_type = OtaTypeToString(ota_type);
+  if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) {
+    LOG(INFO) << "Skip package metadata check for ota type " << expected_ota_type;
+    return 0;
+  }
+
+  if (package_ota_type != expected_ota_type) {
+    LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual "
+               << package_ota_type;
+    return INSTALL_ERROR;
+  }
+
+  auto device = android::base::GetProperty("ro.product.device", "");
+  auto pkg_device = get_value(metadata, "pre-device");
+  if (pkg_device != device || pkg_device.empty()) {
+    LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device;
+    return INSTALL_ERROR;
+  }
+
+  // We allow the package to not have any serialno; and we also allow it to carry multiple serial
+  // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
+  // verification if the device's serialno doesn't match any of these carried numbers.
+  auto pkg_serial_no = get_value(metadata, "serialno");
+  if (!pkg_serial_no.empty()) {
+    auto device_serial_no = android::base::GetProperty("ro.serialno", "");
+    bool serial_number_match = false;
+    for (const auto& number : android::base::Split(pkg_serial_no, "|")) {
+      if (device_serial_no == android::base::Trim(number)) {
+        serial_number_match = true;
+      }
+    }
+    if (!serial_number_match) {
+      LOG(ERROR) << "Package is for serial " << pkg_serial_no;
+      return INSTALL_ERROR;
+    }
+  }
+
+  if (ota_type == OtaType::AB) {
+    return CheckAbSpecificMetadata(metadata);
+  }
+
+  return 0;
+}
+
 int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, 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.
@@ -311,20 +318,33 @@
 static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
                              std::vector<std::string>* log_buffer, int retry_count,
                              int* max_temperature) {
-  read_source_target_build(zip, log_buffer);
+  std::map<std::string, std::string> metadata;
+  if (!ReadMetadataFromPackage(zip, &metadata)) {
+    LOG(ERROR) << "Failed to parse metadata in the zip file";
+    return INSTALL_CORRUPT;
+  }
+
+  bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
+  // Verifies against the metadata in the package first.
+  if (int check_status = is_ab ? CheckPackageMetadata(metadata, OtaType::AB) : 0;
+      check_status != 0) {
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
+    return check_status;
+  }
+
+  ReadSourceTargetBuild(metadata, log_buffer);
 
   int pipefd[2];
   pipe(pipefd);
-
-  bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
   std::vector<std::string> args;
-  int ret = is_ab ? SetUpAbUpdateCommands(package, zip, pipefd[1], &args)
-                  : SetUpNonAbUpdateCommands(package, zip, retry_count, pipefd[1], &args);
-  if (ret) {
+  if (int update_status =
+          is_ab ? SetUpAbUpdateCommands(package, zip, pipefd[1], &args)
+                : SetUpNonAbUpdateCommands(package, zip, retry_count, pipefd[1], &args);
+      update_status != 0) {
     close(pipefd[0]);
     close(pipefd[1]);
     log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
-    return ret;
+    return update_status;
   }
 
   // When executing the update binary contained in the package, the
@@ -477,9 +497,16 @@
   if (retry_update) {
     return INSTALL_RETRY;
   }
-  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-    LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")";
+  if (WIFEXITED(status)) {
+    if (WEXITSTATUS(status) != EXIT_SUCCESS) {
+      LOG(ERROR) << "Error in " << package << " (status " << WEXITSTATUS(status) << ")";
+      return INSTALL_ERROR;
+    }
+  } else if (WIFSIGNALED(status)) {
+    LOG(ERROR) << "Error in " << package << " (killed by signal " << WTERMSIG(status) << ")";
     return INSTALL_ERROR;
+  } else {
+    LOG(FATAL) << "Invalid status code " << status;
   }
 
   return INSTALL_SUCCESS;
diff --git a/install.h b/install.h
index 1d3d0cd..c6db1d1 100644
--- a/install.h
+++ b/install.h
@@ -19,6 +19,7 @@
 
 #include <stddef.h>
 
+#include <map>
 #include <string>
 
 #include <ziparchive/zip_archive.h>
@@ -33,6 +34,12 @@
   INSTALL_KEY_INTERRUPTED
 };
 
+enum class OtaType {
+  AB,
+  BLOCK,
+  BRICK,
+};
+
 // Installs the given update package. If INSTALL_SUCCESS is returned and *wipe_cache is true on
 // exit, caller should wipe the cache partition.
 int install_package(const std::string& package, bool* wipe_cache, bool needs_mount,
@@ -42,12 +49,17 @@
 // otherwise return false.
 bool verify_package(const unsigned char* package_data, size_t package_size);
 
-// Read meta data file of the package, write its content in the string pointed by meta_data.
-// Return true if succeed, otherwise return false.
-bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata);
+// Reads meta data file of the package; parses each line in the format "key=value"; and writes the
+// result to |metadata|. Return true if succeed, otherwise return false.
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata);
 
 // Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
 // entry doesn't exist.
 bool verify_package_compatibility(ZipArchiveHandle package_zip);
 
+// Checks if the the metadata in the OTA package has expected values. Returns 0 on success.
+// Mandatory checks: ota-type, pre-device and serial number(if presents)
+// AB OTA specific checks: pre-build version, fingerprint, timestamp.
+int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
+
 #endif  // RECOVERY_INSTALL_H_
diff --git a/minadbd/Android.bp b/minadbd/Android.bp
index 370232b..7e33261 100644
--- a/minadbd/Android.bp
+++ b/minadbd/Android.bp
@@ -15,6 +15,7 @@
 cc_defaults {
     name: "minadbd_defaults",
 
+    cpp_std: "gnu++17",
     cflags: [
         "-DADB_HOST=0",
         "-Wall",
diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp
index 6fc193f..9eea497 100644
--- a/minui/graphics_adf.cpp
+++ b/minui/graphics_adf.cpp
@@ -20,6 +20,7 @@
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/mman.h>
 #include <unistd.h>
 
@@ -28,51 +29,60 @@
 
 #include "minui/minui.h"
 
-MinuiBackendAdf::MinuiBackendAdf()
-    : intf_fd(-1), dev(), current_surface(0), n_surfaces(0), surfaces() {}
+GRSurfaceAdf::~GRSurfaceAdf() {
+  if (mmapped_buffer_) {
+    munmap(mmapped_buffer_, pitch * height);
+  }
+  if (fence_fd != -1) {
+    close(fence_fd);
+  }
+  if (fd != -1) {
+    close(fd);
+  }
+}
 
-int MinuiBackendAdf::SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf) {
-  *surf = {};
-  surf->fence_fd = -1;
-  surf->fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format,
-                                               &surf->offset, &surf->pitch);
-  if (surf->fd < 0) {
-    return surf->fd;
+std::unique_ptr<GRSurfaceAdf> GRSurfaceAdf::Create(int intf_fd, const drm_mode_modeinfo* mode,
+                                                   __u32 format, int* err) {
+  __u32 offset;
+  __u32 pitch;
+  auto fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format,
+                                              &offset, &pitch);
+
+  if (fd < 0) {
+    *err = fd;
+    return nullptr;
   }
 
-  surf->width = mode->hdisplay;
-  surf->height = mode->vdisplay;
-  surf->row_bytes = surf->pitch;
-  surf->pixel_bytes = (format == DRM_FORMAT_RGB565) ? 2 : 4;
+  std::unique_ptr<GRSurfaceAdf> surf = std::unique_ptr<GRSurfaceAdf>(
+      new GRSurfaceAdf(mode->hdisplay, mode->vdisplay, pitch, (format == DRM_FORMAT_RGB565 ? 2 : 4),
+                       offset, pitch, fd));
 
   auto mmapped =
       mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset);
   if (mmapped == MAP_FAILED) {
-    int saved_errno = errno;
-    close(surf->fd);
-    return -saved_errno;
+    *err = -errno;
+    return nullptr;
   }
   surf->mmapped_buffer_ = static_cast<uint8_t*>(mmapped);
-  return 0;
+  return surf;
 }
 
+MinuiBackendAdf::MinuiBackendAdf() : intf_fd(-1), dev(), current_surface(0), n_surfaces(0) {}
+
 int MinuiBackendAdf::InterfaceInit() {
   adf_interface_data intf_data;
-  int err = adf_get_interface_data(intf_fd, &intf_data);
-  if (err < 0) return err;
+  if (int err = adf_get_interface_data(intf_fd, &intf_data); err < 0) return err;
 
-  int ret = 0;
-  err = SurfaceInit(&intf_data.current_mode, &surfaces[0]);
-  if (err < 0) {
-    fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err));
-    ret = err;
+  int result = 0;
+  surfaces[0] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result);
+  if (!surfaces[0]) {
+    fprintf(stderr, "Failed to allocate surface 0: %s\n", strerror(-result));
     goto done;
   }
 
-  err = SurfaceInit(&intf_data.current_mode, &surfaces[1]);
-  if (err < 0) {
-    fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err));
-    surfaces[1] = {};
+  surfaces[1] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result);
+  if (!surfaces[1]) {
+    fprintf(stderr, "Failed to allocate surface 1: %s\n", strerror(-result));
     n_surfaces = 1;
   } else {
     n_surfaces = 2;
@@ -80,7 +90,7 @@
 
 done:
   adf_free_interface_data(&intf_data);
-  return ret;
+  return result;
 }
 
 int MinuiBackendAdf::DeviceInit(adf_device* dev) {
@@ -153,12 +163,12 @@
 }
 
 void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) {
-  static constexpr unsigned int warningTimeout = 3000;
+  static constexpr unsigned int kWarningTimeout = 3000;
 
   if (surf == nullptr) return;
 
   if (surf->fence_fd >= 0) {
-    int err = sync_wait(surf->fence_fd, warningTimeout);
+    int err = sync_wait(surf->fence_fd, kWarningTimeout);
     if (err < 0) {
       perror("adf sync fence wait error\n");
     }
@@ -169,33 +179,22 @@
 }
 
 GRSurface* MinuiBackendAdf::Flip() {
-  GRSurfaceAdf* surf = &surfaces[current_surface];
+  const auto& surf = surfaces[current_surface];
 
   int fence_fd = adf_interface_simple_post(intf_fd, eng_id, surf->width, surf->height, format,
                                            surf->fd, surf->offset, surf->pitch, -1);
   if (fence_fd >= 0) surf->fence_fd = fence_fd;
 
   current_surface = (current_surface + 1) % n_surfaces;
-  Sync(&surfaces[current_surface]);
-  return &surfaces[current_surface];
+  Sync(surfaces[current_surface].get());
+  return surfaces[current_surface].get();
 }
 
 void MinuiBackendAdf::Blank(bool blank) {
   adf_interface_blank(intf_fd, blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON);
 }
 
-void MinuiBackendAdf::SurfaceDestroy(GRSurfaceAdf* surf) {
-  if (surf->mmapped_buffer_) {
-    munmap(surf->mmapped_buffer_, surf->pitch * surf->height);
-  }
-  close(surf->fence_fd);
-  close(surf->fd);
-}
-
 MinuiBackendAdf::~MinuiBackendAdf() {
   adf_device_close(&dev);
-  for (unsigned int i = 0; i < n_surfaces; i++) {
-    SurfaceDestroy(&surfaces[i]);
-  }
   if (intf_fd >= 0) close(intf_fd);
 }
diff --git a/minui/graphics_adf.h b/minui/graphics_adf.h
index 099d329..bf98428 100644
--- a/minui/graphics_adf.h
+++ b/minui/graphics_adf.h
@@ -17,6 +17,9 @@
 #pragma once
 
 #include <stdint.h>
+#include <sys/types.h>
+
+#include <memory>
 
 #include <adf/adf.h>
 
@@ -25,6 +28,11 @@
 
 class GRSurfaceAdf : public GRSurface {
  public:
+  ~GRSurfaceAdf() override;
+
+  static std::unique_ptr<GRSurfaceAdf> Create(int intf_fd, const drm_mode_modeinfo* mode,
+                                              __u32 format, int* err);
+
   uint8_t* data() override {
     return mmapped_buffer_;
   }
@@ -32,34 +40,36 @@
  private:
   friend class MinuiBackendAdf;
 
-  int fence_fd;
-  int fd;
-  __u32 offset;
-  __u32 pitch;
+  GRSurfaceAdf(int width, int height, int row_bytes, int pixel_bytes, __u32 offset, __u32 pitch,
+               int fd)
+      : GRSurface(width, height, row_bytes, pixel_bytes), offset(offset), pitch(pitch), fd(fd) {}
 
+  const __u32 offset;
+  const __u32 pitch;
+
+  int fd;
+  int fence_fd{ -1 };
   uint8_t* mmapped_buffer_{ nullptr };
 };
 
 class MinuiBackendAdf : public MinuiBackend {
  public:
+  MinuiBackendAdf();
+  ~MinuiBackendAdf() override;
   GRSurface* Init() override;
   GRSurface* Flip() override;
   void Blank(bool) override;
-  ~MinuiBackendAdf() override;
-  MinuiBackendAdf();
 
  private:
-  int SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf);
   int InterfaceInit();
   int DeviceInit(adf_device* dev);
-  void SurfaceDestroy(GRSurfaceAdf* surf);
   void Sync(GRSurfaceAdf* surf);
 
   int intf_fd;
   adf_id_t eng_id;
   __u32 format;
   adf_device dev;
-  unsigned int current_surface;
-  unsigned int n_surfaces;
-  GRSurfaceAdf surfaces[2];
+  size_t current_surface;
+  size_t n_surfaces;
+  std::unique_ptr<GRSurfaceAdf> surfaces[2];
 };
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
index f81fd9d..765e262 100644
--- a/minui/graphics_drm.cpp
+++ b/minui/graphics_drm.cpp
@@ -102,15 +102,15 @@
     return nullptr;
   }
 
-  std::unique_ptr<GRSurfaceDrm> surface = std::make_unique<GRSurfaceDrm>(drm_fd);
-  surface->handle = create_dumb.handle;
+  // Cannot use std::make_unique to access non-public ctor.
+  auto surface = std::unique_ptr<GRSurfaceDrm>(new GRSurfaceDrm(
+      width, height, create_dumb.pitch, create_dumb.bpp / 8, drm_fd, create_dumb.handle));
 
   uint32_t handles[4], pitches[4], offsets[4];
 
   handles[0] = surface->handle;
   pitches[0] = create_dumb.pitch;
   offsets[0] = 0;
-
   if (drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &surface->fb_id, 0) !=
       0) {
     perror("Failed to drmModeAddFB2");
@@ -124,10 +124,6 @@
     return nullptr;
   }
 
-  surface->height = height;
-  surface->width = width;
-  surface->row_bytes = create_dumb.pitch;
-  surface->pixel_bytes = create_dumb.bpp / 8;
   auto mmapped = mmap(nullptr, surface->height * surface->row_bytes, PROT_READ | PROT_WRITE,
                       MAP_SHARED, drm_fd, map_dumb.offset);
   if (mmapped == MAP_FAILED) {
diff --git a/minui/graphics_drm.h b/minui/graphics_drm.h
index 02db89f..6ba46e6 100644
--- a/minui/graphics_drm.h
+++ b/minui/graphics_drm.h
@@ -20,7 +20,6 @@
 
 #include <memory>
 
-#include <android-base/macros.h>
 #include <xf86drmMode.h>
 
 #include "graphics.h"
@@ -28,7 +27,6 @@
 
 class GRSurfaceDrm : public GRSurface {
  public:
-  explicit GRSurfaceDrm(int drm_fd) : drm_fd_(drm_fd) {}
   ~GRSurfaceDrm() override;
 
   // Creates a GRSurfaceDrm instance.
@@ -41,13 +39,14 @@
  private:
   friend class MinuiBackendDrm;
 
+  GRSurfaceDrm(int width, int height, int row_bytes, int pixel_bytes, int drm_fd, uint32_t handle)
+      : GRSurface(width, height, row_bytes, pixel_bytes), drm_fd_(drm_fd), handle(handle) {}
+
   const int drm_fd_;
 
   uint32_t fb_id{ 0 };
   uint32_t handle{ 0 };
   uint8_t* mmapped_buffer_{ nullptr };
-
-  DISALLOW_COPY_AND_ASSIGN(GRSurfaceDrm);
 };
 
 class MinuiBackendDrm : public MinuiBackend {
diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp
index f958d62..93e4420 100644
--- a/minui/graphics_fbdev.cpp
+++ b/minui/graphics_fbdev.cpp
@@ -26,21 +26,29 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <memory>
+
+#include <android-base/unique_fd.h>
+
 #include "minui/minui.h"
 
-MinuiBackendFbdev::MinuiBackendFbdev() : gr_draw(nullptr), fb_fd(-1) {}
+std::unique_ptr<GRSurfaceFbdev> GRSurfaceFbdev::Create(int width, int height, int row_bytes,
+                                                       int pixel_bytes) {
+  // Cannot use std::make_unique to access non-public ctor.
+  return std::unique_ptr<GRSurfaceFbdev>(new GRSurfaceFbdev(width, height, row_bytes, pixel_bytes));
+}
 
 void MinuiBackendFbdev::Blank(bool blank) {
   int ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
   if (ret < 0) perror("ioctl(): blank");
 }
 
-void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) {
+void MinuiBackendFbdev::SetDisplayedFramebuffer(size_t n) {
   if (n > 1 || !double_buffered) return;
 
-  vi.yres_virtual = gr_framebuffer[0].height * 2;
-  vi.yoffset = n * gr_framebuffer[0].height;
-  vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
+  vi.yres_virtual = gr_framebuffer[0]->height * 2;
+  vi.yoffset = n * gr_framebuffer[0]->height;
+  vi.bits_per_pixel = gr_framebuffer[0]->pixel_bytes * 8;
   if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
     perror("active fb swap failed");
   }
@@ -48,7 +56,7 @@
 }
 
 GRSurface* MinuiBackendFbdev::Init() {
-  int fd = open("/dev/graphics/fb0", O_RDWR);
+  android::base::unique_fd fd(open("/dev/graphics/fb0", O_RDWR));
   if (fd == -1) {
     perror("cannot open fb0");
     return nullptr;
@@ -57,13 +65,11 @@
   fb_fix_screeninfo fi;
   if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
     perror("failed to get fb0 info");
-    close(fd);
     return nullptr;
   }
 
   if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
     perror("failed to get fb0 info");
-    close(fd);
     return nullptr;
   }
 
@@ -90,46 +96,41 @@
   void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
   if (bits == MAP_FAILED) {
     perror("failed to mmap framebuffer");
-    close(fd);
     return nullptr;
   }
 
   memset(bits, 0, fi.smem_len);
 
-  gr_framebuffer[0].width = vi.xres;
-  gr_framebuffer[0].height = vi.yres;
-  gr_framebuffer[0].row_bytes = fi.line_length;
-  gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
-  gr_framebuffer[0].buffer_ = static_cast<uint8_t*>(bits);
-  memset(gr_framebuffer[0].buffer_, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
+  gr_framebuffer[0] =
+      GRSurfaceFbdev::Create(vi.xres, vi.yres, fi.line_length, vi.bits_per_pixel / 8);
+  gr_framebuffer[0]->buffer_ = static_cast<uint8_t*>(bits);
+  memset(gr_framebuffer[0]->buffer_, 0, gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes);
+
+  gr_framebuffer[1] =
+      GRSurfaceFbdev::Create(gr_framebuffer[0]->width, gr_framebuffer[0]->height,
+                             gr_framebuffer[0]->row_bytes, gr_framebuffer[0]->pixel_bytes);
 
   /* check if we can use double buffering */
   if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
     double_buffered = true;
 
-    gr_framebuffer[1] = gr_framebuffer[0];
-    gr_framebuffer[1].buffer_ =
-        gr_framebuffer[0].buffer_ + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
-
-    gr_draw = gr_framebuffer + 1;
-
+    gr_framebuffer[1]->buffer_ =
+        gr_framebuffer[0]->buffer_ + gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes;
   } else {
     double_buffered = false;
 
-    // Without double-buffering, we allocate RAM for a buffer to
-    // draw in, and then "flipping" the buffer consists of a
-    // memcpy from the buffer we allocated to the framebuffer.
-
-    gr_draw = new GRSurfaceFbdev;
-    *gr_draw = gr_framebuffer[0];
-    gr_draw->buffer_ = new uint8_t[gr_draw->height * gr_draw->row_bytes];
+    // Without double-buffering, we allocate RAM for a buffer to draw in, and then "flipping" the
+    // buffer consists of a memcpy from the buffer we allocated to the framebuffer.
+    memory_buffer.resize(gr_framebuffer[1]->height * gr_framebuffer[1]->row_bytes);
+    gr_framebuffer[1]->buffer_ = memory_buffer.data();
   }
 
+  gr_draw = gr_framebuffer[1].get();
   memset(gr_draw->buffer_, 0, gr_draw->height * gr_draw->row_bytes);
-  fb_fd = fd;
+  fb_fd = std::move(fd);
   SetDisplayedFramebuffer(0);
 
-  printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
+  printf("framebuffer: %d (%d x %d)\n", fb_fd.get(), gr_draw->width, gr_draw->height);
 
   Blank(true);
   Blank(false);
@@ -139,25 +140,13 @@
 
 GRSurface* MinuiBackendFbdev::Flip() {
   if (double_buffered) {
-    // Change gr_draw to point to the buffer currently displayed,
-    // then flip the driver so we're displaying the other buffer
-    // instead.
-    gr_draw = gr_framebuffer + displayed_buffer;
+    // Change gr_draw to point to the buffer currently displayed, then flip the driver so we're
+    // displaying the other buffer instead.
+    gr_draw = gr_framebuffer[displayed_buffer].get();
     SetDisplayedFramebuffer(1 - displayed_buffer);
   } else {
     // Copy from the in-memory surface to the framebuffer.
-    memcpy(gr_framebuffer[0].buffer_, gr_draw->buffer_, gr_draw->height * gr_draw->row_bytes);
+    memcpy(gr_framebuffer[0]->buffer_, gr_draw->buffer_, gr_draw->height * gr_draw->row_bytes);
   }
   return gr_draw;
 }
-
-MinuiBackendFbdev::~MinuiBackendFbdev() {
-  close(fb_fd);
-  fb_fd = -1;
-
-  if (!double_buffered && gr_draw) {
-    delete[] gr_draw->buffer_;
-    delete gr_draw;
-  }
-  gr_draw = nullptr;
-}
diff --git a/minui/graphics_fbdev.h b/minui/graphics_fbdev.h
index be813dc..016ab88 100644
--- a/minui/graphics_fbdev.h
+++ b/minui/graphics_fbdev.h
@@ -19,37 +19,52 @@
 #include <linux/fb.h>
 #include <stdint.h>
 
+#include <memory>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
 #include "graphics.h"
 #include "minui/minui.h"
 
 class GRSurfaceFbdev : public GRSurface {
  public:
+  // Creates and returns a GRSurfaceFbdev instance, or nullptr on error.
+  static std::unique_ptr<GRSurfaceFbdev> Create(int width, int height, int row_bytes,
+                                                int pixel_bytes);
+
   uint8_t* data() override {
     return buffer_;
   }
 
+ protected:
+  using GRSurface::GRSurface;
+
  private:
   friend class MinuiBackendFbdev;
 
   // Points to the start of the buffer: either the mmap'd framebuffer or one allocated in-memory.
-  uint8_t* buffer_;
+  uint8_t* buffer_{ nullptr };
 };
 
 class MinuiBackendFbdev : public MinuiBackend {
  public:
+  MinuiBackendFbdev() = default;
+  ~MinuiBackendFbdev() override = default;
+
   GRSurface* Init() override;
   GRSurface* Flip() override;
   void Blank(bool) override;
-  ~MinuiBackendFbdev() override;
-  MinuiBackendFbdev();
 
  private:
-  void SetDisplayedFramebuffer(unsigned n);
+  void SetDisplayedFramebuffer(size_t n);
 
-  GRSurfaceFbdev gr_framebuffer[2];
+  std::unique_ptr<GRSurfaceFbdev> gr_framebuffer[2];
+  // Points to the current surface (i.e. one of the two gr_framebuffer's).
+  GRSurfaceFbdev* gr_draw{ nullptr };
   bool double_buffered;
-  GRSurfaceFbdev* gr_draw;
-  int displayed_buffer;
+  std::vector<uint8_t> memory_buffer;
+  size_t displayed_buffer{ 0 };
   fb_var_screeninfo vi;
-  int fb_fd;
+  android::base::unique_fd fb_fd;
 };
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index e9bd1c4..3231248 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <stdint.h>
+#include <stdlib.h>
 #include <sys/types.h>
 
 #include <functional>
@@ -24,22 +25,27 @@
 #include <string>
 #include <vector>
 
+#include <android-base/macros.h>
+
 //
 // Graphics.
 //
 
 class GRSurface {
  public:
-  GRSurface() = default;
-  virtual ~GRSurface();
+  virtual ~GRSurface() = default;
 
-  // Creates and returns a GRSurface instance for the given data_size. The starting address of the
-  // surface data is aligned to SURFACE_DATA_ALIGNMENT. Returns the created GRSurface instance (in
-  // std::unique_ptr), or nullptr on error.
-  static std::unique_ptr<GRSurface> Create(size_t data_size);
+  // Creates and returns a GRSurface instance that's sufficient for storing an image of the given
+  // size. The starting address of the surface data is aligned to SURFACE_DATA_ALIGNMENT. Returns
+  // the created GRSurface instance (in std::unique_ptr), or nullptr on error.
+  static std::unique_ptr<GRSurface> Create(int width, int height, int row_bytes, int pixel_bytes,
+                                           size_t data_size);
+
+  // Clones the current GRSurface instance (i.e. an image).
+  std::unique_ptr<GRSurface> Clone() const;
 
   virtual uint8_t* data() {
-    return data_;
+    return data_.get();
   }
 
   const uint8_t* data() const {
@@ -51,8 +57,22 @@
   int row_bytes;
   int pixel_bytes;
 
+ protected:
+  GRSurface(int width, int height, int row_bytes, int pixel_bytes)
+      : width(width), height(height), row_bytes(row_bytes), pixel_bytes(pixel_bytes) {}
+
  private:
-  uint8_t* data_{ nullptr };
+  // The deleter for data_, whose data is allocated via aligned_alloc(3).
+  struct DataDeleter {
+    void operator()(uint8_t* data) {
+      free(data);
+    }
+  };
+
+  std::unique_ptr<uint8_t, DataDeleter> data_;
+  size_t data_size_;
+
+  DISALLOW_COPY_AND_ASSIGN(GRSurface);
 };
 
 struct GRFont {
diff --git a/minui/resources.cpp b/minui/resources.cpp
index c01c186..c7af190 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -39,21 +39,24 @@
 
 static std::string g_resource_dir{ "/res/images" };
 
-std::unique_ptr<GRSurface> GRSurface::Create(size_t data_size) {
+std::unique_ptr<GRSurface> GRSurface::Create(int width, int height, int row_bytes, int pixel_bytes,
+                                             size_t data_size) {
   static constexpr size_t kSurfaceDataAlignment = 8;
-  std::unique_ptr<GRSurface> result = std::make_unique<GRSurface>();
-  size_t aligned_size =
+  // Cannot use std::make_unique to access non-public ctor.
+  auto result = std::unique_ptr<GRSurface>(new GRSurface(width, height, row_bytes, pixel_bytes));
+  result->data_size_ =
       (data_size + kSurfaceDataAlignment - 1) / kSurfaceDataAlignment * kSurfaceDataAlignment;
-  result->data_ = static_cast<uint8_t*>(aligned_alloc(kSurfaceDataAlignment, aligned_size));
-  if (result->data_ == nullptr) return nullptr;
+  result->data_.reset(
+      static_cast<uint8_t*>(aligned_alloc(kSurfaceDataAlignment, result->data_size_)));
+  if (!result->data_) return nullptr;
   return result;
 }
 
-GRSurface::~GRSurface() {
-  if (data_ != nullptr) {
-    free(data_);
-    data_ = nullptr;
-  }
+std::unique_ptr<GRSurface> GRSurface::Clone() const {
+  auto result = GRSurface::Create(width, height, row_bytes, pixel_bytes, data_size_);
+  if (!result) return nullptr;
+  memcpy(result->data(), data(), data_size_);
+  return result;
 }
 
 PngHandler::PngHandler(const std::string& name) {
@@ -68,7 +71,7 @@
     return;
   }
 
-  unsigned char header[8];
+  uint8_t header[8];
   size_t bytesRead = fread(header, 1, sizeof(header), png_fp_.get());
   if (bytesRead != sizeof(header)) {
     error_code_ = -2;
@@ -131,70 +134,49 @@
   }
 }
 
-// "display" surfaces are transformed into the framebuffer's required
-// pixel format (currently only RGBX is supported) at load time, so
-// gr_blit() can be nothing more than a memcpy() for each row.  The
-// next two functions are the only ones that know anything about the
-// framebuffer pixel format; they need to be modified if the
-// framebuffer format changes (but nothing else should).
+// "display" surfaces are transformed into the framebuffer's required pixel format (currently only
+// RGBX is supported) at load time, so gr_blit() can be nothing more than a memcpy() for each row.
 
-// Allocates and returns a GRSurface* sufficient for storing an image of the indicated size in the
-// framebuffer pixel format.
-static std::unique_ptr<GRSurface> init_display_surface(png_uint_32 width, png_uint_32 height) {
-  std::unique_ptr<GRSurface> surface = GRSurface::Create(width * height * 4);
-  if (!surface) return nullptr;
-
-  surface->width = width;
-  surface->height = height;
-  surface->row_bytes = width * 4;
-  surface->pixel_bytes = 4;
-
-  return surface;
-}
-
-// Copy 'input_row' to 'output_row', transforming it to the
-// framebuffer pixel format.  The input format depends on the value of
-// 'channels':
+// Copies 'input_row' to 'output_row', transforming it to the framebuffer pixel format. The input
+// format depends on the value of 'channels':
 //
 //   1 - input is 8-bit grayscale
 //   3 - input is 24-bit RGB
 //   4 - input is 32-bit RGBA/RGBX
 //
 // 'width' is the number of pixels in the row.
-static void transform_rgb_to_draw(unsigned char* input_row,
-                                  unsigned char* output_row,
-                                  int channels, int width) {
-    int x;
-    unsigned char* ip = input_row;
-    unsigned char* op = output_row;
+static void TransformRgbToDraw(const uint8_t* input_row, uint8_t* output_row, int channels,
+                               int width) {
+  const uint8_t* ip = input_row;
+  uint8_t* op = output_row;
 
-    switch (channels) {
-        case 1:
-            // expand gray level to RGBX
-            for (x = 0; x < width; ++x) {
-                *op++ = *ip;
-                *op++ = *ip;
-                *op++ = *ip;
-                *op++ = 0xff;
-                ip++;
-            }
-            break;
+  switch (channels) {
+    case 1:
+      // expand gray level to RGBX
+      for (int x = 0; x < width; ++x) {
+        *op++ = *ip;
+        *op++ = *ip;
+        *op++ = *ip;
+        *op++ = 0xff;
+        ip++;
+      }
+      break;
 
-        case 3:
-            // expand RGBA to RGBX
-            for (x = 0; x < width; ++x) {
-                *op++ = *ip++;
-                *op++ = *ip++;
-                *op++ = *ip++;
-                *op++ = 0xff;
-            }
-            break;
+    case 3:
+      // expand RGBA to RGBX
+      for (int x = 0; x < width; ++x) {
+        *op++ = *ip++;
+        *op++ = *ip++;
+        *op++ = *ip++;
+        *op++ = 0xff;
+      }
+      break;
 
-        case 4:
-            // copy RGBA to RGBX
-            memcpy(output_row, input_row, width*4);
-            break;
-    }
+    case 4:
+      // copy RGBA to RGBX
+      memcpy(output_row, input_row, width * 4);
+      break;
+  }
 }
 
 int res_create_display_surface(const char* name, GRSurface** pSurface) {
@@ -207,7 +189,7 @@
   png_uint_32 width = png_handler.width();
   png_uint_32 height = png_handler.height();
 
-  std::unique_ptr<GRSurface> surface = init_display_surface(width, height);
+  auto surface = GRSurface::Create(width, height, width * 4, 4, width * height * 4);
   if (!surface) {
     return -8;
   }
@@ -218,10 +200,10 @@
   }
 
   for (png_uint_32 y = 0; y < height; ++y) {
-    std::vector<unsigned char> p_row(width * 4);
+    std::vector<uint8_t> p_row(width * 4);
     png_read_row(png_ptr, p_row.data(), nullptr);
-    transform_rgb_to_draw(p_row.data(), surface->data() + y * surface->row_bytes,
-                          png_handler.channels(), width);
+    TransformRgbToDraw(p_row.data(), surface->data() + y * surface->row_bytes,
+                       png_handler.channels(), width);
   }
 
   *pSurface = surface.release();
@@ -277,7 +259,9 @@
     goto exit;
   }
   for (int i = 0; i < *frames; ++i) {
-    auto created_surface = init_display_surface(width, height / *frames);
+    auto height_per_frame = height / *frames;
+    auto created_surface =
+        GRSurface::Create(width, height_per_frame, width * 4, 4, width * height_per_frame);
     if (!created_surface) {
       result = -8;
       goto exit;
@@ -290,11 +274,11 @@
   }
 
   for (png_uint_32 y = 0; y < height; ++y) {
-    std::vector<unsigned char> p_row(width * 4);
+    std::vector<uint8_t> p_row(width * 4);
     png_read_row(png_ptr, p_row.data(), nullptr);
     int frame = y % *frames;
-    unsigned char* out_row = surface[frame]->data() + (y / *frames) * surface[frame]->row_bytes;
-    transform_rgb_to_draw(p_row.data(), out_row, png_handler.channels(), width);
+    uint8_t* out_row = surface[frame]->data() + (y / *frames) * surface[frame]->row_bytes;
+    TransformRgbToDraw(p_row.data(), out_row, png_handler.channels(), width);
   }
 
   *pSurface = surface;
@@ -325,14 +309,10 @@
   png_uint_32 width = png_handler.width();
   png_uint_32 height = png_handler.height();
 
-  std::unique_ptr<GRSurface> surface = GRSurface::Create(width * height);
+  auto surface = GRSurface::Create(width, height, width, 1, width * height);
   if (!surface) {
     return -8;
   }
-  surface->width = width;
-  surface->height = height;
-  surface->row_bytes = width;
-  surface->pixel_bytes = 1;
 
   PixelFormat pixel_format = gr_pixel_format();
   if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) {
@@ -340,7 +320,7 @@
   }
 
   for (png_uint_32 y = 0; y < height; ++y) {
-    unsigned char* p_row = surface->data() + y * surface->row_bytes;
+    uint8_t* p_row = surface->data() + y * surface->row_bytes;
     png_read_row(png_ptr, p_row, nullptr);
   }
 
@@ -389,7 +369,7 @@
   }
 
   std::vector<std::string> result;
-  std::vector<unsigned char> row(png_handler.width());
+  std::vector<uint8_t> row(png_handler.width());
   for (png_uint_32 y = 0; y < png_handler.height(); ++y) {
     png_read_row(png_handler.png_ptr(), row.data(), nullptr);
     int h = (row[3] << 8) | row[2];
@@ -425,7 +405,7 @@
   png_uint_32 height = png_handler.height();
 
   for (png_uint_32 y = 0; y < height; ++y) {
-    std::vector<unsigned char> row(width);
+    std::vector<uint8_t> row(width);
     png_read_row(png_ptr, row.data(), nullptr);
     int w = (row[1] << 8) | row[0];
     int h = (row[3] << 8) | row[2];
@@ -435,14 +415,10 @@
     if (y + 1 + h >= height || matches_locale(loc, locale)) {
       printf("  %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
 
-      std::unique_ptr<GRSurface> surface = GRSurface::Create(w * h);
+      auto surface = GRSurface::Create(w, h, w, 1, w * h);
       if (!surface) {
         return -8;
       }
-      surface->width = w;
-      surface->height = h;
-      surface->row_bytes = w;
-      surface->pixel_bytes = 1;
 
       for (int i = 0; i < h; ++i, ++y) {
         png_read_row(png_ptr, row.data(), nullptr);
diff --git a/recovery.cpp b/recovery.cpp
index d7bc6fd..e17526a 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -520,34 +520,17 @@
         LOG(ERROR) << "Can't open wipe package : " << ErrorCodeString(err);
         return false;
     }
-    std::string metadata;
-    if (!read_metadata_from_package(zip, &metadata)) {
-        CloseArchive(zip);
-        return false;
+
+    std::map<std::string, std::string> metadata;
+    if (!ReadMetadataFromPackage(zip, &metadata)) {
+      LOG(ERROR) << "Failed to parse metadata in the zip file";
+      return false;
     }
+
+    int result = CheckPackageMetadata(metadata, OtaType::BRICK);
     CloseArchive(zip);
 
-    // Check metadata
-    std::vector<std::string> lines = android::base::Split(metadata, "\n");
-    bool ota_type_matched = false;
-    bool device_type_matched = false;
-    bool has_serial_number = false;
-    bool serial_number_matched = false;
-    for (const auto& line : lines) {
-        if (line == "ota-type=BRICK") {
-            ota_type_matched = true;
-        } else if (android::base::StartsWith(line, "pre-device=")) {
-            std::string device_type = line.substr(strlen("pre-device="));
-            std::string real_device_type = android::base::GetProperty("ro.build.product", "");
-            device_type_matched = (device_type == real_device_type);
-        } else if (android::base::StartsWith(line, "serialno=")) {
-            std::string serial_no = line.substr(strlen("serialno="));
-            std::string real_serial_no = android::base::GetProperty("ro.serialno", "");
-            has_serial_number = true;
-            serial_number_matched = (serial_no == real_serial_no);
-        }
-    }
-    return ota_type_matched && device_type_matched && (!has_serial_number || serial_number_matched);
+    return result == 0;
 }
 
 // Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE.
diff --git a/recovery_main.cpp b/recovery_main.cpp
index 7835094..6f50802 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -365,7 +365,7 @@
         if (option == "locale") {
           locale = optarg;
         } else if (option == "fastboot" &&
-                   android::base::GetBoolProperty("ro.boot.logical_partitions", false)) {
+                   android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
           fastboot = true;
         }
         break;
@@ -426,7 +426,7 @@
     device->RemoveMenuItemForAction(Device::WIPE_CACHE);
   }
 
-  if (!android::base::GetBoolProperty("ro.boot.logical_partitions", false)) {
+  if (!android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
     device->RemoveMenuItemForAction(Device::ENTER_FASTBOOT);
   }
 
@@ -478,8 +478,13 @@
         break;
 
       case Device::ENTER_FASTBOOT:
-        LOG(INFO) << "Entering fastboot";
-        fastboot = true;
+        if (logical_partitions_mapped()) {
+          ui->Print("Partitions may be mounted - rebooting to enter fastboot.");
+          android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot");
+        } else {
+          LOG(INFO) << "Entering fastboot";
+          fastboot = true;
+        }
         break;
 
       case Device::ENTER_RECOVERY:
diff --git a/roots.cpp b/roots.cpp
index c29771a..dc34784 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -38,10 +38,12 @@
 #include <cryptfs.h>
 #include <ext4_utils/wipe.h>
 #include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
 
 #include "otautil/mounts.h"
 
 static struct fstab* fstab = nullptr;
+static bool did_map_logical_partitions = false;
 
 extern struct selabel_handle* sehandle;
 
@@ -117,6 +119,25 @@
     mount_point = v->mount_point;
   }
 
+  // If we can't acquire the block device for a logical partition, it likely
+  // was never created. In that case we try to create it.
+  if (fs_mgr_is_logical(v) && !fs_mgr_update_logical_partition(v)) {
+    if (did_map_logical_partitions) {
+      LOG(ERROR) << "Failed to find block device for partition";
+      return -1;
+    }
+    std::string super_name = fs_mgr_get_super_partition_name();
+    if (!android::fs_mgr::CreateLogicalPartitions(super_name)) {
+      LOG(ERROR) << "Failed to create logical partitions";
+      return -1;
+    }
+    did_map_logical_partitions = true;
+    if (!fs_mgr_update_logical_partition(v)) {
+      LOG(ERROR) << "Failed to find block device for partition";
+      return -1;
+    }
+  }
+
   const MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point);
   if (mv != nullptr) {
     // Volume is already mounted.
@@ -387,3 +408,7 @@
   }
   return 0;
 }
+
+bool logical_partitions_mapped() {
+  return did_map_logical_partitions;
+}
diff --git a/roots.h b/roots.h
index 46bb77e..702af8d 100644
--- a/roots.h
+++ b/roots.h
@@ -53,4 +53,6 @@
 // mounted (/tmp and /cache) are mounted.  Returns 0 on success.
 int setup_install_mounts();
 
+bool logical_partitions_mapped();
+
 #endif  // RECOVERY_ROOTS_H_
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 2db27d6..ed71888 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -197,11 +197,16 @@
   return offset;
 }
 
-GraphicMenu::GraphicMenu(GRSurface* graphic_headers, const std::vector<GRSurface*>& graphic_items,
+GraphicMenu::GraphicMenu(const GRSurface* graphic_headers,
+                         const std::vector<const GRSurface*>& graphic_items,
                          size_t initial_selection, const DrawInterface& draw_funcs)
-    : Menu(initial_selection, draw_funcs),
-      graphic_headers_(graphic_headers),
-      graphic_items_(graphic_items) {}
+    : Menu(initial_selection, draw_funcs) {
+  graphic_headers_ = graphic_headers->Clone();
+  graphic_items_.reserve(graphic_items.size());
+  for (const auto& item : graphic_items) {
+    graphic_items_.emplace_back(item->Clone());
+  }
+}
 
 int GraphicMenu::Select(int sel) {
   CHECK_LE(graphic_items_.size(), static_cast<size_t>(std::numeric_limits<int>::max()));
@@ -221,7 +226,7 @@
 
 int GraphicMenu::DrawHeader(int x, int y) const {
   draw_funcs_.SetColor(UIElement::HEADER);
-  draw_funcs_.DrawTextIcon(x, y, graphic_headers_);
+  draw_funcs_.DrawTextIcon(x, y, graphic_headers_.get());
   return graphic_headers_->height;
 }
 
@@ -242,7 +247,7 @@
       // Bold white text for the selected item.
       draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
     }
-    draw_funcs_.DrawTextIcon(x, y + offset, item);
+    draw_funcs_.DrawTextIcon(x, y + offset, item.get());
     offset += item->height;
 
     draw_funcs_.SetColor(UIElement::MENU);
@@ -251,8 +256,8 @@
   return offset;
 }
 
-bool GraphicMenu::Validate(size_t max_width, size_t max_height, GRSurface* graphic_headers,
-                           const std::vector<GRSurface*>& graphic_items) {
+bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers,
+                           const std::vector<const GRSurface*>& graphic_items) {
   int offset = 0;
   if (!ValidateGraphicSurface(max_width, max_height, offset, graphic_headers)) {
     return false;
@@ -307,7 +312,9 @@
       animation_fps_(
           android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)),
       density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
-      currentIcon(NONE),
+      current_icon_(NONE),
+      current_frame_(0),
+      intro_done_(false),
       progressBarType(EMPTY),
       progressScopeStart(0),
       progressScopeSize(0),
@@ -322,10 +329,6 @@
       show_text_ever(false),
       scrollable_menu_(scrollable_menu),
       file_viewer_text_(nullptr),
-      intro_frames(0),
-      loop_frames(0),
-      current_frame(0),
-      intro_done(false),
       stage(-1),
       max_stage(-1),
       locale_(""),
@@ -340,23 +343,23 @@
   gr_exit();
 }
 
-GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
-  if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
-    return intro_done ? loopFrames[current_frame] : introFrames[current_frame];
+const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const {
+  if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) {
+    return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get();
   }
-  return error_icon;
+  return error_icon_.get();
 }
 
-GRSurface* ScreenRecoveryUI::GetCurrentText() const {
-  switch (currentIcon) {
+const GRSurface* ScreenRecoveryUI::GetCurrentText() const {
+  switch (current_icon_) {
     case ERASING:
-      return erasing_text;
+      return erasing_text_.get();
     case ERROR:
-      return error_text;
+      return error_text_.get();
     case INSTALLING_UPDATE:
-      return installing_text;
+      return installing_text_.get();
     case NO_COMMAND:
-      return no_command_text;
+      return no_command_text_.get();
     case NONE:
       abort();
   }
@@ -391,20 +394,21 @@
 };
 
 int ScreenRecoveryUI::GetAnimationBaseline() const {
-  return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]);
+  return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) -
+         gr_get_height(loop_frames_[0].get());
 }
 
 int ScreenRecoveryUI::GetTextBaseline() const {
   return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) -
-         gr_get_height(installing_text);
+         gr_get_height(installing_text_.get());
 }
 
 int ScreenRecoveryUI::GetProgressBaseline() const {
-  int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) +
-                     gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) +
-                     gr_get_height(progressBarFill);
+  int elements_sum = gr_get_height(loop_frames_[0].get()) + PixelsFromDp(kLayouts[layout_][ICON]) +
+                     gr_get_height(installing_text_.get()) + PixelsFromDp(kLayouts[layout_][TEXT]) +
+                     gr_get_height(progress_bar_fill_.get());
   int bottom_gap = (ScreenHeight() - elements_sum) / 2;
-  return ScreenHeight() - bottom_gap - gr_get_height(progressBarFill);
+  return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get());
 }
 
 // Clear the screen and draw the currently selected background icon (if any).
@@ -413,20 +417,20 @@
   pagesIdentical = false;
   gr_color(0, 0, 0, 255);
   gr_clear();
-  if (currentIcon != NONE) {
+  if (current_icon_ != NONE) {
     if (max_stage != -1) {
-      int stage_height = gr_get_height(stageMarkerEmpty);
-      int stage_width = gr_get_width(stageMarkerEmpty);
-      int x = (ScreenWidth() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
+      int stage_height = gr_get_height(stage_marker_empty_.get());
+      int stage_width = gr_get_width(stage_marker_empty_.get());
+      int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2;
       int y = ScreenHeight() - stage_height - margin_height_;
       for (int i = 0; i < max_stage; ++i) {
-        GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty;
-        DrawSurface(stage_surface, 0, 0, stage_width, stage_height, x, y);
+        const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_;
+        DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y);
         x += stage_width;
       }
     }
 
-    GRSurface* text_surface = GetCurrentText();
+    const auto& text_surface = GetCurrentText();
     int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2;
     int text_y = GetTextBaseline();
     gr_color(255, 255, 255, 255);
@@ -437,8 +441,8 @@
 // Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be
 // called with updateMutex locked.
 void ScreenRecoveryUI::draw_foreground_locked() {
-  if (currentIcon != NONE) {
-    GRSurface* frame = GetCurrentFrame();
+  if (current_icon_ != NONE) {
+    const auto& frame = GetCurrentFrame();
     int frame_width = gr_get_width(frame);
     int frame_height = gr_get_height(frame);
     int frame_x = (ScreenWidth() - frame_width) / 2;
@@ -447,8 +451,8 @@
   }
 
   if (progressBarType != EMPTY) {
-    int width = gr_get_width(progressBarEmpty);
-    int height = gr_get_height(progressBarEmpty);
+    int width = gr_get_width(progress_bar_empty_.get());
+    int height = gr_get_height(progress_bar_empty_.get());
 
     int progress_x = (ScreenWidth() - width) / 2;
     int progress_y = GetProgressBaseline();
@@ -464,19 +468,20 @@
       if (rtl_locale_) {
         // Fill the progress bar from right to left.
         if (pos > 0) {
-          DrawSurface(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos,
-                      progress_y);
+          DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height,
+                      progress_x + width - pos, progress_y);
         }
         if (pos < width - 1) {
-          DrawSurface(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y);
+          DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y);
         }
       } else {
         // Fill the progress bar from left to right.
         if (pos > 0) {
-          DrawSurface(progressBarFill, 0, 0, pos, height, progress_x, progress_y);
+          DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y);
         }
         if (pos < width - 1) {
-          DrawSurface(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y);
+          DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos,
+                      progress_y);
         }
       }
     }
@@ -518,15 +523,14 @@
   SetLocale(locales_entries[sel]);
   std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text",
                                          "installing_security_text", "no_command_text" };
-  std::unordered_map<std::string, std::unique_ptr<GRSurface, decltype(&free)>> surfaces;
+  std::unordered_map<std::string, std::unique_ptr<GRSurface>> surfaces;
   for (const auto& name : text_name) {
-    GRSurface* text_image = nullptr;
-    LoadLocalizedBitmap(name.c_str(), &text_image);
+    auto text_image = LoadLocalizedBitmap(name);
     if (!text_image) {
       Print("Failed to load %s\n", name.c_str());
       return;
     }
-    surfaces.emplace(name, std::unique_ptr<GRSurface, decltype(&free)>(text_image, &free));
+    surfaces.emplace(name, std::move(text_image));
   }
 
   std::lock_guard<std::mutex> lg(updateMutex);
@@ -753,16 +757,16 @@
 
       // update the installation animation, if active
       // skip this if we have a text overlay (too expensive to update)
-      if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) {
-        if (!intro_done) {
-          if (current_frame == intro_frames - 1) {
-            intro_done = true;
-            current_frame = 0;
+      if ((current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) && !show_text) {
+        if (!intro_done_) {
+          if (current_frame_ == intro_frames_.size() - 1) {
+            intro_done_ = true;
+            current_frame_ = 0;
           } else {
-            ++current_frame;
+            ++current_frame_;
           }
         } else {
-          current_frame = (current_frame + 1) % loop_frames;
+          current_frame_ = (current_frame_ + 1) % loop_frames_.size();
         }
 
         redraw = true;
@@ -791,18 +795,23 @@
   }
 }
 
-void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) {
-  int result = res_create_display_surface(filename, surface);
-  if (result < 0) {
-    LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
+std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadBitmap(const std::string& filename) {
+  GRSurface* surface;
+  if (auto result = res_create_display_surface(filename.c_str(), &surface); result < 0) {
+    LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")";
+    return nullptr;
   }
+  return std::unique_ptr<GRSurface>(surface);
 }
 
-void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
-  int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface);
-  if (result < 0) {
-    LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")";
+std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) {
+  GRSurface* surface;
+  if (auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface);
+      result < 0) {
+    LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")";
+    return nullptr;
   }
+  return std::unique_ptr<GRSurface>(surface);
 }
 
 static char** Alloc2d(size_t rows, size_t cols) {
@@ -817,9 +826,9 @@
 // Choose the right background string to display during update.
 void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) {
   if (security_update) {
-    LoadLocalizedBitmap("installing_security_text", &installing_text);
+    installing_text_ = LoadLocalizedBitmap("installing_security_text");
   } else {
-    LoadLocalizedBitmap("installing_text", &installing_text);
+    installing_text_ = LoadLocalizedBitmap("installing_text");
   }
   Redraw();
 }
@@ -838,10 +847,6 @@
 // TODO(xunchang) load localized text icons for the menu. (Init for screenRecoveryUI but
 // not wearRecoveryUI).
 bool ScreenRecoveryUI::LoadWipeDataMenuText() {
-  wipe_data_menu_header_text_ = nullptr;
-  factory_data_reset_text_ = nullptr;
-  try_again_text_ = nullptr;
-
   return true;
 }
 
@@ -869,21 +874,20 @@
   // Set up the locale info.
   SetLocale(locale);
 
-  LoadBitmap("icon_error", &error_icon);
+  error_icon_ = LoadBitmap("icon_error");
 
-  LoadBitmap("progress_empty", &progressBarEmpty);
-  LoadBitmap("progress_fill", &progressBarFill);
+  progress_bar_empty_ = LoadBitmap("progress_empty");
+  progress_bar_fill_ = LoadBitmap("progress_fill");
+  stage_marker_empty_ = LoadBitmap("stage_empty");
+  stage_marker_fill_ = LoadBitmap("stage_fill");
 
-  LoadBitmap("stage_empty", &stageMarkerEmpty);
-  LoadBitmap("stage_fill", &stageMarkerFill);
+  erasing_text_ = LoadLocalizedBitmap("erasing_text");
+  no_command_text_ = LoadLocalizedBitmap("no_command_text");
+  error_text_ = LoadLocalizedBitmap("error_text");
 
-  // Background text for "installing_update" could be "installing update"
-  // or "installing security update". It will be set after UI init according
-  // to commands in BCB.
-  installing_text = nullptr;
-  LoadLocalizedBitmap("erasing_text", &erasing_text);
-  LoadLocalizedBitmap("no_command_text", &no_command_text);
-  LoadLocalizedBitmap("error_text", &error_text);
+  // Background text for "installing_update" could be "installing update" or
+  // "installing security update". It will be set after Init() according to the commands in BCB.
+  installing_text_.reset();
 
   LoadWipeDataMenuText();
 
@@ -915,32 +919,34 @@
     }
   }
 
-  intro_frames = intro_frame_names.size();
-  loop_frames = loop_frame_names.size();
+  size_t intro_frames = intro_frame_names.size();
+  size_t loop_frames = loop_frame_names.size();
 
   // It's okay to not have an intro.
-  if (intro_frames == 0) intro_done = true;
+  if (intro_frames == 0) intro_done_ = true;
   // But you must have an animation.
   if (loop_frames == 0) abort();
 
   std::sort(intro_frame_names.begin(), intro_frame_names.end());
   std::sort(loop_frame_names.begin(), loop_frame_names.end());
 
-  introFrames = new GRSurface*[intro_frames];
-  for (size_t i = 0; i < intro_frames; i++) {
-    LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]);
+  intro_frames_.clear();
+  intro_frames_.reserve(intro_frames);
+  for (const auto& frame_name : intro_frame_names) {
+    intro_frames_.emplace_back(LoadBitmap(frame_name));
   }
 
-  loopFrames = new GRSurface*[loop_frames];
-  for (size_t i = 0; i < loop_frames; i++) {
-    LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]);
+  loop_frames_.clear();
+  loop_frames_.reserve(loop_frames);
+  for (const auto& frame_name : loop_frame_names) {
+    loop_frames_.emplace_back(LoadBitmap(frame_name));
   }
 }
 
 void ScreenRecoveryUI::SetBackground(Icon icon) {
   std::lock_guard<std::mutex> lg(updateMutex);
 
-  currentIcon = icon;
+  current_icon_ = icon;
   update_screen_locked();
 }
 
@@ -972,7 +978,7 @@
   if (fraction > 1.0) fraction = 1.0;
   if (progressBarType == DETERMINATE && fraction > progress) {
     // Skip updates that aren't visibly different.
-    int width = gr_get_width(progressBarEmpty);
+    int width = gr_get_width(progress_bar_empty_.get());
     float scale = width * progressScopeSize;
     if ((int)(progress * scale) != (int)(fraction * scale)) {
       progress = fraction;
@@ -1115,11 +1121,10 @@
   text_row_ = old_text_row;
 }
 
-std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(GRSurface* graphic_header,
-                                                   const std::vector<GRSurface*>& graphic_items,
-                                                   const std::vector<std::string>& text_headers,
-                                                   const std::vector<std::string>& text_items,
-                                                   size_t initial_selection) const {
+std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(
+    const GRSurface* graphic_header, const std::vector<const GRSurface*>& graphic_items,
+    const std::vector<std::string>& text_headers, const std::vector<std::string>& text_items,
+    size_t initial_selection) const {
   // horizontal unusable area: margin width + menu indent
   size_t max_width = ScreenWidth() - margin_width_ - kMenuIndent;
   // vertical unusable area: margin height + title lines + helper message + high light bar.
@@ -1235,9 +1240,9 @@
 size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers,
                                                 const std::vector<std::string>& backup_items,
                                                 const std::function<int(int, bool)>& key_handler) {
-  auto wipe_data_menu =
-      CreateMenu(wipe_data_menu_header_text_, { try_again_text_, factory_data_reset_text_ },
-                 backup_headers, backup_items, 0);
+  auto wipe_data_menu = CreateMenu(wipe_data_menu_header_text_.get(),
+                                   { try_again_text_.get(), factory_data_reset_text_.get() },
+                                   backup_headers, backup_items, 0);
   if (wipe_data_menu == nullptr) {
     return 0;
   }
diff --git a/screen_ui.h b/screen_ui.h
index 2d0d97d..ff245a2 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -162,32 +162,31 @@
   int char_height_;
 };
 
-// This class uses GRSurfaces* as the menu header and items.
+// This class uses GRSurface's as the menu header and items.
 class GraphicMenu : public Menu {
  public:
   // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial
-  // selection to |initial_selection|.
-  GraphicMenu(GRSurface* graphic_headers, const std::vector<GRSurface*>& graphic_items,
+  // selection to |initial_selection|. |headers| and |items| will be made local copies.
+  GraphicMenu(const GRSurface* graphic_headers, const std::vector<const GRSurface*>& graphic_items,
               size_t initial_selection, const DrawInterface& draw_funcs);
 
   int Select(int sel) override;
   int DrawHeader(int x, int y) const override;
   int DrawItems(int x, int y, int screen_width, bool long_press) const override;
 
-  // Checks if all the header and items are valid GRSurfaces; and that they can fit in the area
+  // Checks if all the header and items are valid GRSurface's; and that they can fit in the area
   // defined by |max_width| and |max_height|.
-  static bool Validate(size_t max_width, size_t max_height, GRSurface* graphic_headers,
-                       const std::vector<GRSurface*>& graphic_items);
+  static bool Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers,
+                       const std::vector<const GRSurface*>& graphic_items);
 
   // Returns true if |surface| fits on the screen with a vertical offset |y|.
   static bool ValidateGraphicSurface(size_t max_width, size_t max_height, int y,
                                      const GRSurface* surface);
 
  private:
-  // Pointers to the menu headers and items in graphic icons. This class does not have the ownership
-  // of the these objects.
-  GRSurface* graphic_headers_;
-  std::vector<GRSurface*> graphic_items_;
+  // Menu headers and items in graphic icons. These are the copies owned by the class instance.
+  std::unique_ptr<GRSurface> graphic_headers_;
+  std::vector<std::unique_ptr<GRSurface>> graphic_items_;
 };
 
 // Implementation of RecoveryUI appropriate for devices with a screen
@@ -243,6 +242,7 @@
 
  protected:
   static constexpr int kMenuIndent = 4;
+
   // The margin that we don't want to use for showing texts (e.g. round screen, or screen with
   // rounded corners).
   const int margin_width_;
@@ -261,8 +261,8 @@
   // Creates a GraphicMenu with |graphic_header| and |graphic_items|. If the GraphicMenu isn't
   // valid or it doesn't fit on the screen; falls back to create a TextMenu instead. If succeeds,
   // returns a unique pointer to the created menu; otherwise returns nullptr.
-  virtual std::unique_ptr<Menu> CreateMenu(GRSurface* graphic_header,
-                                           const std::vector<GRSurface*>& graphic_items,
+  virtual std::unique_ptr<Menu> CreateMenu(const GRSurface* graphic_header,
+                                           const std::vector<const GRSurface*>& graphic_items,
                                            const std::vector<std::string>& text_headers,
                                            const std::vector<std::string>& text_items,
                                            size_t initial_selection) const;
@@ -288,8 +288,8 @@
   virtual void update_screen_locked();
   virtual void update_progress_locked();
 
-  GRSurface* GetCurrentFrame() const;
-  GRSurface* GetCurrentText() const;
+  const GRSurface* GetCurrentFrame() const;
+  const GRSurface* GetCurrentText() const;
 
   void ProgressThreadLoop();
 
@@ -299,8 +299,8 @@
   void ClearText();
 
   void LoadAnimation();
-  void LoadBitmap(const char* filename, GRSurface** surface);
-  void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
+  std::unique_ptr<GRSurface> LoadBitmap(const std::string& filename);
+  std::unique_ptr<GRSurface> LoadLocalizedBitmap(const std::string& filename);
 
   int PixelsFromDp(int dp) const;
   virtual int GetAnimationBaseline() const;
@@ -324,30 +324,34 @@
   int DrawTextLines(int x, int y, const std::vector<std::string>& lines) const override;
   int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const override;
 
-  Icon currentIcon;
-
   // The layout to use.
   int layout_;
 
-  GRSurface* error_icon;
+  // The images that contain localized texts.
+  std::unique_ptr<GRSurface> erasing_text_;
+  std::unique_ptr<GRSurface> error_text_;
+  std::unique_ptr<GRSurface> installing_text_;
+  std::unique_ptr<GRSurface> no_command_text_;
 
-  GRSurface* erasing_text;
-  GRSurface* error_text;
-  GRSurface* installing_text;
-  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> factory_data_reset_text_;
 
-  // Graphs for the wipe data menu
-  GRSurface* wipe_data_menu_header_text_;
-  GRSurface* try_again_text_;
-  GRSurface* factory_data_reset_text_;
+  // current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by
+  // current_frame_, or error_icon_.
+  Icon current_icon_;
+  std::unique_ptr<GRSurface> error_icon_;
+  std::vector<std::unique_ptr<GRSurface>> intro_frames_;
+  std::vector<std::unique_ptr<GRSurface>> loop_frames_;
+  size_t current_frame_;
+  bool intro_done_;
 
-  GRSurface** introFrames;
-  GRSurface** loopFrames;
-
-  GRSurface* progressBarEmpty;
-  GRSurface* progressBarFill;
-  GRSurface* stageMarkerEmpty;
-  GRSurface* stageMarkerFill;
+  // progress_bar and stage_marker images.
+  std::unique_ptr<GRSurface> progress_bar_empty_;
+  std::unique_ptr<GRSurface> progress_bar_fill_;
+  std::unique_ptr<GRSurface> stage_marker_empty_;
+  std::unique_ptr<GRSurface> stage_marker_fill_;
 
   ProgressType progressBarType;
 
@@ -377,13 +381,6 @@
   std::thread progress_thread_;
   std::atomic<bool> progress_thread_stopped_{ false };
 
-  // Number of intro frames and loop frames in the animation.
-  size_t intro_frames;
-  size_t loop_frames;
-
-  size_t current_frame;
-  bool intro_done;
-
   int stage, max_stage;
 
   int char_width_;
diff --git a/tests/Android.bp b/tests/Android.bp
index 2cfc325..5165ccb 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -92,7 +92,7 @@
     "libhidl-gen-utils",
     "libhidlbase",
     "libhidltransport",
-    "libhwbinder",
+    "libhwbinder_noltopgo",
     "libbinderthreadstate",
     "libvndksupport",
     "libtinyxml2",
diff --git a/tests/component/applypatch_modes_test.cpp b/tests/component/applypatch_modes_test.cpp
index ce01f4f..08414b7 100644
--- a/tests/component/applypatch_modes_test.cpp
+++ b/tests/component/applypatch_modes_test.cpp
@@ -23,7 +23,6 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/strings.h>
-#include <android-base/test_utils.h>
 #include <bsdiff/bsdiff.h>
 #include <gtest/gtest.h>
 #include <openssl/sha.h>
diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp
index 6cc59a4..b005d19 100644
--- a/tests/component/bootloader_message_test.cpp
+++ b/tests/component/bootloader_message_test.cpp
@@ -17,8 +17,8 @@
 #include <string>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/strings.h>
-#include <android-base/test_utils.h>
 #include <bootloader_message/bootloader_message.h>
 #include <gtest/gtest.h>
 
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
index cb4868a..e76ccbd 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/component/imgdiff_test.cpp
@@ -25,7 +25,6 @@
 #include <android-base/memory.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <android-base/test_utils.h>
 #include <applypatch/imgdiff.h>
 #include <applypatch/imgdiff_image.h>
 #include <applypatch/imgpatch.h>
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
index 08b4290..27a01cb 100644
--- a/tests/component/install_test.cpp
+++ b/tests/component/install_test.cpp
@@ -20,13 +20,13 @@
 #include <unistd.h>
 
 #include <algorithm>
+#include <random>
 #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>
@@ -36,15 +36,23 @@
 #include "otautil/paths.h"
 #include "private/install.h"
 
-TEST(InstallTest, verify_package_compatibility_no_entry) {
-  TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.release(), "w");
+static void BuildZipArchive(const std::map<std::string, std::string>& file_map, int fd,
+                            int compression_type) {
+  FILE* zip_file = fdopen(fd, "w");
   ZipWriter writer(zip_file);
-  // The archive must have something to be opened correctly.
-  ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
-  ASSERT_EQ(0, writer.FinishEntry());
+  for (const auto& [name, content] : file_map) {
+    ASSERT_EQ(0, writer.StartEntry(name.c_str(), compression_type));
+    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));
+}
+
+TEST(InstallTest, verify_package_compatibility_no_entry) {
+  TemporaryFile temp_file;
+  // The archive must have something to be opened correctly.
+  BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
 
   // Doesn't contain compatibility zip entry.
   ZipArchiveHandle zip;
@@ -55,12 +63,7 @@
 
 TEST(InstallTest, verify_package_compatibility_invalid_entry) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.release(), "w");
-  ZipWriter writer(zip_file);
-  ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0));
-  ASSERT_EQ(0, writer.FinishEntry());
-  ASSERT_EQ(0, writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file));
+  BuildZipArchive({ { "compatibility.zip", "" } }, temp_file.release(), kCompressStored);
 
   // Empty compatibility zip entry.
   ZipArchiveHandle zip;
@@ -71,77 +74,51 @@
 
 TEST(InstallTest, read_metadata_from_package_smoke) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.release(), "w");
-  ZipWriter writer(zip_file);
-  ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
-  const std::string content("abcdefg");
-  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));
+  const std::string content("abc=defg");
+  BuildZipArchive({ { "META-INF/com/android/metadata", content } }, temp_file.release(),
+                  kCompressStored);
 
   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);
+  std::map<std::string, std::string> metadata;
+  ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
+  ASSERT_EQ("defg", metadata["abc"]);
   CloseArchive(zip);
 
   TemporaryFile temp_file2;
-  FILE* zip_file2 = fdopen(temp_file2.release(), "w");
-  ZipWriter writer2(zip_file2);
-  ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated));
-  ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size()));
-  ASSERT_EQ(0, writer2.FinishEntry());
-  ASSERT_EQ(0, writer2.Finish());
-  ASSERT_EQ(0, fclose(zip_file2));
+  BuildZipArchive({ { "META-INF/com/android/metadata", content } }, temp_file2.release(),
+                  kCompressDeflated);
 
   ASSERT_EQ(0, OpenArchive(temp_file2.path, &zip));
   metadata.clear();
-  ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
-  ASSERT_EQ(content, metadata);
+  ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
+  ASSERT_EQ("defg", metadata["abc"]);
   CloseArchive(zip);
 }
 
 TEST(InstallTest, read_metadata_from_package_no_entry) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.release(), "w");
-  ZipWriter writer(zip_file);
-  ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored));
-  ASSERT_EQ(0, writer.FinishEntry());
-  ASSERT_EQ(0, writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file));
+  BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
 
   ZipArchiveHandle zip;
   ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
-  std::string metadata;
-  ASSERT_FALSE(read_metadata_from_package(zip, &metadata));
+  std::map<std::string, std::string> metadata;
+  ASSERT_FALSE(ReadMetadataFromPackage(zip, &metadata));
   CloseArchive(zip);
 }
 
 TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
   TemporaryFile compatibility_zip_file;
-  FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
-  ZipWriter compatibility_zip_writer(compatibility_zip);
-  ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
   std::string malformed_xml = "malformed";
-  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));
+  BuildZipArchive({ { "system_manifest.xml", malformed_xml } }, compatibility_zip_file.release(),
+                  kCompressDeflated);
 
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.release(), "w");
-  ZipWriter writer(zip_file);
-  ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
   std::string compatibility_zip_content;
   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));
+  BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(),
+                  kCompressStored);
 
   ZipArchiveHandle zip;
   ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
@@ -166,27 +143,15 @@
   ASSERT_TRUE(
       android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
   TemporaryFile compatibility_zip_file;
-  FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "w");
-  ZipWriter compatibility_zip_writer(compatibility_zip);
-  ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
-  ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(),
-                                                   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));
+  BuildZipArchive({ { "system_manifest.xml", system_manifest_xml_content } },
+                  compatibility_zip_file.release(), kCompressDeflated);
 
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.release(), "w");
-  ZipWriter writer(zip_file);
-  ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
   std::string compatibility_zip_content;
   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));
+  BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(),
+                  kCompressStored);
 
   ZipArchiveHandle zip;
   ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
@@ -202,13 +167,8 @@
 
 TEST(InstallTest, SetUpNonAbUpdateCommands) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.release(), "w");
-  ZipWriter writer(zip_file);
   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
-  ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
-  ASSERT_EQ(0, writer.FinishEntry());
-  ASSERT_EQ(0, writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file));
+  BuildZipArchive({ { UPDATE_BINARY_NAME, "" } }, temp_file.release(), kCompressStored);
 
   ZipArchiveHandle zip;
   ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
@@ -246,13 +206,8 @@
 
 TEST(InstallTest, SetUpNonAbUpdateCommands_MissingUpdateBinary) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.release(), "w");
-  ZipWriter writer(zip_file);
   // The archive must have something to be opened correctly.
-  ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
-  ASSERT_EQ(0, writer.FinishEntry());
-  ASSERT_EQ(0, writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file));
+  BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
 
   // Missing update binary.
   ZipArchiveHandle zip;
@@ -268,16 +223,8 @@
 
 static void VerifyAbUpdateCommands(const std::string& serialno, bool success = true) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.release(), "w");
-  ZipWriter writer(zip_file);
-  ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
-  ASSERT_EQ(0, writer.FinishEntry());
-  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", "");
@@ -288,21 +235,27 @@
   if (!serialno.empty()) {
     meta.push_back("serialno=" + serialno);
   }
-  std::string metadata = android::base::Join(meta, "\n");
-  ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
-  ASSERT_EQ(0, writer.FinishEntry());
-  ASSERT_EQ(0, writer.Finish());
-  ASSERT_EQ(0, fclose(zip_file));
+  std::string metadata_string = android::base::Join(meta, "\n");
+
+  BuildZipArchive({ { "payload.bin", "" },
+                    { "payload_properties.txt", properties },
+                    { "META-INF/com/android/metadata", metadata_string } },
+                  temp_file.release(), kCompressStored);
 
   ZipArchiveHandle zip;
   ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
   ZipString payload_name("payload.bin");
   ZipEntry payload_entry;
   ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry));
-  int status_fd = 10;
-  std::string package = "/path/to/update.zip";
-  std::vector<std::string> cmd;
+
+  std::map<std::string, std::string> metadata;
+  ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
   if (success) {
+    ASSERT_EQ(0, CheckPackageMetadata(metadata, OtaType::AB));
+
+    int status_fd = 10;
+    std::string package = "/path/to/update.zip";
+    std::vector<std::string> cmd;
     ASSERT_EQ(0, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
     ASSERT_EQ(5U, cmd.size());
     ASSERT_EQ("/system/bin/update_engine_sideload", cmd[0]);
@@ -311,7 +264,7 @@
     ASSERT_EQ("--headers=" + properties, cmd[3]);
     ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
   } else {
-    ASSERT_EQ(INSTALL_ERROR, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
+    ASSERT_EQ(INSTALL_ERROR, CheckPackageMetadata(metadata, OtaType::AB));
   }
   CloseArchive(zip);
 }
@@ -323,13 +276,7 @@
 
 TEST(InstallTest, SetUpAbUpdateCommands_MissingPayloadPropertiesTxt) {
   TemporaryFile temp_file;
-  FILE* zip_file = fdopen(temp_file.release(), "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", "");
@@ -339,10 +286,13 @@
           "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));
+
+  BuildZipArchive(
+      {
+          { "payload.bin", "" },
+          { "META-INF/com/android/metadata", metadata },
+      },
+      temp_file.release(), kCompressStored);
 
   ZipArchiveHandle zip;
   ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
@@ -381,3 +331,241 @@
   // String with the matching serialno should pass the verification.
   VerifyAbUpdateCommands(long_serialno);
 }
+
+static void test_check_package_metadata(const std::string& metadata_string, OtaType ota_type,
+                                        int exptected_result) {
+  TemporaryFile temp_file;
+  BuildZipArchive(
+      {
+          { "META-INF/com/android/metadata", metadata_string },
+      },
+      temp_file.release(), kCompressStored);
+
+  ZipArchiveHandle zip;
+  ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+
+  std::map<std::string, std::string> metadata;
+  ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
+  ASSERT_EQ(exptected_result, CheckPackageMetadata(metadata, ota_type));
+  CloseArchive(zip);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ota_type) {
+  std::string device = android::base::GetProperty("ro.product.device", "");
+  ASSERT_NE("", device);
+
+  // ota-type must be present
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "pre-device=" + device,
+          "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+
+  // Checks if ota-type matches
+  metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB",
+          "pre-device=" + device,
+          "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::AB, 0);
+
+  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+}
+
+TEST(InstallTest, CheckPackageMetadata_device_type) {
+  // device type can not be empty
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=BRICK",
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+
+  // device type mismatches
+  metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=BRICK",
+          "pre-device=dummy_device_type",
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+}
+
+TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) {
+  std::string device = android::base::GetProperty("ro.product.device", "");
+  ASSERT_NE("", device);
+
+  // Serial number doesn't need to exist
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=BRICK",
+          "pre-device=" + device,
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::BRICK, 0);
+
+  // Serial number mismatches
+  metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=BRICK",
+          "pre-device=" + device,
+          "serialno=dummy_serial",
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+
+  std::string serialno = android::base::GetProperty("ro.serialno", "");
+  ASSERT_NE("", serialno);
+  metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=BRICK",
+          "pre-device=" + device,
+          "serialno=" + serialno,
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::BRICK, 0);
+}
+
+TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) {
+  std::string device = android::base::GetProperty("ro.product.device", "");
+  ASSERT_NE("", device);
+
+  std::string serialno = android::base::GetProperty("ro.serialno", "");
+  ASSERT_NE("", serialno);
+
+  std::vector<std::string> serial_numbers;
+  // Creates a dummy serial number string.
+  for (size_t c = 'a'; c <= 'z'; c++) {
+    serial_numbers.emplace_back(c, serialno.size());
+  }
+
+  // No matched serialno found.
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=BRICK",
+          "pre-device=" + device,
+          "serialno=" + android::base::Join(serial_numbers, '|'),
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR);
+
+  serial_numbers.emplace_back(serialno);
+  std::shuffle(serial_numbers.begin(), serial_numbers.end(), std::default_random_engine());
+  metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=BRICK",
+          "pre-device=" + device,
+          "serialno=" + android::base::Join(serial_numbers, '|'),
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::BRICK, 0);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ab_build_version) {
+  std::string device = android::base::GetProperty("ro.product.device", "");
+  ASSERT_NE("", device);
+
+  std::string build_version = android::base::GetProperty("ro.build.version.incremental", "");
+  ASSERT_NE("", build_version);
+
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB",
+          "pre-device=" + device,
+          "pre-build-incremental=" + build_version,
+          "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::AB, 0);
+
+  metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB",
+          "pre-device=" + device,
+          "pre-build-incremental=dummy_build",
+          "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) {
+  std::string device = android::base::GetProperty("ro.product.device", "");
+  ASSERT_NE("", device);
+
+  std::string finger_print = android::base::GetProperty("ro.build.fingerprint", "");
+  ASSERT_NE("", finger_print);
+
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB",
+          "pre-device=" + device,
+          "pre-build=" + finger_print,
+          "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::AB, 0);
+
+  metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB",
+          "pre-device=" + device,
+          "pre-build=dummy_build_fingerprint",
+          "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) {
+  std::string device = android::base::GetProperty("ro.product.device", "");
+  ASSERT_NE("", device);
+
+  // post timestamp is required for upgrade.
+  std::string metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB",
+          "pre-device=" + device,
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+
+  // post timestamp should be larger than the timestamp on device.
+  metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB",
+          "pre-device=" + device,
+          "post-timestamp=0",
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+
+  // fingerprint is required for downgrade
+  metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB",
+          "pre-device=" + device,
+          "post-timestamp=0",
+          "ota-downgrade=yes",
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR);
+
+  std::string finger_print = android::base::GetProperty("ro.build.fingerprint", "");
+  ASSERT_NE("", finger_print);
+
+  metadata = android::base::Join(
+      std::vector<std::string>{
+          "ota-type=AB",
+          "pre-device=" + device,
+          "post-timestamp=0",
+          "pre-build=" + finger_print,
+          "ota-downgrade=yes",
+      },
+      "\n");
+  test_check_package_metadata(metadata, OtaType::AB, 0);
+}
diff --git a/tests/component/resources_test.cpp b/tests/component/resources_test.cpp
index 54329db..d7fdb8f 100644
--- a/tests/component/resources_test.cpp
+++ b/tests/component/resources_test.cpp
@@ -101,7 +101,7 @@
     EXPECT_LT(0, len) << "Locale string should be non-empty.";
     EXPECT_NE(0, row[5]) << "Locale string is missing.";
 
-    ASSERT_GT(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
+    ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
     char* loc = reinterpret_cast<char*>(&row[5]);
     if (matches_locale(loc, kLocale.c_str())) {
       EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp
index b7109fc..d5e074c 100644
--- a/tests/component/sideload_test.cpp
+++ b/tests/component/sideload_test.cpp
@@ -21,7 +21,6 @@
 
 #include <android-base/file.h>
 #include <android-base/strings.h>
-#include <android-base/test_utils.h>
 #include <gtest/gtest.h>
 
 #include "fuse_sideload.h"
diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp
index 55baca2..e97d589 100644
--- a/tests/component/uncrypt_test.cpp
+++ b/tests/component/uncrypt_test.cpp
@@ -26,7 +26,6 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
-#include <android-base/test_utils.h>
 #include <android-base/unique_fd.h>
 #include <bootloader_message/bootloader_message.h>
 #include <gtest/gtest.h>
diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp
index 2420c27..0a59403 100644
--- a/tests/component/update_verifier_test.cpp
+++ b/tests/component/update_verifier_test.cpp
@@ -24,7 +24,6 @@
 #include <android-base/file.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
-#include <android-base/test_utils.h>
 #include <google/protobuf/repeated_field.h>
 #include <gtest/gtest.h>
 
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index 24c63e7..a0a7b66 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -23,6 +23,7 @@
 #include <algorithm>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <unordered_map>
 #include <vector>
 
@@ -32,7 +33,6 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <android-base/test_utils.h>
 #include <bootloader_message/bootloader_message.h>
 #include <brotli/encode.h>
 #include <bsdiff/bsdiff.h>
@@ -134,9 +134,9 @@
   CloseArchive(handle);
 }
 
-static std::string get_sha1(const std::string& content) {
+static std::string GetSha1(std::string_view content) {
   uint8_t digest[SHA_DIGEST_LENGTH];
-  SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest);
+  SHA1(reinterpret_cast<const uint8_t*>(content.data()), content.size(), digest);
   return print_sha1(digest);
 }
 
@@ -187,7 +187,7 @@
 
     // Clear partition updated marker if any.
     std::string updated_marker{ temp_stash_base_.path };
-    updated_marker += "/" + get_sha1(image_temp_file_.path) + ".UPDATED";
+    updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED";
     ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
   }
 
@@ -223,14 +223,14 @@
   std::string source_content;
   ASSERT_TRUE(android::base::ReadFileToString(source_file, &source_content));
   size_t source_size = source_content.size();
-  std::string source_hash = get_sha1(source_content);
+  std::string source_hash = GetSha1(source_content);
   Partition source(source_file, source_size, source_hash);
 
   std::string target_file = from_testdata_base("recovery.img");
   std::string target_content;
   ASSERT_TRUE(android::base::ReadFileToString(target_file, &target_content));
   size_t target_size = target_content.size();
-  std::string target_hash = get_sha1(target_content);
+  std::string target_hash = GetSha1(target_content);
   Partition target(target_file, target_size, target_hash);
 
   // One argument is not valid.
@@ -619,54 +619,100 @@
   RunBlockImageUpdate(false, entries, image_file_, "", kArgsParsingFailure);
 }
 
-TEST_F(UpdaterTest, block_image_update_patch_data) {
-  std::string src_content = std::string(4096, 'a') + std::string(4096, 'c');
-  std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd');
-
+// Generates the bsdiff of the given source and target images, and writes the result entries.
+// target_blocks specifies the block count to be written into the `bsdiff` command, which may be
+// different from the given target size in order to trigger overrun / underrun paths.
+static void GetEntriesForBsdiff(std::string_view source, std::string_view target,
+                                size_t target_blocks, PackageEntries* entries) {
   // Generate the patch data.
   TemporaryFile patch_file;
-  ASSERT_EQ(0,
-            bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(),
-                           reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(),
-                           patch_file.path, nullptr));
+  ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(source.data()), source.size(),
+                              reinterpret_cast<const uint8_t*>(target.data()), target.size(),
+                              patch_file.path, nullptr));
   std::string patch_content;
   ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
 
   // Create the transfer list that contains a bsdiff.
-  std::string src_hash = get_sha1(src_content);
-  std::string tgt_hash = get_sha1(tgt_content);
+  std::string src_hash = GetSha1(source);
+  std::string tgt_hash = GetSha1(target);
+  size_t source_blocks = source.size() / 4096;
   std::vector<std::string> transfer_list{
     // clang-format off
     "4",
-    "2",
+    std::to_string(target_blocks),
     "0",
-    "2",
-    "stash " + src_hash + " 2,0,2",
-    android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(),
-                                src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
-    "free " + src_hash,
+    "0",
+    // bsdiff patch_offset patch_length source_hash target_hash target_range source_block_count
+    // source_range
+    android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,%zu %zu 2,0,%zu", patch_content.size(),
+                                src_hash.c_str(), tgt_hash.c_str(), target_blocks, source_blocks,
+                                source_blocks),
     // clang-format on
   };
 
-  PackageEntries entries{
+  *entries = {
     { "new_data", "" },
     { "patch_data", patch_content },
     { "transfer_list", android::base::Join(transfer_list, '\n') },
   };
+}
 
-  ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_));
+TEST_F(UpdaterTest, block_image_update_patch_data) {
+  // Both source and target images have 10 blocks.
+  std::string source =
+      std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0');
+  std::string target =
+      std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0');
+  ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_));
 
+  PackageEntries entries;
+  GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2),
+                      std::string_view(target).substr(0, 4096 * 2), 2, &entries);
   RunBlockImageUpdate(false, entries, image_file_, "t");
 
   // The update_file should be patched correctly.
-  std::string updated_content;
-  ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content));
-  ASSERT_EQ(tgt_content, updated_content);
+  std::string updated;
+  ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated));
+  ASSERT_EQ(target, updated);
+}
+
+TEST_F(UpdaterTest, block_image_update_patch_overrun) {
+  // Both source and target images have 10 blocks.
+  std::string source =
+      std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0');
+  std::string target =
+      std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0');
+  ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_));
+
+  // Provide one less block to trigger the overrun path.
+  PackageEntries entries;
+  GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2),
+                      std::string_view(target).substr(0, 4096 * 2), 1, &entries);
+
+  // The update should fail due to overrun.
+  RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure);
+}
+
+TEST_F(UpdaterTest, block_image_update_patch_underrun) {
+  // Both source and target images have 10 blocks.
+  std::string source =
+      std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0');
+  std::string target =
+      std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0');
+  ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_));
+
+  // Provide one more block to trigger the overrun path.
+  PackageEntries entries;
+  GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2),
+                      std::string_view(target).substr(0, 4096 * 2), 3, &entries);
+
+  // The update should fail due to underrun.
+  RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure);
 }
 
 TEST_F(UpdaterTest, block_image_update_fail) {
   std::string src_content(4096 * 2, 'e');
-  std::string src_hash = get_sha1(src_content);
+  std::string src_hash = GetSha1(src_content);
   // Stash and free some blocks, then fail the update intentionally.
   std::vector<std::string> transfer_list{
     // clang-format off
@@ -692,7 +738,7 @@
   RunBlockImageUpdate(false, entries, image_file_, "");
 
   // Updater generates the stash name based on the input file name.
-  std::string name_digest = get_sha1(image_file_);
+  std::string name_digest = GetSha1(image_file_);
   std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest;
   ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
   // Expect the stashed blocks to be freed.
@@ -796,9 +842,9 @@
   std::string block1(4096, '1');
   std::string block2(4096, '2');
   std::string block3(4096, '3');
-  std::string block1_hash = get_sha1(block1);
-  std::string block2_hash = get_sha1(block2);
-  std::string block3_hash = get_sha1(block3);
+  std::string block1_hash = GetSha1(block1);
+  std::string block2_hash = GetSha1(block2);
+  std::string block3_hash = GetSha1(block3);
 
   // Compose the transfer list to fail the first update.
   std::vector<std::string> transfer_list_fail{
@@ -864,8 +910,8 @@
 TEST_F(UpdaterTest, last_command_update_unresumable) {
   std::string block1(4096, '1');
   std::string block2(4096, '2');
-  std::string block1_hash = get_sha1(block1);
-  std::string block2_hash = get_sha1(block2);
+  std::string block1_hash = GetSha1(block1);
+  std::string block2_hash = GetSha1(block2);
 
   // Construct an unresumable update with source blocks mismatch.
   std::vector<std::string> transfer_list_unresumable{
@@ -901,9 +947,9 @@
   std::string block1(4096, '1');
   std::string block2(4096, '2');
   std::string block3(4096, '3');
-  std::string block1_hash = get_sha1(block1);
-  std::string block2_hash = get_sha1(block2);
-  std::string block3_hash = get_sha1(block3);
+  std::string block1_hash = GetSha1(block1);
+  std::string block2_hash = GetSha1(block2);
+  std::string block3_hash = GetSha1(block3);
 
   std::vector<std::string> transfer_list_verify{
     // clang-format off
@@ -972,7 +1018,7 @@
 
     // Clear partition updated marker if any.
     std::string updated_marker{ temp_stash_base_.path };
-    updated_marker += "/" + get_sha1(image_temp_file_.path) + ".UPDATED";
+    updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED";
     ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
   }
 
@@ -1003,10 +1049,10 @@
   std::string i(4096, 'i');
   std::string zero(4096, '\0');
 
-  std::string a_hash = get_sha1(a);
-  std::string b_hash = get_sha1(b);
-  std::string c_hash = get_sha1(c);
-  std::string e_hash = get_sha1(e);
+  std::string a_hash = GetSha1(a);
+  std::string b_hash = GetSha1(b);
+  std::string c_hash = GetSha1(c);
+  std::string e_hash = GetSha1(e);
 
   auto loc = [](const std::string& range_text) {
     std::vector<std::string> pieces = android::base::Split(range_text, "-");
@@ -1027,8 +1073,8 @@
   // patch 1: "b d c" -> "g"
   TemporaryFile patch_file_bdc_g;
   std::string bdc = b + d + c;
-  std::string bdc_hash = get_sha1(bdc);
-  std::string g_hash = get_sha1(g);
+  std::string bdc_hash = GetSha1(bdc);
+  std::string g_hash = GetSha1(g);
   CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(bdc.data()), bdc.size(),
                              reinterpret_cast<const uint8_t*>(g.data()), g.size(),
                              patch_file_bdc_g.path, nullptr));
@@ -1038,9 +1084,9 @@
   // patch 2: "a b c d" -> "d c b"
   TemporaryFile patch_file_abcd_dcb;
   std::string abcd = a + b + c + d;
-  std::string abcd_hash = get_sha1(abcd);
+  std::string abcd_hash = GetSha1(abcd);
   std::string dcb = d + c + b;
-  std::string dcb_hash = get_sha1(dcb);
+  std::string dcb_hash = GetSha1(dcb);
   CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(abcd.data()), abcd.size(),
                              reinterpret_cast<const uint8_t*>(dcb.data()), dcb.size(),
                              patch_file_abcd_dcb.path, nullptr));
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index 480f3c9..9fcaa0b 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -27,7 +27,6 @@
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <android-base/test_utils.h>
 #include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
 #include <openssl/bn.h>
diff --git a/tests/testdata/jarsigned.zip b/tests/testdata/jarsigned.zip
deleted file mode 100644
index 8b1ef8b..0000000
--- a/tests/testdata/jarsigned.zip
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/patch.bsdiff b/tests/testdata/patch.bsdiff
deleted file mode 100644
index b78d385..0000000
--- a/tests/testdata/patch.bsdiff
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/unsigned.zip b/tests/testdata/unsigned.zip
deleted file mode 100644
index 24e3ead..0000000
--- a/tests/testdata/unsigned.zip
+++ /dev/null
Binary files differ
diff --git a/tests/unit/applypatch_test.cpp b/tests/unit/applypatch_test.cpp
index 066f981..794f2c1 100644
--- a/tests/unit/applypatch_test.cpp
+++ b/tests/unit/applypatch_test.cpp
@@ -32,7 +32,6 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
-#include <android-base/test_utils.h>
 #include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
 
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
index 1ca786c..4dd111a 100644
--- a/tests/unit/dirutil_test.cpp
+++ b/tests/unit/dirutil_test.cpp
@@ -20,7 +20,7 @@
 
 #include <string>
 
-#include <android-base/test_utils.h>
+#include <android-base/file.h>
 #include <gtest/gtest.h>
 
 #include "otautil/dirutil.h"
diff --git a/tests/unit/minui_test.cpp b/tests/unit/minui_test.cpp
index cad6a3d..d68e5e3 100644
--- a/tests/unit/minui_test.cpp
+++ b/tests/unit/minui_test.cpp
@@ -15,8 +15,9 @@
  */
 
 #include <stdint.h>
+#include <stdlib.h>
 
-#include <memory>
+#include <vector>
 
 #include <gtest/gtest.h>
 
@@ -25,8 +26,19 @@
 TEST(GRSurfaceTest, Create_aligned) {
   static constexpr size_t kSurfaceDataAlignment = 8;
   for (size_t data_size = 100; data_size < 128; data_size++) {
-    std::unique_ptr<GRSurface> surface(GRSurface::Create(data_size));
+    auto surface = GRSurface::Create(10, 1, 10, 1, data_size);
     ASSERT_TRUE(surface);
     ASSERT_EQ(0, reinterpret_cast<uintptr_t>(surface->data()) % kSurfaceDataAlignment);
   }
 }
+
+TEST(GRSurfaceTest, Clone) {
+  static constexpr size_t kImageSize = 10 * 50;
+  auto image = GRSurface::Create(50, 10, 50, 1, kImageSize);
+  for (auto i = 0; i < kImageSize; i++) {
+    image->data()[i] = rand() % 128;
+  }
+  auto image_copy = image->Clone();
+  ASSERT_EQ(std::vector(image->data(), image->data() + kImageSize),
+            std::vector(image_copy->data(), image_copy->data() + kImageSize));
+}
diff --git a/tests/unit/parse_install_logs_test.cpp b/tests/unit/parse_install_logs_test.cpp
index 8061f3b..72169a0 100644
--- a/tests/unit/parse_install_logs_test.cpp
+++ b/tests/unit/parse_install_logs_test.cpp
@@ -20,7 +20,6 @@
 
 #include <android-base/file.h>
 #include <android-base/strings.h>
-#include <android-base/test_utils.h>
 #include <gtest/gtest.h>
 
 #include "otautil/parse_install_logs.h"
diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp
index 0014e45..09c4997 100644
--- a/tests/unit/screen_ui_test.cpp
+++ b/tests/unit/screen_ui_test.cpp
@@ -23,10 +23,11 @@
 #include <string>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
-#include <android-base/test_utils.h>
 #include <gtest/gtest.h>
+#include <gtest/gtest_prod.h>
 
 #include "common/test_constants.h"
 #include "device.h"
@@ -69,17 +70,6 @@
   MockDrawFunctions draw_funcs_;
 };
 
-// TODO(xunchang) Create a constructor.
-static GRSurface CreateFakeGRSurface(int width, int height, int row_bytes, int pixel_bytes) {
-  GRSurface fake_surface;
-  fake_surface.width = width;
-  fake_surface.height = height;
-  fake_surface.row_bytes = row_bytes;
-  fake_surface.pixel_bytes = pixel_bytes;
-
-  return fake_surface;
-}
-
 TEST_F(ScreenUITest, StartPhoneMenuSmoke) {
   TextMenu menu(false, 10, 20, HEADERS, ITEMS, 0, 20, draw_funcs_);
   ASSERT_FALSE(menu.scrollable());
@@ -241,9 +231,14 @@
 }
 
 TEST_F(ScreenUITest, GraphicMenuSelection) {
-  auto fake_surface = CreateFakeGRSurface(50, 50, 50, 1);
-  std::vector<GRSurface*> items = { &fake_surface, &fake_surface, &fake_surface };
-  GraphicMenu menu(&fake_surface, items, 0, draw_funcs_);
+  auto image = GRSurface::Create(50, 50, 50, 1, 50 * 50);
+  auto header = image->Clone();
+  std::vector<const GRSurface*> items = {
+    image.get(),
+    image.get(),
+    image.get(),
+  };
+  GraphicMenu menu(header.get(), items, 0, draw_funcs_);
 
   ASSERT_EQ(0, menu.selection());
 
@@ -263,18 +258,23 @@
 }
 
 TEST_F(ScreenUITest, GraphicMenuValidate) {
-  auto fake_surface = CreateFakeGRSurface(50, 50, 50, 1);
-  std::vector<GRSurface*> items = { &fake_surface, &fake_surface, &fake_surface };
+  auto image = GRSurface::Create(50, 50, 50, 1, 50 * 50);
+  auto header = image->Clone();
+  std::vector<const GRSurface*> items = {
+    image.get(),
+    image.get(),
+    image.get(),
+  };
 
-  ASSERT_TRUE(GraphicMenu::Validate(200, 200, &fake_surface, items));
+  ASSERT_TRUE(GraphicMenu::Validate(200, 200, header.get(), items));
 
   // Menu exceeds the horizontal boundary.
-  auto wide_surface = CreateFakeGRSurface(300, 50, 300, 1);
-  ASSERT_FALSE(GraphicMenu::Validate(299, 200, &wide_surface, items));
+  auto wide_surface = GRSurface::Create(300, 50, 300, 1, 300 * 50);
+  ASSERT_FALSE(GraphicMenu::Validate(299, 200, wide_surface.get(), items));
 
   // Menu exceeds the vertical boundary.
-  items.push_back(&fake_surface);
-  ASSERT_FALSE(GraphicMenu::Validate(200, 249, &fake_surface, items));
+  items.emplace_back(image.get());
+  ASSERT_FALSE(GraphicMenu::Validate(200, 249, header.get(), items));
 }
 
 static constexpr int kMagicAction = 101;
@@ -307,24 +307,13 @@
 
   int KeyHandler(int key, bool visible) const;
 
-  // The following functions expose the protected members for test purpose.
-  void RunLoadAnimation() {
-    LoadAnimation();
-  }
-
-  size_t GetLoopFrames() const {
-    return loop_frames;
-  }
-
-  size_t GetIntroFrames() const {
-    return intro_frames;
-  }
-
-  bool GetRtlLocale() const {
-    return rtl_locale_;
-  }
-
  private:
+  FRIEND_TEST(ScreenRecoveryUITest, Init);
+  FRIEND_TEST(ScreenRecoveryUITest, RtlLocale);
+  FRIEND_TEST(ScreenRecoveryUITest, RtlLocaleWithSuffix);
+  FRIEND_TEST(ScreenRecoveryUITest, LoadAnimation);
+  FRIEND_TEST(ScreenRecoveryUITest, LoadAnimation_MissingAnimation);
+
   std::vector<KeyCode> key_buffer_;
   size_t key_buffer_index_;
 };
@@ -388,7 +377,7 @@
 
   ASSERT_TRUE(ui_->Init(kTestLocale));
   ASSERT_EQ(kTestLocale, ui_->GetLocale());
-  ASSERT_FALSE(ui_->GetRtlLocale());
+  ASSERT_FALSE(ui_->rtl_locale_);
   ASSERT_FALSE(ui_->IsTextVisible());
   ASSERT_FALSE(ui_->WasTextEverVisible());
 }
@@ -416,14 +405,14 @@
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestRtlLocale));
-  ASSERT_TRUE(ui_->GetRtlLocale());
+  ASSERT_TRUE(ui_->rtl_locale_);
 }
 
 TEST_F(ScreenRecoveryUITest, RtlLocaleWithSuffix) {
   RETURN_IF_NO_GRAPHICS;
 
   ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix));
-  ASSERT_TRUE(ui_->GetRtlLocale());
+  ASSERT_TRUE(ui_->rtl_locale_);
 }
 
 TEST_F(ScreenRecoveryUITest, ShowMenu) {
@@ -548,10 +537,10 @@
   }
   Paths::Get().set_resource_dir(resource_dir.path);
 
-  ui_->RunLoadAnimation();
+  ui_->LoadAnimation();
 
-  ASSERT_EQ(2u, ui_->GetIntroFrames());
-  ASSERT_EQ(3u, ui_->GetLoopFrames());
+  ASSERT_EQ(2u, ui_->intro_frames_.size());
+  ASSERT_EQ(3u, ui_->loop_frames_.size());
 
   for (const auto& name : tempfiles) {
     ASSERT_EQ(0, unlink(name.c_str()));
@@ -567,7 +556,7 @@
   Paths::Get().set_resource_dir("/proc/self");
 
   ::testing::FLAGS_gtest_death_test_style = "threadsafe";
-  ASSERT_EXIT(ui_->RunLoadAnimation(), ::testing::KilledBySignal(SIGABRT), "");
+  ASSERT_EXIT(ui_->LoadAnimation(), ::testing::KilledBySignal(SIGABRT), "");
 }
 
 #undef RETURN_IF_NO_GRAPHICS
diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp
index de8ff70..77625db 100644
--- a/tests/unit/sysutil_test.cpp
+++ b/tests/unit/sysutil_test.cpp
@@ -17,7 +17,6 @@
 #include <string>
 
 #include <android-base/file.h>
-#include <android-base/test_utils.h>
 #include <gtest/gtest.h>
 
 #include "otautil/sysutil.h"
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index 47f33d9..dfe617e 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -21,7 +21,6 @@
 #include <vector>
 
 #include <android-base/file.h>
-#include <android-base/test_utils.h>
 #include <gtest/gtest.h>
 #include <ziparchive/zip_archive.h>
 
diff --git a/tools/image_generator/Android.bp b/tools/image_generator/Android.bp
index 3f718fe..ce6e277 100644
--- a/tools/image_generator/Android.bp
+++ b/tools/image_generator/Android.bp
@@ -17,7 +17,11 @@
 
     manifest: "ImageGenerator.mf",
 
+    static_libs: [
+        "commons-cli-1.2",
+    ],
+
     srcs: [
         "ImageGenerator.java",
     ],
-}
\ No newline at end of file
+}
diff --git a/tools/image_generator/ImageGenerator.java b/tools/image_generator/ImageGenerator.java
index f226216..8730945 100644
--- a/tools/image_generator/ImageGenerator.java
+++ b/tools/image_generator/ImageGenerator.java
@@ -16,6 +16,16 @@
 
 package com.android.recovery.tools;
 
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
 import java.awt.Color;
 import java.awt.Font;
 import java.awt.FontFormatException;
@@ -26,369 +36,586 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Comparator;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.TreeMap;
+import java.util.Set;
 import java.util.StringTokenizer;
+import java.util.TreeMap;
 
 import javax.imageio.ImageIO;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-/**
- * Command line tool to generate the localized image for recovery mode.
- */
+/** Command line tool to generate the localized image for recovery mode. */
 public class ImageGenerator {
-  // Initial height of the image to draw.
-  private static final int INITIAL_HEIGHT = 20000;
+    // Initial height of the image to draw.
+    private static final int INITIAL_HEIGHT = 20000;
 
-  private static final float DEFAULT_FONT_SIZE = 40;
+    private static final float DEFAULT_FONT_SIZE = 40;
 
-  // This is the canvas we used to draw texts.
-  private BufferedImage mBufferedImage;
+    // This is the canvas we used to draw texts.
+    private BufferedImage mBufferedImage;
 
-  // The width in pixels of our image. Once set, its value won't change.
-  private final int mImageWidth;
+    // The width in pixels of our image. The value will be adjusted once when we calculate the
+    // maximum width to fit the wrapped text strings.
+    private int mImageWidth;
 
-  // The current height in pixels of our image. We will adjust the value when drawing more texts.
-  private int mImageHeight;
+    // The current height in pixels of our image. We will adjust the value when drawing more texts.
+    private int mImageHeight;
 
-  // The current vertical offset in pixels to draw the top edge of new text strings.
-  private int mVerticalOffset;
+    // The current vertical offset in pixels to draw the top edge of new text strings.
+    private int mVerticalOffset;
 
-  // The font size to draw the texts.
-  private final float mFontSize;
+    // The font size to draw the texts.
+    private final float mFontSize;
 
-  // The name description of the text to localize. It's used to find the translated strings in the
-  // resource file.
-  private final String mTextName;
+    // The name description of the text to localize. It's used to find the translated strings in the
+    // resource file.
+    private final String mTextName;
 
-  // The directory that contains all the needed font files (e.g. ttf, otf, ttc files).
-  private final String mFontDirPath;
+    // The directory that contains all the needed font files (e.g. ttf, otf, ttc files).
+    private final String mFontDirPath;
 
-  // An explicit map from language to the font name to use.
-  // The map is extracted from frameworks/base/data/fonts/fonts.xml.
-  // And the language-subtag-registry is found in:
-  // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
-  private static final String DEFAULT_FONT_NAME = "Roboto-Regular";
-  private static final Map<String, String> LANGUAGE_TO_FONT_MAP = new TreeMap<String, String>() {{
-    put("am", "NotoSansEthiopic-Regular");
-    put("ar", "NotoNaskhArabicUI-Regular");
-    put("as", "NotoSansBengaliUI-Regular");
-    put("bn", "NotoSansBengaliUI-Regular");
-    put("fa", "NotoNaskhArabicUI-Regular");
-    put("gu", "NotoSansGujaratiUI-Regular");
-    put("hi", "NotoSansDevanagariUI-Regular");
-    put("hy", "NotoSansArmenian-Regular");
-    put("iw", "NotoSansHebrew-Regular");
-    put("ja", "NotoSansCJK-Regular");
-    put("ka", "NotoSansGeorgian-Regular");
-    put("ko", "NotoSansCJK-Regular");
-    put("km", "NotoSansKhmerUI-Regular");
-    put("kn", "NotoSansKannadaUI-Regular");
-    put("lo", "NotoSansLaoUI-Regular");
-    put("ml", "NotoSansMalayalamUI-Regular");
-    put("mr", "NotoSansDevanagariUI-Regular");
-    put("my", "NotoSansMyanmarUI-Regular");
-    put("ne", "NotoSansDevanagariUI-Regular");
-    put("or", "NotoSansOriya-Regular");
-    put("pa", "NotoSansGurmukhiUI-Regular");
-    put("si", "NotoSansSinhala-Regular");
-    put("ta", "NotoSansTamilUI-Regular");
-    put("te", "NotoSansTeluguUI-Regular");
-    put("th", "NotoSansThaiUI-Regular");
-    put("ur", "NotoNaskhArabicUI-Regular");
-    put("zh", "NotoSansCJK-Regular");
-  }};
+    // Align the text in the center of the image.
+    private final boolean mCenterAlignment;
 
-  /**
-   * Exception to indicate the failure to find the translated text strings.
-   */
-  public static class LocalizedStringNotFoundException extends Exception {
-    public LocalizedStringNotFoundException(String message) {
-      super(message);
+    // An explicit map from language to the font name to use.
+    // The map is extracted from frameworks/base/data/fonts/fonts.xml.
+    // And the language-subtag-registry is found in:
+    // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
+    private static final String DEFAULT_FONT_NAME = "Roboto-Regular";
+    private static final Map<String, String> LANGUAGE_TO_FONT_MAP =
+            new TreeMap<String, String>() {
+                {
+                    put("am", "NotoSansEthiopic-Regular");
+                    put("ar", "NotoNaskhArabicUI-Regular");
+                    put("as", "NotoSansBengaliUI-Regular");
+                    put("bn", "NotoSansBengaliUI-Regular");
+                    put("fa", "NotoNaskhArabicUI-Regular");
+                    put("gu", "NotoSansGujaratiUI-Regular");
+                    put("hi", "NotoSansDevanagariUI-Regular");
+                    put("hy", "NotoSansArmenian-Regular");
+                    put("iw", "NotoSansHebrew-Regular");
+                    put("ja", "NotoSansCJK-Regular");
+                    put("ka", "NotoSansGeorgian-Regular");
+                    put("ko", "NotoSansCJK-Regular");
+                    put("km", "NotoSansKhmerUI-Regular");
+                    put("kn", "NotoSansKannadaUI-Regular");
+                    put("lo", "NotoSansLaoUI-Regular");
+                    put("ml", "NotoSansMalayalamUI-Regular");
+                    put("mr", "NotoSansDevanagariUI-Regular");
+                    put("my", "NotoSansMyanmarUI-Regular");
+                    put("ne", "NotoSansDevanagariUI-Regular");
+                    put("or", "NotoSansOriya-Regular");
+                    put("pa", "NotoSansGurmukhiUI-Regular");
+                    put("si", "NotoSansSinhala-Regular");
+                    put("ta", "NotoSansTamilUI-Regular");
+                    put("te", "NotoSansTeluguUI-Regular");
+                    put("th", "NotoSansThaiUI-Regular");
+                    put("ur", "NotoNaskhArabicUI-Regular");
+                    put("zh", "NotoSansCJK-Regular");
+                }
+            };
+
+    // Languages that write from right to left.
+    private static final Set<String> RTL_LANGUAGE =
+            new HashSet<String>() {
+                {
+                    add("ar"); // Arabic
+                    add("fa"); // Persian
+                    add("he"); // Hebrew
+                    add("iw"); // Hebrew
+                    add("ur"); // Urdu
+                }
+            };
+
+    // Languages that breaks on arbitrary characters.
+    // TODO(xunchang) switch to icu library if possible. For example, for Thai and Khmer, there is
+    // no space between words; and word breaking is based on grammatical analysis and on word
+    // matching in dictionaries.
+    private static final Set<String> LOGOGRAM_LANGUAGE =
+            new HashSet<String>() {
+                {
+                    add("ja"); // Japanese
+                    add("km"); // Khmer
+                    add("ko"); // Korean
+                    add("lo"); // Lao
+                    add("th"); // Thai
+                    add("zh"); // Chinese
+                }
+            };
+
+    /** Exception to indicate the failure to find the translated text strings. */
+    public static class LocalizedStringNotFoundException extends Exception {
+        public LocalizedStringNotFoundException(String message) {
+            super(message);
+        }
+
+        public LocalizedStringNotFoundException(String message, Throwable cause) {
+            super(message, cause);
+        }
     }
 
-    public LocalizedStringNotFoundException(String message, Throwable cause) {
-      super(message, cause);
-    }
-  }
+    /** Initailizes the fields of the image image. */
+    public ImageGenerator(
+            int initialImageWidth,
+            String textName,
+            float fontSize,
+            String fontDirPath,
+            boolean centerAlignment) {
+        mImageWidth = initialImageWidth;
+        mImageHeight = INITIAL_HEIGHT;
+        mVerticalOffset = 0;
 
-  /**
-   * Initailizes the fields of the image image.
-   */
-  public ImageGenerator(int imageWidth, String textName, float fontSize, String fontDirPath) {
-    mImageWidth = imageWidth;
-    mImageHeight = INITIAL_HEIGHT;
-    mVerticalOffset = 0;
+        // Initialize the canvas with the default height.
+        mBufferedImage = new BufferedImage(mImageWidth, mImageHeight, BufferedImage.TYPE_BYTE_GRAY);
 
-    // Initialize the canvas with the default height.
-    mBufferedImage = new BufferedImage(mImageWidth, mImageHeight, BufferedImage.TYPE_BYTE_GRAY);
+        mTextName = textName;
+        mFontSize = fontSize;
+        mFontDirPath = fontDirPath;
 
-    mTextName = textName;
-    mFontSize = fontSize;
-    mFontDirPath = fontDirPath;
-  }
-
-  /**
-   * Finds the translated text string for the given textName by parsing the resourceFile.
-   * Example of the xml fields:
-   * <resources xmlns:android="http://schemas.android.com/apk/res/android">
-   *   <string name="recovery_installing_security" msgid="9184031299717114342">
-   * "Sicherheitsupdate wird installiert"</string>
-   * </resources>
-   *
-   * @param resourceFile the input resource file in xml format.
-   * @param textName the name description of the text.
-   *
-   * @return the string representation of the translated text.
-   */
-  private String getTextString(File resourceFile, String textName) throws IOException,
-      ParserConfigurationException, org.xml.sax.SAXException, LocalizedStringNotFoundException {
-    DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
-    DocumentBuilder db = builder.newDocumentBuilder();
-
-    Document doc = db.parse(resourceFile);
-    doc.getDocumentElement().normalize();
-
-    NodeList nodeList = doc.getElementsByTagName("string");
-    for (int i = 0; i < nodeList.getLength(); i++) {
-      Node node = nodeList.item(i);
-      String name = node.getAttributes().getNamedItem("name").getNodeValue();
-      if (name.equals(textName)) {
-        return node.getTextContent();
-      }
+        mCenterAlignment = centerAlignment;
     }
 
-    throw new LocalizedStringNotFoundException(textName + " not found in "
-        + resourceFile.getName());
-  }
+    /**
+     * Finds the translated text string for the given textName by parsing the resourceFile. Example
+     * of the xml fields: <resources xmlns:android="http://schemas.android.com/apk/res/android">
+     * <string name="recovery_installing_security" msgid="9184031299717114342"> "Sicherheitsupdate
+     * wird installiert"</string> </resources>
+     *
+     * @param resourceFile the input resource file in xml format.
+     * @param textName the name description of the text.
+     * @return the string representation of the translated text.
+     */
+    private String getTextString(File resourceFile, String textName)
+            throws IOException, ParserConfigurationException, org.xml.sax.SAXException,
+                    LocalizedStringNotFoundException {
+        DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
+        DocumentBuilder db = builder.newDocumentBuilder();
 
-  /**
-   * Constructs the locale from the name of the resource file.
-   */
-  private Locale getLocaleFromFilename(String filename) throws IOException {
-    // Gets the locale string by trimming the top "values-".
-    String localeString = filename.substring(7);
-    if (localeString.matches("[A-Za-z]+")) {
-      return Locale.forLanguageTag(localeString);
-    }
-    if (localeString.matches("[A-Za-z]+-r[A-Za-z]+")) {
-      // "${Language}-r${Region}". e.g. en-rGB
-      String[] tokens = localeString.split("-r");
-      return Locale.forLanguageTag(String.join("-", tokens));
-    }
-    if (localeString.startsWith("b+")) {
-      // The special case of b+sr+Latn, which has the form "b+${Language}+${ScriptName}"
-      String[] tokens = localeString.substring(2).split("\\+");
-      return Locale.forLanguageTag(String.join("-", tokens));
-    }
+        Document doc = db.parse(resourceFile);
+        doc.getDocumentElement().normalize();
 
-    throw new IOException("Unrecognized locale string " + localeString);
-  }
+        NodeList nodeList = doc.getElementsByTagName("string");
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            Node node = nodeList.item(i);
+            String name = node.getAttributes().getNamedItem("name").getNodeValue();
+            if (name.equals(textName)) {
+                return node.getTextContent();
+            }
+        }
 
-  /**
-   * Iterates over the xml files in the format of values-$LOCALE/strings.xml under the resource
-   * directory and collect the translated text.
-   *
-   * @param resourcePath the path to the resource directory
-   *
-   * @return a map with the locale as key, and translated text as value
-   *
-   * @throws LocalizedStringNotFoundException if we cannot find the translated text for the given
-   *    locale
-   **/
-  public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath) throws
-      IOException, LocalizedStringNotFoundException {
-    File resourceDir = new File(resourcePath);
-    if (!resourceDir.isDirectory()) {
-      throw new LocalizedStringNotFoundException(resourcePath + " is not a directory.");
-    }
-
-    Map<Locale, String> result =
-        new TreeMap<Locale, String>(Comparator.comparing(Locale::toLanguageTag));
-
-    // Find all the localized resource subdirectories in the format of values-$LOCALE
-    String[] nameList = resourceDir.list(
-        (File file, String name) -> name.startsWith("values-"));
-    for (String name : nameList) {
-      File textFile = new File(resourcePath, name + "/strings.xml");
-      String localizedText;
-      try {
-        localizedText = getTextString(textFile, mTextName);
-      } catch (IOException | ParserConfigurationException | org.xml.sax.SAXException e) {
         throw new LocalizedStringNotFoundException(
-            "Failed to read the translated text for locale " + name, e);
-      }
-
-      Locale locale = getLocaleFromFilename(name);
-      // Removes the double quotation mark from the text.
-      result.put(locale, localizedText.substring(1, localizedText.length() - 1));
+                textName + " not found in " + resourceFile.getName());
     }
 
-    return result;
-  }
+    /** Constructs the locale from the name of the resource file. */
+    private Locale getLocaleFromFilename(String filename) throws IOException {
+        // Gets the locale string by trimming the top "values-".
+        String localeString = filename.substring(7);
+        if (localeString.matches("[A-Za-z]+")) {
+            return Locale.forLanguageTag(localeString);
+        }
+        if (localeString.matches("[A-Za-z]+-r[A-Za-z]+")) {
+            // "${Language}-r${Region}". e.g. en-rGB
+            String[] tokens = localeString.split("-r");
+            return Locale.forLanguageTag(String.join("-", tokens));
+        }
+        if (localeString.startsWith("b+")) {
+            // The special case of b+sr+Latn, which has the form "b+${Language}+${ScriptName}"
+            String[] tokens = localeString.substring(2).split("\\+");
+            return Locale.forLanguageTag(String.join("-", tokens));
+        }
 
-  /**
-   * Returns a font object associated given the given locale
-   *
-   * @throws IOException if the font file fails to open
-   * @throws FontFormatException if the font file doesn't have the expected format
-   */
-  private Font loadFontsByLocale(String language) throws IOException, FontFormatException {
-    String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME);
-    String[] suffixes = {".otf", ".ttf", ".ttc"};
-    for (String suffix : suffixes ) {
-      File fontFile = new File(mFontDirPath, fontName + suffix);
-      if (fontFile.isFile()) {
-        return Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize);
-      }
+        throw new IOException("Unrecognized locale string " + localeString);
     }
 
-    throw new IOException("Can not find the font file " + fontName + " for language " + language);
-  }
+    /**
+     * Iterates over the xml files in the format of values-$LOCALE/strings.xml under the resource
+     * directory and collect the translated text.
+     *
+     * @param resourcePath the path to the resource directory
+     * @return a map with the locale as key, and translated text as value
+     * @throws LocalizedStringNotFoundException if we cannot find the translated text for the given
+     *     locale
+     */
+    public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath)
+            throws IOException, LocalizedStringNotFoundException {
+        File resourceDir = new File(resourcePath);
+        if (!resourceDir.isDirectory()) {
+            throw new LocalizedStringNotFoundException(resourcePath + " is not a directory.");
+        }
 
-  /**
-   * Separates the text string by spaces and wraps it by words.
-  **/
-  private List<String> wrapTextByWords(String text, FontMetrics metrics) {
-    List<String> wrappedText = new ArrayList<>();
-    StringTokenizer st = new StringTokenizer(text, " \n");
+        Map<Locale, String> result =
+                // Overrides the string comparator so that sr is sorted behind sr-Latn. And thus
+                // recovery can find the most relevant locale when going down the list.
+                new TreeMap<>(
+                        (Locale l1, Locale l2) -> {
+                            if (l1.toLanguageTag().equals(l2.toLanguageTag())) {
+                                return 0;
+                            }
+                            if (l1.getLanguage().equals(l2.toLanguageTag())) {
+                                return -1;
+                            }
+                            if (l2.getLanguage().equals(l1.toLanguageTag())) {
+                                return 1;
+                            }
+                            return l1.toLanguageTag().compareTo(l2.toLanguageTag());
+                        });
 
-    StringBuilder line = new StringBuilder();
-    while (st.hasMoreTokens()) {
-      String token = st.nextToken();
-      if (metrics.stringWidth(line + token + " ") > mImageWidth) {
+        // Find all the localized resource subdirectories in the format of values-$LOCALE
+        String[] nameList =
+                resourceDir.list((File file, String name) -> name.startsWith("values-"));
+        for (String name : nameList) {
+            File textFile = new File(resourcePath, name + "/strings.xml");
+            String localizedText;
+            try {
+                localizedText = getTextString(textFile, mTextName);
+            } catch (IOException | ParserConfigurationException | org.xml.sax.SAXException e) {
+                throw new LocalizedStringNotFoundException(
+                        "Failed to read the translated text for locale " + name, e);
+            }
+
+            Locale locale = getLocaleFromFilename(name);
+            // Removes the double quotation mark from the text.
+            result.put(locale, localizedText.substring(1, localizedText.length() - 1));
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a font object associated given the given locale
+     *
+     * @throws IOException if the font file fails to open
+     * @throws FontFormatException if the font file doesn't have the expected format
+     */
+    private Font loadFontsByLocale(String language) throws IOException, FontFormatException {
+        String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME);
+        String[] suffixes = {".otf", ".ttf", ".ttc"};
+        for (String suffix : suffixes) {
+            File fontFile = new File(mFontDirPath, fontName + suffix);
+            if (fontFile.isFile()) {
+                return Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize);
+            }
+        }
+
+        throw new IOException(
+                "Can not find the font file " + fontName + " for language " + language);
+    }
+
+    /** Separates the text string by spaces and wraps it by words. */
+    private List<String> wrapTextByWords(String text, FontMetrics metrics) {
+        List<String> wrappedText = new ArrayList<>();
+        StringTokenizer st = new StringTokenizer(text, " \n");
+
+        StringBuilder line = new StringBuilder();
+        while (st.hasMoreTokens()) {
+            String token = st.nextToken();
+            if (metrics.stringWidth(line + token + " ") > mImageWidth) {
+                wrappedText.add(line.toString());
+                line = new StringBuilder();
+            }
+            line.append(token).append(" ");
+        }
         wrappedText.add(line.toString());
-        line = new StringBuilder();
-      }
-      line.append(token).append(" ");
-    }
-    wrappedText.add(line.toString());
 
-    return wrappedText;
-  }
-
-  /**
-   * Wraps the text with a maximum of mImageWidth pixels per line.
-   *
-   * @param text the string representation of text to wrap
-   * @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of
-   *    the text given its string representation
-   *
-   * @return a list of strings with their width smaller than mImageWidth pixels
-   */
-  private List<String> wrapText(String text, FontMetrics metrics) {
-    // TODO(xunchang) handle other cases of text wrapping
-    // 1. RTL languages: "ar"(Arabic), "fa"(Persian), "he"(Hebrew), "iw"(Hebrew), "ur"(Urdu)
-    // 2. Language uses characters: CJK, "lo"(lao), "km"(khmer)
-
-    return wrapTextByWords(text, metrics);
-  }
-
-  /**
-   * Draws the text string on the canvas for given locale.
-   *
-   * @param text the string to draw on canvas
-   * @param locale the current locale tag of the string to draw
-   *
-   * @throws IOException if we cannot find the corresponding font file for the given locale.
-   * @throws FontFormatException if we failed to load the font file for the given locale.
-   */
-  private void drawText(String text, Locale locale) throws IOException, FontFormatException  {
-    Graphics2D graphics = mBufferedImage.createGraphics();
-    graphics.setColor(Color.WHITE);
-    graphics.setRenderingHint(
-        RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
-    graphics.setFont(loadFontsByLocale(locale.getLanguage()));
-
-    System.out.println("Drawing text for locale " + locale + " text " + text);
-
-    FontMetrics fontMetrics = graphics.getFontMetrics();
-    List<String> wrappedText = wrapTextByWords(text, fontMetrics);
-    for (String line : wrappedText) {
-      int lineHeight = fontMetrics.getHeight();
-      // Doubles the height of the image if we are short of space.
-      if (mVerticalOffset + lineHeight >= mImageHeight) {
-        resizeHeight(mImageHeight * 2);
-      }
-
-      // Draws the text at mVerticalOffset and increments the offset with line space.
-      int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent();
-      graphics.drawString(line, 0, baseLine);
-      mVerticalOffset += lineHeight;
-    }
-  }
-
-  /**
-   * Redraws the image with the new height.
-   *
-   * @param height the new height of the image in pixels.
-   */
-  private void resizeHeight(int height) {
-    BufferedImage resizedImage =
-        new BufferedImage(mImageWidth, height, BufferedImage.TYPE_BYTE_GRAY);
-    Graphics2D graphic = resizedImage.createGraphics();
-    graphic.drawImage(mBufferedImage, 0, 0, null);
-    graphic.dispose();
-
-    mBufferedImage = resizedImage;
-    mImageHeight = height;
-  }
-
-  /**
-   *  This function draws the font characters and saves the result to outputPath.
-   *
-   * @param localizedTextMap a map from locale to its translated text string
-   * @param outputPath the path to write the generated image file.
-   *
-   * @throws FontFormatException if there's a format error in one of the font file
-   * @throws IOException if we cannot find the font file for one of the locale, or we failed to
-   *    write the image file.
-   */
-  public void generateImage(Map<Locale, String> localizedTextMap, String outputPath) throws
-      FontFormatException, IOException {
-    for (Locale locale : localizedTextMap.keySet()) {
-      // TODO(xunchang) reprocess the locales for the same language and make the last locale the
-      // catch-all type. e.g. "zh-CN, zh-HK, zh-TW" will become "zh-CN, zh-HK, zh"
-      // Or maybe we don't need to support these variants?
-      drawText(localizedTextMap.get(locale), locale);
+        return wrappedText;
     }
 
-    // TODO(xunchang) adjust the width to save some space if all texts are smaller than imageWidth.
-    resizeHeight(mVerticalOffset);
-    ImageIO.write(mBufferedImage, "png", new File(outputPath));
-  }
+    /** One character is a word for CJK. */
+    private List<String> wrapTextByCharacters(String text, FontMetrics metrics) {
+        List<String> wrappedText = new ArrayList<>();
 
-  public static void printUsage() {
-    System.out.println("Usage: java -jar path_to_jar imageWidth textName fontDirectory"
-        + " resourceDirectory outputFilename");
-  }
+        StringBuilder line = new StringBuilder();
+        for (char token : text.toCharArray()) {
+            if (metrics.stringWidth(line + Character.toString(token)) > mImageWidth) {
+                wrappedText.add(line.toString());
+                line = new StringBuilder();
+            }
+            line.append(token);
+        }
+        wrappedText.add(line.toString());
 
-  public static void main(String[] args) throws NumberFormatException, IOException,
-      FontFormatException, LocalizedStringNotFoundException {
-    if (args.length != 5) {
-      printUsage();
-      System.err.println("We expect 5 arguments, get " + args.length);
-      System.exit(1);
+        return wrappedText;
     }
 
-    // TODO(xunchang) switch to commandline parser
-    int imageWidth = Integer.parseUnsignedInt(args[0]);
+    /**
+     * Wraps the text with a maximum of mImageWidth pixels per line.
+     *
+     * @param text the string representation of text to wrap
+     * @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of
+     *     the text given its string representation
+     * @return a list of strings with their width smaller than mImageWidth pixels
+     */
+    private List<String> wrapText(String text, FontMetrics metrics, String language) {
+        if (LOGOGRAM_LANGUAGE.contains(language)) {
+            return wrapTextByCharacters(text, metrics);
+        }
 
-    ImageGenerator imageGenerator =
-        new ImageGenerator(imageWidth, args[1], DEFAULT_FONT_SIZE, args[2]);
+        return wrapTextByWords(text, metrics);
+    }
 
-    Map<Locale, String> localizedStringMap =
-        imageGenerator.readLocalizedStringFromXmls(args[3]);
-    imageGenerator.generateImage(localizedStringMap, args[4]);
-  }
+    /**
+     * Encodes the information of the text image for |locale|. According to minui/resources.cpp, the
+     * width, height and locale of the image is decoded as: int w = (row[1] << 8) | row[0]; int h =
+     * (row[3] << 8) | row[2]; __unused int len = row[4]; char* loc =
+     * reinterpret_cast<char*>(&row[5]);
+     */
+    private List<Integer> encodeTextInfo(int width, int height, String locale) {
+        List<Integer> info =
+                new ArrayList<>(
+                        Arrays.asList(
+                                width & 0xff,
+                                width >> 8,
+                                height & 0xff,
+                                height >> 8,
+                                locale.length()));
+
+        byte[] localeBytes = locale.getBytes();
+        for (byte b : localeBytes) {
+            info.add((int) b);
+        }
+        info.add(0);
+
+        return info;
+    }
+
+    /** Returns Graphics2D object that uses the given locale. */
+    private Graphics2D createGraphics(Locale locale) throws IOException, FontFormatException {
+        Graphics2D graphics = mBufferedImage.createGraphics();
+        graphics.setColor(Color.WHITE);
+        graphics.setRenderingHint(
+                RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
+        graphics.setFont(loadFontsByLocale(locale.getLanguage()));
+
+        return graphics;
+    }
+
+    /** Returns the maximum screen width needed to fit the given text after wrapping. */
+    private int measureTextWidth(String text, Locale locale)
+            throws IOException, FontFormatException {
+        Graphics2D graphics = createGraphics(locale);
+        FontMetrics fontMetrics = graphics.getFontMetrics();
+        List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
+
+        int textWidth = 0;
+        for (String line : wrappedText) {
+            textWidth = Math.max(textWidth, fontMetrics.stringWidth(line));
+        }
+
+        // This may happen if one single word is larger than the image width.
+        if (textWidth > mImageWidth) {
+            throw new IllegalStateException(
+                    "Wrapped text width "
+                            + textWidth
+                            + " is larger than image width "
+                            + mImageWidth
+                            + " for locale: "
+                            + locale);
+        }
+
+        return textWidth;
+    }
+
+    /**
+     * Draws the text string on the canvas for given locale.
+     *
+     * @param text the string to draw on canvas
+     * @param locale the current locale tag of the string to draw
+     * @throws IOException if we cannot find the corresponding font file for the given locale.
+     * @throws FontFormatException if we failed to load the font file for the given locale.
+     */
+    private void drawText(String text, Locale locale, String languageTag)
+            throws IOException, FontFormatException {
+        System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text);
+
+        Graphics2D graphics = createGraphics(locale);
+        FontMetrics fontMetrics = graphics.getFontMetrics();
+        List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
+
+        // Marks the start y offset for the text image of current locale; and reserves one line to
+        // encode the image metadata.
+        int currentImageStart = mVerticalOffset;
+        mVerticalOffset += 1;
+        for (String line : wrappedText) {
+            int lineHeight = fontMetrics.getHeight();
+            // Doubles the height of the image if we are short of space.
+            if (mVerticalOffset + lineHeight >= mImageHeight) {
+                resize(mImageWidth, mImageHeight * 2);
+            }
+
+            // Draws the text at mVerticalOffset and increments the offset with line space.
+            int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent();
+
+            // Draws from right if it's an RTL language.
+            int x =
+                    mCenterAlignment
+                            ? (mImageWidth - fontMetrics.stringWidth(line)) / 2
+                            : RTL_LANGUAGE.contains(languageTag)
+                                    ? mImageWidth - fontMetrics.stringWidth(line)
+                                    : 0;
+
+            graphics.drawString(line, x, baseLine);
+
+            mVerticalOffset += lineHeight;
+        }
+
+        // Encodes the metadata of the current localized image as pixels.
+        int currentImageHeight = mVerticalOffset - currentImageStart - 1;
+        List<Integer> info = encodeTextInfo(mImageWidth, currentImageHeight, languageTag);
+        for (int i = 0; i < info.size(); i++) {
+            int[] pixel = {info.get(i)};
+            mBufferedImage.getRaster().setPixel(i, currentImageStart, pixel);
+        }
+    }
+
+    /**
+     * Redraws the image with the new width and new height.
+     *
+     * @param width the new width of the image in pixels.
+     * @param height the new height of the image in pixels.
+     */
+    private void resize(int width, int height) {
+        BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
+        Graphics2D graphic = resizedImage.createGraphics();
+        graphic.drawImage(mBufferedImage, 0, 0, null);
+        graphic.dispose();
+
+        mBufferedImage = resizedImage;
+        mImageWidth = width;
+        mImageHeight = height;
+    }
+
+    /**
+     * This function draws the font characters and saves the result to outputPath.
+     *
+     * @param localizedTextMap a map from locale to its translated text string
+     * @param outputPath the path to write the generated image file.
+     * @throws FontFormatException if there's a format error in one of the font file
+     * @throws IOException if we cannot find the font file for one of the locale, or we failed to
+     *     write the image file.
+     */
+    public void generateImage(Map<Locale, String> localizedTextMap, String outputPath)
+            throws FontFormatException, IOException {
+        Map<String, Integer> languageCount = new TreeMap<>();
+        int textWidth = 0;
+        for (Locale locale : localizedTextMap.keySet()) {
+            String language = locale.getLanguage();
+            languageCount.put(language, languageCount.getOrDefault(language, 0) + 1);
+            textWidth = Math.max(textWidth, measureTextWidth(localizedTextMap.get(locale), locale));
+        }
+
+        // Removes the black margins to reduce the size of the image.
+        resize(textWidth, mImageHeight);
+
+        for (Locale locale : localizedTextMap.keySet()) {
+            Integer count = languageCount.get(locale.getLanguage());
+            // Recovery expects en-US instead of en_US.
+            String languageTag = locale.toLanguageTag();
+            if (count == 1) {
+                // Make the last country variant for a given language be the catch-all for that
+                // language.
+                languageTag = locale.getLanguage();
+            } else {
+                languageCount.put(locale.getLanguage(), count - 1);
+            }
+
+            drawText(localizedTextMap.get(locale), locale, languageTag);
+        }
+
+        resize(mImageWidth, mVerticalOffset);
+        ImageIO.write(mBufferedImage, "png", new File(outputPath));
+    }
+
+    /** Prints the helper message. */
+    public static void printUsage(Options options) {
+        new HelpFormatter().printHelp("java -jar path_to_jar [required_options]", options);
+    }
+
+    /** Creates the command line options. */
+    public static Options createOptions() {
+        Options options = new Options();
+        options.addOption(
+                OptionBuilder.withLongOpt("image_width")
+                        .withDescription("The initial width of the image in pixels.")
+                        .hasArgs(1)
+                        .isRequired()
+                        .create());
+
+        options.addOption(
+                OptionBuilder.withLongOpt("text_name")
+                        .withDescription(
+                                "The description of the text string, e.g. recovery_erasing")
+                        .hasArgs(1)
+                        .isRequired()
+                        .create());
+
+        options.addOption(
+                OptionBuilder.withLongOpt("font_dir")
+                        .withDescription(
+                                "The directory that contains all the support font format files, "
+                                        + "e.g. $OUT/system/fonts/")
+                        .hasArgs(1)
+                        .isRequired()
+                        .create());
+
+        options.addOption(
+                OptionBuilder.withLongOpt("resource_dir")
+                        .withDescription(
+                                "The resource directory that contains all the translated strings in"
+                                        + " xml format, e.g."
+                                        + " bootable/recovery/tools/recovery_l10n/res/")
+                        .hasArgs(1)
+                        .isRequired()
+                        .create());
+
+        options.addOption(
+                OptionBuilder.withLongOpt("output_file")
+                        .withDescription("Path to the generated image.")
+                        .hasArgs(1)
+                        .isRequired()
+                        .create());
+
+        options.addOption(
+                OptionBuilder.withLongOpt("center_alignment")
+                        .withDescription("Align the text in the center of the screen.")
+                        .hasArg(false)
+                        .create());
+
+        return options;
+    }
+
+    /** The main function parses the command line options and generates the desired text image. */
+    public static void main(String[] args)
+            throws NumberFormatException, IOException, FontFormatException,
+                    LocalizedStringNotFoundException {
+        Options options = createOptions();
+        CommandLine cmd;
+        try {
+            cmd = new GnuParser().parse(options, args);
+        } catch (ParseException e) {
+            System.err.println(e.getMessage());
+            printUsage(options);
+            return;
+        }
+
+        int imageWidth = Integer.parseUnsignedInt(cmd.getOptionValue("image_width"));
+
+        ImageGenerator imageGenerator =
+                new ImageGenerator(
+                        imageWidth,
+                        cmd.getOptionValue("text_name"),
+                        DEFAULT_FONT_SIZE,
+                        cmd.getOptionValue("font_dir"),
+                        cmd.hasOption("center_alignment"));
+
+        Map<Locale, String> localizedStringMap =
+                imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"));
+        imageGenerator.generateImage(localizedStringMap, cmd.getOptionValue("output_file"));
+    }
 }
-
diff --git a/tools/image_generator/README.md b/tools/image_generator/README.md
index 22e32f6..5d70354 100644
--- a/tools/image_generator/README.md
+++ b/tools/image_generator/README.md
@@ -6,7 +6,8 @@
 emulators with different dpi.
 
 # Usage:
-  `java -jar path_to_jar imageWidth textName fontDirectory resourceDirectory outputFilename`
+  `java -jar path_to_jar --image_width imageWidth --text_name textName --font_dir fontDirectory
+   --resource_dir resourceDirectory --output_file outputFilename`
 
 # Description of the parameters:
 1. `imageWidth`: The number of pixels per line; and the text strings will be
diff --git a/tools/recovery_l10n/res/values-af/strings.xml b/tools/recovery_l10n/res/values-af/strings.xml
index b1974da..85a3c90 100644
--- a/tools/recovery_l10n/res/values-af/strings.xml
+++ b/tools/recovery_l10n/res/values-af/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Geen opdrag nie"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Fout!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installeer tans sekuriteitopdatering"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Kan nie Android-stelsel laai nie. Jou data is dalk korrup. As jy aanhou om hierdie boodskap te kry, sal jy dalk \'n fabrieksterugstelling moet doen en alle gebruikerdata moet uitvee wat op hierdie toestel geberg word."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Probeer weer"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Fabrieksterugstelling"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Vee alle gebruikerdata uit?\n\n DIT KAN NIE ONTDOEN WORD NIE!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Kanselleer"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-am/strings.xml b/tools/recovery_l10n/res/values-am/strings.xml
index 75c17fb..353f223 100644
--- a/tools/recovery_l10n/res/values-am/strings.xml
+++ b/tools/recovery_l10n/res/values-am/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"ምንም ትዕዛዝ የለም"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ስህተት!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"የደህንነት ዝማኔ በመጫን ላይ"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"የAndroid ስርዓትን መጫን አልተቻለም። የእርስዎ ውሂብ የተበላሸ ሊሆን ይችላል። ይህን መልዕክት ማግኘቱን ከቀጠሉ የፋብሪካ ውሂብ ዳግም ማስጀመር ማከናወንና በዚህ መሣሪያ ላይ የተከማቸ ሁሉንም የተጠቃሚ ውሂብ መሰረዝ ሊኖርብዎት ይችላል።"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"እንደገና ሞክር"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"የፋብሪካ ውሂብ ዳግም ማስጀመር"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"ሁሉም የተጠቃሚ ውሂብ ይሰረዝ?\n\n ይህ ሊቀለበስ አይችልም!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"ይቅር"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ar/strings.xml b/tools/recovery_l10n/res/values-ar/strings.xml
index 601b583..2af36d6 100644
--- a/tools/recovery_l10n/res/values-ar/strings.xml
+++ b/tools/recovery_l10n/res/values-ar/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"ليس هناك أي أمر"</string>
     <string name="recovery_error" msgid="5748178989622716736">"خطأ!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"جارٍ تثبيت تحديث الأمان"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"‏يتعذَّر تحميل نظام Android، حيث قد تكون بياناتك تالفة. وإذا استمر ظهور هذه الرسالة، قد يتعيَّن عليك إجراء إعادة الضبط بحسب بيانات المصنع ومحو جميع بيانات المستخدم المُخزَّنة على هذا الجهاز."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"إعادة المحاولة"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"إعادة الضبط بحسب بيانات المصنع"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"هل تريد حجب كل بيانات المستخدم؟\n\n لا يمكن التراجع عن هذا الإجراء."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"إلغاء"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-as/strings.xml b/tools/recovery_l10n/res/values-as/strings.xml
index 2624ceb..33a204d 100644
--- a/tools/recovery_l10n/res/values-as/strings.xml
+++ b/tools/recovery_l10n/res/values-as/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"কোনো আদেশ নাই"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ত্ৰুটি!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"সুৰক্ষা আপডেইট ইনষ্টল কৰি থকা হৈছে"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android ছিষ্টেম ল\'ড কৰিব নোৱাৰি। আপোনাৰ ডেটাত কিবা আসোঁৱাহ থকা যেন লাগিছে। আপুনি যদি এই বাৰ্তাটো পায়েই থাকে, আপুনি নিজৰ ডিভাইচটো ফেক্টৰী ডেটা ৰিছেট কৰি সেইটোত থকা ব্যৱহাৰকাৰীৰ সকলো ডেটা মচিব লগা হ\'ব পাৰে।"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"আকৌ চেষ্টা কৰক"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ফেক্টৰী ডেটা ৰিছেট"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"ব্যৱহাৰকাৰীৰ সকলো ডেটা মচিবনে?\n\n এইটো কৰাৰ পিছত আনডু কৰিব নোৱাৰি!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"বাতিল কৰক"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-az/strings.xml b/tools/recovery_l10n/res/values-az/strings.xml
index c6765a9..35194c4 100644
--- a/tools/recovery_l10n/res/values-az/strings.xml
+++ b/tools/recovery_l10n/res/values-az/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Əmr yoxdur"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Xəta!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Təhlükəsizlik güncəlləməsi yüklənir"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android sistemi yüklənmir. Datanız zədələnə bilər. Bu mesajı yenə qəbul etsəniz, data zavod sıfırlamasını həyata keçirməli və bu cihazda saxlanmış istifadəçi datasının hamısını silməlisiniz."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Yenidən cəhd edin"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Data zavod sıfırlaması"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Bütün istifadəçi datası silinsin?\n\n BU ƏMƏLİYYATI GERİ QAYTARMAQ MÜMKÜN DEYİL!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Ləğv edin"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml b/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml
index c2d8f22..19c6f41 100644
--- a/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml
+++ b/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nema komande"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Greška!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instalira se bezbednosno ažuriranje"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Učitavanje Android sistema nije uspelo. Podaci su možda oštećeni. Ako nastavite da dobijate ovu poruku, možda ćete morati da resetujete uređaj na fabrička podešavanja i obrišete sve podatke korisnika koje čuvate na njemu."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Probaj ponovo"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Resetovanje na fabrička podešavanja"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Želite li da izbrišete sve podatke korisnika?\n\n OVO NE MOŽE DA SE OPOZOVE!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Otkaži"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-be/strings.xml b/tools/recovery_l10n/res/values-be/strings.xml
index 7c0954d..ad14fbe 100644
--- a/tools/recovery_l10n/res/values-be/strings.xml
+++ b/tools/recovery_l10n/res/values-be/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Няма каманды"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Памылка"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Усталёўка абнаўлення сістэмы бяспекі"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Не ўдалося загрузіць сістэму Android. Магчыма, вашы даныя пашкоджаны. Калі вы зноў убачыце гэта паведамленне, скіньце налады прылады да заводскіх значэнняў і сатрыце ўсе карыстальніцкія даныя, якія на ёй захоўваюцца."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Паўтарыць спробу"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Скінуць да заводскіх налад"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Ачысціць усе карыстальніцкія даныя?\n\n ГЭТА ДЗЕЯННЕ НЕЛЬГА АДРАБІЦЬ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Скасаваць"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-bg/strings.xml b/tools/recovery_l10n/res/values-bg/strings.xml
index 9e628a2..e96ff44 100644
--- a/tools/recovery_l10n/res/values-bg/strings.xml
+++ b/tools/recovery_l10n/res/values-bg/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Без команда"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Грешка!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Актуализацията на сигурносттa се инсталира"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Системата Android не може да се зареди. Данните ви може да са повредени. Ако продължите да получавате това съобщение, може да е необходимо да възстановите фабричните настройки и да изтриете всички потребителски данни, съхранени на това устройство."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Нов опит"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Възстановяване на фабричните настройки"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Да се изчистят ли всички потребителски данни?\n\n ТОВА ДЕЙСТВИЕ НЕ МОЖЕ ДА БЪДЕ ОТМЕНЕНО!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Отказ"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-bn/strings.xml b/tools/recovery_l10n/res/values-bn/strings.xml
index 0a481fa..5967bc4 100644
--- a/tools/recovery_l10n/res/values-bn/strings.xml
+++ b/tools/recovery_l10n/res/values-bn/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"কোনো আদেশ নেই"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ত্রুটি!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"নিরাপত্তার আপডেট ইনস্টল করা হচ্ছে"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android সিস্টেম লোড করা যায়নি। আপনার ডেটা হয়ত নষ্ট হয়ে গেছে। যদি এই মেসেজটি আসতেই থাকে তাহলে হয়ত ফ্যাক্টরি ডেটা রিসেট করে এই ডিভাইসে থাকা ব্যবহারকারীর সব ডেটা মুছে ফেলতে হবে।"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"আবার চেষ্টা করুন"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ফ্যাক্টরি ডেটা রিসেট করুন"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"ব্যবহারকারীর সব ডেটা মুছে দিতে চান?\n\n এই ডেটা আর ফিরে পাওয়া যাবে না!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"বাতিল করুন"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-bs/strings.xml b/tools/recovery_l10n/res/values-bs/strings.xml
index 412cf02..38f197f 100644
--- a/tools/recovery_l10n/res/values-bs/strings.xml
+++ b/tools/recovery_l10n/res/values-bs/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nema komande"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Greška!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instaliranje sigurnosnog ažuriranja…"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Nije moguće učitati Android sistem. Podaci su možda oštećeni. Ako opet primite ovu poruku, možda ćete morati vratiti uređaj na fabričke postavke i izbrisati sve podatke korisnika pohranjene na ovom uređaju."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Pokušaj ponovo"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Vraćanje na fabričke postavke"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Izbrisati sve podatke korisnika?\n\n TA RADNJA SE NE MOŽE PONIŠTITI!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Otkaži"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ca/strings.xml b/tools/recovery_l10n/res/values-ca/strings.xml
index 3f266d2..6b7bec0 100644
--- a/tools/recovery_l10n/res/values-ca/strings.xml
+++ b/tools/recovery_l10n/res/values-ca/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"No hi ha cap ordre"</string>
     <string name="recovery_error" msgid="5748178989622716736">"S\'ha produït un error"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"S\'està instal·lant una actualització de seguretat"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"No s\'ha pogut carregar el sistema Android. És possible que les teves dades estiguin malmeses. Si continues veient aquest missatge, pot ser que hagis de restablir les dades de fàbrica i esborrar totes les dades d\'usuari emmagatzemades en aquest dispositiu."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Torna-ho a provar"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Restableix les dades de fàbrica"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Vols eliminar totes les dades d\'usuari?\n\n AQUESTA ACCIÓ NO ES POT DESFER."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancel·la"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-cs/strings.xml b/tools/recovery_l10n/res/values-cs/strings.xml
index eb436a8..c42dab2 100644
--- a/tools/recovery_l10n/res/values-cs/strings.xml
+++ b/tools/recovery_l10n/res/values-cs/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Žádný příkaz"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Chyba!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instalace aktualizace zabezpečení"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Systém Android se nepodařilo načíst. Vaše data jsou možná poškozena. Pokud se tato zpráva bude zobrazovat i nadále, bude nutné vymazat všechna uživatelská data v zařízení a obnovit tovární data."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Zkusit znovu"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Obnovení továrních dat"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Vymazat všechna uživatelská data?\n\nTUTO AKCI NELZE VRÁTIT ZPĚT!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Zrušit"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-da/strings.xml b/tools/recovery_l10n/res/values-da/strings.xml
index c6e64a2..814c0df 100644
--- a/tools/recovery_l10n/res/values-da/strings.xml
+++ b/tools/recovery_l10n/res/values-da/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Ingen kommando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Fejl!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installerer sikkerhedsopdateringen"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android-systemet kan ikke indlæses. Dine data er muligvis beskadigede. Hvis du bliver ved med at få denne meddelelse, er du måske nødt til at udføre en gendannelse af fabriksdata og slette alle brugerdata, der er gemt på denne enhed."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Prøv igen"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Gendannelse af fabriksdata"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Vil du rydde alle brugerdata?\n\n DETTE KAN IKKE FORTRYDES!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Annuller"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-de/strings.xml b/tools/recovery_l10n/res/values-de/strings.xml
index 6b6726a..80fa971 100644
--- a/tools/recovery_l10n/res/values-de/strings.xml
+++ b/tools/recovery_l10n/res/values-de/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Kein Befehl"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Fehler"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Sicherheitsupdate wird installiert"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android-System kann nicht geladen werden. Deine Daten sind eventuell beschädigt. Wenn du diese Nachricht weiterhin erhältst, musst du dein Gerät unter Umständen auf die Werkseinstellungen zurücksetzen und alle darauf gespeicherten Nutzerdaten löschen."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Noch einmal versuchen"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Zurücksetzen auf Werkseinstellungen"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Alle Nutzerdaten löschen?\n\n DIESE AKTION KANN NICHT RÜCKGÄNGIG GEMACHT WERDEN."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Abbrechen"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-el/strings.xml b/tools/recovery_l10n/res/values-el/strings.xml
index 4cb2da5..204ae40 100644
--- a/tools/recovery_l10n/res/values-el/strings.xml
+++ b/tools/recovery_l10n/res/values-el/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Καμία εντολή"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Σφάλμα!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Εγκατάσταση ενημέρωσης ασφαλείας"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Δεν είναι δυνατή η φόρτωση του συστήματος Android. Τα δεδομένα σας μπορεί να είναι κατεστραμμένα. Εάν εξακολουθήσετε να λαμβάνετε αυτό το μήνυμα, μπορεί να χρειαστεί να κάνετε επαναφορά εργοστασιακών ρυθμίσεων και να διαγράψετε όλα τα δεδομένα που έχουν αποθηκευτεί σε αυτήν τη συσκευή."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Δοκιμάστε ξανά"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Επαναφορά εργοστασιακών δεδομένων"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Να διαγραφούν όλα τα δεδομένα χρήστη;\n\n ΔΕΝ ΕΙΝΑΙ ΔΥΝΑΤΗ Η ΑΝΑΙΡΕΣΗ ΑΥΤΗΣ ΤΗΣ ΕΝΕΡΓΕΙΑΣ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Ακύρωση"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-en-rAU/strings.xml b/tools/recovery_l10n/res/values-en-rAU/strings.xml
index dc75c23..6451e5b 100644
--- a/tools/recovery_l10n/res/values-en-rAU/strings.xml
+++ b/tools/recovery_l10n/res/values-en-rAU/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"No command"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Error!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installing security update"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Try again"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Factory data reset"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Wipe all user data?\n\n THIS CANNOT BE UNDONE!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancel"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-en-rCA/strings.xml b/tools/recovery_l10n/res/values-en-rCA/strings.xml
index dc75c23..6451e5b 100644
--- a/tools/recovery_l10n/res/values-en-rCA/strings.xml
+++ b/tools/recovery_l10n/res/values-en-rCA/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"No command"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Error!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installing security update"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Try again"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Factory data reset"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Wipe all user data?\n\n THIS CANNOT BE UNDONE!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancel"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-en-rGB/strings.xml b/tools/recovery_l10n/res/values-en-rGB/strings.xml
index dc75c23..6451e5b 100644
--- a/tools/recovery_l10n/res/values-en-rGB/strings.xml
+++ b/tools/recovery_l10n/res/values-en-rGB/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"No command"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Error!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installing security update"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Try again"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Factory data reset"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Wipe all user data?\n\n THIS CANNOT BE UNDONE!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancel"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-en-rIN/strings.xml b/tools/recovery_l10n/res/values-en-rIN/strings.xml
index dc75c23..6451e5b 100644
--- a/tools/recovery_l10n/res/values-en-rIN/strings.xml
+++ b/tools/recovery_l10n/res/values-en-rIN/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"No command"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Error!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installing security update"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Try again"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Factory data reset"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Wipe all user data?\n\n THIS CANNOT BE UNDONE!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancel"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-en-rXC/strings.xml b/tools/recovery_l10n/res/values-en-rXC/strings.xml
index 2d528b3..61390f1 100644
--- a/tools/recovery_l10n/res/values-en-rXC/strings.xml
+++ b/tools/recovery_l10n/res/values-en-rXC/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎No command‎‏‎‎‏‎"</string>
     <string name="recovery_error" msgid="5748178989622716736">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎Error!‎‏‎‎‏‎"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎Installing security update‎‏‎‎‏‎"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎Cannot load Android system. Your data may be corrupt. If you continue to get this message, you may need to perform a factory data reset and erase all user data stored on this device.‎‏‎‎‏‎"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‎Try again‎‏‎‎‏‎"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎Factory data reset‎‏‎‎‏‎"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‎Wipe all user data?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎ THIS CAN NOT BE UNDONE!‎‏‎‎‏‎"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‎Cancel‎‏‎‎‏‎"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-es-rUS/strings.xml b/tools/recovery_l10n/res/values-es-rUS/strings.xml
index 06b8606..c0baa59 100644
--- a/tools/recovery_l10n/res/values-es-rUS/strings.xml
+++ b/tools/recovery_l10n/res/values-es-rUS/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Ningún comando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Error"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instalando actualización de seguridad"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"No se puede cargar el sistema Android. Es posible que los datos estén dañados. Si este mensaje no desaparece, es posible que debas restablecer la configuración de fábrica del dispositivo y borrar todos los datos del usuario almacenados en él."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Reintentar"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Restablecer configuración de fábrica"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"¿Quieres borrar todos los datos del usuario?\n\n ESTA ACCIÓN NO SE PUEDE DESHACER"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancelar"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-es/strings.xml b/tools/recovery_l10n/res/values-es/strings.xml
index d8618f2..de3b69b 100644
--- a/tools/recovery_l10n/res/values-es/strings.xml
+++ b/tools/recovery_l10n/res/values-es/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Sin comandos"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Error"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instalando actualización de seguridad"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"No se puede cargar el sistema Android. Es posible que tus datos estén dañados. Si sigue apareciendo este mensaje, es posible que tengas que restablecer el estado de fábrica y borrar todos los datos de usuario almacenados en este dispositivo."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Reintentar"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Restablecer estado de fábrica"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"¿Quieres borrar todos los datos de usuario?\n\n ESTA ACCIÓN NO SE PUEDE DESHACER."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancelar"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-et/strings.xml b/tools/recovery_l10n/res/values-et/strings.xml
index 072a9ef..cafb32f 100644
--- a/tools/recovery_l10n/res/values-et/strings.xml
+++ b/tools/recovery_l10n/res/values-et/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Käsk puudub"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Viga!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Turvavärskenduse installimine"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android-süsteemi ei saa laadida. Teie andmed on võib-olla rikutud. Kui jätkate selle sõnumi hankimist, peate võib-olla tegema tehaseandmetele lähtestamise ja kustutama kõik sellesse seadmesse salvestatud kasutajaandmed."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Proovige uuesti"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Tehaseandmetele lähtestamine"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Kas kustutada kõik kasutajaandmed?\n\n SEDA TOIMINGUT EI SAA TAGASI VÕTTA!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Tühista"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-eu/strings.xml b/tools/recovery_l10n/res/values-eu/strings.xml
index 5540469..005a042 100644
--- a/tools/recovery_l10n/res/values-eu/strings.xml
+++ b/tools/recovery_l10n/res/values-eu/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Ez dago agindurik"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Errorea"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Segurtasun-eguneratzea instalatzen"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Ezin da kargatu Android sistema. Zure datuak hondatuta egon daitezke. Mezu hau jasotzen jarraitzen baduzu, jatorrizko datuak berrezarri beharko dituzu eta gailuan gordetako erabiltzaile-datu guztiak ezabatu beharko dituzu."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Saiatu berriro"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Berrezarri jatorrizko datuak"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Erabiltzailearen datu guztiak xahutu nahi dituzu?\n\n EKINTZA HORI EZIN DA DESEGIN!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Utzi"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-fa/strings.xml b/tools/recovery_l10n/res/values-fa/strings.xml
index cc390ae..1c1be9a 100644
--- a/tools/recovery_l10n/res/values-fa/strings.xml
+++ b/tools/recovery_l10n/res/values-fa/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"فرمانی وجود ندارد"</string>
     <string name="recovery_error" msgid="5748178989622716736">"خطا!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"در حال نصب به‌روزرسانی امنیتی"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"‏نمی‌توان سیستم Android را بارگیری کرد. ممکن است داده‌های شما خراب باشند. اگر همچنان این پیام را دریافت می‌کنید، شاید لازم باشد بازنشانی داده‌های کارخانه‌ای انجام دهید و همه داده‌های کاربر را که در این دستگاه ذخیره شده است پاک کنید."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"تلاش مجدد"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"بازنشانی داده‌های کارخانه"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"همه داده‌های کاربر پاک شود؟\n\n این کار قابل‌واگرد نیست!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"لغو"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-fi/strings.xml b/tools/recovery_l10n/res/values-fi/strings.xml
index 5141642..fddaf14 100644
--- a/tools/recovery_l10n/res/values-fi/strings.xml
+++ b/tools/recovery_l10n/res/values-fi/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Ei komentoa"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Virhe!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Asennetaan tietoturvapäivitystä"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android-järjestelmän lataaminen epäonnistui. Datasi voi olla vioittunut. Jos näet tämän viestin toistuvasti, sinun on ehkä palautettava tehdasasetukset ja poistettava kaikki laitteella olevat käyttäjätiedot."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Yritä uudelleen"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Tehdasasetuksien palauttaminen"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Poistetaanko kaikki käyttäjätiedot?\n\nTÄTÄ EI VOI PERUA!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Peruuta"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-fr-rCA/strings.xml b/tools/recovery_l10n/res/values-fr-rCA/strings.xml
index b241529..978e9ff 100644
--- a/tools/recovery_l10n/res/values-fr-rCA/strings.xml
+++ b/tools/recovery_l10n/res/values-fr-rCA/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Aucune commande"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Erreur!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installation de la mise à jour de sécurité en cours..."</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Impossible de charger le système Android. Il se peut que vos données soient corrompues. Si vous continuez de recevoir ce message, vous devrez peut-être effectuer une réinitialisation de l\'appareil à ses paramètres d\'usine et effacer toutes les données d\'utilisateur qu\'il contient."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Réessayer"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Réinitialiser aux paramètres d\'usine"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Effacer toutes les données de l\'utilisateur?\n\n CETTE ACTION NE PEUT PAS ÊTRE ANNULÉE!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Annuler"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-fr/strings.xml b/tools/recovery_l10n/res/values-fr/strings.xml
index f0472b5..693a5dd 100644
--- a/tools/recovery_l10n/res/values-fr/strings.xml
+++ b/tools/recovery_l10n/res/values-fr/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Aucune commande"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Erreur !"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installation de la mise à jour de sécurité…"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Impossible de charger le système Android. Vos données sont peut-être corrompues. Si vous continuez à recevoir ce message, vous devrez peut-être rétablir la configuration d\'usine de votre appareil et effacer toutes les données utilisateur stockées sur cet appareil."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Réessayer"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Rétablir la configuration d\'usine"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Effacer toutes les données utilisateur ?\n\n CETTE ACTION NE PEUT PAS ÊTRE ANNULÉE."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Annuler"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-gl/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml
index 42b2016..e51b36d 100644
--- a/tools/recovery_l10n/res/values-gl/strings.xml
+++ b/tools/recovery_l10n/res/values-gl/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Non hai ningún comando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Erro"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instalando actualización de seguranza"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Non se puido cargar o sistema Android. Os teus datos poden estar danados. Se segue aparecendo esta mensaxe, pode ser necesario restablecer os datos de fábrica e borrar todos os datos de usuario almacenados neste dispositivo."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Tentar de novo"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Restablecemento dos datos de fábrica"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Queres borrar todos os datos de usuario?\n\n ESTA ACCIÓN NON SE PODE DESFACER."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancelar"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-gu/strings.xml b/tools/recovery_l10n/res/values-gu/strings.xml
index 2355a0f..bd83447 100644
--- a/tools/recovery_l10n/res/values-gu/strings.xml
+++ b/tools/recovery_l10n/res/values-gu/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"કોઈ આદેશ નથી"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ભૂલ!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"સુરક્ષા અપડેટ ઇન્સ્ટૉલ કરી રહ્યાં છે"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android સિસ્ટમ લોડ કરી શકાતી નથી. તમારો ડેટા કદાચ દૂષિત થયો હોઈ શકે છે. જો તમને આ સંદેશ મળવાનું ચાલુ રહે, તો કદાચ તમારે આ ડિવાઇસ માટે ફેક્ટરી ડેટા રીસેટ કરવાની પ્રક્રિયા કરવી અને આના પર સ્ટોર કરેલો વપરાશકર્તાનો બધો ડેટા કાઢી નાખવો જરૂરી રહેશે."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"ફરી પ્રયાસ કરો"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ફેક્ટરી ડેટા રીસેટ કરો"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"શું વપરાશકર્તાનો બધો ડેટા વાઇપ કરીએ?\n\n આ ક્રિયામાં કરેલો ફેરફાર રદ કરી શકાતો નથી!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"રદ કરો"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-hi/strings.xml b/tools/recovery_l10n/res/values-hi/strings.xml
index 65d0033..c1aa2e9 100644
--- a/tools/recovery_l10n/res/values-hi/strings.xml
+++ b/tools/recovery_l10n/res/values-hi/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"कोई निर्देश नहीं मिला"</string>
     <string name="recovery_error" msgid="5748178989622716736">"गड़बड़ी!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अपडेट इंस्टॉल किया जा रहा है"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android सिस्टम लोड नहीं किया जा सकता. शायद आपके डेटा में गड़बड़ी है. अगर आपको यह मैसेज मिलता रहता है, तो शायद आपको फ़ैक्ट्री डेटा रीसेट करना पड़े और इस डिवाइस की मेमोरी में मौजूद उपयोगकर्ता का सभी डेटा हमेशा के लिए मिटाना पड़े."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"फिर से कोशिश करें"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"फ़ैक्ट्री डेटा रीसेट"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"क्या उपयोगकर्ता का सभी डेटा मिटाएं?\n\n इसे वापस नहीं लाया जा सकता!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"अभी नहीं"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-hr/strings.xml b/tools/recovery_l10n/res/values-hr/strings.xml
index 3b75ff1..0fa8fa9 100644
--- a/tools/recovery_l10n/res/values-hr/strings.xml
+++ b/tools/recovery_l10n/res/values-hr/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nema naredbe"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Pogreška!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instaliranje sigurnosnog ažuriranja"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Sustav Android ne može se učitati. Podaci su možda oštećeni. Ako opet primite ovu poruku, možda ćete morati vratiti uređaj na tvorničko stanje i izbrisati sve podatke korisnika pohranjene na ovom uređaju."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Pokušaj ponovo"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Vraćanje na tvorničko stanje"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Želite li izbrisati sve podatke korisnika?\n\n TO SE NE MOŽE PONIŠTITI!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Odustani"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-hu/strings.xml b/tools/recovery_l10n/res/values-hu/strings.xml
index 12d4d9f..b7998ce 100644
--- a/tools/recovery_l10n/res/values-hu/strings.xml
+++ b/tools/recovery_l10n/res/values-hu/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nincs parancs"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Hiba!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Biztonsági frissítés telepítése"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Nem sikerült az Android rendszer betöltése. Az adatok sérültek lehetnek. Ha újra megjelenik ez az üzenet, előfordulhat, hogy vissza kell állítania az eszköz gyári adatait, és törölnie kell az eszközön tárolt összes felhasználói adatot."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Újra"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Gyári adatok visszaállítása"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Törli az összes felhasználói adatot?\n\n A MŰVELET NEM VONHATÓ VISSZA."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Mégse"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-hy/strings.xml b/tools/recovery_l10n/res/values-hy/strings.xml
index 9d62bb7..35a0ab1 100644
--- a/tools/recovery_l10n/res/values-hy/strings.xml
+++ b/tools/recovery_l10n/res/values-hy/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Հրամանը տրված չէ"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Սխալ"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Անվտանգության թարմացման տեղադրում"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Չհաջողվեց բեռնել Android համակարգը։ Հնարավոր է՝ ձեր տվյալները վնասված են։ Եթե նորից տեսնեք այս հաղորդագրությունը, փորձեք վերակայել սարքի կարգավորումները և ջնջել օգտատիրոջ բոլոր տվյալները։"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Նորից փորձել"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Վերակայել բոլոր տվյալները"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Մաքրե՞լ օգտատիրոջ բոլոր տվյալները։\n\n ԱՅՍ ԳՈՐԾՈՂՈՒԹՅՈՒՆԸ ՀՆԱՐԱՎՈՐ ՉԻ ԼԻՆԻ ՀԵՏԱՐԿԵԼ"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Չեղարկել"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-in/strings.xml b/tools/recovery_l10n/res/values-in/strings.xml
index 0e56e0d..15a78ec 100644
--- a/tools/recovery_l10n/res/values-in/strings.xml
+++ b/tools/recovery_l10n/res/values-in/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Tidak ada perintah"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Error!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Memasang pembaruan keamanan"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Tidak dapat memuat sistem Android. Data Anda mungkin rusak. Jika terus mendapatkan pesan ini, Anda mungkin perlu melakukan reset ke setelan pabrik dan menghapus semua data pengguna yang disimpan di perangkat ini."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Coba lagi"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Reset ke setelan pabrik"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Wipe semua data pengguna?\n\n TINDAKAN INI TIDAK DAPAT DIURUNGKAN!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Batal"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-is/strings.xml b/tools/recovery_l10n/res/values-is/strings.xml
index 5065b65..4a6295a 100644
--- a/tools/recovery_l10n/res/values-is/strings.xml
+++ b/tools/recovery_l10n/res/values-is/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Engin skipun"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Villa!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Setur upp öryggisuppfærslu"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Ekki er hægt að hlaða Android kerfi. Gögnin þín kunna að vera skemmd. Ef þessi skilaboð halda áfram að birtast gætirðu þurft að núllstilla og eyða öllum notandagögnum sem eru vistuð í þessu tæki."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Reyna aftur"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Núllstilling"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Viltu eyða öllum notandagögnum?\n\n EKKI ER HÆGT AÐ AFTURKALLA ÞETTA!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Hætta við"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-it/strings.xml b/tools/recovery_l10n/res/values-it/strings.xml
index 2c0364e..8bc203c 100644
--- a/tools/recovery_l10n/res/values-it/strings.xml
+++ b/tools/recovery_l10n/res/values-it/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nessun comando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Errore!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installazione aggiornamento sicurezza…"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Impossibile caricare il sistema Android. I tuoi dati potrebbero essere danneggiati. Se continui a ricevere questo messaggio, potrebbe essere necessario eseguire un ripristino dei dati di fabbrica e cancellare tutti i dati utente memorizzati su questo dispositivo."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Riprova"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Ripristino dati di fabbrica"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Vuoi cancellare tutti i dati utente?\n\n NON È POSSIBILE ANNULLARE L\'OPERAZIONE."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Annulla"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-iw/strings.xml b/tools/recovery_l10n/res/values-iw/strings.xml
index ea5e6f2..8ca3bdf 100644
--- a/tools/recovery_l10n/res/values-iw/strings.xml
+++ b/tools/recovery_l10n/res/values-iw/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"אין פקודה"</string>
     <string name="recovery_error" msgid="5748178989622716736">"שגיאה!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"מתקין עדכון אבטחה"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"‏לא ניתן לטעון את מערכת Android. ייתכן שהנתונים שלך פגומים. אם הודעה זו תופיע שוב, ייתכן שיהיה עליך לבצע איפוס לנתוני היצרן ולמחוק את כל נתוני המשתמש ששמורים במכשיר זה."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"ניסיון נוסף"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"איפוס לנתוני היצרן"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"לאפס את כל נתוני המשתמש?\n\n לא ניתן לבטל פעולה זו!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"ביטול"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml
index 36e029b..3d66372 100644
--- a/tools/recovery_l10n/res/values-ja/strings.xml
+++ b/tools/recovery_l10n/res/values-ja/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"コマンドが指定されていません"</string>
     <string name="recovery_error" msgid="5748178989622716736">"エラーが発生しました。"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"セキュリティ アップデートをインストールしています"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android システムを読み込めません。データが破損している可能性があります。このメッセージが引き続き表示される場合は、データの初期化を行い、この端末に保存されているすべてのユーザー データを消去することが必要な場合があります。"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"再試行"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"データの初期化"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"すべてのユーザー データをワイプしますか?\n\nこの操作は元に戻せません。"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"キャンセル"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ka/strings.xml b/tools/recovery_l10n/res/values-ka/strings.xml
index 6a46b36..04b8a41 100644
--- a/tools/recovery_l10n/res/values-ka/strings.xml
+++ b/tools/recovery_l10n/res/values-ka/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"ბრძანება არ არის"</string>
     <string name="recovery_error" msgid="5748178989622716736">"წარმოიქმნა შეცდომა!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"მიმდინარეობს უსაფრთხოების განახლების ინსტალაცია"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android სისტემის ჩატვირთვა ვერ მოხერხდა. შესაძლოა თქვენი მონაცემები დაზიანებულია. თუ ამ შეტყობინებას კვლავ მიიღებთ, შეიძლება საჭირო იყოს ქარხნული მონაცემების აღდგენა და ამ მოწყობილობაზე შენახული მომხმარებლის ყველა მონაცემის ამოშლა."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"ხელახლა ცდა"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ქარხნული მონაცემების აღდგენა"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"გსურთ მომხმარებლის ყველა მონაცემის ამოშლა?\n\n ამ მოქმედების გაუქმება ვერ მოხერხდება!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"გაუქმება"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-kk/strings.xml b/tools/recovery_l10n/res/values-kk/strings.xml
index a4bd86e..3f6aa23 100644
--- a/tools/recovery_l10n/res/values-kk/strings.xml
+++ b/tools/recovery_l10n/res/values-kk/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Пәрмен жоқ"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Қате!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Қауіпсіздік жаңартуы орнатылуда"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android жүйесі жүктелмейді. Деректеріңіз бүлінген болуы мүмкін. Егер осы хабар қайта шықса, зауыттық деректерді қалпына келтіріп, пайдаланушы деректерін жойып көріңіз."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Қайталау"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Зауыттық деректерді қалпына келтіру"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Пайдаланушының барлық деректері жойылсын ба?\n\n БҰЛ ӘРЕКЕТТІ ҚАЙТАРЫЛМАЙДЫ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Бас тарту"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-km/strings.xml b/tools/recovery_l10n/res/values-km/strings.xml
index 313c0f4..0cedb6b 100644
--- a/tools/recovery_l10n/res/values-km/strings.xml
+++ b/tools/recovery_l10n/res/values-km/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"គ្មានពាក្យបញ្ជាទេ"</string>
     <string name="recovery_error" msgid="5748178989622716736">"កំហុស!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"កំពុងដំឡើងការអាប់ដេតសុវត្ថិភាព"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"មិនអាច​ផ្ទុកប្រព័ន្ធ Android បានទេ។ ទិន្នន័យ​របស់​អ្នកអាច​នឹងខូច។ ប្រសិនបើ​អ្នក​បន្តទទួល​បានសារនេះ អ្នកអាចនឹងត្រូវកំណត់​ទិន្នន័យ​ដូច​ចេញ​ពី​រោងចក្រ និងលុបទិន្នន័យ​ទាំងអស់​របស់អ្នក​ប្រើប្រាស់​ដែលបានផ្ទុកនៅ​លើ​ឧបករណ៍​នេះ។"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"ព្យាយាម​ម្ដងទៀត"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"កំណត់​ទិន្នន័យ​ដូច​ចេញ​ពី​រោងចក្រ"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"ឈូស​ទិន្នន័យ​ទាំងអស់​របស់អ្នក​ប្រើប្រាស់?\n\nសកម្មភាព​នេះមិនអាចត្រឡប់វិញបានទេ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"បោះបង់"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-kn/strings.xml b/tools/recovery_l10n/res/values-kn/strings.xml
index 5bf6260..a98f469 100644
--- a/tools/recovery_l10n/res/values-kn/strings.xml
+++ b/tools/recovery_l10n/res/values-kn/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"ಯಾವುದೇ ಆದೇಶವಿಲ್ಲ"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ದೋಷ!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"ಭದ್ರತೆಯ ಅಪ್‌ಡೇಟ್‌ ಸ್ಥಾಪಿಸಲಾಗುತ್ತಿದೆ"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android ಸಿಸ್ಟಂ ಅನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಿಮ್ಮ ಡೇಟಾ ದೋಷಪೂರಿತವಾಗಿರಬಹುದು. ನೀವು ಈ ಸಂದೇಶ ಪಡೆಯುವುದು ಮುಂದುವರಿದರೆ, ನೀವು ಫ್ಯಾಕ್ಟರಿ ಡೇಟಾ ರಿಸೆಟ್ ಮಾಡುವ ಅಗತ್ಯವಿದೆ ಮತ್ತು ಈ ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾದ ಎಲ್ಲಾ ಬಳಕೆದಾರರ ಡೇಟಾವನ್ನು ಅಳಿಸಬೇಕಾಗುತ್ತದೆ."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ಫ್ಯಾಕ್ಟರಿ ಡೇಟಾ ರಿಸೆಟ್‌"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"ಎಲ್ಲಾ ಬಳಕೆದಾರರ ಡೇಟಾವನ್ನು ಅಳಿಸುವುದೇ?\n\n ಇದನ್ನು ರದ್ದುಗೊಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"ರದ್ದುಮಾಡಿ"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ko/strings.xml b/tools/recovery_l10n/res/values-ko/strings.xml
index aca13bb..9067f4c 100644
--- a/tools/recovery_l10n/res/values-ko/strings.xml
+++ b/tools/recovery_l10n/res/values-ko/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"명령어 없음"</string>
     <string name="recovery_error" msgid="5748178989622716736">"오류!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"보안 업데이트 설치 중"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android 시스템을 로드할 수 없습니다. 데이터가 손상되었을 수 있습니다. 이 메시지가 계속 표시되면 초기화를 실행하여 기기에 저장된 사용자 데이터를 모두 삭제해야 할 수도 있습니다."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"다시 시도"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"초기화"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"사용자 데이터를 모두 삭제하시겠습니까?\n\n 이 작업은 실행취소할 수 없습니다."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"취소"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ky/strings.xml b/tools/recovery_l10n/res/values-ky/strings.xml
index 0a6bd78..1cd69ea 100644
--- a/tools/recovery_l10n/res/values-ky/strings.xml
+++ b/tools/recovery_l10n/res/values-ky/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Буйрук берилген жок"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Ката!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Коопсуздук жаңыртуусу орнотулууда"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android тутуму жүктөлбөй жатат. Дайындарыңыз бузук болушу мүмкүн. Бул билдирүү дагы деле келе берсе, түзмөктү кайра башынан жөндөп, анда сакталган бардык колдонуучу дайындарын тазалашыңыз керек."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Кайталоо"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Кайра башынан жөндөө"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Колдонуучу дайындарынын баары жашырылсынбы?\n\n МУНУ АРТКА КАЙТАРУУ МҮМКҮН ЭМЕС!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Жок"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-lo/strings.xml b/tools/recovery_l10n/res/values-lo/strings.xml
index d3dbb39..4a81427 100644
--- a/tools/recovery_l10n/res/values-lo/strings.xml
+++ b/tools/recovery_l10n/res/values-lo/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"ບໍ່ມີຄຳສັ່ງ"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ຜິດພາດ!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"ກຳລັງຕິດຕັ້ງອັບເດດຄວາມປອດໄພ"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"ບໍ່ສາມາດໂຫຼດລະບົບ Android ໄດ້. ຂໍ້ມູນຂອງທ່ານອາດເສຍຫາຍ. ຫາກທ່ານຍັງໄດ້ຮັບຂໍ້ຄວາມນີ້ຕໍ່ໄປ, ທ່ານອາດຕ້ອງຣີເຊັດເປັນຄ່າຈາກໂຮງງານ ແລະ ລຶບຂໍ້ມູນຜູ້ໃຊ້ທັງໝົດທີ່ຈັດເກັບໄວ້ຢູ່ອຸປະກອນນີ້ອອກ."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"ລອງໃໝ່"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ຣີເຊັດຄ່າຈາກໂຮງງານ"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"ລຶບລ້າງຂໍ້ມູນຜູ້ໃຊ້ທັງໝົດບໍ?\n\n ຄຳສັ່ງນີ້ຈະບໍ່ສາມາດຍົກເລີກໄດ້!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"ຍົກເລີກ"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-lt/strings.xml b/tools/recovery_l10n/res/values-lt/strings.xml
index d5d5e88..f9b7d39 100644
--- a/tools/recovery_l10n/res/values-lt/strings.xml
+++ b/tools/recovery_l10n/res/values-lt/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nėra jokių komandų"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Klaida!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Diegiamas saugos naujinys"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Negalima įkelti „Android“ sistemos. Duomenys gali būti pažeisti. Jei ir toliau gausite šį pranešimą, jums gali reikėti atkurti gamyklinius duomenis ir ištrinti visus naudotojo duomenis, saugomus šiame įrenginyje."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Bandyti dar kartą"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Gamyklinių duomenų atkūrimas"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Išvalyti visus naudotojo duomenis?\n\n ŠIO VEIKSMO NEGALIMA ANULIUOTI!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Atšaukti"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-lv/strings.xml b/tools/recovery_l10n/res/values-lv/strings.xml
index d877f6a..6cf8ce3 100644
--- a/tools/recovery_l10n/res/values-lv/strings.xml
+++ b/tools/recovery_l10n/res/values-lv/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nav nevienas komandas"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Kļūda!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Notiek drošības atjauninājuma instalēšana"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Nevar ielādēt Android sistēmu. Jūsu dati var būt bojāti. Ja šis ziņojums tiek rādīts atkārtoti, iespējams, jums ir jāveic rūpnīcas datu atiestatīšana un jādzēš visi šajā ierīcē saglabātie lietotāja dati."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Mēģināt vēlreiz"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Rūpnīcas datu atiestatīšana"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Vai dzēst visus lietotāja datus?\n\n ŠO DARBĪBU NEVAR ATSAUKT!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Atcelt"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-mk/strings.xml b/tools/recovery_l10n/res/values-mk/strings.xml
index 3514597..ff56131 100644
--- a/tools/recovery_l10n/res/values-mk/strings.xml
+++ b/tools/recovery_l10n/res/values-mk/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Нема наредба"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Грешка!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Се инсталира безбедносно ажурирање"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Не може да се вчита системот Android. Можно е податоците да се оштетени. Ако и понатаму ја примате поракава, можеби ќе треба да извршите ресетирање на фабрички податоци и да ги избришете сите кориснички податоци меморирани на уредов."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Обиди се пак"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Ресетирање на фабрички податоци"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Да се избришат ли сите кориснички податоци?\n\n ОВА НЕ МОЖЕ ДА СЕ ВРАТИ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Откажи"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ml/strings.xml b/tools/recovery_l10n/res/values-ml/strings.xml
index b506e25..2b331ac 100644
--- a/tools/recovery_l10n/res/values-ml/strings.xml
+++ b/tools/recovery_l10n/res/values-ml/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"കമാൻഡ് ഒന്നുമില്ല"</string>
     <string name="recovery_error" msgid="5748178989622716736">"പിശക്!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"സുരക്ഷാ അപ്ഡേറ്റ് ഇൻസ്റ്റാൾ ചെയ്യുന്നു"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android സിസ്‌റ്റം ലോഡ് ചെയ്യാനാവില്ല. നിങ്ങളുടെ ഡാറ്റ കേടായിരിക്കാം. ഈ സന്ദേശം തുടർന്നും ലഭിക്കുകയാണെങ്കിൽ, നിങ്ങൾ ഒരു ഫാക്‌ടറി ഡാറ്റ പുനഃക്രമീകരണം നടത്തേണ്ടതുണ്ട് ഒപ്പം ഈ ഉപകരണത്തിൽ സ്‌റ്റോർ ചെയ്‌തിട്ടുള്ള എല്ലാ ഉപയോക്തൃ ഡാറ്റകളും മായ്‌ക്കേണ്ടതുണ്ട്."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"വീണ്ടും ശ്രമിക്കുക"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ഫാക്‌ടറി ഡാറ്റ പുനഃക്രമീകരണം"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"എല്ലാ ഉപയോക്തൃ ഡാറ്റകളും മായ്‌ക്കണോ?\n\n ഇത് പഴയപടിയാക്കാനാവില്ല!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"റദ്ദാക്കുക"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-mn/strings.xml b/tools/recovery_l10n/res/values-mn/strings.xml
index e3dd2e9..b0a57ed 100644
--- a/tools/recovery_l10n/res/values-mn/strings.xml
+++ b/tools/recovery_l10n/res/values-mn/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Тушаал байхгүй"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Алдаа!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Аюулгүй байдлын шинэчлэлтийг суулгаж байна"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Андройд системийг ачаалах боломжгүй байна. Таны өгөгдөл эвдэрч болзошгүй. Хэрэв та энэ мессежийг үргэлжлүүлэн авах бол үйлдвэрээс гарсан төлөвийг ажиллуулж, энэ төхөөрөмжид хадгалсан хэрэглэгчийн бүх өгөгдлийг устгах шаардлагатай байж болзошгүй."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Дахин оролдох"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Үйлдвэрээс гарсан төлөвт"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Хэрэглэгчийн бүх өгөгдлийг арчих уу?\n\n ҮҮНИЙГ БУЦААХ БОЛОМЖГҮЙ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Цуцлах"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-mr/strings.xml b/tools/recovery_l10n/res/values-mr/strings.xml
index 5f82033..9b13707 100644
--- a/tools/recovery_l10n/res/values-mr/strings.xml
+++ b/tools/recovery_l10n/res/values-mr/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"कोणतीही कमांड नाही"</string>
     <string name="recovery_error" msgid="5748178989622716736">"एरर!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा अपडेट इंस्टॉल करत आहे"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android सिस्टम लोड करू शकत नाही. तुमचा डेटा धोक्यात असू शकतो.तुम्हाला हा मेसेज मिळत राहिल्यास, फॅक्टरी डेटा रीसेट करणे आणि या डिव्हाइसवर स्टोअर केलेला सर्व वापरकर्ता डेटा मिटवणे आवश्यक आहे."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"पुन्हा प्रयत्न करा"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"फॅक्‍टरी डेटा रीसेट"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"सर्व वापरकर्ता डेटा पुसून टाकायचा का?\n\n हे पहिल्‍यासारखे करू शकत नाही!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"रद्द करा"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ms/strings.xml b/tools/recovery_l10n/res/values-ms/strings.xml
index 0e24ac4..d094f54 100644
--- a/tools/recovery_l10n/res/values-ms/strings.xml
+++ b/tools/recovery_l10n/res/values-ms/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Tiada perintah"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Ralat!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Memasang kemas kini keselamatan"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Tidak dapat memuatkan sistem Android. Data anda mungkin rosak. Jika anda menerima mesej ini secara berterusan, anda mungkin perlu melaksanakan tetapan semula data kilang dan memadamkan semua data pengguna yang disimpan pada peranti ini."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Cuba lagi"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Tetapan semula data kilang"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Lapkan semua data pengguna?\n\n TINDAKAN INI TIDAK BOLEH DIBUAT ASAL!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Batal"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-my/strings.xml b/tools/recovery_l10n/res/values-my/strings.xml
index f137524..09cd4ea 100644
--- a/tools/recovery_l10n/res/values-my/strings.xml
+++ b/tools/recovery_l10n/res/values-my/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"ညွှန်ကြားချက်မပေးထားပါ"</string>
     <string name="recovery_error" msgid="5748178989622716736">"မှားနေပါသည်!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"လုံခြုံရေး အပ်ဒိတ်ကို ထည့်သွင်းနေသည်"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android စနစ် ဖွင့်၍မရပါ။ သင့်ဒေတာများ ပျက်နေခြင်း ဖြစ်နိုင်သည်။ ဤမက်ဆေ့ဂျ် ဆက်လက်ရရှိနေလျှင် စက်ရုံထုတ်အခြေအနေပြန်ယူပြီး ဤစက်ပေါ်တွင် သိမ်းထားသော အသုံးပြုသူဒေတာအားလုံး ဖျက်ရန် လိုအပ်နိုင်သည်။"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"ထပ်စမ်းကြည့်ပါ"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"စက်ရုံထုတ်အခြေအနေပြန်ယူခြင်း"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"အသုံးပြုသူဒေတာ အားလုံးကို ရှင်းလင်းမလား။\n\n ၎င်းကို ပြန်ပြင်၍မရပါ။"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"မလုပ်တော့"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-nb/strings.xml b/tools/recovery_l10n/res/values-nb/strings.xml
index ad6f20e..e8cad13 100644
--- a/tools/recovery_l10n/res/values-nb/strings.xml
+++ b/tools/recovery_l10n/res/values-nb/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Ingen kommandoer"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Feil!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Installerer sikkerhetsoppdateringen"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Kan ikke laste inn Android-systemet. Dataene dine er muligens skadet. Hvis du fortsetter å se denne meldingen, må du muligens tilbakestille til fabrikkstandard og tømme alle brukerdataene som er lagret på denne enheten."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Prøv igjen"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Tilbakestill til fabrikkstandard"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Vil du viske ut alle brukerdataene?\n\n DETTE KAN IKKE ANGRES!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Avbryt"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ne/strings.xml b/tools/recovery_l10n/res/values-ne/strings.xml
index 1880e80..fa53e9d 100644
--- a/tools/recovery_l10n/res/values-ne/strings.xml
+++ b/tools/recovery_l10n/res/values-ne/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"कुनै आदेश छैन"</string>
     <string name="recovery_error" msgid="5748178989622716736">"त्रुटि!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"सुरक्षा सम्बन्धी अद्यावधिकलाई स्थापना गर्दै"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android प्रणाली लोड गर्न सकिएन। तपाईंको डेटा बिग्रेको हुन सक्छ। तपाईं यो सन्देश प्राप्त गर्नुहुन्छ भने तपाईंले फ्याक्ट्री डेटा रिसेट गर्न आवश्यक छ र यो यन्त्रमा भण्डारण गरेका सबै प्रयोगकर्ताको डेटा मेट्न पर्छ।"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"फेरि प्रयास गर्नुहोस्"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"फ्याक्ट्री डेटा रिसेट"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"प्रयोगकर्ताको सबै डेटा मेट्ने हो?\n\n यो अन्डू गर्न सकिँदैन!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"रद्द गर्नुहोस्"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-nl/strings.xml b/tools/recovery_l10n/res/values-nl/strings.xml
index 0d6c15a..b42bb65 100644
--- a/tools/recovery_l10n/res/values-nl/strings.xml
+++ b/tools/recovery_l10n/res/values-nl/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Geen opdracht"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Fout!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Beveiligingsupdate installeren"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Kan het Android-systeem niet laden. Je gegevens zijn mogelijk beschadigd. Als je dit bericht blijft ontvangen, moet je mogelijk de fabrieksinstellingen terugzetten en alle gebruikersgegevens wissen die op dit apparaat zijn opgeslagen."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Opnieuw proberen"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Terugzetten op fabrieksinstellingen"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Alle gebruikersgegevens wissen?\n\n DIT KAN NIET ONGEDAAN WORDEN GEMAAKT."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Annuleren"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-or/strings.xml b/tools/recovery_l10n/res/values-or/strings.xml
index 2b0851c..25b28e6 100644
--- a/tools/recovery_l10n/res/values-or/strings.xml
+++ b/tools/recovery_l10n/res/values-or/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"କୌଣସି କମାଣ୍ଡ ନାହିଁ"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ତ୍ରୁଟି!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"ସୁରକ୍ଷା ଅପ୍‌ଡେଟ୍‌ ଇନ୍‌ଷ୍ଟଲ୍‌ କରୁଛି"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android ସିଷ୍ଟମ୍‍ ଲୋଡ୍‍ କରାଯାଇପାରିବ ନାହିଁ। ଆପଣଙ୍କ ଡାଟା ହୁଏତ ତ୍ରୁଟି ରହିଥାଇ ପାରେ। ଯଦି ଆପଣ ଏହି ମେସେଜ୍‍ ପାଇବା ଜାରି ରଖନ୍ତି, ତେବେ ଆପଣଙ୍କୁ ଫ୍ୟାକ୍ଟେରୀ ଡାଟା ରିସେଟ୍‍ କରିବାକୁ ହେବ ଏବଂ ଏହି ଡିଭାଇସ୍‍‍ରେ ଷ୍ଟୋର୍‍ ହୋଇଥିବା ସମସ୍ତ ଡାଟା ଇରେଜ୍‍ କରନ୍ତୁ।"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ଫ୍ୟାକ୍ଟୋରୀ ଡାଟା ରିସେଟ୍‌"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"ସମସ୍ଯ ଉପଯୋଗକର୍ତ୍ତା ଡାଟା ୱାଇପ୍‍ କରିବେ?\n\n ଏହା ଫେରାଇ ନିଆଯାଇପାରିବ ନାହିଁ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"ବାତିଲ୍‌ କରନ୍ତୁ"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-pa/strings.xml b/tools/recovery_l10n/res/values-pa/strings.xml
index 27972d1..3743068 100644
--- a/tools/recovery_l10n/res/values-pa/strings.xml
+++ b/tools/recovery_l10n/res/values-pa/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"ਕੋਈ ਆਦੇਸ਼ ਨਹੀਂ"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ਅਸ਼ੁੱਧੀ!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android ਸਿਸਟਮ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਸ਼ਾਇਦ ਤੁਹਾਡਾ ਡਾਟਾ ਖਰਾਬ ਹੈ। ਜੇਕਰ ਤੁਹਾਨੂੰ ਇਹ ਸੁਨੇਹਾ ਪ੍ਰਾਪਤ ਹੋਣਾ ਜਾਰੀ ਰਹਿੰਦਾ ਹੈ, ਤਾਂ ਸ਼ਾਇਦ ਤੁਹਾਨੂੰ ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰਨਾ ਪਵੇ ਅਤੇ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ ਵਰਤੋਂਕਾਰ ਡਾਟੇ ਨੂੰ ਮਿਟਾਉਣਾ ਪਵੇ।"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰੋ"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"ਕੀ ਸਾਰਾ ਵਰਤੋਂਕਾਰ ਡਾਟਾ ਸਾਫ਼ ਕਰਨਾ ਹੈ?\n\n ਇਸਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"ਰੱਦ ਕਰੋ"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-pl/strings.xml b/tools/recovery_l10n/res/values-pl/strings.xml
index 8d6db38..48d3dbf 100644
--- a/tools/recovery_l10n/res/values-pl/strings.xml
+++ b/tools/recovery_l10n/res/values-pl/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Brak polecenia"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Błąd"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instaluję aktualizację zabezpieczeń"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Nie można załadować systemu Android. Dane mogą być uszkodzone. Jeśli ten komunikat nadal będzie się pojawiać, może być konieczne przywrócenie danych fabrycznych urządzenia i usunięcie wszystkich zapisanych na nim danych użytkownika."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Ponów próbę"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Przywracanie danych fabrycznych"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Wyczyścić wszystkie dane użytkownika?\n\n TEJ CZYNNOŚCI NIE MOŻNA COFNĄĆ."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Anuluj"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-pt-rBR/strings.xml b/tools/recovery_l10n/res/values-pt-rBR/strings.xml
index b727043..0df3edc 100644
--- a/tools/recovery_l10n/res/values-pt-rBR/strings.xml
+++ b/tools/recovery_l10n/res/values-pt-rBR/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nenhum comando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Erro!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instalando atualização de segurança"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Não é possível carregar o sistema Android. Seus dados podem estar corrompidos. Se você continuar recebendo esta mensagem, talvez seja necessário realizar uma redefinição para a configuração original e limpar todos os dados do usuário armazenados neste dispositivo."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Tentar novamente"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Redefinição para configuração original"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Limpar todos os dados do usuário?\n\n NÃO É POSSÍVEL DESFAZER ESSA AÇÃO."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancelar"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-pt-rPT/strings.xml b/tools/recovery_l10n/res/values-pt-rPT/strings.xml
index 9814637..08eb3c9 100644
--- a/tools/recovery_l10n/res/values-pt-rPT/strings.xml
+++ b/tools/recovery_l10n/res/values-pt-rPT/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nenhum comando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Erro!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"A instalar atualização de segurança"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Não é possível carregar o sistema Android. Os seus dados podem estar danificados. Se continuar a receber esta mensagem, pode ter de efetuar uma reposição de dados de fábrica e apagar todos os dados do utilizador armazenados neste dispositivo."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Tentar novamente"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Reposição de dados de fábrica"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Pretende limpar todos os dados do utilizador?\n\n NÃO É POSSÍVEL ANULAR ESTA AÇÃO."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancelar"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-pt/strings.xml b/tools/recovery_l10n/res/values-pt/strings.xml
index b727043..0df3edc 100644
--- a/tools/recovery_l10n/res/values-pt/strings.xml
+++ b/tools/recovery_l10n/res/values-pt/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nenhum comando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Erro!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Instalando atualização de segurança"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Não é possível carregar o sistema Android. Seus dados podem estar corrompidos. Se você continuar recebendo esta mensagem, talvez seja necessário realizar uma redefinição para a configuração original e limpar todos os dados do usuário armazenados neste dispositivo."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Tentar novamente"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Redefinição para configuração original"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Limpar todos os dados do usuário?\n\n NÃO É POSSÍVEL DESFAZER ESSA AÇÃO."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Cancelar"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ro/strings.xml b/tools/recovery_l10n/res/values-ro/strings.xml
index 8032865..585db83 100644
--- a/tools/recovery_l10n/res/values-ro/strings.xml
+++ b/tools/recovery_l10n/res/values-ro/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nicio comandă"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Eroare!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Se instalează actualizarea de securitate"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Nu se poate încărca sistemul Android. Datele dvs. pot fi corupte. Dacă primiți în continuare acest mesaj, poate fi necesar să reveniți la setările din fabrică și să ștergeți toate datele utilizatorului stocate pe acest dispozitiv."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Reîncercați"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Revenire la setările din fabrică"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Ștergeți toate datele utilizatorului?\n\n ACEST LUCRU NU POATE FI ANULAT!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Anulați"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ru/strings.xml b/tools/recovery_l10n/res/values-ru/strings.xml
index feebecf..db8b761 100644
--- a/tools/recovery_l10n/res/values-ru/strings.xml
+++ b/tools/recovery_l10n/res/values-ru/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Команды нет"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Ошибка"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Установка обновления системы безопасности…"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Не удалось загрузить систему Android. Возможно, данные повреждены. Если вы снова увидите это сообщение, попробуйте сбросить настройки устройства и удалить все пользовательские данные."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Повторить попытку"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Сбросить настройки"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Стереть все пользовательские данные?\n\nЭТО ДЕЙСТВИЕ НЕЛЬЗЯ ОТМЕНИТЬ."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Отмена"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-si/strings.xml b/tools/recovery_l10n/res/values-si/strings.xml
index 456cdc5..67aca72 100644
--- a/tools/recovery_l10n/res/values-si/strings.xml
+++ b/tools/recovery_l10n/res/values-si/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"විධානයක් නොමැත"</string>
     <string name="recovery_error" msgid="5748178989622716736">"දෝෂය!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"ආරක්ෂක යාවත්කාලීනය ස්ථාපනය කරමින්"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android පද්ධතිය පූරණය කළ නොහැකිය. ඔබේ දත්ත දූෂිත විය හැකිය. ඔබට මෙම පණිවිඩය දිගටම ලැබෙන්නේ නම්, කර්මාන්ත ශාලා දත්ත යළි සැකසීමක් සිදු කර මෙම උපාංගයේ ගබඩා කළ සියලු පරිශීලක දත්ත මකා දැමීමට ඔබට අවශ්‍ය විය හැකිය."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"නැවත උත්සාහ කරන්න"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"කර්මාන්ත ශාලා දත්ත යළි සැකසීම"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"සියලු පරිශීලක දත්ත මකා දමන්නද?\n\n මෙය පසුගමනය කළ නොහැකිය!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"අවලංගු කරන්න"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-sk/strings.xml b/tools/recovery_l10n/res/values-sk/strings.xml
index b15f380..8a2d2e0 100644
--- a/tools/recovery_l10n/res/values-sk/strings.xml
+++ b/tools/recovery_l10n/res/values-sk/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Žiadny príkaz"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Chyba!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Inštaluje sa bezpečnostná aktualizácia"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Systém Android sa nedá načítať. Vaše údaje môžu byť poškodené. Ak chcete získať túto správu a budete pokračovať, zrejme budete musieť obnoviť výrobné nastavenia a vymazať tak všetky údaje používateľa uložené v tomto zariadení."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Skúsiť znova"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Obnovenie výrobných nastavení"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Chcete vymazať všetky údaje používateľa?\n\n TÁTO AKCIA SA NEDÁ VRÁTIŤ SPÄŤ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Zrušiť"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-sl/strings.xml b/tools/recovery_l10n/res/values-sl/strings.xml
index d608b75..653c427 100644
--- a/tools/recovery_l10n/res/values-sl/strings.xml
+++ b/tools/recovery_l10n/res/values-sl/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Ni ukaza"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Napaka"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Nameščanje varnostne posodobitve"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Sistema Android ni mogoče naložiti. Podatki so morda poškodovani. Če se bo to sporočilo še naprej prikazovalo, boste morda morali izvesti ponastavitev na tovarniške nastavitve in izbrisati vse uporabniške podatke, ki so shranjeni v tej napravi."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Poskusi znova"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Ponastavitev na tovarniške nastavitve"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Želite izbrisati vse uporabniške podatke?\n\n TEGA NI MOGOČE RAZVELJAVITI!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Prekliči"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-sq/strings.xml b/tools/recovery_l10n/res/values-sq/strings.xml
index 1156931..5c824e6 100644
--- a/tools/recovery_l10n/res/values-sq/strings.xml
+++ b/tools/recovery_l10n/res/values-sq/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Nuk ka komanda"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Gabim!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Po instalon përditësimin e sigurisë"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Sistemi Android nuk mund të ngarkohet. Të dhënat e tua mund të jenë të dëmtuara. Nëse vazhdon të marrësh këtë mesazh, mund të jetë e nevojshme të kryesh një rivendosje të të dhënave të fabrikës dhe të spastrosh të gjitha të dhënat e përdoruesit të ruajtura në këtë pajisje."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Provo përsëri"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Rivendosja e të dhënave të fabrikës"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Të pastrohen të gjitha të dhënat e përdoruesit?\n\n KJO NUK MUND TË ZHBËHET!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Anulo"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-sr/strings.xml b/tools/recovery_l10n/res/values-sr/strings.xml
index a593d8f..1583bea 100644
--- a/tools/recovery_l10n/res/values-sr/strings.xml
+++ b/tools/recovery_l10n/res/values-sr/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Нема команде"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Грешка!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Инсталира се безбедносно ажурирање"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Учитавање Android система није успело. Подаци су можда оштећени. Ако наставите да добијате ову поруку, можда ћете морати да ресетујете уређај на фабричка подешавања и обришете све податке корисника које чувате на њему."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Пробај поново"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Ресетовање на фабричка подешавања"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Желите ли да избришете све податке корисника?\n\n ОВО НЕ МОЖЕ ДА СЕ ОПОЗОВЕ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Откажи"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-sv/strings.xml b/tools/recovery_l10n/res/values-sv/strings.xml
index b33ce25..cf43b25 100644
--- a/tools/recovery_l10n/res/values-sv/strings.xml
+++ b/tools/recovery_l10n/res/values-sv/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Inget kommando"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Fel!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Säkerhetsuppdatering installeras"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Det gick inte att läsa in Android-systemet. Data kan ha skadats. Om det här meddelandet visas igen kan du behöva återställa standardinställningarna så att all användardata som sparats på enheten raderas."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Försök igen"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Återställ standardinställningarna"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Vill du rensa bort all användardata?\n\n DET GÅR INTE ATT ÅNGRA DENNA ÅTGÄRD."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Avbryt"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-sw/strings.xml b/tools/recovery_l10n/res/values-sw/strings.xml
index 1567658..6fa7282 100644
--- a/tools/recovery_l10n/res/values-sw/strings.xml
+++ b/tools/recovery_l10n/res/values-sw/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Hakuna amri"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Hitilafu fulani imetokea!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Inasakinisha sasisho la usalama"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Imeshindwa kupakia mfumo wa Android. Huenda data yako imeharibika. Kama utandelea kupata ujumbe huu, huenda ukahitaji kurejesha data iliyotoka nayo kiwandani na ufute data yote ya mtumiaji iliyohifadhiwa kwenye kifaa hiki."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Jaribu tena"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Kurejesha data iliyotoka nayo kiwandani"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Ungependa kufuta data yote ya mtumiaji?\n\n KITENDO HIKI HAKIWEZI KUTENDULIWA!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Ghairi"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ta/strings.xml b/tools/recovery_l10n/res/values-ta/strings.xml
index d49186d..bc370f7 100644
--- a/tools/recovery_l10n/res/values-ta/strings.xml
+++ b/tools/recovery_l10n/res/values-ta/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"கட்டளை இல்லை"</string>
     <string name="recovery_error" msgid="5748178989622716736">"பிழை!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"பாதுகாப்புப் புதுப்பிப்பை நிறுவுகிறது"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android சிஸ்டத்தைக் காண்பிக்க இயலவில்லை. உங்களின் தரவு சிதைந்திருக்கலாம். இந்த மெசேஜ் உங்களுக்குத் தொடர்ந்து வந்தால், தரவின் ஆரம்பநிலைக்கு மீட்டமைத்தல் மற்றும் இந்தச் சாதனத்தில் சேமிக்கப்பட்டுள்ள அனைத்துப் பயனர் தரவையும் அழித்தல் ஆகியவற்றைச் செய்ய வேண்டியிருக்கலாம்."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"மீண்டும் முயல்க"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"தரவின் ஆரம்பநிலை மீட்டமைப்பு"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"பயனரின் அனைத்துத் தரவையும் நீக்கவா?\n\n இதைச் செயல்தவிர்க்க இயலாது!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"இல்லை"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-te/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml
index e35c82b..4d52114 100644
--- a/tools/recovery_l10n/res/values-te/strings.xml
+++ b/tools/recovery_l10n/res/values-te/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"ఆదేశం లేదు"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ఎర్రర్ సంభవించింది!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"భద్రతా నవీకరణను ఇన్‌స్టాల్ చేస్తోంది"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android సిస్టమ్‌ని లోడ్ చేయడం సాధ్యం కాదు. మీ డేటా పాడై ఉండవచ్చు. మీకు ఈ సందేశం వస్తూనే ఉంటే, మీరు ఫ్యాక్టరీ డేటా రీసెట్ చేసి, పరికరంలో నిల్వ అయిన వినియోగదారు డేటా మొత్తాన్ని తొలగించాల్సి రావచ్చు."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"మళ్లీ ప్రయత్నించు"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"ఫ్యాక్టరీ డేటా రీసెట్"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"వినియోగదారు డేటా మొత్తాన్ని తొలగించాలా?\n\n ఈ చర్యను రద్దు చేయలేరు!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"రద్దు చేయి"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-th/strings.xml b/tools/recovery_l10n/res/values-th/strings.xml
index 155affe..83d445d 100644
--- a/tools/recovery_l10n/res/values-th/strings.xml
+++ b/tools/recovery_l10n/res/values-th/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"ไม่มีคำสั่ง"</string>
     <string name="recovery_error" msgid="5748178989622716736">"ข้อผิดพลาด!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"กำลังติดตั้งการอัปเดตความปลอดภัย"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"โหลดระบบ Android ไม่ได้ ข้อมูลของคุณอาจเสียหาย หากคุณยังคงได้รับข้อความนี้อยู่ คุณอาจต้องรีเซ็ตข้อมูลเป็นค่าเริ่มต้นและลบข้อมูลผู้ใช้ทั้งหมดที่เก็บอยู่ในอุปกรณ์นี้"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"ลองอีกครั้ง"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"รีเซ็ตข้อมูลเป็นค่าเริ่มต้น"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"ต้องการล้างข้อมูลผู้ใช้ทั้งหมดใช่ไหม\n\n การกระทำนี้จะยกเลิกไม่ได้"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"ยกเลิก"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-tl/strings.xml b/tools/recovery_l10n/res/values-tl/strings.xml
index 555b42b..6621473 100644
--- a/tools/recovery_l10n/res/values-tl/strings.xml
+++ b/tools/recovery_l10n/res/values-tl/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Walang command"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Error!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Nag-i-install ng update sa seguridad"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Hindi ma-load ang Android system. Maaaring sira ang iyong data. Kung patuloy mong matatanggap ang mensaheng ito, maaaring kailanganin mong magsagawa ng pag-reset ng factory data at burahin ang lahat ng data ng user na naka-store sa device na ito."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Subukang muli"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Pag-reset ng factory data"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"I-wipe ang lahat ng data ng user?\n\n HINDI ITO MAA-UNDO!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Kanselahin"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-tr/strings.xml b/tools/recovery_l10n/res/values-tr/strings.xml
index 5387cb2..e4eca52 100644
--- a/tools/recovery_l10n/res/values-tr/strings.xml
+++ b/tools/recovery_l10n/res/values-tr/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Komut yok"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Hata!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Güvenlik güncellemesi yükleniyor"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android sisteminiz yüklenemedi. Verileriniz bozulmuş olabilir. Bu mesajı almaya devam ederseniz fabrika verilerine sıfırlama işlemi yapmanız ve bu cihazda depolanan tüm kullanıcı verilerini silmeniz gerekebilir."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Tekrar dene"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Fabrika verilerine sıfırla"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Tüm kullanıcı verileri silinsin mi?\n\n BU İŞLEM GERİ ALINAMAZ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"İptal"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-uk/strings.xml b/tools/recovery_l10n/res/values-uk/strings.xml
index 0c2fa16..7bd6fec 100644
--- a/tools/recovery_l10n/res/values-uk/strings.xml
+++ b/tools/recovery_l10n/res/values-uk/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Немає команди"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Помилка!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Установлюється оновлення системи безпеки"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Не вдається завантажити систему Android. Можливо, ваші дані пошкоджено. Якщо ви далі отримуватимете це повідомлення, можливо, доведеться відновити заводські налаштування й видалити всі дані користувача з цього пристрою."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Повторити"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Відновити заводські налаштування"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Видалити всі дані користувача?\n\n ЦЮ ДІЮ НЕ МОЖНА ВІДМІНИТИ."</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Скасувати"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-ur/strings.xml b/tools/recovery_l10n/res/values-ur/strings.xml
index 12e32fb..da03f19 100644
--- a/tools/recovery_l10n/res/values-ur/strings.xml
+++ b/tools/recovery_l10n/res/values-ur/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"کوئی کمانڈ نہیں ہے"</string>
     <string name="recovery_error" msgid="5748178989622716736">"خرابی!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"سیکیورٹی اپ ڈیٹ انسٹال ہو رہی ہے"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"‏Android سسٹم لوڈ نہیں کیا جا سکتا۔ آپ کا ڈیٹا خراب ہو سکتا ہے۔ اگر آپ کو مستقل یہ پیغام موصول ہوتا ہے تو آپ کو فیکٹری ڈیٹا کی دوبارہ ترتیب انجام دینے اور اس آلہ پر اسٹور کردہ سبھی صارف ڈیٹا کو مٹانے کی ضرورت پڑ سکتی ہے۔"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"دوبارہ کوشش کریں"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"فیکٹری ڈیٹا کی دوبارہ ترتیب"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"سبھی صارف ڈیٹا صاف کریں؟\n\n اسے کالعدم نہیں کیا جا سکتا!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"منسوخ کریں"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-uz/strings.xml b/tools/recovery_l10n/res/values-uz/strings.xml
index 2c309d6..9bde4c6 100644
--- a/tools/recovery_l10n/res/values-uz/strings.xml
+++ b/tools/recovery_l10n/res/values-uz/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Buyruq yo‘q"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Xato!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Xavfsizlik yangilanishi o‘rnatilmoqda"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Android tizimi yuklanmadi. Maʼlumotlaringiz buzuq shekilli. Yana shu xabarni olsangiz, zavod sozlamalarini tiklashingiz va bu qurilmadagi barcha maʼlumotlarni tozalab tashlashingiz lozim."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Qayta urinish"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Zavod sozlamalarini tiklash"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Barcha maʼlumotlar tozalab tashlansinmi?\n\n ULARNI TIKLASH IMKONSIZ!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Bekor qilish"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-vi/strings.xml b/tools/recovery_l10n/res/values-vi/strings.xml
index c77d0c8..3753394 100644
--- a/tools/recovery_l10n/res/values-vi/strings.xml
+++ b/tools/recovery_l10n/res/values-vi/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Không có lệnh nào"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Lỗi!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Đang cài đặt bản cập nhật bảo mật"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Không thể tải hệ thống Android. Dữ liệu của bạn có thể bị hỏng. Nếu tiếp tục thấy thông báo này, bạn có thể cần phải thiết lập lại dữ liệu ban đầu và xóa tất cả dữ liệu người dùng lưu trữ trên thiết bị này."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Thử lại"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Thiết lập lại dữ liệu ban đầu"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Xóa sạch tất cả dữ liệu người dùng?\n\n KHÔNG THỂ HOÀN TÁC THAO TÁC NÀY!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Hủy"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-zh-rCN/strings.xml b/tools/recovery_l10n/res/values-zh-rCN/strings.xml
index e061497..ab1fdbb 100644
--- a/tools/recovery_l10n/res/values-zh-rCN/strings.xml
+++ b/tools/recovery_l10n/res/values-zh-rCN/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"无命令"</string>
     <string name="recovery_error" msgid="5748178989622716736">"出错了!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"正在安装安全更新"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"无法加载 Android 系统。您的数据可能已损坏。如果系统仍然显示这条消息,您可能需要恢复出厂设置,并清空存储在此设备上的所有用户数据。"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"重试"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"恢复出厂设置"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"是否清除所有用户数据?\n\n此操作无法撤消!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"取消"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-zh-rHK/strings.xml b/tools/recovery_l10n/res/values-zh-rHK/strings.xml
index ec3315d..55ce31e 100644
--- a/tools/recovery_l10n/res/values-zh-rHK/strings.xml
+++ b/tools/recovery_l10n/res/values-zh-rHK/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"沒有指令"</string>
     <string name="recovery_error" msgid="5748178989622716736">"錯誤!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"正在安裝安全性更新"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"無法載入 Android 系統。您的資料可能已損壞。如您繼續收到此訊息,則可能需要將裝置回復原廠設定,並清除儲存在裝置上的所有使用者資料。"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"再試一次"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"回復原廠設定"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"要清除所有使用者資料嗎?\n\n這項操作無法復原!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"取消"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-zh-rTW/strings.xml b/tools/recovery_l10n/res/values-zh-rTW/strings.xml
index 78eae24..0a777a6 100644
--- a/tools/recovery_l10n/res/values-zh-rTW/strings.xml
+++ b/tools/recovery_l10n/res/values-zh-rTW/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"沒有指令"</string>
     <string name="recovery_error" msgid="5748178989622716736">"錯誤!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"正在安裝安全性更新"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"無法載入 Android 系統。你的資料可能已經損毀。如果系統持續顯示這則訊息,你可能必須恢復原廠設定,並清除裝置上儲存的所有使用者資料。"</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"再試一次"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"恢復原廠設定"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"要抹除所有使用者資料嗎?\n\n請注意,一旦抹除就無法復原!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"取消"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values-zu/strings.xml b/tools/recovery_l10n/res/values-zu/strings.xml
index 6b815e1..4667dac 100644
--- a/tools/recovery_l10n/res/values-zu/strings.xml
+++ b/tools/recovery_l10n/res/values-zu/strings.xml
@@ -6,4 +6,9 @@
     <string name="recovery_no_command" msgid="4465476568623024327">"Awukho umyalo"</string>
     <string name="recovery_error" msgid="5748178989622716736">"Iphutha!"</string>
     <string name="recovery_installing_security" msgid="9184031299717114342">"Ifaka isibuyekezo sokuphepha"</string>
+    <string name="recovery_wipe_data_menu_header" msgid="550255032058254478">"Ayikwazi ukulayisha isistimu ye-Android. Idatha yakho kungenzeka yonakele. Uma uqhubeka ukuthola lo mlayezo, kungenzeka kumele wenze ukusethwa kabusha kwasekuqaleni kwedatha uphinde usule yonke idatha yomsebenzisi egcinwe kule divayisi."</string>
+    <string name="recovery_try_again" msgid="7168248750158873496">"Zama futhi"</string>
+    <string name="recovery_factory_data_reset" msgid="7321351565602894783">"Ukuhlela kabusha idatha yasembonini"</string>
+    <string name="recovery_wipe_data_confirmation" msgid="5439823343348043954">"Sula yonke idatha yomsebenzisi?\n\n LOKHU AKUKWAZI UKUHLEHLISWA!"</string>
+    <string name="recovery_cancel_wipe_data" msgid="66987687653647384">"Khansela"</string>
 </resources>
diff --git a/tools/recovery_l10n/res/values/strings.xml b/tools/recovery_l10n/res/values/strings.xml
index d56d073..a557ba8 100644
--- a/tools/recovery_l10n/res/values/strings.xml
+++ b/tools/recovery_l10n/res/values/strings.xml
@@ -36,4 +36,36 @@
        system is installing a security update. [CHAR LIMIT=60] -->
   <string name="recovery_installing_security">Installing security update</string>
 
+  <!-- Displayed on the screen beneath the recovery titles when the
+       device enters the recovery mode and prompts a data wipe. [CHAR
+       LIMIT=400] -->
+  <string name="recovery_wipe_data_menu_header">Cannot load Android
+    system. Your data may be corrupt. If you continue to get this
+    message, you may need to perform a factory data reset and erase
+    all user data stored on this device.</string>
+
+  <!-- Displayed on the screen as the first element of the menu to
+       prompt the wipe data, beneath the menu header. The menu shows
+       up when the device enters the recovery mode and prompts a data
+       wipe. [CHAR LIMIT=60] -->
+  <string name="recovery_try_again">Try again</string>
+
+  <!-- Displayed on the screen as the second element of the menu to
+       prompt the wipe data, beneath the menu header. The menu shows
+       up when the device enters the recovery mode and prompts a data
+       wipe. [CHAR LIMIT=60] -->
+  <string name="recovery_factory_data_reset">Factory data reset</string>
+
+  <!-- Displayed on the screen beneath the recovery titles when users
+       select "Factory data reset" in the previous menu. [CHAR
+       LIMIT=150] -->
+  <string name="recovery_wipe_data_confirmation">Wipe all user data?\n\n
+    THIS CAN NOT BE UNDONE!</string>
+
+  <!-- Displayed on the screen as the first element of the wipe data
+       confirmation menu. The menu shows up when users select
+       "Factory data reset" when prompted to wipe data. [CHAR
+       LIMIT=60] -->
+  <string name="recovery_cancel_wipe_data">Cancel</string>
+
 </resources>
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 47849a1..c4c0909 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -1399,7 +1399,10 @@
 
       // We expect the output of the patcher to fill the tgt ranges exactly.
       if (!writer.Finished()) {
-        LOG(ERROR) << "range sink underrun?";
+        LOG(ERROR) << "Failed to fully write target blocks (range sink underrun): Missing "
+                   << writer.AvailableSpace() << " bytes";
+        failure_type = kPatchApplicationFailure;
+        return -1;
       }
     } else {
       LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " ["
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 0611f94..6da84c9 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -51,8 +51,8 @@
   gr_color(0, 0, 0, 255);
   gr_fill(0, 0, gr_fb_width(), gr_fb_height());
 
-  if (currentIcon != NONE) {
-    GRSurface* frame = GetCurrentFrame();
+  if (current_icon_ != NONE) {
+    const auto& frame = GetCurrentFrame();
     int frame_width = gr_get_width(frame);
     int frame_height = gr_get_height(frame);
     int frame_x = (gr_fb_width() - frame_width) / 2;
@@ -60,7 +60,7 @@
     gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y);
 
     // Draw recovery text on screen above progress bar.
-    GRSurface* text = GetCurrentText();
+    const auto& text = GetCurrentText();
     int text_x = (ScreenWidth() - gr_get_width(text)) / 2;
     int text_y = GetProgressBaseline() - gr_get_height(text) - 10;
     gr_color(255, 255, 255, 255);