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/.clang-format b/cmds/idmap2/.clang-format
new file mode 100644
index 0000000..c91502a
--- /dev/null
+++ b/cmds/idmap2/.clang-format
@@ -0,0 +1,7 @@
+BasedOnStyle: Google
+ColumnLimit: 100
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
new file mode 100644
index 0000000..5a6c813
--- /dev/null
+++ b/cmds/idmap2/Android.bp
@@ -0,0 +1,191 @@
+// 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.
+
+cc_library {
+    name: "libidmap2",
+    host_supported: true,
+    tidy: true,
+    tidy_flags: [
+        "-system-headers",
+        "-warnings-as-errors=*",
+    ],
+    srcs: [
+        "libidmap2/BinaryStreamVisitor.cpp",
+        "libidmap2/CommandLineOptions.cpp",
+        "libidmap2/FileUtils.cpp",
+        "libidmap2/Idmap.cpp",
+        "libidmap2/PrettyPrintVisitor.cpp",
+        "libidmap2/RawPrintVisitor.cpp",
+        "libidmap2/ResourceUtils.cpp",
+        "libidmap2/Xml.cpp",
+        "libidmap2/ZipFile.cpp",
+    ],
+    export_include_dirs: ["include"],
+    target: {
+        android: {
+            static: {
+                enabled: false,
+            },
+            shared_libs: [
+                "libandroidfw",
+                "libbase",
+                "libutils",
+                "libziparchive",
+            ],
+        },
+        host: {
+            shared: {
+                enabled: false,
+            },
+            static_libs: [
+                "libandroidfw",
+                "libbase",
+                "libutils",
+                "libziparchive",
+            ],
+        },
+    },
+}
+
+cc_test {
+    name: "idmap2_tests",
+    host_supported: true,
+    tidy: true,
+    tidy_flags: [
+        "-system-headers",
+        "-warnings-as-errors=*",
+    ],
+    srcs: [
+        "tests/BinaryStreamVisitorTests.cpp",
+        "tests/CommandLineOptionsTests.cpp",
+        "tests/FileUtilsTests.cpp",
+        "tests/Idmap2BinaryTests.cpp",
+        "tests/IdmapTests.cpp",
+        "tests/Main.cpp",
+        "tests/PrettyPrintVisitorTests.cpp",
+        "tests/RawPrintVisitorTests.cpp",
+        "tests/ResourceUtilsTests.cpp",
+        "tests/XmlTests.cpp",
+        "tests/ZipFileTests.cpp",
+    ],
+    required: [
+        "idmap2",
+    ],
+    static_libs: ["libgmock"],
+    target: {
+        android: {
+            shared_libs: [
+                "libandroidfw",
+                "libbase",
+                "libidmap2",
+                "liblog",
+                "libutils",
+                "libz",
+                "libziparchive",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libandroidfw",
+                "libbase",
+                "libidmap2",
+                "liblog",
+                "libutils",
+                "libziparchive",
+            ],
+            shared_libs: [
+                "libz",
+            ],
+        },
+    },
+    data: ["tests/data/**/*.apk"],
+}
+
+cc_binary {
+    name: "idmap2",
+    host_supported: true,
+    tidy: true,
+    tidy_flags: [
+        "-system-headers",
+        "-warnings-as-errors=*",
+    ],
+    srcs: [
+        "idmap2/Create.cpp",
+        "idmap2/Dump.cpp",
+        "idmap2/Lookup.cpp",
+        "idmap2/Main.cpp",
+        "idmap2/Scan.cpp",
+        "idmap2/Verify.cpp",
+    ],
+    target: {
+        android: {
+            shared_libs: [
+                "libandroidfw",
+                "libbase",
+                "libidmap2",
+                "libutils",
+                "libziparchive",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libandroidfw",
+                "libbase",
+                "libidmap2",
+                "liblog",
+                "libutils",
+                "libziparchive",
+            ],
+            shared_libs: [
+                "libz",
+            ],
+        },
+    },
+}
+
+cc_binary {
+    name: "idmap2d",
+    host_supported: false,
+    tidy: true,
+    tidy_checks: [
+        // remove google-default-arguments or clang-tidy will complain about
+        // the auto-generated file IIdmap2.cpp
+        "-google-default-arguments",
+    ],
+    tidy_flags: [
+        "-system-headers",
+        "-warnings-as-errors=*",
+    ],
+    srcs: [
+        ":idmap2_aidl",
+        "idmap2d/Idmap2Service.cpp",
+        "idmap2d/Main.cpp",
+    ],
+    shared_libs: [
+        "libandroidfw",
+        "libbase",
+        "libbinder",
+        "libcutils",
+        "libidmap2",
+        "libutils",
+        "libziparchive",
+    ],
+}
+
+filegroup {
+    name: "idmap2_aidl",
+    srcs: [
+        "idmap2d/aidl/android/os/IIdmap2.aidl",
+    ],
+}
diff --git a/cmds/idmap2/AndroidTest.xml b/cmds/idmap2/AndroidTest.xml
new file mode 100644
index 0000000..5147f4e
--- /dev/null
+++ b/cmds/idmap2/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for idmap2_tests">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="idmap2_tests->/data/local/tmp/idmap2_tests" />
+    </target_preparer>
+    <option name="test-suite-tag" value="idmap2_tests" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="idmap2_tests" />
+    </test>
+</configuration>
diff --git a/cmds/idmap2/CPPLINT.cfg b/cmds/idmap2/CPPLINT.cfg
new file mode 100644
index 0000000..9dc6b4a
--- /dev/null
+++ b/cmds/idmap2/CPPLINT.cfg
@@ -0,0 +1,18 @@
+# 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.
+
+set noparent
+linelength=100
+root=..
+filter=+build/include_alpha
diff --git a/cmds/idmap2/OWNERS b/cmds/idmap2/OWNERS
new file mode 100644
index 0000000..23ec5ab
--- /dev/null
+++ b/cmds/idmap2/OWNERS
@@ -0,0 +1,2 @@
+set noparent
+toddke@google.com
diff --git a/cmds/idmap2/TEST_MAPPING b/cmds/idmap2/TEST_MAPPING
new file mode 100644
index 0000000..26ccf03
--- /dev/null
+++ b/cmds/idmap2/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit" : [
+    {
+      "name" : "idmap2_tests"
+    }
+  ]
+}
diff --git a/cmds/idmap2/idmap2/Commands.h b/cmds/idmap2/idmap2/Commands.h
new file mode 100644
index 0000000..dcc69b3
--- /dev/null
+++ b/cmds/idmap2/idmap2/Commands.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_IDMAP2_COMMANDS_H_
+#define IDMAP2_IDMAP2_COMMANDS_H_
+
+#include <string>
+#include <vector>
+
+bool Create(const std::vector<std::string>& args, std::ostream& out_error);
+bool Dump(const std::vector<std::string>& args, std::ostream& out_error);
+bool Lookup(const std::vector<std::string>& args, std::ostream& out_error);
+bool Scan(const std::vector<std::string>& args, std::ostream& out_error);
+bool Verify(const std::vector<std::string>& args, std::ostream& out_error);
+
+#endif  // IDMAP2_IDMAP2_COMMANDS_H_
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
new file mode 100644
index 0000000..291eaeb
--- /dev/null
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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 <sys/stat.h>   // umask
+#include <sys/types.h>  // umask
+#include <fstream>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+
+using android::ApkAssets;
+using android::idmap2::BinaryStreamVisitor;
+using android::idmap2::CommandLineOptions;
+using android::idmap2::Idmap;
+
+bool Create(const std::vector<std::string>& args, std::ostream& out_error) {
+  std::string target_apk_path, overlay_apk_path, idmap_path;
+
+  const CommandLineOptions opts =
+      CommandLineOptions("idmap2 create")
+          .MandatoryOption("--target-apk-path",
+                           "input: path to apk which will have its resources overlaid",
+                           &target_apk_path)
+          .MandatoryOption("--overlay-apk-path",
+                           "input: path to apk which contains the new resource values",
+                           &overlay_apk_path)
+          .MandatoryOption("--idmap-path", "output: path to where to write idmap file",
+                           &idmap_path);
+  if (!opts.Parse(args, out_error)) {
+    return false;
+  }
+
+  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  if (!target_apk) {
+    out_error << "error: failed to load apk " << target_apk_path << std::endl;
+    return false;
+  }
+
+  const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  if (!overlay_apk) {
+    out_error << "error: failed to load apk " << overlay_apk_path << std::endl;
+    return false;
+  }
+
+  const std::unique_ptr<const Idmap> idmap =
+      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, out_error);
+  if (!idmap) {
+    return false;
+  }
+
+  umask(0133);  // u=rw,g=r,o=r
+  std::ofstream fout(idmap_path);
+  if (fout.fail()) {
+    out_error << "failed to open idmap path " << idmap_path << std::endl;
+    return false;
+  }
+  BinaryStreamVisitor visitor(fout);
+  idmap->accept(&visitor);
+  fout.close();
+  if (fout.fail()) {
+    out_error << "failed to write to idmap path " << idmap_path << std::endl;
+    return false;
+  }
+
+  return true;
+}
diff --git a/cmds/idmap2/idmap2/Dump.cpp b/cmds/idmap2/idmap2/Dump.cpp
new file mode 100644
index 0000000..c8cdcfa
--- /dev/null
+++ b/cmds/idmap2/idmap2/Dump.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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 <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/Idmap.h"
+#include "idmap2/PrettyPrintVisitor.h"
+#include "idmap2/RawPrintVisitor.h"
+
+using android::idmap2::CommandLineOptions;
+using android::idmap2::Idmap;
+using android::idmap2::PrettyPrintVisitor;
+using android::idmap2::RawPrintVisitor;
+
+bool Dump(const std::vector<std::string>& args, std::ostream& out_error) {
+  std::string idmap_path;
+  bool verbose;
+
+  const CommandLineOptions opts =
+      CommandLineOptions("idmap2 dump")
+          .MandatoryOption("--idmap-path", "input: path to idmap file to pretty-print", &idmap_path)
+          .OptionalFlag("--verbose", "annotate every byte of the idmap", &verbose);
+  if (!opts.Parse(args, out_error)) {
+    return false;
+  }
+  std::ifstream fin(idmap_path);
+  const std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(fin, out_error);
+  fin.close();
+  if (!idmap) {
+    return false;
+  }
+
+  if (verbose) {
+    RawPrintVisitor visitor(std::cout);
+    idmap->accept(&visitor);
+  } else {
+    PrettyPrintVisitor visitor(std::cout);
+    idmap->accept(&visitor);
+  }
+
+  return true;
+}
diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp
new file mode 100644
index 0000000..1191e6a
--- /dev/null
+++ b/cmds/idmap2/idmap2/Lookup.cpp
@@ -0,0 +1,249 @@
+/*
+ * 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 <fstream>
+#include <iterator>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ConfigDescription.h"
+#include "androidfw/ResourceUtils.h"
+#include "androidfw/StringPiece.h"
+#include "androidfw/Util.h"
+#include "utils/String16.h"
+#include "utils/String8.h"
+
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/Idmap.h"
+#include "idmap2/Xml.h"
+#include "idmap2/ZipFile.h"
+
+using android::ApkAssets;
+using android::ApkAssetsCookie;
+using android::AssetManager2;
+using android::ConfigDescription;
+using android::is_valid_resid;
+using android::kInvalidCookie;
+using android::Res_value;
+using android::ResStringPool;
+using android::ResTable_config;
+using android::String16;
+using android::String8;
+using android::StringPiece16;
+using android::base::StringPrintf;
+using android::idmap2::CommandLineOptions;
+using android::idmap2::IdmapHeader;
+using android::idmap2::ResourceId;
+using android::idmap2::Xml;
+using android::idmap2::ZipFile;
+using android::util::Utf16ToUtf8;
+
+namespace {
+std::pair<bool, ResourceId> WARN_UNUSED ParseResReference(const AssetManager2& am,
+                                                          const std::string& res,
+                                                          const std::string& fallback_package) {
+  // first, try to parse as a hex number
+  char* endptr = nullptr;
+  ResourceId resid;
+  resid = strtol(res.c_str(), &endptr, 16);
+  if (*endptr == '\0') {
+    return std::make_pair(true, resid);
+  }
+
+  // next, try to parse as a package:type/name string
+  resid = am.GetResourceId(res, "", fallback_package);
+  if (is_valid_resid(resid)) {
+    return std::make_pair(true, resid);
+  }
+
+  // end of the road: res could not be parsed
+  return std::make_pair(false, 0);
+}
+
+std::pair<bool, std::string> WARN_UNUSED GetValue(const AssetManager2& am, ResourceId resid) {
+  Res_value value;
+  ResTable_config config;
+  uint32_t flags;
+  ApkAssetsCookie cookie = am.GetResource(resid, false, 0, &value, &config, &flags);
+  if (cookie == kInvalidCookie) {
+    return std::make_pair(false, "");
+  }
+
+  std::string out;
+
+  // TODO(martenkongstad): use optional parameter GetResource(..., std::string*
+  // stacktrace = NULL) instead
+  out.append(StringPrintf("cookie=%d ", cookie));
+
+  out.append("config='");
+  out.append(config.toString().c_str());
+  out.append("' value=");
+
+  switch (value.dataType) {
+    case Res_value::TYPE_INT_DEC:
+      out.append(StringPrintf("%d", value.data));
+      break;
+    case Res_value::TYPE_INT_HEX:
+      out.append(StringPrintf("0x%08x", value.data));
+      break;
+    case Res_value::TYPE_INT_BOOLEAN:
+      out.append(value.data != 0 ? "true" : "false");
+      break;
+    case Res_value::TYPE_STRING: {
+      const ResStringPool* pool = am.GetStringPoolForCookie(cookie);
+      size_t len;
+      if (pool->isUTF8()) {
+        const char* str = pool->string8At(value.data, &len);
+        out.append(str, len);
+      } else {
+        const char16_t* str16 = pool->stringAt(value.data, &len);
+        out += Utf16ToUtf8(StringPiece16(str16, len));
+      }
+    } break;
+    default:
+      out.append(StringPrintf("dataType=0x%02x data=0x%08x", value.dataType, value.data));
+      break;
+  }
+  return std::make_pair(true, out);
+}
+
+std::pair<bool, std::string> GetTargetPackageNameFromManifest(const std::string& apk_path) {
+  const auto zip = ZipFile::Open(apk_path);
+  if (!zip) {
+    return std::make_pair(false, "");
+  }
+  const auto entry = zip->Uncompress("AndroidManifest.xml");
+  if (!entry) {
+    return std::make_pair(false, "");
+  }
+  const auto xml = Xml::Create(entry->buf, entry->size);
+  if (!xml) {
+    return std::make_pair(false, "");
+  }
+  const auto tag = xml->FindTag("overlay");
+  if (!tag) {
+    return std::make_pair(false, "");
+  }
+  const auto iter = tag->find("targetPackage");
+  if (iter == tag->end()) {
+    return std::make_pair(false, "");
+  }
+  return std::make_pair(true, iter->second);
+}
+}  // namespace
+
+bool Lookup(const std::vector<std::string>& args, std::ostream& out_error) {
+  std::vector<std::string> idmap_paths;
+  std::string config_str, resid_str;
+  const CommandLineOptions opts =
+      CommandLineOptions("idmap2 lookup")
+          .MandatoryOption("--idmap-path", "input: path to idmap file to load", &idmap_paths)
+          .MandatoryOption("--config", "configuration to use", &config_str)
+          .MandatoryOption("--resid",
+                           "Resource ID (in the target package; '0xpptteeee' or "
+                           "'[package:]type/name') to look up",
+                           &resid_str);
+
+  if (!opts.Parse(args, out_error)) {
+    return false;
+  }
+
+  ConfigDescription config;
+  if (!ConfigDescription::Parse(config_str, &config)) {
+    out_error << "error: failed to parse config" << std::endl;
+    return false;
+  }
+
+  std::vector<std::unique_ptr<const ApkAssets>> apk_assets;
+  std::string target_path;
+  std::string target_package_name;
+  for (size_t i = 0; i < idmap_paths.size(); i++) {
+    const auto& idmap_path = idmap_paths[i];
+    std::fstream fin(idmap_path);
+    auto idmap_header = IdmapHeader::FromBinaryStream(fin);
+    fin.close();
+    if (!idmap_header) {
+      out_error << "error: failed to read idmap from " << idmap_path << std::endl;
+      return false;
+    }
+
+    if (i == 0) {
+      target_path = idmap_header->GetTargetPath().to_string();
+      auto target_apk = ApkAssets::Load(target_path);
+      if (!target_apk) {
+        out_error << "error: failed to read target apk from " << target_path << std::endl;
+        return false;
+      }
+      apk_assets.push_back(std::move(target_apk));
+
+      bool lookup_ok;
+      std::tie(lookup_ok, target_package_name) =
+          GetTargetPackageNameFromManifest(idmap_header->GetOverlayPath().to_string());
+      if (!lookup_ok) {
+        out_error << "error: failed to parse android:targetPackage from overlay manifest"
+                  << std::endl;
+        return false;
+      }
+    } else if (target_path != idmap_header->GetTargetPath()) {
+      out_error << "error: different target APKs (expected target APK " << target_path << " but "
+                << idmap_path << " has target APK " << idmap_header->GetTargetPath() << ")"
+                << std::endl;
+      return false;
+    }
+
+    auto overlay_apk = ApkAssets::LoadOverlay(idmap_path);
+    if (!overlay_apk) {
+      out_error << "error: failed to read overlay apk from " << idmap_header->GetOverlayPath()
+                << std::endl;
+      return false;
+    }
+    apk_assets.push_back(std::move(overlay_apk));
+  }
+
+  // AssetManager2::SetApkAssets requires raw ApkAssets pointers, not unique_ptrs
+  std::vector<const ApkAssets*> raw_pointer_apk_assets;
+  std::transform(apk_assets.cbegin(), apk_assets.cend(), std::back_inserter(raw_pointer_apk_assets),
+                 [](const auto& p) -> const ApkAssets* { return p.get(); });
+  AssetManager2 am;
+  am.SetApkAssets(raw_pointer_apk_assets);
+  am.SetConfiguration(config);
+
+  ResourceId resid;
+  bool lookup_ok;
+  std::tie(lookup_ok, resid) = ParseResReference(am, resid_str, target_package_name);
+  if (!lookup_ok) {
+    out_error << "error: failed to parse resource ID" << std::endl;
+    return false;
+  }
+
+  std::string value;
+  std::tie(lookup_ok, value) = GetValue(am, resid);
+  if (!lookup_ok) {
+    out_error << StringPrintf("error: resource 0x%08x not found", resid) << std::endl;
+    return false;
+  }
+  std::cout << value << std::endl;
+
+  return true;
+}
diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp
new file mode 100644
index 0000000..5d9ea77
--- /dev/null
+++ b/cmds/idmap2/idmap2/Main.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 <cstdlib>  // EXIT_{FAILURE,SUCCESS}
+#include <functional>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "idmap2/CommandLineOptions.h"
+
+#include "Commands.h"
+
+using android::idmap2::CommandLineOptions;
+
+typedef std::map<std::string, std::function<int(const std::vector<std::string>&, std::ostream&)>>
+    NameToFunctionMap;
+
+static void PrintUsage(const NameToFunctionMap& commands, std::ostream& out) {
+  out << "usage: idmap2 [";
+  for (auto iter = commands.cbegin(); iter != commands.cend(); iter++) {
+    if (iter != commands.cbegin()) {
+      out << "|";
+    }
+    out << iter->first;
+  }
+  out << "]" << std::endl;
+}
+
+int main(int argc, char** argv) {
+  const NameToFunctionMap commands = {
+      {"create", Create}, {"dump", Dump}, {"lookup", Lookup}, {"scan", Scan}, {"verify", Verify},
+  };
+  if (argc <= 1) {
+    PrintUsage(commands, std::cerr);
+    return EXIT_FAILURE;
+  }
+  const std::unique_ptr<std::vector<std::string>> args =
+      CommandLineOptions::ConvertArgvToVector(argc - 1, const_cast<const char**>(argv + 1));
+  if (!args) {
+    std::cerr << "error: failed to parse command line options" << std::endl;
+    return EXIT_FAILURE;
+  }
+  const auto iter = commands.find(argv[1]);
+  if (iter == commands.end()) {
+    std::cerr << argv[1] << ": command not found" << std::endl;
+    PrintUsage(commands, std::cerr);
+    return EXIT_FAILURE;
+  }
+  return iter->second(*args, std::cerr) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
new file mode 100644
index 0000000..33c274e
--- /dev/null
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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 <fstream>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+#include "idmap2/Xml.h"
+#include "idmap2/ZipFile.h"
+
+#include "Commands.h"
+
+using android::idmap2::CommandLineOptions;
+using android::idmap2::Idmap;
+using android::idmap2::MemoryChunk;
+using android::idmap2::Xml;
+using android::idmap2::ZipFile;
+using android::idmap2::utils::FindFiles;
+
+namespace {
+std::unique_ptr<std::vector<std::string>> FindApkFiles(const std::vector<std::string>& dirs,
+                                                       bool recursive, std::ostream& out_error) {
+  const auto predicate = [](unsigned char type, const std::string& path) -> bool {
+    static constexpr size_t kExtLen = 4;  // strlen(".apk")
+    return type == DT_REG && path.size() > kExtLen &&
+           !path.compare(path.size() - kExtLen, kExtLen, ".apk");
+  };
+  // pass apk paths through a set to filter out duplicates
+  std::set<std::string> paths;
+  for (const auto& dir : dirs) {
+    const auto apk_paths = FindFiles(dir, recursive, predicate);
+    if (!apk_paths) {
+      out_error << "error: failed to open directory " << dir << std::endl;
+      return nullptr;
+    }
+    paths.insert(apk_paths->cbegin(), apk_paths->cend());
+  }
+  return std::unique_ptr<std::vector<std::string>>(
+      new std::vector<std::string>(paths.cbegin(), paths.cend()));
+}
+}  // namespace
+
+bool Scan(const std::vector<std::string>& args, std::ostream& out_error) {
+  std::vector<std::string> input_directories;
+  std::string target_package_name, target_apk_path, output_directory;
+  bool recursive = false;
+
+  const CommandLineOptions opts =
+      CommandLineOptions("idmap2 scan")
+          .MandatoryOption("--input-directory", "directory containing overlay apks to scan",
+                           &input_directories)
+          .OptionalFlag("--recursive", "also scan subfolders of overlay-directory", &recursive)
+          .MandatoryOption("--target-package-name", "package name of target package",
+                           &target_package_name)
+          .MandatoryOption("--target-apk-path", "path to target apk", &target_apk_path)
+          .MandatoryOption("--output-directory",
+                           "directory in which to write artifacts (idmap files and overlays.list)",
+                           &output_directory);
+  if (!opts.Parse(args, out_error)) {
+    return false;
+  }
+
+  const auto apk_paths = FindApkFiles(input_directories, recursive, out_error);
+  if (!apk_paths) {
+    return false;
+  }
+
+  std::vector<std::string> interesting_apks;
+  for (const std::string& path : *apk_paths) {
+    std::unique_ptr<const ZipFile> zip = ZipFile::Open(path);
+    if (!zip) {
+      out_error << "error: failed to open " << path << " as a zip file" << std::endl;
+      return false;
+    }
+
+    std::unique_ptr<const MemoryChunk> entry = zip->Uncompress("AndroidManifest.xml");
+    if (!entry) {
+      out_error << "error: failed to uncompress AndroidManifest.xml from " << path << std::endl;
+      return false;
+    }
+
+    std::unique_ptr<const Xml> xml = Xml::Create(entry->buf, entry->size);
+    if (!xml) {
+      out_error << "error: failed to parse AndroidManifest.xml from " << path << std::endl;
+      continue;
+    }
+
+    const auto tag = xml->FindTag("overlay");
+    if (!tag) {
+      continue;
+    }
+
+    auto iter = tag->find("isStatic");
+    if (iter == tag->end() || std::stoul(iter->second) == 0u) {
+      continue;
+    }
+
+    iter = tag->find("targetPackage");
+    if (iter == tag->end() || iter->second != target_package_name) {
+      continue;
+    }
+
+    iter = tag->find("priority");
+    if (iter == tag->end()) {
+      continue;
+    }
+
+    const int priority = std::stoi(iter->second);
+    if (priority < 0) {
+      continue;
+    }
+
+    interesting_apks.insert(
+        std::lower_bound(interesting_apks.begin(), interesting_apks.end(), path), path);
+  }
+
+  std::stringstream stream;
+  for (auto iter = interesting_apks.cbegin(); iter != interesting_apks.cend(); ++iter) {
+    const std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, *iter);
+    if (!Verify(std::vector<std::string>({"--idmap-path", idmap_path}), out_error) &&
+        !Create(std::vector<std::string>({
+                    "--target-apk-path",
+                    target_apk_path,
+                    "--overlay-apk-path",
+                    *iter,
+                    "--idmap-path",
+                    idmap_path,
+                }),
+                out_error)) {
+      return false;
+    }
+    stream << idmap_path << std::endl;
+  }
+
+  std::cout << stream.str();
+
+  return true;
+}
diff --git a/cmds/idmap2/idmap2/Verify.cpp b/cmds/idmap2/idmap2/Verify.cpp
new file mode 100644
index 0000000..b5fa438
--- /dev/null
+++ b/cmds/idmap2/idmap2/Verify.cpp
@@ -0,0 +1,46 @@
+/*
+ * 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 <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/Idmap.h"
+
+using android::idmap2::CommandLineOptions;
+using android::idmap2::IdmapHeader;
+
+bool Verify(const std::vector<std::string>& args, std::ostream& out_error) {
+  std::string idmap_path;
+  const CommandLineOptions opts =
+      CommandLineOptions("idmap2 verify")
+          .MandatoryOption("--idmap-path", "input: path to idmap file to verify", &idmap_path);
+  if (!opts.Parse(args, out_error)) {
+    return false;
+  }
+
+  std::ifstream fin(idmap_path);
+  const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
+  fin.close();
+  if (!header) {
+    out_error << "error: failed to parse idmap header" << std::endl;
+    return false;
+  }
+
+  return header->IsUpToDate(out_error);
+}
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
new file mode 100644
index 0000000..cf72cb9
--- /dev/null
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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 <sys/stat.h>   // umask
+#include <sys/types.h>  // umask
+#include <unistd.h>
+
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "android-base/macros.h"
+#include "utils/String8.h"
+#include "utils/Trace.h"
+
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+
+#include "idmap2d/Idmap2Service.h"
+
+using android::binder::Status;
+using android::idmap2::BinaryStreamVisitor;
+using android::idmap2::Idmap;
+using android::idmap2::IdmapHeader;
+
+namespace {
+
+static constexpr const char* kIdmapCacheDir = "/data/resource-cache";
+
+Status ok() {
+  return Status::ok();
+}
+
+Status error(const std::string& msg) {
+  LOG(ERROR) << msg;
+  return Status::fromExceptionCode(Status::EX_NONE, msg.c_str());
+}
+
+}  // namespace
+
+namespace android {
+namespace os {
+
+Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path,
+                                   int32_t user_id ATTRIBUTE_UNUSED, std::string* _aidl_return) {
+  assert(_aidl_return);
+  *_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  return ok();
+}
+
+Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path,
+                                  int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
+  assert(_aidl_return);
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  if (unlink(idmap_path.c_str()) == 0) {
+    *_aidl_return = true;
+    return ok();
+  } else {
+    *_aidl_return = false;
+    return error("failed to unlink " + idmap_path + ": " + strerror(errno));
+  }
+}
+
+Status Idmap2Service::createIdmap(const std::string& target_apk_path,
+                                  const std::string& overlay_apk_path, int32_t user_id,
+                                  std::unique_ptr<std::string>* _aidl_return) {
+  assert(_aidl_return);
+  std::stringstream trace;
+  trace << __FUNCTION__ << " " << target_apk_path << " " << overlay_apk_path << " "
+        << std::to_string(user_id);
+  ATRACE_NAME(trace.str().c_str());
+  std::cout << trace.str() << std::endl;
+
+  _aidl_return->reset(nullptr);
+
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  std::ifstream fin(idmap_path);
+  const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
+  fin.close();
+  // do not reuse error stream from IsUpToDate below, or error messages will be
+  // polluted with irrelevant data
+  std::stringstream dev_null;
+  if (header && header->IsUpToDate(dev_null)) {
+    return ok();
+  }
+
+  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  if (!target_apk) {
+    return error("failed to load apk " + target_apk_path);
+  }
+
+  const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  if (!overlay_apk) {
+    return error("failed to load apk " + overlay_apk_path);
+  }
+
+  std::stringstream err;
+  const std::unique_ptr<const Idmap> idmap =
+      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, err);
+  if (!idmap) {
+    return error(err.str());
+  }
+
+  umask(0133);  // u=rw,g=r,o=r
+  std::ofstream fout(idmap_path);
+  if (fout.fail()) {
+    return error("failed to open idmap path " + idmap_path);
+  }
+  BinaryStreamVisitor visitor(fout);
+  idmap->accept(&visitor);
+  fout.close();
+  if (fout.fail()) {
+    return error("failed to write to idmap path " + idmap_path);
+  }
+
+  _aidl_return->reset(new std::string(idmap_path));
+  return ok();
+}
+
+}  // namespace os
+}  // namespace android
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
new file mode 100644
index 0000000..2b32042
--- /dev/null
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
+#define IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
+
+#include <android-base/unique_fd.h>
+#include <binder/BinderService.h>
+
+#include <memory>
+#include <string>
+
+#include "android/os/BnIdmap2.h"
+
+namespace android {
+namespace os {
+class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 {
+ public:
+  static char const* getServiceName() {
+    return "idmap";
+  }
+
+  binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id,
+                              std::string* _aidl_return);
+
+  binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id,
+                             bool* _aidl_return);
+
+  binder::Status createIdmap(const std::string& target_apk_path,
+                             const std::string& overlay_apk_path, int32_t user_id,
+                             std::unique_ptr<std::string>* _aidl_return);
+};
+}  // namespace os
+}  // namespace android
+
+#endif  // IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
diff --git a/cmds/idmap2/idmap2d/Main.cpp b/cmds/idmap2/idmap2d/Main.cpp
new file mode 100644
index 0000000..d64a87b
--- /dev/null
+++ b/cmds/idmap2/idmap2d/Main.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_RESOURCES
+
+#include <binder/BinderService.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+
+#include <cstdlib>  // EXIT_{FAILURE,SUCCESS}
+
+#include <iostream>
+#include <sstream>
+
+#include "android-base/macros.h"
+
+#include "Idmap2Service.h"
+
+using android::BinderService;
+using android::IPCThreadState;
+using android::ProcessState;
+using android::sp;
+using android::status_t;
+using android::os::Idmap2Service;
+
+int main(int argc ATTRIBUTE_UNUSED, char** argv ATTRIBUTE_UNUSED) {
+  IPCThreadState::self()->disableBackgroundScheduling(true);
+  status_t ret = BinderService<Idmap2Service>::publish();
+  if (ret != android::OK) {
+    return EXIT_FAILURE;
+  }
+  sp<ProcessState> ps(ProcessState::self());
+  ps->startThreadPool();
+  ps->giveThreadPoolName();
+  IPCThreadState::self()->joinThreadPool();
+  return EXIT_SUCCESS;
+}
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
new file mode 100644
index 0000000..5d19610
--- /dev/null
+++ b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package android.os;
+
+/**
+ * @hide
+ */
+interface IIdmap2 {
+  @utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId);
+  boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId);
+  @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath,
+                                          @utf8InCpp String overlayApkPath, int userId);
+}
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
new file mode 100644
index 0000000..2368aea
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_
+#define IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_
+
+#include <cstdint>
+#include <iostream>
+#include <string>
+
+#include "idmap2/Idmap.h"
+
+namespace android {
+namespace idmap2 {
+
+class BinaryStreamVisitor : public Visitor {
+ public:
+  explicit BinaryStreamVisitor(std::ostream& stream) : stream_(stream) {
+  }
+  virtual void visit(const Idmap& idmap);
+  virtual void visit(const IdmapHeader& header);
+  virtual void visit(const IdmapData& data);
+  virtual void visit(const IdmapData::Header& header);
+  virtual void visit(const IdmapData::TypeEntry& type_entry);
+
+ private:
+  void Write16(uint16_t value);
+  void Write32(uint32_t value);
+  void WriteString(const StringPiece& value);
+  std::ostream& stream_;
+};
+
+}  // namespace idmap2
+}  // namespace android
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_
diff --git a/cmds/idmap2/include/idmap2/CommandLineOptions.h b/cmds/idmap2/include/idmap2/CommandLineOptions.h
new file mode 100644
index 0000000..f3aa68b
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/CommandLineOptions.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_
+#define IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_
+
+#include <functional>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <vector>
+
+namespace android {
+namespace idmap2 {
+
+/*
+ * Utility class to convert a command line, including options (--path foo.txt),
+ * into data structures (options.path = "foo.txt").
+ */
+class CommandLineOptions {
+ public:
+  static std::unique_ptr<std::vector<std::string>> ConvertArgvToVector(int argc, const char** argv);
+
+  explicit CommandLineOptions(const std::string& name) : name_(name) {
+  }
+
+  CommandLineOptions& OptionalFlag(const std::string& name, const std::string& description,
+                                   bool* value);
+  CommandLineOptions& MandatoryOption(const std::string& name, const std::string& description,
+                                      std::string* value);
+  CommandLineOptions& MandatoryOption(const std::string& name, const std::string& description,
+                                      std::vector<std::string>* value);
+  CommandLineOptions& OptionalOption(const std::string& name, const std::string& description,
+                                     std::string* value);
+  bool Parse(const std::vector<std::string>& argv, std::ostream& outError) const;
+  void Usage(std::ostream& out) const;
+
+ private:
+  struct Option {
+    std::string name;
+    std::string description;
+    std::function<void(const std::string& value)> action;
+    enum {
+      COUNT_OPTIONAL,
+      COUNT_EXACTLY_ONCE,
+      COUNT_ONCE_OR_MORE,
+    } count;
+    bool argument;
+  };
+
+  mutable std::vector<Option> options_;
+  std::string name_;
+};
+
+}  // namespace idmap2
+}  // namespace android
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_
diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
new file mode 100644
index 0000000..05c6d31
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
+#define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace android {
+namespace idmap2 {
+namespace utils {
+typedef std::function<bool(unsigned char type /* DT_* from dirent.h */, const std::string& path)>
+    FindFilesPredicate;
+std::unique_ptr<std::vector<std::string>> FindFiles(const std::string& root, bool recurse,
+                                                    const FindFilesPredicate& predicate);
+
+std::unique_ptr<std::string> ReadFile(int fd);
+
+std::unique_ptr<std::string> ReadFile(const std::string& path);
+
+}  // namespace utils
+}  // namespace idmap2
+}  // namespace android
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
new file mode 100644
index 0000000..837b7c5
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -0,0 +1,277 @@
+/*
+ * 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.
+ */
+
+/*
+ * # idmap file format (current version)
+ *
+ * idmap             := header data*
+ * header            := magic version target_crc overlay_crc target_path overlay_path
+ * data              := data_header data_block*
+ * data_header       := target_package_id types_count
+ * data_block        := target_type overlay_type entry_count entry_offset entry*
+ * overlay_path      := string
+ * target_path       := string
+ * entry             := <uint32_t>
+ * entry_count       := <uint16_t>
+ * entry_offset      := <uint16_t>
+ * magic             := <uint32_t>
+ * overlay_crc       := <uint32_t>
+ * overlay_type      := <uint16_t>
+ * string            := <uint8_t>[256]
+ * target_crc        := <uint32_t>
+ * target_package_id := <uint16_t>
+ * target_type       := <uint16_t>
+ * types_count       := <uint16_t>
+ * version           := <uint32_t>
+ *
+ *
+ * # idmap file format changelog
+ * ## v1
+ * - Identical to idmap v1.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
+#define IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/macros.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+
+namespace android {
+namespace idmap2 {
+
+class Idmap;
+class Visitor;
+
+// use typedefs to let the compiler warn us about implicit casts
+typedef uint32_t ResourceId;  // 0xpptteeee
+typedef uint8_t PackageId;    // pp in 0xpptteeee
+typedef uint8_t TypeId;       // tt in 0xpptteeee
+typedef uint16_t EntryId;     // eeee in 0xpptteeee
+
+static constexpr const ResourceId kPadding = 0xffffffffu;
+
+static constexpr const EntryId kNoEntry = 0xffffu;
+
+// magic number: all idmap files start with this
+static constexpr const uint32_t kIdmapMagic = android::kIdmapMagic;
+
+// current version of the idmap binary format; must be incremented when the format is changed
+static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVersion;
+
+// strings in the idmap are encoded char arrays of length 'kIdmapStringLength' (including mandatory
+// terminating null)
+static constexpr const size_t kIdmapStringLength = 256;
+
+class IdmapHeader {
+ public:
+  static std::unique_ptr<const IdmapHeader> FromBinaryStream(std::istream& stream);
+
+  inline uint32_t GetMagic() const {
+    return magic_;
+  }
+
+  inline uint32_t GetVersion() const {
+    return version_;
+  }
+
+  inline uint32_t GetTargetCrc() const {
+    return target_crc_;
+  }
+
+  inline uint32_t GetOverlayCrc() const {
+    return overlay_crc_;
+  }
+
+  inline StringPiece GetTargetPath() const {
+    return StringPiece(target_path_);
+  }
+
+  inline StringPiece GetOverlayPath() const {
+    return StringPiece(overlay_path_);
+  }
+
+  // Invariant: anytime the idmap data encoding is changed, the idmap version
+  // field *must* be incremented. Because of this, we know that if the idmap
+  // header is up-to-date the entire file is up-to-date.
+  bool IsUpToDate(std::ostream& out_error) const;
+
+  void accept(Visitor* v) const;
+
+ private:
+  IdmapHeader() {
+  }
+
+  uint32_t magic_;
+  uint32_t version_;
+  uint32_t target_crc_;
+  uint32_t overlay_crc_;
+  char target_path_[kIdmapStringLength];
+  char overlay_path_[kIdmapStringLength];
+
+  friend Idmap;
+  DISALLOW_COPY_AND_ASSIGN(IdmapHeader);
+};
+
+class IdmapData {
+ public:
+  class Header {
+   public:
+    static std::unique_ptr<const Header> FromBinaryStream(std::istream& stream);
+
+    inline PackageId GetTargetPackageId() const {
+      return target_package_id_;
+    }
+
+    inline uint16_t GetTypeCount() const {
+      return type_count_;
+    }
+
+    void accept(Visitor* v) const;
+
+   private:
+    Header() {
+    }
+
+    PackageId target_package_id_;
+    uint16_t type_count_;
+
+    friend Idmap;
+    DISALLOW_COPY_AND_ASSIGN(Header);
+  };
+
+  class TypeEntry {
+   public:
+    static std::unique_ptr<const TypeEntry> FromBinaryStream(std::istream& stream);
+
+    inline TypeId GetTargetTypeId() const {
+      return target_type_id_;
+    }
+
+    inline TypeId GetOverlayTypeId() const {
+      return overlay_type_id_;
+    }
+
+    inline uint16_t GetEntryCount() const {
+      return entries_.size();
+    }
+
+    inline uint16_t GetEntryOffset() const {
+      return entry_offset_;
+    }
+
+    inline EntryId GetEntry(size_t i) const {
+      return i < entries_.size() ? entries_[i] : 0xffffu;
+    }
+
+    void accept(Visitor* v) const;
+
+   private:
+    TypeEntry() {
+    }
+
+    TypeId target_type_id_;
+    TypeId overlay_type_id_;
+    uint16_t entry_offset_;
+    std::vector<EntryId> entries_;
+
+    friend Idmap;
+    DISALLOW_COPY_AND_ASSIGN(TypeEntry);
+  };
+
+  static std::unique_ptr<const IdmapData> FromBinaryStream(std::istream& stream);
+
+  inline const std::unique_ptr<const Header>& GetHeader() const {
+    return header_;
+  }
+
+  inline const std::vector<std::unique_ptr<const TypeEntry>>& GetTypeEntries() const {
+    return type_entries_;
+  }
+
+  void accept(Visitor* v) const;
+
+ private:
+  IdmapData() {
+  }
+
+  std::unique_ptr<const Header> header_;
+  std::vector<std::unique_ptr<const TypeEntry>> type_entries_;
+
+  friend Idmap;
+  DISALLOW_COPY_AND_ASSIGN(IdmapData);
+};
+
+class Idmap {
+ public:
+  static std::string CanonicalIdmapPathFor(const std::string& absolute_dir,
+                                           const std::string& absolute_apk_path);
+
+  static std::unique_ptr<const Idmap> FromBinaryStream(std::istream& stream,
+                                                       std::ostream& out_error);
+
+  // In the current version of idmap, the first package in each resources.arsc
+  // file is used; change this in the next version of idmap to use a named
+  // package instead; also update FromApkAssets to take additional parameters:
+  // the target and overlay package names
+  static std::unique_ptr<const 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);
+
+  inline const std::unique_ptr<const IdmapHeader>& GetHeader() const {
+    return header_;
+  }
+
+  inline const std::vector<std::unique_ptr<const IdmapData>>& GetData() const {
+    return data_;
+  }
+
+  void accept(Visitor* v) const;
+
+ private:
+  Idmap() {
+  }
+
+  std::unique_ptr<const IdmapHeader> header_;
+  std::vector<std::unique_ptr<const IdmapData>> data_;
+
+  DISALLOW_COPY_AND_ASSIGN(Idmap);
+};
+
+class Visitor {
+ public:
+  virtual ~Visitor() {
+  }
+  virtual void visit(const Idmap& idmap) = 0;
+  virtual void visit(const IdmapHeader& header) = 0;
+  virtual void visit(const IdmapData& data) = 0;
+  virtual void visit(const IdmapData::Header& header) = 0;
+  virtual void visit(const IdmapData::TypeEntry& type_entry) = 0;
+};
+
+}  // namespace idmap2
+}  // namespace android
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
new file mode 100644
index 0000000..c388f4b
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_
+#define IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_
+
+#include <iostream>
+#include <memory>
+
+#include "androidfw/AssetManager2.h"
+
+#include "idmap2/Idmap.h"
+
+namespace android {
+
+class ApkAssets;
+
+namespace idmap2 {
+
+class PrettyPrintVisitor : public Visitor {
+ public:
+  explicit PrettyPrintVisitor(std::ostream& stream) : stream_(stream) {
+  }
+  virtual void visit(const Idmap& idmap);
+  virtual void visit(const IdmapHeader& header);
+  virtual void visit(const IdmapData& data);
+  virtual void visit(const IdmapData::Header& header);
+  virtual void visit(const IdmapData::TypeEntry& type_entry);
+
+ private:
+  std::ostream& stream_;
+  std::unique_ptr<const ApkAssets> target_apk_;
+  AssetManager2 target_am_;
+  PackageId last_seen_package_id_;
+};
+
+}  // namespace idmap2
+}  // namespace android
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
new file mode 100644
index 0000000..7e33b3b
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_
+#define IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_
+
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "androidfw/AssetManager2.h"
+
+#include "idmap2/Idmap.h"
+
+namespace android {
+
+class ApkAssets;
+
+namespace idmap2 {
+
+class RawPrintVisitor : public Visitor {
+ public:
+  explicit RawPrintVisitor(std::ostream& stream) : stream_(stream), offset_(0) {
+  }
+  virtual void visit(const Idmap& idmap);
+  virtual void visit(const IdmapHeader& header);
+  virtual void visit(const IdmapData& data);
+  virtual void visit(const IdmapData::Header& header);
+  virtual void visit(const IdmapData::TypeEntry& type_entry);
+
+ private:
+  void print(uint16_t value, const char* fmt, ...);
+  void print(uint32_t value, const char* fmt, ...);
+  void print(const std::string& value, const char* fmt, ...);
+
+  std::ostream& stream_;
+  std::unique_ptr<const ApkAssets> target_apk_;
+  AssetManager2 target_am_;
+  size_t offset_;
+  PackageId last_seen_package_id_;
+};
+
+}  // namespace idmap2
+}  // namespace android
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
new file mode 100644
index 0000000..88a835b
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
+#define IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
+
+#include <string>
+#include <utility>
+
+#include "android-base/macros.h"
+#include "androidfw/AssetManager2.h"
+
+#include "idmap2/Idmap.h"
+
+namespace android {
+namespace idmap2 {
+namespace utils {
+
+std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am,
+                                                            ResourceId resid);
+
+}  // namespace utils
+}  // namespace idmap2
+}  // namespace android
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/Xml.h b/cmds/idmap2/include/idmap2/Xml.h
new file mode 100644
index 0000000..9ab5ec4
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/Xml.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_XML_H_
+#define IDMAP2_INCLUDE_IDMAP2_XML_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+#include "utils/String16.h"
+
+namespace android {
+namespace idmap2 {
+
+class Xml {
+ public:
+  static std::unique_ptr<const Xml> Create(const uint8_t* data, size_t size, bool copyData = false);
+
+  std::unique_ptr<std::map<std::string, std::string>> FindTag(const std::string& name) const;
+
+  ~Xml();
+
+ private:
+  Xml() {
+  }
+
+  mutable ResXMLTree xml_;
+
+  DISALLOW_COPY_AND_ASSIGN(Xml);
+};
+
+}  // namespace idmap2
+}  // namespace android
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_XML_H_
diff --git a/cmds/idmap2/include/idmap2/ZipFile.h b/cmds/idmap2/include/idmap2/ZipFile.h
new file mode 100644
index 0000000..328bd36
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/ZipFile.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
+#define IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "android-base/macros.h"
+#include "ziparchive/zip_archive.h"
+
+namespace android {
+namespace idmap2 {
+
+struct MemoryChunk {
+  size_t size;
+  uint8_t buf[0];
+
+  static std::unique_ptr<MemoryChunk> Allocate(size_t size);
+
+ private:
+  MemoryChunk() {
+  }
+};
+
+class ZipFile {
+ public:
+  static std::unique_ptr<const ZipFile> Open(const std::string& path);
+
+  std::unique_ptr<const MemoryChunk> Uncompress(const std::string& entryPath) const;
+  std::pair<bool, uint32_t> Crc(const std::string& entryPath) const;
+
+  ~ZipFile();
+
+ private:
+  explicit ZipFile(const ::ZipArchiveHandle handle) : handle_(handle) {
+  }
+
+  const ::ZipArchiveHandle handle_;
+
+  DISALLOW_COPY_AND_ASSIGN(ZipFile);
+};
+
+}  // namespace idmap2
+}  // namespace android
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
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
diff --git a/cmds/idmap2/static-checks.sh b/cmds/idmap2/static-checks.sh
new file mode 100755
index 0000000..560ccb6
--- /dev/null
+++ b/cmds/idmap2/static-checks.sh
@@ -0,0 +1,121 @@
+#!/bin/bash
+#
+# 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.
+#
+
+function _log()
+{
+    echo -e "$*" >&2
+}
+
+function _eval()
+{
+    local label="$1"
+    local cmd="$2"
+    local red="\e[31m"
+    local green="\e[32m"
+    local reset="\e[0m"
+
+    _log "${green}[ RUN      ]${reset} ${label}"
+    local output="$(eval "$cmd")"
+    if [[ -z "${output}" ]]; then
+        _log "${green}[       OK ]${reset} ${label}"
+        return 0
+    else
+        echo "${output}"
+        _log "${red}[  FAILED  ]${reset} ${label}"
+        errors=$((errors + 1))
+        return 1
+    fi
+}
+
+function _clang_format()
+{
+    local path
+    local errors=0
+
+    for path in $cpp_files; do
+        local output="$(clang-format -style=file "$path" | diff $path -)"
+        if [[ "$output" ]]; then
+            echo "$path"
+            echo "$output"
+            errors=1
+        fi
+    done
+    return $errors
+}
+
+function _bpfmt()
+{
+    local output="$(bpfmt -s -d $bp_files)"
+    if [[ "$output" ]]; then
+        echo "$output"
+        return 1
+    fi
+    return 0
+}
+
+function _cpplint()
+{
+    local cpplint="${ANDROID_BUILD_TOP}/tools/repohooks/tools/cpplint.py"
+    $cpplint --quiet $cpp_files
+}
+
+function _parse_args()
+{
+    local opts
+
+    opts="$(getopt -o cfh --long check,fix,help -- "$@")"
+    if [[ $? -ne 0 ]]; then
+        exit 1
+    fi
+    eval set -- "$opts"
+    while true; do
+        case "$1" in
+            -c|--check) opt_mode="check"; shift ;;
+            -f|--fix) opt_mode="fix"; shift ;;
+            -h|--help) opt_mode="help"; shift ;;
+            *) break ;;
+        esac
+    done
+}
+
+errors=0
+script="$(readlink -f "$BASH_SOURCE")"
+prefix="$(dirname "$script")"
+cpp_files="$(find "$prefix" -name '*.cpp' -or -name '*.h')"
+bp_files="$(find "$prefix" -name 'Android.bp')"
+opt_mode="check"
+
+_parse_args "$@"
+if [[ $opt_mode == "check" ]]; then
+    _eval "clang-format" "_clang_format"
+    _eval "bpfmt" "_bpfmt"
+    _eval "cpplint" "_cpplint"
+    exit $errors
+elif [[ $opt_mode == "fix" ]]; then
+    clang-format -style=file -i $cpp_files
+    bpfmt -s -w $bp_files
+    exit 0
+elif [[ $opt_mode == "help" ]]; then
+    echo "Run static analysis tools such as clang-format and cpplint on the idmap2"
+    echo "module. Optionally fix some of the issues found (--fix). Intended to be run"
+    echo "before merging any changes."
+    echo
+    echo "usage: $(basename $script) [--check|--fix|--help]"
+    exit 0
+else
+    exit 1
+fi
diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
new file mode 100644
index 0000000..8b552dc
--- /dev/null
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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 <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/Idmap.h"
+
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/Idmap.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) {
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::istringstream raw_stream(raw);
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap1 = Idmap::FromBinaryStream(raw_stream, error);
+  ASSERT_THAT(idmap1, NotNull());
+
+  std::stringstream stream;
+  BinaryStreamVisitor visitor(stream);
+  idmap1->accept(&visitor);
+
+  std::unique_ptr<const Idmap> idmap2 = Idmap::FromBinaryStream(stream, error);
+  ASSERT_THAT(idmap2, NotNull());
+
+  ASSERT_EQ(idmap1->GetHeader()->GetTargetCrc(), idmap2->GetHeader()->GetTargetCrc());
+  ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath());
+  ASSERT_EQ(idmap1->GetData().size(), 1u);
+  ASSERT_EQ(idmap1->GetData().size(), idmap2->GetData().size());
+
+  const auto& data1 = idmap1->GetData()[0];
+  const auto& data2 = idmap2->GetData()[0];
+
+  ASSERT_EQ(data1->GetHeader()->GetTargetPackageId(), data2->GetHeader()->GetTargetPackageId());
+  ASSERT_EQ(data1->GetTypeEntries().size(), 2u);
+  ASSERT_EQ(data1->GetTypeEntries().size(), data2->GetTypeEntries().size());
+  ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(0), data2->GetTypeEntries()[0]->GetEntry(0));
+  ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(1), data2->GetTypeEntries()[0]->GetEntry(1));
+  ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(2), data2->GetTypeEntries()[0]->GetEntry(2));
+  ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(0), data2->GetTypeEntries()[1]->GetEntry(0));
+  ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(1), data2->GetTypeEntries()[1]->GetEntry(1));
+  ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(2), data2->GetTypeEntries()[1]->GetEntry(2));
+}
+
+TEST(BinaryStreamVisitorTests, CreateIdmapFromApkAssetsInteropWithLoadedIdmap) {
+  const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap =
+      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+  ASSERT_THAT(idmap, NotNull());
+
+  std::stringstream stream;
+  BinaryStreamVisitor visitor(stream);
+  idmap->accept(&visitor);
+  const std::string str = stream.str();
+  const StringPiece data(str);
+  std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(data);
+  ASSERT_THAT(loaded_idmap, NotNull());
+  ASSERT_EQ(loaded_idmap->TargetPackageId(), 0x7f);
+
+  const IdmapEntry_header* header = loaded_idmap->GetEntryMapForType(0x01);
+  ASSERT_THAT(header, NotNull());
+
+  EntryId entry;
+  bool success = LoadedIdmap::Lookup(header, 0x0000, &entry);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(entry, 0x0000);
+
+  header = loaded_idmap->GetEntryMapForType(0x02);
+  ASSERT_THAT(header, NotNull());
+
+  success = LoadedIdmap::Lookup(header, 0x0002, &entry);
+  ASSERT_FALSE(success);
+
+  success = LoadedIdmap::Lookup(header, 0x0003, &entry);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(entry, 0x0000);
+
+  success = LoadedIdmap::Lookup(header, 0x0004, &entry);
+  ASSERT_FALSE(success);
+
+  success = LoadedIdmap::Lookup(header, 0x0005, &entry);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(entry, 0x0001);
+
+  success = LoadedIdmap::Lookup(header, 0x0006, &entry);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(entry, 0x0002);
+
+  success = LoadedIdmap::Lookup(header, 0x0007, &entry);
+  ASSERT_FALSE(success);
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/tests/CommandLineOptionsTests.cpp b/cmds/idmap2/tests/CommandLineOptionsTests.cpp
new file mode 100644
index 0000000..b04b256
--- /dev/null
+++ b/cmds/idmap2/tests/CommandLineOptionsTests.cpp
@@ -0,0 +1,192 @@
+/*
+ * 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 <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "android-base/file.h"
+#include "androidfw/ApkAssets.h"
+#include "androidfw/Idmap.h"
+#include "androidfw/LoadedArsc.h"
+
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/Idmap.h"
+
+#include "TestHelpers.h"
+
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(CommandLineOptionsTests, Flag) {
+  bool foo = true, bar = false;
+
+  CommandLineOptions opts =
+      CommandLineOptions("test").OptionalFlag("--foo", "", &foo).OptionalFlag("--bar", "", &bar);
+
+  std::ostream fakeStdErr(nullptr);
+  bool success = opts.Parse({"--foo", "--bar"}, fakeStdErr);
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(foo);
+  ASSERT_TRUE(bar);
+
+  foo = bar = false;
+  success = opts.Parse({"--foo"}, fakeStdErr);
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(foo);
+  ASSERT_FALSE(bar);
+}
+
+TEST(CommandLineOptionsTests, MandatoryOption) {
+  std::string foo, bar;
+  CommandLineOptions opts = CommandLineOptions("test")
+                                .MandatoryOption("--foo", "", &foo)
+                                .MandatoryOption("--bar", "", &bar);
+  std::ostream fakeStdErr(nullptr);
+  bool success = opts.Parse({"--foo", "FOO", "--bar", "BAR"}, fakeStdErr);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(foo, "FOO");
+  ASSERT_EQ(bar, "BAR");
+
+  success = opts.Parse({"--foo"}, fakeStdErr);
+  ASSERT_FALSE(success);
+}
+
+TEST(CommandLineOptionsTests, MandatoryOptionMultipleArgsButExpectedOnce) {
+  std::string foo;
+  CommandLineOptions opts = CommandLineOptions("test").MandatoryOption("--foo", "", &foo);
+  std::ostream fakeStdErr(nullptr);
+  bool success = opts.Parse({"--foo", "FIRST", "--foo", "SECOND"}, fakeStdErr);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(foo, "SECOND");
+}
+
+TEST(CommandLineOptionsTests, MandatoryOptionMultipleArgsAndExpectedOnceOrMore) {
+  std::vector<std::string> args;
+  CommandLineOptions opts = CommandLineOptions("test").MandatoryOption("--foo", "", &args);
+  std::ostream fakeStdErr(nullptr);
+  bool success = opts.Parse({"--foo", "FOO", "--foo", "BAR"}, fakeStdErr);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(args.size(), 2u);
+  ASSERT_EQ(args[0], "FOO");
+  ASSERT_EQ(args[1], "BAR");
+}
+
+TEST(CommandLineOptionsTests, OptionalOption) {
+  std::string foo, bar;
+  CommandLineOptions opts = CommandLineOptions("test")
+                                .OptionalOption("--foo", "", &foo)
+                                .OptionalOption("--bar", "", &bar);
+  std::ostream fakeStdErr(nullptr);
+  bool success = opts.Parse({"--foo", "FOO", "--bar", "BAR"}, fakeStdErr);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(foo, "FOO");
+  ASSERT_EQ(bar, "BAR");
+
+  success = opts.Parse({"--foo", "BAZ"}, fakeStdErr);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(foo, "BAZ");
+
+  success = opts.Parse({"--foo"}, fakeStdErr);
+  ASSERT_FALSE(success);
+
+  success = opts.Parse({"--foo", "--bar", "BAR"}, fakeStdErr);
+  ASSERT_FALSE(success);
+
+  success = opts.Parse({"--foo", "FOO", "--bar"}, fakeStdErr);
+  ASSERT_FALSE(success);
+}
+
+TEST(CommandLineOptionsTests, CornerCases) {
+  std::string foo, bar;
+  bool baz = false;
+  CommandLineOptions opts = CommandLineOptions("test")
+                                .MandatoryOption("--foo", "", &foo)
+                                .OptionalFlag("--baz", "", &baz)
+                                .OptionalOption("--bar", "", &bar);
+  std::ostream fakeStdErr(nullptr);
+  bool success = opts.Parse({"--unexpected"}, fakeStdErr);
+  ASSERT_FALSE(success);
+
+  success = opts.Parse({"--bar", "BAR"}, fakeStdErr);
+  ASSERT_FALSE(success);
+
+  success = opts.Parse({"--baz", "--foo", "FOO"}, fakeStdErr);
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(baz);
+  ASSERT_EQ(foo, "FOO");
+}
+
+TEST(CommandLineOptionsTests, ConvertArgvToVector) {
+  const char* argv[] = {
+      "program-name",
+      "--foo",
+      "FOO",
+      nullptr,
+  };
+  std::unique_ptr<std::vector<std::string>> v = CommandLineOptions::ConvertArgvToVector(3, argv);
+  ASSERT_EQ(v->size(), 2ul);
+  ASSERT_EQ((*v)[0], "--foo");
+  ASSERT_EQ((*v)[1], "FOO");
+}
+
+TEST(CommandLineOptionsTests, ConvertArgvToVectorNoArgs) {
+  const char* argv[] = {
+      "program-name",
+      nullptr,
+  };
+  std::unique_ptr<std::vector<std::string>> v = CommandLineOptions::ConvertArgvToVector(1, argv);
+  ASSERT_EQ(v->size(), 0ul);
+}
+
+TEST(CommandLineOptionsTests, Usage) {
+  std::string arg1, arg2, arg3, arg4;
+  bool arg5 = false, arg6 = false;
+  std::vector<std::string> arg7;
+  CommandLineOptions opts = CommandLineOptions("test")
+                                .MandatoryOption("--aa", "description-aa", &arg1)
+                                .OptionalFlag("--bb", "description-bb", &arg5)
+                                .OptionalOption("--cc", "description-cc", &arg2)
+                                .OptionalOption("--dd", "description-dd", &arg3)
+                                .MandatoryOption("--ee", "description-ee", &arg4)
+                                .OptionalFlag("--ff", "description-ff", &arg6)
+                                .MandatoryOption("--gg", "description-gg", &arg7);
+  std::stringstream stream;
+  opts.Usage(stream);
+  const std::string s = stream.str();
+  ASSERT_NE(s.find("usage: test --aa arg [--bb] [--cc arg] [--dd arg] --ee arg [--ff] --gg arg "
+                   "[--gg arg [..]]"),
+            std::string::npos);
+  ASSERT_NE(s.find("--aa arg    description-aa"), std::string::npos);
+  ASSERT_NE(s.find("--ff        description-ff"), std::string::npos);
+  ASSERT_NE(s.find("--gg arg    description-gg (can be provided multiple times)"),
+            std::string::npos);
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp
new file mode 100644
index 0000000..0c6439a
--- /dev/null
+++ b/cmds/idmap2/tests/FileUtilsTests.cpp
@@ -0,0 +1,76 @@
+/*
+ * 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 <set>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "android-base/macros.h"
+
+#include "idmap2/FileUtils.h"
+
+#include "TestHelpers.h"
+
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+namespace utils {
+
+TEST(FileUtilsTests, FindFilesFindEverythingNonRecursive) {
+  const auto& root = GetTestDataPath();
+  auto v = utils::FindFiles(root, false,
+                            [](unsigned char type ATTRIBUTE_UNUSED,
+                               const std::string& path ATTRIBUTE_UNUSED) -> bool { return true; });
+  ASSERT_THAT(v, NotNull());
+  ASSERT_EQ(v->size(), 4u);
+  ASSERT_EQ(
+      std::set<std::string>(v->begin(), v->end()),
+      std::set<std::string>({root + "/.", root + "/..", root + "/overlay", root + "/target"}));
+}
+
+TEST(FileUtilsTests, FindFilesFindApkFilesRecursive) {
+  const auto& root = GetTestDataPath();
+  auto v = utils::FindFiles(root, true, [](unsigned char type, const std::string& path) -> bool {
+    return type == DT_REG && path.size() > 4 && !path.compare(path.size() - 4, 4, ".apk");
+  });
+  ASSERT_THAT(v, NotNull());
+  ASSERT_EQ(v->size(), 4u);
+  ASSERT_EQ(std::set<std::string>(v->begin(), v->end()),
+            std::set<std::string>({root + "/target/target.apk", root + "/overlay/overlay.apk",
+                                   root + "/overlay/overlay-static-1.apk",
+                                   root + "/overlay/overlay-static-2.apk"}));
+}
+
+TEST(FileUtilsTests, ReadFile) {
+  int pipefd[2];
+  ASSERT_EQ(pipe(pipefd), 0);
+
+  ASSERT_EQ(write(pipefd[1], "foobar", 6), 6);
+  close(pipefd[1]);
+
+  auto data = ReadFile(pipefd[0]);
+  ASSERT_THAT(data, NotNull());
+  ASSERT_EQ(*data, "foobar");
+  close(pipefd[0]);
+}
+
+}  // namespace utils
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
new file mode 100644
index 0000000..5c4e857
--- /dev/null
+++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
@@ -0,0 +1,313 @@
+/*
+ * 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.
+ */
+
+/*
+ * The tests in this file operate on a higher level than the tests in the other
+ * files. Here, all tests execute the idmap2 binary and only depend on
+ * libidmap2 to verify the output of idmap2.
+ */
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>  // strerror
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "androidfw/PosixUtils.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+
+#include "TestHelpers.h"
+
+using ::android::util::ExecuteBinary;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+class Idmap2BinaryTests : public Idmap2Tests {};
+
+static void AssertIdmap(const Idmap& idmap, const std::string& target_apk_path,
+                        const std::string& overlay_apk_path) {
+  // check that the idmap file looks reasonable (IdmapTests is responsible for
+  // more in-depth verification)
+  ASSERT_EQ(idmap.GetHeader()->GetMagic(), kIdmapMagic);
+  ASSERT_EQ(idmap.GetHeader()->GetVersion(), kIdmapCurrentVersion);
+  ASSERT_EQ(idmap.GetHeader()->GetTargetPath(), target_apk_path);
+  ASSERT_EQ(idmap.GetHeader()->GetOverlayPath(), overlay_apk_path);
+  ASSERT_EQ(idmap.GetData().size(), 1u);
+}
+
+#define ASSERT_IDMAP(idmap_ref, target_apk_path, overlay_apk_path)                      \
+  do {                                                                                  \
+    ASSERT_NO_FATAL_FAILURE(AssertIdmap(idmap_ref, target_apk_path, overlay_apk_path)); \
+  } while (0)
+
+TEST_F(Idmap2BinaryTests, Create) {
+  // clang-format off
+  auto result = ExecuteBinary({"idmap2",
+                               "create",
+                               "--target-apk-path", GetTargetApkPath(),
+                               "--overlay-apk-path", GetOverlayApkPath(),
+                               "--idmap-path", GetIdmapPath()});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+
+  struct stat st;
+  ASSERT_EQ(stat(GetIdmapPath().c_str(), &st), 0);
+
+  std::stringstream error;
+  std::ifstream fin(GetIdmapPath());
+  std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(fin, error);
+  fin.close();
+
+  ASSERT_THAT(idmap, NotNull());
+  ASSERT_IDMAP(*idmap, GetTargetApkPath(), GetOverlayApkPath());
+
+  unlink(GetIdmapPath().c_str());
+}
+
+TEST_F(Idmap2BinaryTests, Dump) {
+  // clang-format off
+  auto result = ExecuteBinary({"idmap2",
+                               "create",
+                               "--target-apk-path", GetTargetApkPath(),
+                               "--overlay-apk-path", GetOverlayApkPath(),
+                               "--idmap-path", GetIdmapPath()});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "dump",
+                          "--idmap-path", GetIdmapPath()});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+  ASSERT_NE(result->stdout.find("0x7f010000 -> 0x7f010000 integer/int1"), std::string::npos);
+  ASSERT_NE(result->stdout.find("0x7f020003 -> 0x7f020000 string/str1"), std::string::npos);
+  ASSERT_NE(result->stdout.find("0x7f020005 -> 0x7f020001 string/str3"), std::string::npos);
+  ASSERT_EQ(result->stdout.find("00000210:     007f  target package id"), std::string::npos);
+
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "dump",
+                          "--verbose",
+                          "--idmap-path", GetIdmapPath()});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+  ASSERT_NE(result->stdout.find("00000000: 504d4449  magic"), std::string::npos);
+  ASSERT_NE(result->stdout.find("00000210:     007f  target package id"), std::string::npos);
+
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "dump",
+                          "--verbose",
+                          "--idmap-path", GetTestDataPath() + "/DOES-NOT-EXIST"});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_NE(result->status, EXIT_SUCCESS);
+
+  unlink(GetIdmapPath().c_str());
+}
+
+TEST_F(Idmap2BinaryTests, Scan) {
+  const std::string overlay_static_1_apk_path = GetTestDataPath() + "/overlay/overlay-static-1.apk";
+  const std::string overlay_static_2_apk_path = GetTestDataPath() + "/overlay/overlay-static-2.apk";
+  const std::string idmap_static_1_path =
+      Idmap::CanonicalIdmapPathFor(GetTempDirPath(), overlay_static_1_apk_path);
+  const std::string idmap_static_2_path =
+      Idmap::CanonicalIdmapPathFor(GetTempDirPath(), overlay_static_2_apk_path);
+
+  // single input directory, recursive
+  // clang-format off
+  auto result = ExecuteBinary({"idmap2",
+                               "scan",
+                               "--input-directory", GetTestDataPath(),
+                               "--recursive",
+                               "--target-package-name", "test.target",
+                               "--target-apk-path", GetTargetApkPath(),
+                               "--output-directory", GetTempDirPath()});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+  std::stringstream expected;
+  expected << idmap_static_1_path << std::endl;
+  expected << idmap_static_2_path << std::endl;
+  ASSERT_EQ(result->stdout, expected.str());
+
+  std::stringstream error;
+  auto idmap_static_1_raw_string = utils::ReadFile(idmap_static_1_path);
+  auto idmap_static_1_raw_stream = std::istringstream(*idmap_static_1_raw_string);
+  auto idmap_static_1 = Idmap::FromBinaryStream(idmap_static_1_raw_stream, error);
+  ASSERT_THAT(idmap_static_1, NotNull());
+  ASSERT_IDMAP(*idmap_static_1, GetTargetApkPath(), overlay_static_1_apk_path);
+
+  auto idmap_static_2_raw_string = utils::ReadFile(idmap_static_2_path);
+  auto idmap_static_2_raw_stream = std::istringstream(*idmap_static_2_raw_string);
+  auto idmap_static_2 = Idmap::FromBinaryStream(idmap_static_2_raw_stream, error);
+  ASSERT_THAT(idmap_static_2, NotNull());
+  ASSERT_IDMAP(*idmap_static_2, GetTargetApkPath(), overlay_static_2_apk_path);
+
+  unlink(idmap_static_2_path.c_str());
+  unlink(idmap_static_1_path.c_str());
+
+  // multiple input directories, non-recursive
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "scan",
+                          "--input-directory", GetTestDataPath() + "/target",
+                          "--input-directory", GetTestDataPath() + "/overlay",
+                          "--target-package-name", "test.target",
+                          "--target-apk-path", GetTargetApkPath(),
+                          "--output-directory", GetTempDirPath()});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+  ASSERT_EQ(result->stdout, expected.str());
+  unlink(idmap_static_2_path.c_str());
+  unlink(idmap_static_1_path.c_str());
+
+  // the same input directory given twice, but no duplicate entries
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "scan",
+                          "--input-directory", GetTestDataPath(),
+                          "--input-directory", GetTestDataPath(),
+                          "--recursive",
+                          "--target-package-name", "test.target",
+                          "--target-apk-path", GetTargetApkPath(),
+                          "--output-directory", GetTempDirPath()});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+  ASSERT_EQ(result->stdout, expected.str());
+  unlink(idmap_static_2_path.c_str());
+  unlink(idmap_static_1_path.c_str());
+
+  // no APKs in input-directory: ok, but no output
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "scan",
+                          "--input-directory", GetTempDirPath(),
+                          "--target-package-name", "test.target",
+                          "--target-apk-path", GetTargetApkPath(),
+                          "--output-directory", GetTempDirPath()});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+  ASSERT_EQ(result->stdout, "");
+}
+
+TEST_F(Idmap2BinaryTests, Lookup) {
+  // clang-format off
+  auto result = ExecuteBinary({"idmap2",
+                               "create",
+                               "--target-apk-path", GetTargetApkPath(),
+                               "--overlay-apk-path", GetOverlayApkPath(),
+                               "--idmap-path", GetIdmapPath()});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "lookup",
+                          "--idmap-path", GetIdmapPath(),
+                          "--config", "",
+                          "--resid", "0x7f020003"});  // string/str1
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+  ASSERT_NE(result->stdout.find("overlay-1"), std::string::npos);
+  ASSERT_EQ(result->stdout.find("overlay-1-sv"), std::string::npos);
+
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "lookup",
+                          "--idmap-path", GetIdmapPath(),
+                          "--config", "",
+                          "--resid", "test.target:string/str1"});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+  ASSERT_NE(result->stdout.find("overlay-1"), std::string::npos);
+  ASSERT_EQ(result->stdout.find("overlay-1-sv"), std::string::npos);
+
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "lookup",
+                          "--idmap-path", GetIdmapPath(),
+                          "--config", "sv",
+                          "--resid", "test.target:string/str1"});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+  ASSERT_NE(result->stdout.find("overlay-1-sv"), std::string::npos);
+
+  unlink(GetIdmapPath().c_str());
+}
+
+TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) {
+  const std::string invalid_target_apk_path = GetTestDataPath() + "/DOES-NOT-EXIST";
+
+  // missing mandatory options
+  // clang-format off
+  auto result = ExecuteBinary({"idmap2",
+                               "create"});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_NE(result->status, EXIT_SUCCESS);
+
+  // missing argument to option
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "create",
+                          "--target-apk-path", GetTargetApkPath(),
+                          "--overlay-apk-path", GetOverlayApkPath(),
+                          "--idmap-path"});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_NE(result->status, EXIT_SUCCESS);
+
+  // invalid target apk path
+  // clang-format off
+  result = ExecuteBinary({"idmap2",
+                          "create",
+                          "--target-apk-path", invalid_target_apk_path,
+                          "--overlay-apk-path", GetOverlayApkPath(),
+                          "--idmap-path", GetIdmapPath()});
+  // clang-format on
+  ASSERT_THAT(result, NotNull());
+  ASSERT_NE(result->status, EXIT_SUCCESS);
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
new file mode 100644
index 0000000..0379aa4
--- /dev/null
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -0,0 +1,395 @@
+/*
+ * 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 <cstdio>  // fclose
+
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "android-base/macros.h"
+#include "androidfw/ApkAssets.h"
+
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/Idmap.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(IdmapTests, TestCanonicalIdmapPathFor) {
+  ASSERT_EQ(Idmap::CanonicalIdmapPathFor("/foo", "/vendor/overlay/bar.apk"),
+            "/foo/vendor@overlay@bar.apk@idmap");
+}
+
+TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) {
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::istringstream stream(raw);
+  std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
+  ASSERT_THAT(header, NotNull());
+  ASSERT_EQ(header->GetMagic(), 0x504d4449u);
+  ASSERT_EQ(header->GetVersion(), 0x01u);
+  ASSERT_EQ(header->GetTargetCrc(), 0x1234u);
+  ASSERT_EQ(header->GetOverlayCrc(), 0x5678u);
+  ASSERT_EQ(header->GetTargetPath().to_string(), "target.apk");
+  ASSERT_EQ(header->GetOverlayPath().to_string(), "overlay.apk");
+}
+
+TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) {
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  // overwrite the target path string, including the terminating null, with '.'
+  for (size_t i = 0x10; i < 0x110; i++) {
+    raw[i] = '.';
+  }
+  std::istringstream stream(raw);
+  std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
+  ASSERT_THAT(header, IsNull());
+}
+
+TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
+  const size_t offset = 0x210;
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
+                  idmap_raw_data_len - offset);
+  std::istringstream stream(raw);
+
+  std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream);
+  ASSERT_THAT(header, NotNull());
+  ASSERT_EQ(header->GetTargetPackageId(), 0x7fu);
+  ASSERT_EQ(header->GetTypeCount(), 2u);
+}
+
+TEST(IdmapTests, CreateIdmapDataResourceTypeFromBinaryStream) {
+  const size_t offset = 0x214;
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
+                  idmap_raw_data_len - offset);
+  std::istringstream stream(raw);
+
+  std::unique_ptr<const IdmapData::TypeEntry> data = IdmapData::TypeEntry::FromBinaryStream(stream);
+  ASSERT_THAT(data, NotNull());
+  ASSERT_EQ(data->GetTargetTypeId(), 0x02u);
+  ASSERT_EQ(data->GetOverlayTypeId(), 0x02u);
+  ASSERT_EQ(data->GetEntryCount(), 1u);
+  ASSERT_EQ(data->GetEntryOffset(), 0u);
+  ASSERT_EQ(data->GetEntry(0), 0u);
+}
+
+TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
+  const size_t offset = 0x210;
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
+                  idmap_raw_data_len - offset);
+  std::istringstream stream(raw);
+
+  std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
+  ASSERT_THAT(data, NotNull());
+  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu);
+  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u);
+  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
+  ASSERT_EQ(types.size(), 2u);
+
+  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02u);
+  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02u);
+  ASSERT_EQ(types[0]->GetEntryCount(), 1u);
+  ASSERT_EQ(types[0]->GetEntryOffset(), 0u);
+  ASSERT_EQ(types[0]->GetEntry(0), 0x0000u);
+
+  ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03u);
+  ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03u);
+  ASSERT_EQ(types[1]->GetEntryCount(), 3u);
+  ASSERT_EQ(types[1]->GetEntryOffset(), 3u);
+  ASSERT_EQ(types[1]->GetEntry(0), 0x0000u);
+  ASSERT_EQ(types[1]->GetEntry(1), kNoEntry);
+  ASSERT_EQ(types[1]->GetEntry(2), 0x0001u);
+}
+
+TEST(IdmapTests, CreateIdmapFromBinaryStream) {
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::istringstream stream(raw);
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error);
+  ASSERT_THAT(idmap, NotNull());
+
+  ASSERT_THAT(idmap->GetHeader(), NotNull());
+  ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449u);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01u);
+  ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234u);
+  ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678u);
+  ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "target.apk");
+  ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlay.apk");
+
+  const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
+  ASSERT_EQ(dataBlocks.size(), 1u);
+
+  const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu);
+  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u);
+  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
+  ASSERT_EQ(types.size(), 2u);
+
+  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02u);
+  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02u);
+  ASSERT_EQ(types[0]->GetEntryCount(), 1u);
+  ASSERT_EQ(types[0]->GetEntryOffset(), 0u);
+  ASSERT_EQ(types[0]->GetEntry(0), 0x0000u);
+
+  ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03u);
+  ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03u);
+  ASSERT_EQ(types[1]->GetEntryCount(), 3u);
+  ASSERT_EQ(types[1]->GetEntryOffset(), 3u);
+  ASSERT_EQ(types[1]->GetEntry(0), 0x0000u);
+  ASSERT_EQ(types[1]->GetEntry(1), kNoEntry);
+  ASSERT_EQ(types[1]->GetEntry(2), 0x0001u);
+}
+
+TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) {
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data),
+                  10);  // data too small
+  std::istringstream stream(raw);
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error);
+  ASSERT_THAT(idmap, IsNull());
+}
+
+TEST(IdmapTests, CreateIdmapFromApkAssets) {
+  const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap =
+      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+  ASSERT_THAT(idmap, NotNull());
+
+  ASSERT_THAT(idmap->GetHeader(), NotNull());
+  ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449u);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01u);
+  ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0xf5ad1d1d);
+  ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0xd470336b);
+  ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path);
+  ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path);
+  ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path);
+
+  const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
+  ASSERT_EQ(dataBlocks.size(), 1u);
+
+  const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+
+  ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu);
+  ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u);
+
+  const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
+  ASSERT_EQ(types.size(), 2u);
+
+  ASSERT_EQ(types[0]->GetTargetTypeId(), 0x01u);
+  ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01u);
+  ASSERT_EQ(types[0]->GetEntryCount(), 1u);
+  ASSERT_EQ(types[0]->GetEntryOffset(), 0u);
+  ASSERT_EQ(types[0]->GetEntry(0), 0x0000u);
+
+  ASSERT_EQ(types[1]->GetTargetTypeId(), 0x02u);
+  ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x02u);
+  ASSERT_EQ(types[1]->GetEntryCount(), 4u);
+  ASSERT_EQ(types[1]->GetEntryOffset(), 3u);
+  ASSERT_EQ(types[1]->GetEntry(0), 0x0000u);
+  ASSERT_EQ(types[1]->GetEntry(1), kNoEntry);
+  ASSERT_EQ(types[1]->GetEntry(2), 0x0001u);
+  ASSERT_EQ(types[1]->GetEntry(3), 0x0002u);
+}
+
+TEST(IdmapTests, FailToCreateIdmapFromApkAssetsIfPathTooLong) {
+  std::string target_apk_path(GetTestDataPath());
+  for (int i = 0; i < 32; i++) {
+    target_apk_path += "/target/../";
+  }
+  target_apk_path += "/target/target.apk";
+  ASSERT_GT(target_apk_path.size(), kIdmapStringLength);
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap =
+      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+  ASSERT_THAT(idmap, IsNull());
+}
+
+TEST(IdmapTests, IdmapHeaderIsUpToDate) {
+  fclose(stderr);  // silence expected warnings from libandroidfw
+
+  const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap =
+      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+  ASSERT_THAT(idmap, NotNull());
+
+  std::stringstream stream;
+  BinaryStreamVisitor visitor(stream);
+  idmap->accept(&visitor);
+
+  std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
+  ASSERT_THAT(header, NotNull());
+  ASSERT_TRUE(header->IsUpToDate(error)) << error.str();
+
+  // magic: bytes (0x0, 0x03)
+  std::string bad_magic_string(stream.str());
+  bad_magic_string[0x0] = '.';
+  bad_magic_string[0x1] = '.';
+  bad_magic_string[0x2] = '.';
+  bad_magic_string[0x3] = '.';
+  std::stringstream bad_magic_stream(bad_magic_string);
+  std::unique_ptr<const IdmapHeader> bad_magic_header =
+      IdmapHeader::FromBinaryStream(bad_magic_stream);
+  ASSERT_THAT(bad_magic_header, NotNull());
+  ASSERT_NE(header->GetMagic(), bad_magic_header->GetMagic());
+  ASSERT_FALSE(bad_magic_header->IsUpToDate(error));
+
+  // version: bytes (0x4, 0x07)
+  std::string bad_version_string(stream.str());
+  bad_version_string[0x4] = '.';
+  bad_version_string[0x5] = '.';
+  bad_version_string[0x6] = '.';
+  bad_version_string[0x7] = '.';
+  std::stringstream bad_version_stream(bad_version_string);
+  std::unique_ptr<const IdmapHeader> bad_version_header =
+      IdmapHeader::FromBinaryStream(bad_version_stream);
+  ASSERT_THAT(bad_version_header, NotNull());
+  ASSERT_NE(header->GetVersion(), bad_version_header->GetVersion());
+  ASSERT_FALSE(bad_version_header->IsUpToDate(error));
+
+  // target crc: bytes (0x8, 0xb)
+  std::string bad_target_crc_string(stream.str());
+  bad_target_crc_string[0x8] = '.';
+  bad_target_crc_string[0x9] = '.';
+  bad_target_crc_string[0xa] = '.';
+  bad_target_crc_string[0xb] = '.';
+  std::stringstream bad_target_crc_stream(bad_target_crc_string);
+  std::unique_ptr<const IdmapHeader> bad_target_crc_header =
+      IdmapHeader::FromBinaryStream(bad_target_crc_stream);
+  ASSERT_THAT(bad_target_crc_header, NotNull());
+  ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc());
+  ASSERT_FALSE(bad_target_crc_header->IsUpToDate(error));
+
+  // overlay crc: bytes (0xc, 0xf)
+  std::string bad_overlay_crc_string(stream.str());
+  bad_overlay_crc_string[0xc] = '.';
+  bad_overlay_crc_string[0xd] = '.';
+  bad_overlay_crc_string[0xe] = '.';
+  bad_overlay_crc_string[0xf] = '.';
+  std::stringstream bad_overlay_crc_stream(bad_overlay_crc_string);
+  std::unique_ptr<const IdmapHeader> bad_overlay_crc_header =
+      IdmapHeader::FromBinaryStream(bad_overlay_crc_stream);
+  ASSERT_THAT(bad_overlay_crc_header, NotNull());
+  ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc());
+  ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate(error));
+
+  // target path: bytes (0x10, 0x10f)
+  std::string bad_target_path_string(stream.str());
+  bad_target_path_string[0x10] = '\0';
+  std::stringstream bad_target_path_stream(bad_target_path_string);
+  std::unique_ptr<const IdmapHeader> bad_target_path_header =
+      IdmapHeader::FromBinaryStream(bad_target_path_stream);
+  ASSERT_THAT(bad_target_path_header, NotNull());
+  ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath());
+  ASSERT_FALSE(bad_target_path_header->IsUpToDate(error));
+
+  // overlay path: bytes (0x110, 0x20f)
+  std::string bad_overlay_path_string(stream.str());
+  bad_overlay_path_string[0x110] = '\0';
+  std::stringstream bad_overlay_path_stream(bad_overlay_path_string);
+  std::unique_ptr<const IdmapHeader> bad_overlay_path_header =
+      IdmapHeader::FromBinaryStream(bad_overlay_path_stream);
+  ASSERT_THAT(bad_overlay_path_header, NotNull());
+  ASSERT_NE(header->GetOverlayPath(), bad_overlay_path_header->GetOverlayPath());
+  ASSERT_FALSE(bad_overlay_path_header->IsUpToDate(error));
+}
+
+class TestVisitor : public Visitor {
+ public:
+  explicit TestVisitor(std::ostream& stream) : stream_(stream) {
+  }
+
+  void visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
+    stream_ << "TestVisitor::visit(Idmap)" << std::endl;
+  }
+
+  void visit(const IdmapHeader& idmap ATTRIBUTE_UNUSED) {
+    stream_ << "TestVisitor::visit(IdmapHeader)" << std::endl;
+  }
+
+  void visit(const IdmapData& idmap ATTRIBUTE_UNUSED) {
+    stream_ << "TestVisitor::visit(IdmapData)" << std::endl;
+  }
+
+  void visit(const IdmapData::Header& idmap ATTRIBUTE_UNUSED) {
+    stream_ << "TestVisitor::visit(IdmapData::Header)" << std::endl;
+  }
+
+  void visit(const IdmapData::TypeEntry& idmap ATTRIBUTE_UNUSED) {
+    stream_ << "TestVisitor::visit(IdmapData::TypeEntry)" << std::endl;
+  }
+
+ private:
+  std::ostream& stream_;
+};
+
+TEST(IdmapTests, TestVisitor) {
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::istringstream stream(raw);
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error);
+  ASSERT_THAT(idmap, NotNull());
+
+  std::stringstream test_stream;
+  TestVisitor visitor(test_stream);
+  idmap->accept(&visitor);
+
+  ASSERT_EQ(test_stream.str(),
+            "TestVisitor::visit(Idmap)\n"
+            "TestVisitor::visit(IdmapHeader)\n"
+            "TestVisitor::visit(IdmapData)\n"
+            "TestVisitor::visit(IdmapData::Header)\n"
+            "TestVisitor::visit(IdmapData::TypeEntry)\n"
+            "TestVisitor::visit(IdmapData::TypeEntry)\n");
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/tests/Main.cpp b/cmds/idmap2/tests/Main.cpp
new file mode 100644
index 0000000..f2469ea
--- /dev/null
+++ b/cmds/idmap2/tests/Main.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 "android-base/file.h"
+
+#include "gtest/gtest.h"
+
+#include "TestHelpers.h"
+
+namespace android {
+namespace idmap2 {
+
+const std::string GetTestDataPath() {
+  return base::GetExecutableDirectory() + "/tests/data";
+}
+
+}  // namespace idmap2
+}  // namespace android
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
new file mode 100644
index 0000000..da97792
--- /dev/null
+++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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 <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/Idmap.h"
+
+#include "idmap2/Idmap.h"
+#include "idmap2/PrettyPrintVisitor.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+using android::ApkAssets;
+
+namespace android {
+namespace idmap2 {
+
+TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) {
+  const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap =
+      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+  ASSERT_THAT(idmap, NotNull());
+
+  std::stringstream stream;
+  PrettyPrintVisitor visitor(stream);
+  idmap->accept(&visitor);
+
+  ASSERT_NE(stream.str().find("target apk path  : "), std::string::npos);
+  ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
+  ASSERT_NE(stream.str().find("0x7f010000 -> 0x7f010000 integer/int1\n"), std::string::npos);
+}
+
+TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) {
+  fclose(stderr);  // silence expected warnings from libandroidfw
+
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::istringstream raw_stream(raw);
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(raw_stream, error);
+  ASSERT_THAT(idmap, NotNull());
+
+  std::stringstream stream;
+  PrettyPrintVisitor visitor(stream);
+  idmap->accept(&visitor);
+
+  ASSERT_NE(stream.str().find("target apk path  : "), std::string::npos);
+  ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
+  ASSERT_NE(stream.str().find("0x7f020000 -> 0x7f020000\n"), std::string::npos);
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
new file mode 100644
index 0000000..c28ce2e
--- /dev/null
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -0,0 +1,84 @@
+/*
+ * 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 <cstdio>  // fclose
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "idmap2/Idmap.h"
+#include "idmap2/RawPrintVisitor.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(RawPrintVisitorTests, CreateRawPrintVisitor) {
+  const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
+  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  ASSERT_THAT(target_apk, NotNull());
+
+  const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+  ASSERT_THAT(overlay_apk, NotNull());
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap =
+      Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+  ASSERT_THAT(idmap, NotNull());
+
+  std::stringstream stream;
+  RawPrintVisitor visitor(stream);
+  idmap->accept(&visitor);
+
+  ASSERT_NE(stream.str().find("00000000: 504d4449  magic\n"), std::string::npos);
+  ASSERT_NE(stream.str().find("00000004: 00000001  version\n"), std::string::npos);
+  ASSERT_NE(stream.str().find("00000008: f5ad1d1d  target crc\n"), std::string::npos);
+  ASSERT_NE(stream.str().find("0000000c: d470336b  overlay crc\n"), std::string::npos);
+  ASSERT_NE(stream.str().find("0000021c: 00000000  0x7f010000 -> 0x7f010000 integer/int1\n"),
+            std::string::npos);
+}
+
+TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
+  fclose(stderr);  // silence expected warnings from libandroidfw
+
+  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+  std::istringstream raw_stream(raw);
+
+  std::stringstream error;
+  std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(raw_stream, error);
+  ASSERT_THAT(idmap, NotNull());
+
+  std::stringstream stream;
+  RawPrintVisitor visitor(stream);
+  idmap->accept(&visitor);
+
+  ASSERT_NE(stream.str().find("00000000: 504d4449  magic\n"), std::string::npos);
+  ASSERT_NE(stream.str().find("00000004: 00000001  version\n"), std::string::npos);
+  ASSERT_NE(stream.str().find("00000008: 00001234  target crc\n"), std::string::npos);
+  ASSERT_NE(stream.str().find("0000000c: 00005678  overlay crc\n"), std::string::npos);
+  ASSERT_NE(stream.str().find("0000021c: 00000000  0x7f020000 -> 0x7f020000\n"), std::string::npos);
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp
new file mode 100644
index 0000000..0547fa0
--- /dev/null
+++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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 "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "androidfw/ApkAssets.h"
+#include "idmap2/ResourceUtils.h"
+
+#include "TestHelpers.h"
+
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+class ResourceUtilsTests : public Idmap2Tests {
+ protected:
+  void SetUp() override {
+    Idmap2Tests::SetUp();
+
+    apk_assets_ = ApkAssets::Load(GetTargetApkPath());
+    ASSERT_THAT(apk_assets_, NotNull());
+
+    am_.SetApkAssets({apk_assets_.get()});
+  }
+
+  const AssetManager2& GetAssetManager() {
+    return am_;
+  }
+
+ private:
+  AssetManager2 am_;
+  std::unique_ptr<const ApkAssets> apk_assets_;
+};
+
+TEST_F(ResourceUtilsTests, ResToTypeEntryName) {
+  bool lookup_ok;
+  std::string name;
+  std::tie(lookup_ok, name) = utils::ResToTypeEntryName(GetAssetManager(), 0x7f010000u);
+  ASSERT_TRUE(lookup_ok);
+  ASSERT_EQ(name, "integer/int1");
+}
+
+TEST_F(ResourceUtilsTests, ResToTypeEntryNameNoSuchResourceId) {
+  bool lookup_ok;
+  std::tie(lookup_ok, std::ignore) = utils::ResToTypeEntryName(GetAssetManager(), 0x7f123456u);
+  ASSERT_FALSE(lookup_ok);
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
new file mode 100644
index 0000000..18dc541
--- /dev/null
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_TESTS_TESTHELPERS_H_
+#define IDMAP2_TESTS_TESTHELPERS_H_
+
+#include <string>
+
+namespace android {
+namespace idmap2 {
+
+const unsigned char idmap_raw_data[] = {
+    // IDMAP HEADER
+    // 0x0: magic
+    0x49, 0x44, 0x4d, 0x50,
+
+    // 0x4: version
+    0x01, 0x00, 0x00, 0x00,
+
+    // 0x8: target crc
+    0x34, 0x12, 0x00, 0x00,
+
+    // 0xc: overlay crc
+    0x78, 0x56, 0x00, 0x00,
+
+    // 0x10: target path "target.apk"
+    0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+    // 0x110: overlay path "overlay.apk"
+    0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+    // DATA HEADER
+    // 0x210: target package id
+    0x7f, 0x00,
+
+    // 0x212: types count
+    0x02, 0x00,
+
+    // DATA BLOCK
+    // 0x214: target type
+    0x02, 0x00,
+
+    // 0x216: overlay type
+    0x02, 0x00,
+
+    // 0x218: entry count
+    0x01, 0x00,
+
+    // 0x21a: entry offset
+    0x00, 0x00,
+
+    // 0x21c: entries
+    0x00, 0x00, 0x00, 0x00,
+
+    // DATA BLOCK
+    // 0x220: target type
+    0x03, 0x00,
+
+    // 0x222: overlay type
+    0x03, 0x00,
+
+    // 0x224: entry count
+    0x03, 0x00,
+
+    // 0x226: entry offset
+    0x03, 0x00,
+
+    // 0x228, 0x22c, 0x230: entries
+    0x00, 0x00, 0x00, 0x00,
+
+    0xff, 0xff, 0xff, 0xff,
+
+    0x01, 0x00, 0x00, 0x00};
+
+const unsigned int idmap_raw_data_len = 565;
+
+const std::string GetTestDataPath();
+
+class Idmap2Tests : public testing::Test {
+ protected:
+  virtual void SetUp() {
+#ifdef __ANDROID__
+    tmp_dir_path_ = "/data/local/tmp/idmap2-tests-XXXXXX";
+#else
+    tmp_dir_path_ = "/tmp/idmap2-tests-XXXXXX";
+#endif
+    EXPECT_NE(mkdtemp(const_cast<char*>(tmp_dir_path_.c_str())), nullptr)
+        << "Failed to create temporary directory: " << strerror(errno);
+    target_apk_path_ = GetTestDataPath() + "/target/target.apk";
+    overlay_apk_path_ = GetTestDataPath() + "/overlay/overlay.apk";
+    idmap_path_ = tmp_dir_path_ + "/a.idmap";
+  }
+
+  virtual void TearDown() {
+    EXPECT_EQ(rmdir(tmp_dir_path_.c_str()), 0)
+        << "Failed to remove temporary directory " << tmp_dir_path_ << ": " << strerror(errno);
+  }
+
+  const std::string& GetTempDirPath() {
+    return tmp_dir_path_;
+  }
+
+  const std::string& GetTargetApkPath() {
+    return target_apk_path_;
+  }
+
+  const std::string& GetOverlayApkPath() {
+    return overlay_apk_path_;
+  }
+
+  const std::string& GetIdmapPath() {
+    return idmap_path_;
+  }
+
+ private:
+  std::string tmp_dir_path_;
+  std::string target_apk_path_;
+  std::string overlay_apk_path_;
+  std::string idmap_path_;
+};
+
+}  // namespace idmap2
+}  // namespace android
+
+#endif  // IDMAP2_TESTS_TESTHELPERS_H_
diff --git a/cmds/idmap2/tests/XmlTests.cpp b/cmds/idmap2/tests/XmlTests.cpp
new file mode 100644
index 0000000..97ff03e
--- /dev/null
+++ b/cmds/idmap2/tests/XmlTests.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 <cstdio>  // fclose
+
+#include "idmap2/Xml.h"
+#include "idmap2/ZipFile.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(XmlTests, Create) {
+  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+  ASSERT_THAT(zip, NotNull());
+
+  auto data = zip->Uncompress("AndroidManifest.xml");
+  ASSERT_THAT(data, NotNull());
+
+  auto xml = Xml::Create(data->buf, data->size);
+  ASSERT_THAT(xml, NotNull());
+
+  fclose(stderr);  // silence expected warnings from libandroidfw
+  const char* not_xml = "foo";
+  auto fail = Xml::Create(reinterpret_cast<const uint8_t*>(not_xml), strlen(not_xml));
+  ASSERT_THAT(fail, IsNull());
+}
+
+TEST(XmlTests, FindTag) {
+  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+  ASSERT_THAT(zip, NotNull());
+
+  auto data = zip->Uncompress("res/xml/test.xml");
+  ASSERT_THAT(data, NotNull());
+
+  auto xml = Xml::Create(data->buf, data->size);
+  ASSERT_THAT(xml, NotNull());
+
+  auto attrs = xml->FindTag("c");
+  ASSERT_THAT(attrs, NotNull());
+  ASSERT_EQ(attrs->size(), 4u);
+  ASSERT_EQ(attrs->at("type_string"), "fortytwo");
+  ASSERT_EQ(std::stoi(attrs->at("type_int_dec")), 42);
+  ASSERT_EQ(std::stoi(attrs->at("type_int_hex")), 42);
+  ASSERT_NE(std::stoul(attrs->at("type_int_boolean")), 0u);
+
+  auto fail = xml->FindTag("does-not-exist");
+  ASSERT_THAT(fail, IsNull());
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/tests/ZipFileTests.cpp b/cmds/idmap2/tests/ZipFileTests.cpp
new file mode 100644
index 0000000..a504d31
--- /dev/null
+++ b/cmds/idmap2/tests/ZipFileTests.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 <cstdio>  // fclose
+#include <string>
+#include <utility>
+
+#include "idmap2/ZipFile.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(ZipFileTests, BasicOpen) {
+  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+  ASSERT_THAT(zip, NotNull());
+
+  fclose(stderr);  // silence expected warnings from libziparchive
+  auto fail = ZipFile::Open(GetTestDataPath() + "/does-not-exist");
+  ASSERT_THAT(fail, IsNull());
+}
+
+TEST(ZipFileTests, Crc) {
+  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+  ASSERT_THAT(zip, NotNull());
+
+  bool status;
+  uint32_t crc;
+  std::tie(status, crc) = zip->Crc("AndroidManifest.xml");
+  ASSERT_TRUE(status);
+  ASSERT_EQ(crc, 0x762f3d24);
+
+  std::tie(status, std::ignore) = zip->Crc("does-not-exist");
+  ASSERT_FALSE(status);
+}
+
+TEST(ZipFileTests, Uncompress) {
+  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+  ASSERT_THAT(zip, NotNull());
+
+  auto data = zip->Uncompress("assets/lorem-ipsum.txt");
+  ASSERT_THAT(data, NotNull());
+  const std::string lorem_ipsum("Lorem ipsum dolor sit amet.\n");
+  ASSERT_THAT(data->size, lorem_ipsum.size());
+  ASSERT_THAT(std::string(reinterpret_cast<const char*>(data->buf), data->size), lorem_ipsum);
+
+  auto fail = zip->Uncompress("does-not-exist");
+  ASSERT_THAT(fail, IsNull());
+}
+
+}  // namespace idmap2
+}  // namespace android
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
new file mode 100644
index 0000000..9f89d31
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.overlay">
+    <overlay
+        android:targetPackage="test.target" />
+</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml
new file mode 100644
index 0000000..39336cc
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.overlay.static1">
+    <overlay
+        android:targetPackage="test.target"
+        android:isStatic="true"
+        android:priority="1" />
+</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml
new file mode 100644
index 0000000..e1cc175
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.overlay.static2">
+    <overlay
+        android:targetPackage="test.target"
+        android:isStatic="true"
+        android:priority="2" />
+</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build
new file mode 100644
index 0000000..cba1086
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/build
@@ -0,0 +1,40 @@
+# 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.
+
+FRAMEWORK_RES_APK="$(gettop)/out/target/common/obj/APPS/framework-res_intermediates/package-export.apk"
+
+aapt2 compile --dir res -o compiled.flata
+
+aapt2 link \
+    --no-resource-removal \
+    -I "$FRAMEWORK_RES_APK" \
+    --manifest AndroidManifest.xml \
+    -o overlay.apk \
+    compiled.flata
+
+aapt2 link \
+    --no-resource-removal \
+    -I "$FRAMEWORK_RES_APK" \
+    --manifest AndroidManifestStatic1.xml \
+    -o overlay-static-1.apk \
+    compiled.flata
+
+aapt2 link \
+    --no-resource-removal \
+    -I "$FRAMEWORK_RES_APK" \
+    --manifest AndroidManifestStatic2.xml \
+    -o overlay-static-2.apk \
+    compiled.flata
+
+rm compiled.flata
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
new file mode 100644
index 0000000..9a0f487
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
new file mode 100644
index 0000000..3fc31c7
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay.apk b/cmds/idmap2/tests/data/overlay/overlay.apk
new file mode 100644
index 0000000..b4cd7cf
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml b/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml
new file mode 100644
index 0000000..eed0b3d
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <string name="str1">overlay-1-sv</string>
+    <string name="str4">overlay-4-sv</string>
+</resources>
diff --git a/cmds/idmap2/tests/data/overlay/res/values/values.xml b/cmds/idmap2/tests/data/overlay/res/values/values.xml
new file mode 100644
index 0000000..815d1a8
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/values/values.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <string name="str1">overlay-1</string>
+    <string name="str3">overlay-3</string>
+    <integer name="int1">-1</integer>
+    <integer name="not_in_target">-1</integer>
+</resources>
diff --git a/cmds/idmap2/tests/data/target/AndroidManifest.xml b/cmds/idmap2/tests/data/target/AndroidManifest.xml
new file mode 100644
index 0000000..3a861b4
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.target">
+</manifest>
diff --git a/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt b/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt
new file mode 100644
index 0000000..d2cf010
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt
@@ -0,0 +1 @@
+Lorem ipsum dolor sit amet.
diff --git a/cmds/idmap2/tests/data/target/build b/cmds/idmap2/tests/data/target/build
new file mode 100644
index 0000000..8569c4f
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/build
@@ -0,0 +1,17 @@
+# 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.
+
+aapt2 compile --dir res -o compiled.flata
+aapt2 link --manifest AndroidManifest.xml -A assets -o target.apk compiled.flata
+rm compiled.flata
diff --git a/cmds/idmap2/tests/data/target/res/values/values.xml b/cmds/idmap2/tests/data/target/res/values/values.xml
new file mode 100644
index 0000000..56bf0d6
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/res/values/values.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <string name="a">a</string>
+    <string name="b">b</string>
+    <string name="c">c</string>
+    <string name="str1">target-1</string>
+    <string name="str2">target-2</string>
+    <string name="str3">target-3</string>
+    <string name="str4">target-4</string>
+    <string name="x">x</string>
+    <string name="y">y</string>
+    <string name="z">z</string>
+    <integer name="int1">1</integer>
+</resources>
diff --git a/cmds/idmap2/tests/data/target/res/xml/test.xml b/cmds/idmap2/tests/data/target/res/xml/test.xml
new file mode 100644
index 0000000..0fe21c6
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/res/xml/test.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<a>
+    <b>
+        <c
+            type_string="fortytwo"
+            type_int_dec="42"
+            type_int_hex="0x2a"
+            type_int_boolean="true"
+            />
+    </b>
+</a>
diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk
new file mode 100644
index 0000000..18ecc27
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/target.apk
Binary files differ