idmap2: initial code drop

idmap2 is a reboot of the idmap project. The project aims to

  - use modern C++
  - greatly improve test and debug support
  - interface towards AssetManager2 (instead of AssetManager)
  - provide a solid foundation to add support for new features

To make it easier to verify correctness, this first version of idmap2 is
feature equivalent to idmap. Later versions will add support for new
features such as <overlayable>.

Bug: 78815803
Test: make idmap2_tests
Change-Id: I1d806dc875a493e730ab55d2fdb027618e586d16
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
new file mode 100644
index 0000000..29969a2
--- /dev/null
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <cstring>
+#include <string>
+
+#include "android-base/macros.h"
+
+#include "idmap2/BinaryStreamVisitor.h"
+
+namespace android {
+namespace idmap2 {
+
+void BinaryStreamVisitor::Write16(uint16_t value) {
+  uint16_t x = htodl(value);
+  stream_.write(reinterpret_cast<char*>(&x), sizeof(uint16_t));
+}
+
+void BinaryStreamVisitor::Write32(uint32_t value) {
+  uint32_t x = htodl(value);
+  stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
+}
+
+void BinaryStreamVisitor::WriteString(const StringPiece& value) {
+  char buf[kIdmapStringLength];
+  memset(buf, 0, sizeof(buf));
+  memcpy(buf, value.data(), std::min(value.size(), sizeof(buf)));
+  stream_.write(buf, sizeof(buf));
+}
+
+void BinaryStreamVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
+  // nothing to do
+}
+
+void BinaryStreamVisitor::visit(const IdmapHeader& header) {
+  Write32(header.GetMagic());
+  Write32(header.GetVersion());
+  Write32(header.GetTargetCrc());
+  Write32(header.GetOverlayCrc());
+  WriteString(header.GetTargetPath());
+  WriteString(header.GetOverlayPath());
+}
+
+void BinaryStreamVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
+  // nothing to do
+}
+
+void BinaryStreamVisitor::visit(const IdmapData::Header& header) {
+  Write16(header.GetTargetPackageId());
+  Write16(header.GetTypeCount());
+}
+
+void BinaryStreamVisitor::visit(const IdmapData::TypeEntry& te) {
+  const uint16_t entryCount = te.GetEntryCount();
+
+  Write16(te.GetTargetTypeId());
+  Write16(te.GetOverlayTypeId());
+  Write16(entryCount);
+  Write16(te.GetEntryOffset());
+  for (uint16_t i = 0; i < entryCount; i++) {
+    EntryId entry_id = te.GetEntry(i);
+    Write32(entry_id != kNoEntry ? static_cast<uint32_t>(entry_id) : kPadding);
+  }
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/libidmap2/CommandLineOptions.cpp b/cmds/idmap2/libidmap2/CommandLineOptions.cpp
new file mode 100644
index 0000000..28c3797
--- /dev/null
+++ b/cmds/idmap2/libidmap2/CommandLineOptions.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "android-base/macros.h"
+
+#include "idmap2/CommandLineOptions.h"
+
+namespace android {
+namespace idmap2 {
+
+std::unique_ptr<std::vector<std::string>> CommandLineOptions::ConvertArgvToVector(
+    int argc, const char** argv) {
+  return std::unique_ptr<std::vector<std::string>>(
+      new std::vector<std::string>(argv + 1, argv + argc));
+}
+
+CommandLineOptions& CommandLineOptions::OptionalFlag(const std::string& name,
+                                                     const std::string& description, bool* value) {
+  assert(value != nullptr);
+  auto func = [value](const std::string& arg ATTRIBUTE_UNUSED) -> void { *value = true; };
+  options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL, false});
+  return *this;
+}
+
+CommandLineOptions& CommandLineOptions::MandatoryOption(const std::string& name,
+                                                        const std::string& description,
+                                                        std::string* value) {
+  assert(value != nullptr);
+  auto func = [value](const std::string& arg) -> void { *value = arg; };
+  options_.push_back(Option{name, description, func, Option::COUNT_EXACTLY_ONCE, true});
+  return *this;
+}
+
+CommandLineOptions& CommandLineOptions::MandatoryOption(const std::string& name,
+                                                        const std::string& description,
+                                                        std::vector<std::string>* value) {
+  assert(value != nullptr);
+  auto func = [value](const std::string& arg) -> void { value->push_back(arg); };
+  options_.push_back(Option{name, description, func, Option::COUNT_ONCE_OR_MORE, true});
+  return *this;
+}
+
+CommandLineOptions& CommandLineOptions::OptionalOption(const std::string& name,
+                                                       const std::string& description,
+                                                       std::string* value) {
+  assert(value != nullptr);
+  auto func = [value](const std::string& arg) -> void { *value = arg; };
+  options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL, true});
+  return *this;
+}
+
+bool CommandLineOptions::Parse(const std::vector<std::string>& argv, std::ostream& outError) const {
+  const auto pivot = std::partition(options_.begin(), options_.end(), [](const Option& opt) {
+    return opt.count != Option::COUNT_OPTIONAL;
+  });
+  std::set<std::string> mandatory_opts;
+  std::transform(options_.begin(), pivot, std::inserter(mandatory_opts, mandatory_opts.end()),
+                 [](const Option& opt) -> std::string { return opt.name; });
+
+  const size_t argv_size = argv.size();
+  for (size_t i = 0; i < argv_size; i++) {
+    const std::string arg = argv[i];
+    if ("--help" == arg || "-h" == arg) {
+      Usage(outError);
+      return false;
+    }
+    bool match = false;
+    for (const Option& opt : options_) {
+      if (opt.name == arg) {
+        match = true;
+
+        if (opt.argument) {
+          i++;
+          if (i >= argv_size) {
+            outError << "error: " << opt.name << ": missing argument" << std::endl;
+            Usage(outError);
+            return false;
+          }
+        }
+        opt.action(argv[i]);
+        mandatory_opts.erase(opt.name);
+        break;
+      }
+    }
+    if (!match) {
+      outError << "error: " << arg << ": unknown option" << std::endl;
+      Usage(outError);
+      return false;
+    }
+  }
+
+  if (!mandatory_opts.empty()) {
+    for (auto iter = mandatory_opts.cbegin(); iter != mandatory_opts.cend(); ++iter) {
+      outError << "error: " << *iter << ": missing mandatory option" << std::endl;
+    }
+    Usage(outError);
+    return false;
+  }
+  return true;
+}
+
+void CommandLineOptions::Usage(std::ostream& out) const {
+  size_t maxLength = 0;
+  out << "usage: " << name_;
+  for (const Option& opt : options_) {
+    const bool mandatory = opt.count != Option::COUNT_OPTIONAL;
+    out << " ";
+    if (!mandatory) {
+      out << "[";
+    }
+    if (opt.argument) {
+      out << opt.name << " arg";
+      maxLength = std::max(maxLength, opt.name.size() + 4);
+    } else {
+      out << opt.name;
+      maxLength = std::max(maxLength, opt.name.size());
+    }
+    if (!mandatory) {
+      out << "]";
+    }
+    if (opt.count == Option::COUNT_ONCE_OR_MORE) {
+      out << " [" << opt.name << " arg [..]]";
+    }
+  }
+  out << std::endl << std::endl;
+  for (const Option& opt : options_) {
+    out << std::left << std::setw(maxLength);
+    if (opt.argument) {
+      out << (opt.name + " arg");
+    } else {
+      out << opt.name;
+    }
+    out << "    " << opt.description;
+    if (opt.count == Option::COUNT_ONCE_OR_MORE) {
+      out << " (can be provided multiple times)";
+    }
+    out << std::endl;
+  }
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp
new file mode 100644
index 0000000..4ac4c04
--- /dev/null
+++ b/cmds/idmap2/libidmap2/FileUtils.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "idmap2/FileUtils.h"
+
+namespace android {
+namespace idmap2 {
+namespace utils {
+
+std::unique_ptr<std::vector<std::string>> FindFiles(const std::string& root, bool recurse,
+                                                    const FindFilesPredicate& predicate) {
+  DIR* dir = opendir(root.c_str());
+  if (!dir) {
+    return nullptr;
+  }
+  std::unique_ptr<std::vector<std::string>> vector(new std::vector<std::string>());
+  struct dirent* dirent;
+  while ((dirent = readdir(dir))) {
+    const std::string path = root + "/" + dirent->d_name;
+    if (predicate(dirent->d_type, path)) {
+      vector->push_back(path);
+    }
+    if (recurse && dirent->d_type == DT_DIR && strcmp(dirent->d_name, ".") != 0 &&
+        strcmp(dirent->d_name, "..") != 0) {
+      auto sub_vector = FindFiles(path, recurse, predicate);
+      if (!sub_vector) {
+        closedir(dir);
+        return nullptr;
+      }
+      vector->insert(vector->end(), sub_vector->begin(), sub_vector->end());
+    }
+  }
+  closedir(dir);
+
+  return vector;
+}
+
+std::unique_ptr<std::string> ReadFile(const std::string& path) {
+  std::unique_ptr<std::string> str(new std::string());
+  std::ifstream fin(path);
+  str->append({std::istreambuf_iterator<char>(fin), std::istreambuf_iterator<char>()});
+  fin.close();
+  return str;
+}
+
+std::unique_ptr<std::string> ReadFile(int fd) {
+  std::unique_ptr<std::string> str(new std::string());
+  char buf[1024];
+  ssize_t r;
+  while ((r = read(fd, buf, sizeof(buf))) > 0) {
+    str->append(buf, r);
+  }
+  return r == 0 ? std::move(str) : nullptr;
+}
+
+}  // namespace utils
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
new file mode 100644
index 0000000..5a47e30
--- /dev/null
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <limits>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/AssetManager2.h"
+#include "utils/String16.h"
+#include "utils/String8.h"
+
+#include "idmap2/Idmap.h"
+#include "idmap2/ResourceUtils.h"
+#include "idmap2/ZipFile.h"
+
+namespace android {
+namespace idmap2 {
+
+#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
+
+#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
+
+struct MatchingResources {
+  void Add(ResourceId target_resid, ResourceId overlay_resid) {
+    TypeId target_typeid = EXTRACT_TYPE(target_resid);
+    if (map.find(target_typeid) == map.end()) {
+      map.emplace(target_typeid, std::set<std::pair<ResourceId, ResourceId>>());
+    }
+    map[target_typeid].insert(std::make_pair(target_resid, overlay_resid));
+  }
+
+  // target type id -> set { pair { overlay entry id, overlay entry id } }
+  std::map<TypeId, std::set<std::pair<ResourceId, ResourceId>>> map;
+};
+
+static bool WARN_UNUSED Read16(std::istream& stream, uint16_t* out) {
+  uint16_t value;
+  if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint16_t))) {
+    *out = dtohl(value);
+    return true;
+  }
+  return false;
+}
+
+static bool WARN_UNUSED Read32(std::istream& stream, uint32_t* out) {
+  uint32_t value;
+  if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint32_t))) {
+    *out = dtohl(value);
+    return true;
+  }
+  return false;
+}
+
+// a string is encoded as a kIdmapStringLength char array; the array is always null-terminated
+static bool WARN_UNUSED ReadString(std::istream& stream, char out[kIdmapStringLength]) {
+  char buf[kIdmapStringLength];
+  memset(buf, 0, sizeof(buf));
+  if (!stream.read(buf, sizeof(buf))) {
+    return false;
+  }
+  if (buf[sizeof(buf) - 1] != '\0') {
+    return false;
+  }
+  memcpy(out, buf, sizeof(buf));
+  return true;
+}
+
+static ResourceId NameToResid(const AssetManager2& am, const std::string& name) {
+  return am.GetResourceId(name);
+}
+
+// TODO(martenkongstad): scan for package name instead of assuming package at index 0
+//
+// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
+// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
+// this assumption tends to work out. That said, the correct thing to do is to scan
+// resources.arsc for a package with a given name as read from the package manifest instead of
+// relying on a hard-coded index. This however requires storing the package name in the idmap
+// header, which in turn requires incrementing the idmap version. Because the initial version of
+// idmap2 is compatible with idmap, this will have to wait for now.
+static const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
+  const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
+  if (packages.empty()) {
+    return nullptr;
+  }
+  int id = packages[0]->GetPackageId();
+  return loaded_arsc.GetPackageById(id);
+}
+
+std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) {
+  std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader());
+
+  if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) ||
+      !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) ||
+      !ReadString(stream, idmap_header->target_path_) ||
+      !ReadString(stream, idmap_header->overlay_path_)) {
+    return nullptr;
+  }
+
+  return std::move(idmap_header);
+}
+
+bool IdmapHeader::IsUpToDate(std::ostream& out_error) const {
+  if (magic_ != kIdmapMagic) {
+    out_error << base::StringPrintf("error: bad magic: actual 0x%08x, expected 0x%08x", magic_,
+                                    kIdmapMagic)
+              << std::endl;
+    return false;
+  }
+
+  if (version_ != kIdmapCurrentVersion) {
+    out_error << base::StringPrintf("error: bad version: actual 0x%08x, expected 0x%08x", version_,
+                                    kIdmapCurrentVersion)
+              << std::endl;
+    return false;
+  }
+
+  const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path_);
+  if (!target_zip) {
+    out_error << "error: failed to open target " << target_path_ << std::endl;
+    return false;
+  }
+
+  bool status;
+  uint32_t target_crc;
+  std::tie(status, target_crc) = target_zip->Crc("resources.arsc");
+  if (!status) {
+    out_error << "error: failed to get target crc" << std::endl;
+    return false;
+  }
+
+  if (target_crc_ != target_crc) {
+    out_error << base::StringPrintf(
+                     "error: bad target crc: idmap version 0x%08x, file system version 0x%08x",
+                     target_crc_, target_crc)
+              << std::endl;
+    return false;
+  }
+
+  const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path_);
+  if (!overlay_zip) {
+    out_error << "error: failed to open overlay " << overlay_path_ << std::endl;
+    return false;
+  }
+
+  uint32_t overlay_crc;
+  std::tie(status, overlay_crc) = overlay_zip->Crc("resources.arsc");
+  if (!status) {
+    out_error << "error: failed to get overlay crc" << std::endl;
+    return false;
+  }
+
+  if (overlay_crc_ != overlay_crc) {
+    out_error << base::StringPrintf(
+                     "error: bad overlay crc: idmap version 0x%08x, file system version 0x%08x",
+                     overlay_crc_, overlay_crc)
+              << std::endl;
+    return false;
+  }
+
+  return true;
+}
+
+std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) {
+  std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header());
+
+  uint16_t target_package_id16;
+  if (!Read16(stream, &target_package_id16) || !Read16(stream, &idmap_data_header->type_count_)) {
+    return nullptr;
+  }
+  idmap_data_header->target_package_id_ = target_package_id16;
+
+  return std::move(idmap_data_header);
+}
+
+std::unique_ptr<const IdmapData::TypeEntry> IdmapData::TypeEntry::FromBinaryStream(
+    std::istream& stream) {
+  std::unique_ptr<IdmapData::TypeEntry> data(new IdmapData::TypeEntry());
+
+  uint16_t target_type16, overlay_type16, entry_count;
+  if (!Read16(stream, &target_type16) || !Read16(stream, &overlay_type16) ||
+      !Read16(stream, &entry_count) || !Read16(stream, &data->entry_offset_)) {
+    return nullptr;
+  }
+  data->target_type_id_ = target_type16;
+  data->overlay_type_id_ = overlay_type16;
+  for (uint16_t i = 0; i < entry_count; i++) {
+    ResourceId resid;
+    if (!Read32(stream, &resid)) {
+      return nullptr;
+    }
+    data->entries_.push_back(resid);
+  }
+
+  return std::move(data);
+}
+
+std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& stream) {
+  std::unique_ptr<IdmapData> data(new IdmapData());
+  data->header_ = IdmapData::Header::FromBinaryStream(stream);
+  if (!data->header_) {
+    return nullptr;
+  }
+  for (size_t type_count = 0; type_count < data->header_->GetTypeCount(); type_count++) {
+    std::unique_ptr<const TypeEntry> type = IdmapData::TypeEntry::FromBinaryStream(stream);
+    if (!type) {
+      return nullptr;
+    }
+    data->type_entries_.push_back(std::move(type));
+  }
+  return std::move(data);
+}
+
+std::string Idmap::CanonicalIdmapPathFor(const std::string& absolute_dir,
+                                         const std::string& absolute_apk_path) {
+  assert(absolute_dir.size() > 0 && absolute_dir[0] == "/");
+  assert(absolute_apk_path.size() > 0 && absolute_apk_path[0] == "/");
+  std::string copy(++absolute_apk_path.cbegin(), absolute_apk_path.cend());
+  replace(copy.begin(), copy.end(), '/', '@');
+  return absolute_dir + "/" + copy + "@idmap";
+}
+
+std::unique_ptr<const Idmap> Idmap::FromBinaryStream(std::istream& stream,
+                                                     std::ostream& out_error) {
+  std::unique_ptr<Idmap> idmap(new Idmap());
+
+  idmap->header_ = IdmapHeader::FromBinaryStream(stream);
+  if (!idmap->header_) {
+    out_error << "error: failed to parse idmap header" << std::endl;
+    return nullptr;
+  }
+
+  // idmap version 0x01 does not specify the number of data blocks that follow
+  // the idmap header; assume exactly one data block
+  for (int i = 0; i < 1; i++) {
+    std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
+    if (!data) {
+      out_error << "error: failed to parse data block " << i << std::endl;
+      return nullptr;
+    }
+    idmap->data_.push_back(std::move(data));
+  }
+
+  return std::move(idmap);
+}
+
+std::unique_ptr<const Idmap> Idmap::FromApkAssets(const std::string& target_apk_path,
+                                                  const ApkAssets& target_apk_assets,
+                                                  const std::string& overlay_apk_path,
+                                                  const ApkAssets& overlay_apk_assets,
+                                                  std::ostream& out_error) {
+  AssetManager2 target_asset_manager;
+  if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true, false)) {
+    out_error << "error: failed to create target asset manager" << std::endl;
+    return nullptr;
+  }
+
+  AssetManager2 overlay_asset_manager;
+  if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true, false)) {
+    out_error << "error: failed to create overlay asset manager" << std::endl;
+    return nullptr;
+  }
+
+  const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
+  if (!target_arsc) {
+    out_error << "error: failed to load target resources.arsc" << std::endl;
+    return nullptr;
+  }
+
+  const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
+  if (!overlay_arsc) {
+    out_error << "error: failed to load overlay resources.arsc" << std::endl;
+    return nullptr;
+  }
+
+  const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
+  if (!target_pkg) {
+    out_error << "error: failed to load target package from resources.arsc" << std::endl;
+    return nullptr;
+  }
+
+  const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
+  if (!overlay_pkg) {
+    out_error << "error: failed to load overlay package from resources.arsc" << std::endl;
+    return nullptr;
+  }
+
+  const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path);
+  if (!target_zip) {
+    out_error << "error: failed to open target as zip" << std::endl;
+    return nullptr;
+  }
+
+  const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_apk_path);
+  if (!overlay_zip) {
+    out_error << "error: failed to open overlay as zip" << std::endl;
+    return nullptr;
+  }
+
+  std::unique_ptr<IdmapHeader> header(new IdmapHeader());
+  header->magic_ = kIdmapMagic;
+  header->version_ = kIdmapCurrentVersion;
+  bool crc_status;
+  std::tie(crc_status, header->target_crc_) = target_zip->Crc("resources.arsc");
+  if (!crc_status) {
+    out_error << "error: failed to get zip crc for target" << std::endl;
+    return nullptr;
+  }
+  std::tie(crc_status, header->overlay_crc_) = overlay_zip->Crc("resources.arsc");
+  if (!crc_status) {
+    out_error << "error: failed to get zip crc for overlay" << std::endl;
+    return nullptr;
+  }
+
+  if (target_apk_path.size() > sizeof(header->target_path_)) {
+    out_error << "error: target apk path \"" << target_apk_path << "\" longer that maximum size "
+              << sizeof(header->target_path_) << std::endl;
+    return nullptr;
+  }
+  memset(header->target_path_, 0, sizeof(header->target_path_));
+  memcpy(header->target_path_, target_apk_path.data(), target_apk_path.size());
+
+  if (overlay_apk_path.size() > sizeof(header->overlay_path_)) {
+    out_error << "error: overlay apk path \"" << overlay_apk_path << "\" longer that maximum size "
+              << sizeof(header->overlay_path_) << std::endl;
+    return nullptr;
+  }
+  memset(header->overlay_path_, 0, sizeof(header->overlay_path_));
+  memcpy(header->overlay_path_, overlay_apk_path.data(), overlay_apk_path.size());
+
+  std::unique_ptr<Idmap> idmap(new Idmap());
+  idmap->header_ = std::move(header);
+
+  // find the resources that exist in both packages
+  MatchingResources matching_resources;
+  const auto end = overlay_pkg->end();
+  for (auto iter = overlay_pkg->begin(); iter != end; ++iter) {
+    const ResourceId overlay_resid = *iter;
+    bool lookup_ok;
+    std::string name;
+    std::tie(lookup_ok, name) = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid);
+    if (!lookup_ok) {
+      continue;
+    }
+    // prepend "<package>:" to turn name into "<package>:<type>/<name>"
+    name = base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name.c_str());
+    const ResourceId target_resid = NameToResid(target_asset_manager, name);
+    if (target_resid == 0) {
+      continue;
+    }
+    matching_resources.Add(target_resid, overlay_resid);
+  }
+
+  // encode idmap data
+  std::unique_ptr<IdmapData> data(new IdmapData());
+  const auto types_end = matching_resources.map.cend();
+  for (auto ti = matching_resources.map.cbegin(); ti != types_end; ++ti) {
+    auto ei = ti->second.cbegin();
+    std::unique_ptr<IdmapData::TypeEntry> type(new IdmapData::TypeEntry());
+    type->target_type_id_ = EXTRACT_TYPE(ei->first);
+    type->overlay_type_id_ = EXTRACT_TYPE(ei->second);
+    type->entry_offset_ = EXTRACT_ENTRY(ei->first);
+    EntryId last_target_entry = kNoEntry;
+    for (; ei != ti->second.cend(); ++ei) {
+      if (last_target_entry != kNoEntry) {
+        int count = EXTRACT_ENTRY(ei->first) - last_target_entry - 1;
+        type->entries_.insert(type->entries_.end(), count, kNoEntry);
+      }
+      type->entries_.push_back(EXTRACT_ENTRY(ei->second));
+      last_target_entry = EXTRACT_ENTRY(ei->first);
+    }
+    data->type_entries_.push_back(std::move(type));
+  }
+
+  std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
+  data_header->target_package_id_ = target_pkg->GetPackageId();
+  data_header->type_count_ = data->type_entries_.size();
+  data->header_ = std::move(data_header);
+
+  idmap->data_.push_back(std::move(data));
+
+  return std::move(idmap);
+}
+
+void IdmapHeader::accept(Visitor* v) const {
+  assert(v != nullptr);
+  v->visit(*this);
+}
+
+void IdmapData::Header::accept(Visitor* v) const {
+  assert(v != nullptr);
+  v->visit(*this);
+}
+
+void IdmapData::TypeEntry::accept(Visitor* v) const {
+  assert(v != nullptr);
+  v->visit(*this);
+}
+
+void IdmapData::accept(Visitor* v) const {
+  assert(v != nullptr);
+  v->visit(*this);
+  header_->accept(v);
+  auto end = type_entries_.cend();
+  for (auto iter = type_entries_.cbegin(); iter != end; ++iter) {
+    (*iter)->accept(v);
+  }
+}
+
+void Idmap::accept(Visitor* v) const {
+  assert(v != nullptr);
+  v->visit(*this);
+  header_->accept(v);
+  auto end = data_.cend();
+  for (auto iter = data_.cbegin(); iter != end; ++iter) {
+    (*iter)->accept(v);
+  }
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
new file mode 100644
index 0000000..492e6f0
--- /dev/null
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <utility>
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/ApkAssets.h"
+
+#include "idmap2/PrettyPrintVisitor.h"
+#include "idmap2/ResourceUtils.h"
+
+namespace android {
+namespace idmap2 {
+
+#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
+
+void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
+}
+
+void PrettyPrintVisitor::visit(const IdmapHeader& header) {
+  stream_ << "target apk path  : " << header.GetTargetPath() << std::endl
+          << "overlay apk path : " << header.GetOverlayPath() << std::endl;
+
+  target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
+  if (target_apk_) {
+    target_am_.SetApkAssets({target_apk_.get()});
+  }
+}
+
+void PrettyPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
+}
+
+void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED) {
+  last_seen_package_id_ = header.GetTargetPackageId();
+}
+
+void PrettyPrintVisitor::visit(const IdmapData::TypeEntry& te) {
+  const bool target_package_loaded = !target_am_.GetApkAssets().empty();
+  for (uint16_t i = 0; i < te.GetEntryCount(); i++) {
+    const EntryId entry = te.GetEntry(i);
+    if (entry == kNoEntry) {
+      continue;
+    }
+
+    const ResourceId target_resid =
+        RESID(last_seen_package_id_, te.GetTargetTypeId(), te.GetEntryOffset() + i);
+    const ResourceId overlay_resid = RESID(last_seen_package_id_, te.GetOverlayTypeId(), entry);
+
+    stream_ << base::StringPrintf("0x%08x -> 0x%08x", target_resid, overlay_resid);
+    if (target_package_loaded) {
+      bool lookup_ok;
+      std::string name;
+      std::tie(lookup_ok, name) = utils::ResToTypeEntryName(target_am_, target_resid);
+      if (lookup_ok) {
+        stream_ << " " << name;
+      }
+    }
+    stream_ << std::endl;
+  }
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
new file mode 100644
index 0000000..57cfc8e
--- /dev/null
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdarg>
+#include <string>
+#include <utility>
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/ApkAssets.h"
+
+#include "idmap2/RawPrintVisitor.h"
+#include "idmap2/ResourceUtils.h"
+
+using android::ApkAssets;
+
+namespace android {
+namespace idmap2 {
+
+// verbatim copy fomr PrettyPrintVisitor.cpp, move to common utils
+#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
+
+void RawPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
+}
+
+void RawPrintVisitor::visit(const IdmapHeader& header) {
+  print(header.GetMagic(), "magic");
+  print(header.GetVersion(), "version");
+  print(header.GetTargetCrc(), "target crc");
+  print(header.GetOverlayCrc(), "overlay crc");
+  print(header.GetTargetPath().to_string(), "target path");
+  print(header.GetOverlayPath().to_string(), "overlay path");
+
+  target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
+  if (target_apk_) {
+    target_am_.SetApkAssets({target_apk_.get()});
+  }
+}
+
+void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
+}
+
+void RawPrintVisitor::visit(const IdmapData::Header& header) {
+  print(static_cast<uint16_t>(header.GetTargetPackageId()), "target package id");
+  print(header.GetTypeCount(), "type count");
+  last_seen_package_id_ = header.GetTargetPackageId();
+}
+
+void RawPrintVisitor::visit(const IdmapData::TypeEntry& te) {
+  const bool target_package_loaded = !target_am_.GetApkAssets().empty();
+
+  print(static_cast<uint16_t>(te.GetTargetTypeId()), "target type");
+  print(static_cast<uint16_t>(te.GetOverlayTypeId()), "overlay type");
+  print(static_cast<uint16_t>(te.GetEntryCount()), "entry count");
+  print(static_cast<uint16_t>(te.GetEntryOffset()), "entry offset");
+
+  for (uint16_t i = 0; i < te.GetEntryCount(); i++) {
+    const EntryId entry = te.GetEntry(i);
+    if (entry == kNoEntry) {
+      print(kPadding, "no entry");
+    } else {
+      const ResourceId target_resid =
+          RESID(last_seen_package_id_, te.GetTargetTypeId(), te.GetEntryOffset() + i);
+      const ResourceId overlay_resid = RESID(last_seen_package_id_, te.GetOverlayTypeId(), entry);
+      bool lookup_ok = false;
+      std::string name;
+      if (target_package_loaded) {
+        std::tie(lookup_ok, name) = utils::ResToTypeEntryName(target_am_, target_resid);
+      }
+      if (lookup_ok) {
+        print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x %s", target_resid, overlay_resid,
+              name.c_str());
+      } else {
+        print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x", target_resid, overlay_resid);
+      }
+    }
+  }
+}
+
+void RawPrintVisitor::print(uint16_t value, const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  std::string comment;
+  base::StringAppendV(&comment, fmt, ap);
+  va_end(ap);
+
+  stream_ << base::StringPrintf("%08zx:     %04x", offset_, value) << "  " << comment << std::endl;
+
+  offset_ += sizeof(uint16_t);
+}
+
+void RawPrintVisitor::print(uint32_t value, const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  std::string comment;
+  base::StringAppendV(&comment, fmt, ap);
+  va_end(ap);
+
+  stream_ << base::StringPrintf("%08zx: %08x", offset_, value) << "  " << comment << std::endl;
+
+  offset_ += sizeof(uint32_t);
+}
+
+void RawPrintVisitor::print(const std::string& value, const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  std::string comment;
+  base::StringAppendV(&comment, fmt, ap);
+  va_end(ap);
+
+  stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment << ": " << value
+          << std::endl;
+
+  offset_ += kIdmapStringLength;
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp
new file mode 100644
index 0000000..e98f843
--- /dev/null
+++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <utility>
+
+#include "androidfw/StringPiece.h"
+#include "androidfw/Util.h"
+
+#include "idmap2/ResourceUtils.h"
+
+using android::StringPiece16;
+using android::util::Utf16ToUtf8;
+
+namespace android {
+namespace idmap2 {
+namespace utils {
+
+std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am,
+                                                            ResourceId resid) {
+  AssetManager2::ResourceName name;
+  if (!am.GetResourceName(resid, &name)) {
+    return std::make_pair(false, "");
+  }
+  std::string out;
+  if (name.type != nullptr) {
+    out.append(name.type, name.type_len);
+  } else {
+    out += Utf16ToUtf8(StringPiece16(name.type16, name.type_len));
+  }
+  out.append("/");
+  if (name.entry != nullptr) {
+    out.append(name.entry, name.entry_len);
+  } else {
+    out += Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len));
+  }
+  return std::make_pair(true, out);
+}
+
+}  // namespace utils
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/libidmap2/Xml.cpp b/cmds/idmap2/libidmap2/Xml.cpp
new file mode 100644
index 0000000..5543722
--- /dev/null
+++ b/cmds/idmap2/libidmap2/Xml.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "idmap2/Xml.h"
+
+namespace android {
+namespace idmap2 {
+
+std::unique_ptr<const Xml> Xml::Create(const uint8_t* data, size_t size, bool copyData) {
+  std::unique_ptr<Xml> xml(new Xml());
+  if (xml->xml_.setTo(data, size, copyData) != NO_ERROR) {
+    return nullptr;
+  }
+  return xml;
+}
+
+std::unique_ptr<std::map<std::string, std::string>> Xml::FindTag(const std::string& name) const {
+  const String16 tag_to_find(name.c_str(), name.size());
+  xml_.restart();
+  ResXMLParser::event_code_t type;
+  do {
+    type = xml_.next();
+    if (type == ResXMLParser::START_TAG) {
+      size_t len;
+      const String16 tag(xml_.getElementName(&len));
+      if (tag == tag_to_find) {
+        std::unique_ptr<std::map<std::string, std::string>> map(
+            new std::map<std::string, std::string>());
+        for (size_t i = 0; i < xml_.getAttributeCount(); i++) {
+          const String16 key16(xml_.getAttributeName(i, &len));
+          std::string key = String8(key16).c_str();
+
+          std::string value;
+          switch (xml_.getAttributeDataType(i)) {
+            case Res_value::TYPE_STRING: {
+              const String16 value16(xml_.getAttributeStringValue(i, &len));
+              value = String8(value16).c_str();
+            } break;
+            case Res_value::TYPE_INT_DEC:
+            case Res_value::TYPE_INT_HEX:
+            case Res_value::TYPE_INT_BOOLEAN: {
+              Res_value resValue;
+              xml_.getAttributeValue(i, &resValue);
+              value = std::to_string(resValue.data);
+            } break;
+            default:
+              return nullptr;
+          }
+
+          map->emplace(std::make_pair(key, value));
+        }
+        return map;
+      }
+    }
+  } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT);
+  return nullptr;
+}
+
+Xml::~Xml() {
+  xml_.uninit();
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/libidmap2/ZipFile.cpp b/cmds/idmap2/libidmap2/ZipFile.cpp
new file mode 100644
index 0000000..3f2079a
--- /dev/null
+++ b/cmds/idmap2/libidmap2/ZipFile.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "idmap2/ZipFile.h"
+
+namespace android {
+namespace idmap2 {
+
+std::unique_ptr<MemoryChunk> MemoryChunk::Allocate(size_t size) {
+  void* ptr = ::operator new(sizeof(MemoryChunk) + size);
+  std::unique_ptr<MemoryChunk> chunk(reinterpret_cast<MemoryChunk*>(ptr));
+  chunk->size = size;
+  return chunk;
+}
+
+std::unique_ptr<const ZipFile> ZipFile::Open(const std::string& path) {
+  ::ZipArchiveHandle handle;
+  int32_t status = ::OpenArchive(path.c_str(), &handle);
+  if (status != 0) {
+    return nullptr;
+  }
+  return std::unique_ptr<ZipFile>(new ZipFile(handle));
+}
+
+ZipFile::~ZipFile() {
+  ::CloseArchive(handle_);
+}
+
+std::unique_ptr<const MemoryChunk> ZipFile::Uncompress(const std::string& entryPath) const {
+  ::ZipEntry entry;
+  int32_t status = ::FindEntry(handle_, ::ZipString(entryPath.c_str()), &entry);
+  if (status != 0) {
+    return nullptr;
+  }
+  std::unique_ptr<MemoryChunk> chunk = MemoryChunk::Allocate(entry.uncompressed_length);
+  status = ::ExtractToMemory(handle_, &entry, chunk->buf, chunk->size);
+  if (status != 0) {
+    return nullptr;
+  }
+  return chunk;
+}
+
+std::pair<bool, uint32_t> ZipFile::Crc(const std::string& entryPath) const {
+  ::ZipEntry entry;
+  int32_t status = ::FindEntry(handle_, ::ZipString(entryPath.c_str()), &entry);
+  return std::make_pair(status == 0, entry.crc32);
+}
+
+}  // namespace idmap2
+}  // namespace android