AAPT2: Add a APK filtering.

Allow resource files to be removed from the final artifact based on the
density and locale configuration in the config file. The APK is split
along the density, locale and ABI axis. Each split is generated from the
original APK without modifying the original. The new resource table is
written back to the file system with unneeded assets etc removed.

Test: Unit tests
Test: Manually run optimize command against an APK and inspect results
Test: Installed split searchlite APK (after resigning) and ran on N6

Change-Id: If73597dcfd88c02d2616518585d0e25a5c6a84d1
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index b0ed792..a9d6da0 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -59,6 +59,7 @@
 using ::aapt::xml::XmlActionExecutorPolicy;
 using ::aapt::xml::XmlNodeAction;
 using ::android::base::ReadFileToString;
+using ::android::StringPiece;
 
 const std::unordered_map<std::string, Abi> kStringToAbiMap = {
     {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a},  {"arm64-v8a", Abi::kArm64V8a},
@@ -117,9 +118,9 @@
  * success, or false if the either the placeholder is not found in the name, or the value is not
  * present and the placeholder was.
  */
-static bool ReplacePlaceholder(const std::string& placeholder, const Maybe<std::string>& value,
+static bool ReplacePlaceholder(const StringPiece& placeholder, const Maybe<StringPiece>& value,
                                std::string* name, IDiagnostics* diag) {
-  size_t offset = name->find(placeholder);
+  size_t offset = name->find(placeholder.data());
   bool found = (offset != std::string::npos);
 
   // Make sure the placeholder was present if the desired value is present.
@@ -139,43 +140,83 @@
     return false;
   }
 
-  name->replace(offset, placeholder.length(), value.value());
+  name->replace(offset, placeholder.length(), value.value().data());
 
   // Make sure there was only one instance of the placeholder.
-  if (name->find(placeholder) != std::string::npos) {
+  if (name->find(placeholder.data()) != std::string::npos) {
     diag->Error(DiagMessage() << "Placeholder present multiple times: " << placeholder);
     return false;
   }
   return true;
 }
 
-Maybe<std::string> Artifact::ToArtifactName(const std::string& format, IDiagnostics* diag) const {
-  std::string result = format;
+Maybe<std::string> Artifact::ToArtifactName(const StringPiece& format, IDiagnostics* diag,
+                                            const StringPiece& base_name,
+                                            const StringPiece& ext) const {
+  std::string result = format.to_string();
 
-  if (!ReplacePlaceholder("{abi}", abi_group, &result, diag)) {
+  Maybe<StringPiece> maybe_base_name =
+      base_name.empty() ? Maybe<StringPiece>{} : Maybe<StringPiece>{base_name};
+  if (!ReplacePlaceholder("${basename}", maybe_base_name, &result, diag)) {
     return {};
   }
 
-  if (!ReplacePlaceholder("{density}", screen_density_group, &result, diag)) {
+  // Extension is optional.
+  if (result.find("${ext}") != std::string::npos) {
+    if (!ReplacePlaceholder("${ext}", {ext}, &result, diag)) {
+      return {};
+    }
+  }
+
+  if (!ReplacePlaceholder("${abi}", abi_group, &result, diag)) {
     return {};
   }
 
-  if (!ReplacePlaceholder("{locale}", locale_group, &result, diag)) {
+  if (!ReplacePlaceholder("${density}", screen_density_group, &result, diag)) {
     return {};
   }
 
-  if (!ReplacePlaceholder("{sdk}", android_sdk_group, &result, diag)) {
+  if (!ReplacePlaceholder("${locale}", locale_group, &result, diag)) {
     return {};
   }
 
-  if (!ReplacePlaceholder("{feature}", device_feature_group, &result, diag)) {
+  if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) {
     return {};
   }
 
-  if (!ReplacePlaceholder("{gl}", gl_texture_group, &result, diag)) {
+  if (!ReplacePlaceholder("${feature}", device_feature_group, &result, diag)) {
     return {};
   }
 
+  if (!ReplacePlaceholder("${gl}", gl_texture_group, &result, diag)) {
+    return {};
+  }
+
+  return result;
+}
+
+Maybe<std::string> Artifact::Name(const StringPiece& base_name, const StringPiece& ext,
+                                  IDiagnostics* diag) const {
+  if (!name) {
+    return {};
+  }
+
+  std::string result = name.value();
+
+  // Base name is optional.
+  if (result.find("${basename}") != std::string::npos) {
+    if (!ReplacePlaceholder("${basename}", {base_name}, &result, diag)) {
+      return {};
+    }
+  }
+
+  // Extension is optional.
+  if (result.find("${ext}") != std::string::npos) {
+    if (!ReplacePlaceholder("${ext}", {ext}, &result, diag)) {
+      return {};
+    }
+  }
+
   return result;
 }
 
@@ -346,7 +387,10 @@
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
           ConfigDescription config_descriptor;
           const android::StringPiece& text = TrimWhitespace(t->text);
-          if (ConfigDescription::Parse(text, &config_descriptor)) {
+          bool parsed = ConfigDescription::Parse(text, &config_descriptor);
+          if (parsed &&
+              (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+               android::ResTable_config::CONFIG_DENSITY)) {
             // Copy the density with the minimum SDK version stripped out.
             group.push_back(config_descriptor.CopyWithoutSdkVersion());
           } else {
@@ -379,17 +423,25 @@
                                 << child->name);
       valid = false;
     } else {
-      Locale entry;
-      for (const auto& attr : child->attributes) {
-        if (attr.name == "lang") {
-          entry.lang = {attr.value};
-        } else if (attr.name == "region") {
-          entry.region = {attr.value};
-        } else {
-          diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
+      for (auto& node : child->children) {
+        xml::Text* t;
+        if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
+          ConfigDescription config_descriptor;
+          const android::StringPiece& text = TrimWhitespace(t->text);
+          bool parsed = ConfigDescription::Parse(text, &config_descriptor);
+          if (parsed &&
+              (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+               android::ResTable_config::CONFIG_LOCALE)) {
+            // Copy the locale with the minimum SDK version stripped out.
+            group.push_back(config_descriptor.CopyWithoutSdkVersion());
+          } else {
+            diag->Error(DiagMessage()
+                        << "Could not parse config descriptor for screen-density: " << text);
+            valid = false;
+          }
+          break;
         }
       }
-      group.push_back(entry);
     }
   }
 
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 28c355e..6259ce8 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -36,7 +36,7 @@
 /** Output artifact configuration options. */
 struct Artifact {
   /** Name to use for output of processing foo.apk -> foo.<name>.apk. */
-  std::string name;
+  Maybe<std::string> name;
   /** If present, uses the ABI group with this name. */
   Maybe<std::string> abi_group;
   /** If present, uses the screen density group with this name. */
@@ -51,7 +51,13 @@
   Maybe<std::string> gl_texture_group;
 
   /** Convert an artifact name template into a name string based on configuration contents. */
-  Maybe<std::string> ToArtifactName(const std::string& format, IDiagnostics* diag) const;
+  Maybe<std::string> ToArtifactName(const android::StringPiece& format, IDiagnostics* diag,
+                                    const android::StringPiece& base_name = "",
+                                    const android::StringPiece& ext = "apk") const;
+
+  /** Convert an artifact name template into a name string based on configuration contents. */
+  Maybe<std::string> Name(const android::StringPiece& base_name, const android::StringPiece& ext,
+                          IDiagnostics* diag) const;
 };
 
 /** Enumeration of currently supported ABIs. */
@@ -129,7 +135,7 @@
 
   Group<Abi> abi_groups;
   Group<ConfigDescription> screen_density_groups;
-  Group<Locale> locale_groups;
+  Group<ConfigDescription> locale_groups;
   Group<AndroidSdk> android_sdk_groups;
   Group<DeviceFeature> device_feature_groups;
   Group<GlTexture> gl_texture_groups;
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index ab3b7ec..5bd0831 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -67,18 +67,15 @@
       <screen-density>xxxhdpi</screen-density>
     </screen-density-group>
     <locale-group label="europe">
-      <locale lang="en"/>
-      <locale lang="es"/>
-      <locale lang="fr"/>
-      <locale lang="de"/>
+      <locale>en</locale>
+      <locale>es</locale>
+      <locale>fr</locale>
+      <locale>de</locale>
     </locale-group>
     <locale-group label="north-america">
-      <locale lang="en"/>
-      <locale lang="es" region="MX"/>
-      <locale lang="fr" region="CA"/>
-    </locale-group>
-    <locale-group label="all">
-      <locale/>
+      <locale>en</locale>
+      <locale>es-rMX</locale>
+      <locale>fr-rCA</locale>
     </locale-group>
     <android-sdk-group label="19">
       <android-sdk
@@ -156,10 +153,9 @@
   EXPECT_EQ(3ul, value.screen_density_groups["large"].size());
   EXPECT_EQ(6ul, value.screen_density_groups["alldpi"].size());
 
-  EXPECT_EQ(3ul, value.locale_groups.size());
+  EXPECT_EQ(2ul, value.locale_groups.size());
   EXPECT_EQ(4ul, value.locale_groups["europe"].size());
   EXPECT_EQ(3ul, value.locale_groups["north-america"].size());
-  EXPECT_EQ(1ul, value.locale_groups["all"].size());
 
   EXPECT_EQ(1ul, value.android_sdk_groups.size());
   EXPECT_EQ(1ul, value.android_sdk_groups["19"].size());
@@ -198,7 +194,7 @@
   EXPECT_EQ(1ul, config.artifacts.size());
 
   auto& artifact = config.artifacts.front();
-  EXPECT_EQ("", artifact.name); // TODO: make this fail.
+  EXPECT_FALSE(artifact.name);  // TODO: make this fail.
   EXPECT_EQ("arm", artifact.abi_group.value());
   EXPECT_EQ("large", artifact.screen_density_group.value());
   EXPECT_EQ("europe", artifact.locale_group.value());
@@ -298,10 +294,10 @@
 TEST_F(ConfigurationParserTest, LocaleGroupAction) {
   static constexpr const char* xml = R"xml(
     <locale-group label="europe">
-      <locale lang="en"/>
-      <locale lang="es"/>
-      <locale lang="fr"/>
-      <locale lang="de"/>
+      <locale>en</locale>
+      <locale>es</locale>
+      <locale>fr</locale>
+      <locale>de</locale>
     </locale-group>)xml";
 
   auto doc = test::BuildXmlDom(xml);
@@ -313,16 +309,12 @@
   ASSERT_EQ(1ul, config.locale_groups.size());
   ASSERT_EQ(1u, config.locale_groups.count("europe"));
 
-  auto& out = config.locale_groups["europe"];
+  const auto& out = config.locale_groups["europe"];
 
-  Locale en;
-  en.lang = std::string("en");
-  Locale es;
-  es.lang = std::string("es");
-  Locale fr;
-  fr.lang = std::string("fr");
-  Locale de;
-  de.lang = std::string("de");
+  ConfigDescription en = test::ParseConfigOrDie("en");
+  ConfigDescription es = test::ParseConfigOrDie("es");
+  ConfigDescription fr = test::ParseConfigOrDie("fr");
+  ConfigDescription de = test::ParseConfigOrDie("de");
 
   ASSERT_THAT(out, ElementsAre(en, es, fr, de));
 }
@@ -425,14 +417,14 @@
   Artifact x86;
   x86.abi_group = {"x86"};
 
-  auto x86_result = x86.ToArtifactName("something.{abi}.apk", &diag);
+  auto x86_result = x86.ToArtifactName("something.${abi}.apk", &diag);
   ASSERT_TRUE(x86_result);
   EXPECT_EQ(x86_result.value(), "something.x86.apk");
 
   Artifact arm;
   arm.abi_group = {"armeabi-v7a"};
 
-  auto arm_result = arm.ToArtifactName("app.{abi}.apk", &diag);
+  auto arm_result = arm.ToArtifactName("app.${abi}.apk", &diag);
   ASSERT_TRUE(arm_result);
   EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk");
 }
@@ -447,8 +439,8 @@
   artifact.locale_group = {"en-AU"};
   artifact.android_sdk_group = {"26"};
 
-  auto result =
-      artifact.ToArtifactName("app.{density}_{locale}_{feature}_{gl}.sdk{sdk}.{abi}.apk", &diag);
+  auto result = artifact.ToArtifactName(
+      "app.${density}_${locale}_${feature}_${gl}.sdk${sdk}.${abi}.apk", &diag);
   ASSERT_TRUE(result);
   EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.sdk26.mips64.apk");
 }
@@ -458,7 +450,7 @@
   Artifact x86;
   x86.abi_group = {"x86"};
 
-  EXPECT_FALSE(x86.ToArtifactName("something.{density}.apk", &diag));
+  EXPECT_FALSE(x86.ToArtifactName("something.${density}.apk", &diag));
   EXPECT_FALSE(x86.ToArtifactName("something.apk", &diag));
 }
 
@@ -466,7 +458,7 @@
   StdErrDiagnostics diag;
   Artifact artifact;
 
-  EXPECT_FALSE(artifact.ToArtifactName("something.{density}.apk", &diag));
+  EXPECT_FALSE(artifact.ToArtifactName("something.${density}.apk", &diag));
   EXPECT_TRUE(artifact.ToArtifactName("something.apk", &diag));
 }
 
@@ -475,8 +467,8 @@
   Artifact artifact;
   artifact.screen_density_group = {"mdpi"};
 
-  EXPECT_TRUE(artifact.ToArtifactName("something.{density}.apk", &diag));
-  EXPECT_FALSE(artifact.ToArtifactName("something.{density}.{density}.apk", &diag));
+  ASSERT_TRUE(artifact.ToArtifactName("something.${density}.apk", &diag));
+  EXPECT_FALSE(artifact.ToArtifactName("something.${density}.${density}.apk", &diag));
 }
 
 TEST(ArtifactTest, Nesting) {
@@ -484,36 +476,36 @@
   Artifact x86;
   x86.abi_group = {"x86"};
 
-  EXPECT_FALSE(x86.ToArtifactName("something.{abi{density}}.apk", &diag));
+  EXPECT_FALSE(x86.ToArtifactName("something.${abi${density}}.apk", &diag));
 
-  const Maybe<std::string>& name = x86.ToArtifactName("something.{abi{abi}}.apk", &diag);
-  EXPECT_TRUE(name);
-  EXPECT_EQ(name.value(), "something.{abix86}.apk");
+  const Maybe<std::string>& name = x86.ToArtifactName("something.${abi${abi}}.apk", &diag);
+  ASSERT_TRUE(name);
+  EXPECT_EQ(name.value(), "something.${abix86}.apk");
 }
 
 TEST(ArtifactTest, Recursive) {
   StdErrDiagnostics diag;
   Artifact artifact;
-  artifact.device_feature_group = {"{gl}"};
+  artifact.device_feature_group = {"${gl}"};
   artifact.gl_texture_group = {"glx1"};
 
-  EXPECT_FALSE(artifact.ToArtifactName("app.{feature}.{gl}.apk", &diag));
+  EXPECT_FALSE(artifact.ToArtifactName("app.${feature}.${gl}.apk", &diag));
 
   artifact.device_feature_group = {"df1"};
-  artifact.gl_texture_group = {"{feature}"};
+  artifact.gl_texture_group = {"${feature}"};
   {
-    const auto& result = artifact.ToArtifactName("app.{feature}.{gl}.apk", &diag);
-    EXPECT_TRUE(result);
-    EXPECT_EQ(result.value(), "app.df1.{feature}.apk");
+    const auto& result = artifact.ToArtifactName("app.${feature}.${gl}.apk", &diag);
+    ASSERT_TRUE(result);
+    EXPECT_EQ(result.value(), "app.df1.${feature}.apk");
   }
 
   // This is an invalid case, but should be the only possible case due to the ordering of
   // replacement.
-  artifact.device_feature_group = {"{gl}"};
+  artifact.device_feature_group = {"${gl}"};
   artifact.gl_texture_group = {"glx1"};
   {
-    const auto& result = artifact.ToArtifactName("app.{feature}.apk", &diag);
-    EXPECT_TRUE(result);
+    const auto& result = artifact.ToArtifactName("app.${feature}.apk", &diag);
+    ASSERT_TRUE(result);
     EXPECT_EQ(result.value(), "app.glx1.apk");
   }
 }