Add support for --replace-version to aapt2

Implement --replace-version to match aapt1.

Bug: 79755007
Test: aapt2_tests
Change-Id: Iee2bd9a3981c7d4681509f18b735c6e9e6c1a336
Merged-In: Iee2bd9a3981c7d4681509f18b735c6e9e6c1a336
(cherry picked from commit dcd58c420771af75c625242985bd96ba828aca56)
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 036bf5d8..3a312b0 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1923,6 +1923,12 @@
           .OptionalFlag("--version-name",
                         "Version name to inject into the AndroidManifest.xml if none is present.",
                         &options.manifest_fixer_options.version_name_default)
+          .OptionalSwitch("--replace-version",
+                         "If --version-code and/or --version-name are specified, these\n"
+                         "values will replace any value already in the manifest. By\n"
+                         "default, nothing is changed if the manifest already defines\n"
+                         "these attributes.",
+                         &options.manifest_fixer_options.replace_version)
           .OptionalSwitch("--shared-lib", "Generates a shared Android runtime library.",
                           &shared_lib)
           .OptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib)
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index e6271eb..ca7b653 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -237,6 +237,9 @@
   manifest_action.Action(FixCoreAppAttribute);
   manifest_action.Action([&](xml::Element* el) -> bool {
     if (options_.version_name_default) {
+      if (options_.replace_version) {
+        el->RemoveAttribute(xml::kSchemaAndroid, "versionName");
+      }
       if (el->FindAttribute(xml::kSchemaAndroid, "versionName") == nullptr) {
         el->attributes.push_back(
             xml::Attribute{xml::kSchemaAndroid, "versionName",
@@ -245,6 +248,9 @@
     }
 
     if (options_.version_code_default) {
+      if (options_.replace_version) {
+        el->RemoveAttribute(xml::kSchemaAndroid, "versionCode");
+      }
       if (el->FindAttribute(xml::kSchemaAndroid, "versionCode") == nullptr) {
         el->attributes.push_back(
             xml::Attribute{xml::kSchemaAndroid, "versionCode",
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index dbabab1..d7a5931 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -33,13 +33,22 @@
   Maybe<std::string> target_sdk_version_default;
   Maybe<std::string> rename_manifest_package;
   Maybe<std::string> rename_instrumentation_target_package;
+
+  // The version name to set if 'android:versionName' is not defined in <manifest> or if
+  // replace_version is set.
   Maybe<std::string> version_name_default;
+
+  // The version code to set if 'android:versionCode' is not defined in <manifest> or if
+  // replace_version is set.
   Maybe<std::string> version_code_default;
 
   // Wether validation errors should be treated only as warnings. If this is 'true', then an
   // incorrect node will not result in an error, but only as a warning, and the parsing will
   // continue.
   bool warn_validation = false;
+
+  // Whether to replace the manifest version with the the command line version
+  bool replace_version = false;
 };
 
 /**
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 84e367d..e6410c9 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -21,6 +21,7 @@
 using ::android::StringPiece;
 using ::testing::IsNull;
 using ::testing::NotNull;
+using ::testing::StrEq;
 
 namespace aapt {
 
@@ -339,6 +340,136 @@
   EXPECT_EQ(std::string("0x10000000"), attr->value);
 }
 
+TEST_F(ManifestFixerTest, DontUseDefaultVersionNameAndCode) {
+ManifestFixerOptions options;
+options.version_name_default = std::string("Beta");
+options.version_code_default = std::string("0x10000000");
+
+std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+                package="android"
+                android:versionCode="0x20000000"
+                android:versionName="Alpha" />)EOF",
+                                                          options);
+ASSERT_THAT(doc, NotNull());
+
+xml::Element* manifest_el = doc->root.get();
+ASSERT_THAT(manifest_el, NotNull());
+
+xml::Attribute* attr =
+    manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ASSERT_THAT(attr, NotNull());
+EXPECT_THAT(attr->value, StrEq("Alpha"));
+
+attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ASSERT_THAT(attr, NotNull());
+EXPECT_THAT(attr->value, StrEq("0x20000000"));
+}
+
+TEST_F(ManifestFixerTest, ReplaceVersionNameAndCode) {
+ManifestFixerOptions options;
+options.replace_version = true;
+options.version_name_default = std::string("Beta");
+options.version_code_default = std::string("0x10000000");
+
+std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+              package="android"
+              android:versionCode="0x20000000"
+              android:versionName="Alpha" />)EOF",
+                                                          options);
+ASSERT_THAT(doc, NotNull());
+
+xml::Element* manifest_el = doc->root.get();
+ASSERT_THAT(manifest_el, NotNull());
+
+xml::Attribute* attr =
+    manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ASSERT_THAT(attr, NotNull());
+EXPECT_THAT(attr->value, StrEq("Beta"));
+
+attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ASSERT_THAT(attr, NotNull());
+EXPECT_THAT(attr->value, StrEq("0x10000000"));
+}
+
+TEST_F(ManifestFixerTest, ReplaceVersionName) {
+ManifestFixerOptions options;
+options.replace_version = true;
+options.version_name_default = std::string("Beta");
+
+std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+            package="android"
+            android:versionCode="0x20000000"
+            android:versionName="Alpha" />)EOF",
+                                                          options);
+ASSERT_THAT(doc, NotNull());
+
+xml::Element* manifest_el = doc->root.get();
+ASSERT_THAT(manifest_el, NotNull());
+
+xml::Attribute* attr =
+    manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ASSERT_THAT(attr, NotNull());
+EXPECT_THAT(attr->value, StrEq("Beta"));
+
+attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ASSERT_THAT(attr, NotNull());
+EXPECT_THAT(attr->value, StrEq("0x20000000"));
+}
+
+TEST_F(ManifestFixerTest, ReplaceVersionCode) {
+ManifestFixerOptions options;
+options.replace_version = true;
+options.version_code_default = std::string("0x10000000");
+
+std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+            package="android"
+            android:versionCode="0x20000000"
+            android:versionName="Alpha" />)EOF",
+                                                          options);
+ASSERT_THAT(doc, NotNull());
+
+xml::Element* manifest_el = doc->root.get();
+ASSERT_THAT(manifest_el, NotNull());
+
+xml::Attribute* attr =
+    manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ASSERT_THAT(attr, NotNull());
+EXPECT_THAT(attr->value, StrEq("Alpha"));
+
+attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ASSERT_THAT(attr, NotNull());
+EXPECT_THAT(attr->value, StrEq("0x10000000"));
+}
+
+TEST_F(ManifestFixerTest, DontReplaceVersionNameOrCode) {
+ManifestFixerOptions options;
+options.replace_version = true;
+
+std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android"
+          android:versionCode="0x20000000"
+          android:versionName="Alpha" />)EOF",
+                                                          options);
+ASSERT_THAT(doc, NotNull());
+
+xml::Element* manifest_el = doc->root.get();
+ASSERT_THAT(manifest_el, NotNull());
+
+xml::Attribute* attr =
+    manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ASSERT_THAT(attr, NotNull());
+EXPECT_THAT(attr->value, StrEq("Alpha"));
+
+attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ASSERT_THAT(attr, NotNull());
+EXPECT_THAT(attr->value, StrEq("0x20000000"));
+}
+
 TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) {
   EXPECT_EQ(nullptr,
             Verify("<manifest package=\"android\" coreApp=\"hello\" />"));
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 1d122db..22767781 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -394,6 +394,15 @@
   return nullptr;
 }
 
+void Element::RemoveAttribute(const StringPiece& ns, const StringPiece& name) {
+  auto new_attr_end = std::remove_if(attributes.begin(), attributes.end(),
+    [&](const Attribute& attr) -> bool {
+      return ns == attr.namespace_uri && name == attr.name;
+    });
+
+  attributes.erase(new_attr_end, attributes.end());
+}
+
 Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 1542243..995cdb24 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -98,6 +98,9 @@
   Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
   const Attribute* FindAttribute(const android::StringPiece& ns,
                                  const android::StringPiece& name) const;
+  void RemoveAttribute(const android::StringPiece& ns,
+                       const android::StringPiece& name);
+
   Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
   Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name,
                                   const android::StringPiece& attr_ns,