AAPT2: Split APK by ABI.

Added a FilterChain that can apply multiple filter steps to an APK file
as it is being written to disk. The first filter applied is by ABI. If
a library in the APK does not match the filter it is skipped.

Added an AbiFilter that keeps files that are either not native libs or
are for the set of wanted ABIs

Test: ran unit tests locally
Test: ran against an APK with ARM and x68 libs and diffed the results

Change-Id: I3fb901d3de3513e85f2a2763a8e4487a28ed4881
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index 303a809..555cb35 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 #include <functional>
+#include <map>
 #include <memory>
 #include <utility>
 
@@ -56,15 +57,15 @@
 using ::aapt::xml::XmlNodeAction;
 using ::android::base::ReadFileToString;
 
-const std::unordered_map<std::string, Abi> kAbiMap = {
-    {"armeabi", Abi::kArmeV6},
-    {"armeabi-v7a", Abi::kArmV7a},
-    {"arm64-v8a", Abi::kArm64V8a},
-    {"x86", Abi::kX86},
-    {"x86_64", Abi::kX86_64},
-    {"mips", Abi::kMips},
-    {"mips64", Abi::kMips64},
-    {"universal", Abi::kUniversal},
+const std::unordered_map<std::string, Abi> kStringToAbiMap = {
+    {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a},  {"arm64-v8a", Abi::kArm64V8a},
+    {"x86", Abi::kX86},        {"x86_64", Abi::kX86_64},       {"mips", Abi::kMips},
+    {"mips64", Abi::kMips64},  {"universal", Abi::kUniversal},
+};
+const std::map<Abi, std::string> kAbiToStringMap = {
+    {Abi::kArmeV6, "armeabi"}, {Abi::kArmV7a, "armeabi-v7a"},  {Abi::kArm64V8a, "arm64-v8a"},
+    {Abi::kX86, "x86"},        {Abi::kX86_64, "x86_64"},       {Abi::kMips, "mips"},
+    {Abi::kMips64, "mips64"},  {Abi::kUniversal, "universal"},
 };
 
 constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
@@ -102,7 +103,13 @@
 
 }  // namespace
 
+namespace configuration {
 
+const std::string& AbiToString(Abi abi) {
+  return kAbiToStringMap.find(abi)->second;
+}
+
+}  // namespace configuration
 
 /** Returns a ConfigurationParser for the file located at the provided path. */
 Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) {
@@ -175,6 +182,9 @@
     return {};
   }
 
+  // TODO: Validate all references in the configuration are valid. It should be safe to assume from
+  // this point on that any references from one section to another will be present.
+
   return {config};
 }
 
@@ -201,7 +211,7 @@
               DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " << attr.value);
         }
       }
-      config->artifacts[artifact.name] = artifact;
+      config->artifacts.push_back(artifact);
       return true;
     };
 
@@ -236,7 +246,7 @@
           for (auto& node : child->children) {
             xml::Text* t;
             if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-              group.push_back(kAbiMap.at(TrimWhitespace(t->text).to_string()));
+              group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
               break;
             }
           }
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 8b9c085..0435cbf 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -62,6 +62,9 @@
   kUniversal
 };
 
+/** Helper method to convert an ABI to a string representing the path within the APK. */
+const std::string& AbiToString(Abi abi);
+
 /**
  * Represents an individual locale. When a locale is included, it must be
  * declared from least specific to most specific, as a region does not make
@@ -118,7 +121,8 @@
  * AAPT2 XML configuration binary representation.
  */
 struct Configuration {
-  std::unordered_map<std::string, Artifact> artifacts;
+  // TODO: Support named artifacts?
+  std::vector<Artifact> artifacts;
   Maybe<std::string> artifact_format;
 
   Group<Abi> abi_groups;
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 8421ee3..a8c3858 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -196,7 +196,7 @@
 
   EXPECT_EQ(1ul, config.artifacts.size());
 
-  auto& artifact = config.artifacts.begin()->second;
+  auto& artifact = config.artifacts.front();
   EXPECT_EQ("", artifact.name); // TODO: make this fail.
   EXPECT_EQ("arm", artifact.abi_group.value());
   EXPECT_EQ("large", artifact.screen_density_group.value());
@@ -204,6 +204,21 @@
   EXPECT_EQ("19", artifact.android_sdk_group.value());
   EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
   EXPECT_EQ("low-latency", artifact.device_feature_group.value());
+
+  // Perform a second action to ensure we get 2 artifacts.
+  static constexpr const char* second = R"xml(
+    <artifact
+        abi-group="other"
+        screen-density-group="large"
+        locale-group="europe"
+        android-sdk-group="19"
+        gl-texture-group="dxt1"
+        device-feature-group="low-latency"/>)xml";
+  doc = test::BuildXmlDom(second);
+
+  ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_TRUE(ok);
+  EXPECT_EQ(2ul, config.artifacts.size());
 }
 
 TEST_F(ConfigurationParserTest, ArtifactFormatAction) {