AAPT2: Auto-version adaptive-icon XML

Auto version adaptive-icon XML to v26.

This change makes the logic for generating versioned resources
simpler by changing the comparison function of ResTable_config
to evaluate the sdkVersion property last, making configurations
that differ only in sdkVersion next to each other in a sorted vector.

Bug: 62316340
Test: manual (verified output of tools/aapt2/integration-tests/AppOne)
Change-Id: I977d45821722a65d2135efb4693304eacc565c9a
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 0782269..99ccc98 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1901,8 +1901,6 @@
     if (diff != 0) return diff;
     diff = (int32_t)(screenSize - o.screenSize);
     if (diff != 0) return diff;
-    diff = (int32_t)(version - o.version);
-    if (diff != 0) return diff;
     diff = (int32_t)(screenLayout - o.screenLayout);
     if (diff != 0) return diff;
     diff = (int32_t)(screenLayout2 - o.screenLayout2);
@@ -1914,6 +1912,11 @@
     diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp);
     if (diff != 0) return diff;
     diff = (int32_t)(screenSizeDp - o.screenSizeDp);
+    if (diff != 0) return diff;
+
+    // Version MUST be last to ensure that a sorted list of configurations will always have the
+    // versions beside each other.
+    diff = (int32_t)(version - o.version);
     return (int)diff;
 }
 
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 7ff0c72..f16d806 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -987,4 +987,8 @@
   return !ConflictsWith(o) && !Dominates(o) && !o.Dominates(*this);
 }
 
+::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) {
+  return out << o.toString().string();
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
index 65c9617..8c519a1 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -24,30 +24,22 @@
 
 namespace aapt {
 
-/*
- * Subclass of ResTable_config that adds convenient
- * initialization and comparison methods.
- */
+// Subclass of ResTable_config that adds convenient
+// initialization and comparison methods.
 struct ConfigDescription : public android::ResTable_config {
-  /**
-   * Returns an immutable default config.
-   */
+  // Returns an immutable default config.
   static const ConfigDescription& DefaultConfig();
 
-  /*
-   * Parse a string of the form 'fr-sw600dp-land' and fill in the
-   * given ResTable_config with resulting configuration parameters.
-   *
-   * The resulting configuration has the appropriate sdkVersion defined
-   * for backwards compatibility.
-   */
+  // Parse a string of the form 'fr-sw600dp-land' and fill in the
+  // given ResTable_config with resulting configuration parameters.
+  //
+  // The resulting configuration has the appropriate sdkVersion defined
+  // for backwards compatibility.
   static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr);
 
-  /**
-   * If the configuration uses an axis that was added after
-   * the original Android release, make sure the SDK version
-   * is set accordingly.
-   */
+  // If the configuration uses an axis that was added after
+  // the original Android release, make sure the SDK version
+  // is set accordingly.
   static void ApplyVersionForCompatibility(ConfigDescription* config);
 
   ConfigDescription();
@@ -61,38 +53,30 @@
 
   ConfigDescription CopyWithoutSdkVersion() const;
 
-  /**
-   * A configuration X dominates another configuration Y, if X has at least the
-   * precedence of Y and X is strictly more general than Y: for any type defined
-   * by X, the same type is defined by Y with a value equal to or, in the case
-   * of ranges, more specific than that of X.
-   *
-   * For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It
-   * does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'.
-   */
+  // A configuration X dominates another configuration Y, if X has at least the
+  // precedence of Y and X is strictly more general than Y: for any type defined
+  // by X, the same type is defined by Y with a value equal to or, in the case
+  // of ranges, more specific than that of X.
+  //
+  // For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It
+  // does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'.
   bool Dominates(const ConfigDescription& o) const;
 
-  /**
-   * Returns true if this configuration defines a more important configuration
-   * parameter than o. For example, "en" has higher precedence than "v23",
-   * whereas "en" has the same precedence as "en-v23".
-   */
+  // Returns true if this configuration defines a more important configuration
+  // parameter than o. For example, "en" has higher precedence than "v23",
+  // whereas "en" has the same precedence as "en-v23".
   bool HasHigherPrecedenceThan(const ConfigDescription& o) const;
 
-  /**
-   * A configuration conflicts with another configuration if both
-   * configurations define an incompatible configuration parameter. An
-   * incompatible configuration parameter is a non-range, non-density parameter
-   * that is defined in both configurations as a different, non-default value.
-   */
+  // A configuration conflicts with another configuration if both
+  // configurations define an incompatible configuration parameter. An
+  // incompatible configuration parameter is a non-range, non-density parameter
+  // that is defined in both configurations as a different, non-default value.
   bool ConflictsWith(const ConfigDescription& o) const;
 
-  /**
-   * A configuration is compatible with another configuration if both
-   * configurations can match a common concrete device configuration and are
-   * unrelated by domination. For example, land-v11 conflicts with port-v21
-   * but is compatible with v21 (both land-v11 and v21 would match en-land-v23).
-   */
+  // A configuration is compatible with another configuration if both
+  // configurations can match a common concrete device configuration and are
+  // unrelated by domination. For example, land-v11 conflicts with port-v21
+  // but is compatible with v21 (both land-v11 and v21 would match en-land-v23).
   bool IsCompatibleWith(const ConfigDescription& o) const;
 
   bool MatchWithDensity(const ConfigDescription& o) const;
@@ -105,6 +89,8 @@
   bool operator>(const ConfigDescription& o) const;
 };
 
+::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o);
+
 inline ConfigDescription::ConfigDescription() {
   memset(this, 0, sizeof(*this));
   size = sizeof(android::ResTable_config);
@@ -123,15 +109,13 @@
   *this = o;
 }
 
-inline ConfigDescription& ConfigDescription::operator=(
-    const android::ResTable_config& o) {
+inline ConfigDescription& ConfigDescription::operator=(const android::ResTable_config& o) {
   *static_cast<android::ResTable_config*>(this) = o;
   size = sizeof(android::ResTable_config);
   return *this;
 }
 
-inline ConfigDescription& ConfigDescription::operator=(
-    const ConfigDescription& o) {
+inline ConfigDescription& ConfigDescription::operator=(const ConfigDescription& o) {
   *static_cast<android::ResTable_config*>(this) = o;
   return *this;
 }
@@ -141,8 +125,7 @@
   return *this;
 }
 
-inline bool ConfigDescription::MatchWithDensity(
-    const ConfigDescription& o) const {
+inline bool ConfigDescription::MatchWithDensity(const ConfigDescription& o) const {
   return match(o) && (density == 0 || density == o.density);
 }
 
@@ -170,11 +153,6 @@
   return compare(o) > 0;
 }
 
-inline ::std::ostream& operator<<(::std::ostream& out,
-                                  const ConfigDescription& o) {
-  return out << o.toString().string();
-}
-
 }  // namespace aapt
 
 #endif  // AAPT_CONFIG_DESCRIPTION_H
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index e6bf3a6..353d734 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -60,6 +60,7 @@
 #include "unflatten/BinaryResourceParser.h"
 #include "util/Files.h"
 #include "xml/XmlDom.h"
+#include "xml/XmlUtil.h"
 
 using android::StringPiece;
 using android::base::StringPrintf;
@@ -342,16 +343,18 @@
     ConfigDescription config;
 
     // The entry this file came from.
-    ResourceEntry* entry;
+    ResourceEntry* entry = nullptr;
 
     // The file to copy as-is.
-    io::IFile* file_to_copy;
+    io::IFile* file_to_copy = nullptr;
 
     // The XML to process and flatten.
     std::unique_ptr<xml::XmlResource> xml_to_flatten;
 
     // The destination to write this file to.
     std::string dst_path;
+
+    bool skip_versioning = false;
   };
 
   uint32_t GetCompressionFlags(const StringPiece& str);
@@ -431,19 +434,6 @@
   return ArchiveEntry::kCompress;
 }
 
-static bool IsTransitionElement(const std::string& name) {
-  return name == "fade" || name == "changeBounds" || name == "slide" || name == "explode" ||
-         name == "changeImageTransform" || name == "changeTransform" ||
-         name == "changeClipBounds" || name == "autoTransition" || name == "recolor" ||
-         name == "changeScroll" || name == "transitionSet" || name == "transition" ||
-         name == "transitionManager";
-}
-
-static bool IsVectorElement(const std::string& name) {
-  return name == "vector" || name == "animated-vector" || name == "pathInterpolator" ||
-         name == "objectAnimator";
-}
-
 template <typename T>
 std::vector<T> make_singleton_vec(T&& val) {
   std::vector<T> vec;
@@ -476,21 +466,10 @@
     }
   }
 
-  if (options_.no_auto_version) {
+  if (options_.no_auto_version || file_op->skip_versioning) {
     return make_singleton_vec(std::move(file_op->xml_to_flatten));
   }
 
-  if (options_.no_version_vectors || options_.no_version_transitions) {
-    // Skip this if it is a vector or animated-vector.
-    xml::Element* el = xml::FindRootElement(doc);
-    if (el && el->namespace_uri.empty()) {
-      if ((options_.no_version_vectors && IsVectorElement(el->name)) ||
-          (options_.no_version_transitions && IsTransitionElement(el->name))) {
-        return make_singleton_vec(std::move(file_op->xml_to_flatten));
-      }
-    }
-  }
-
   const ConfigDescription& config = file_op->config;
   ResourceEntry* entry = file_op->entry;
 
@@ -504,15 +483,26 @@
   bool error = false;
   std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> config_sorted_files;
 
+  int tag_version_options = 0;
+  if (options_.no_version_vectors) {
+    tag_version_options |= xml::kNoVersionVector;
+  }
+
+  if (options_.no_version_transitions) {
+    tag_version_options |= xml::kNoVersionTransition;
+  }
+
   for (auto& pkg : table->packages) {
     for (auto& type : pkg->types) {
       // Sort by config and name, so that we get better locality in the zip file.
       config_sorted_files.clear();
-      std::queue<FileOperation> file_operations;
 
       // Populate the queue with all files in the ResourceTable.
       for (auto& entry : type->entries) {
-        for (auto& config_value : entry->values) {
+        const auto values_end = entry->values.end();
+        for (auto values_iter = entry->values.begin(); values_iter != values_end; ++values_iter) {
+          ResourceConfigValue* config_value = values_iter->get();
+
           // WARNING! Do not insert or remove any resources while executing in this scope. It will
           // corrupt the iteration order.
 
@@ -554,6 +544,44 @@
             file_op.xml_to_flatten->file.config = config_value->config;
             file_op.xml_to_flatten->file.source = file_ref->GetSource();
             file_op.xml_to_flatten->file.name = ResourceName(pkg->name, type->type, entry->name);
+
+            // Check if this file needs to be versioned based on tag rules.
+            xml::Element* root_el = xml::FindRootElement(file_op.xml_to_flatten.get());
+            if (root_el == nullptr) {
+              context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+                                                << "failed to find the root XML element");
+              return false;
+            }
+
+            if (root_el->namespace_uri.empty()) {
+              if (Maybe<xml::TagApiVersionResult> result =
+                      xml::GetXmlTagApiVersion(root_el->name, tag_version_options)) {
+                file_op.skip_versioning = result.value().skip_version;
+                if (result.value().api_version && config_value->config.sdkVersion == 0u) {
+                  const ApiVersion min_tag_version = result.value().api_version.value();
+                  // Only version it if it doesn't specify its own version and the version is
+                  // greater than the minSdk.
+                  const util::Range<ApiVersion> valid_range{
+                      context_->GetMinSdkVersion() + 1,
+                      FindNextApiVersionForConfigInSortedVector(values_iter, values_end)};
+                  if (valid_range.Contains(min_tag_version)) {
+                    // Update the configurations. The iteration order will not be affected
+                    // since sdkVersions in ConfigDescriptions are the last property compared
+                    // in the sort function.
+                    if (context_->IsVerbose()) {
+                      context_->GetDiagnostics()->Note(DiagMessage(config_value->value->GetSource())
+                                                       << "auto-versioning XML resource to API "
+                                                       << min_tag_version);
+                    }
+                    const_cast<ConfigDescription&>(config_value->config).sdkVersion =
+                        static_cast<uint16_t>(min_tag_version);
+                    file_op.config.sdkVersion = static_cast<uint16_t>(min_tag_version);
+                    file_op.xml_to_flatten->file.config.sdkVersion =
+                        static_cast<uint16_t>(min_tag_version);
+                  }
+                }
+              }
+            }
           }
 
           // NOTE(adamlesinski): Explicitly construct a StringPiece here, or
diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/adaptive-icon.xml b/tools/aapt2/integration-tests/AppOne/res/drawable/adaptive-icon.xml
new file mode 100644
index 0000000..e3cda7e
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/drawable/adaptive-icon.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<adaptive-icon xmlns:android="http://schema.android.com/apk/res/android">
+    <background android:drawable="@android:color/white" />
+    <foreground android:drawable="@drawable/image" />
+</adaptive-icon>
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
index f80c6e9..4ac70d9 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -34,6 +34,19 @@
   return sdk_version_to_generate < FindNextApiVersionForConfig(entry, config);
 }
 
+ApiVersion FindNextApiVersionForConfigInSortedVector(
+    std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator start,
+    std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator end) {
+  const ConfigDescription start_config = (*start)->config.CopyWithoutSdkVersion();
+  ++start;
+  if (start != end) {
+    if ((*start)->config.CopyWithoutSdkVersion() == start_config) {
+      return static_cast<ApiVersion>((*start)->config.sdkVersion);
+    }
+  }
+  return std::numeric_limits<ApiVersion>::max();
+}
+
 ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry,
                                        const ConfigDescription& config) {
   const auto end_iter = entry->values.end();
@@ -46,25 +59,7 @@
 
   // The source config came from this list, so it should be here.
   CHECK(iter != entry->values.end());
-  ++iter;
-
-  // The next configuration either only varies in sdkVersion, or it is completely different
-  // and therefore incompatible. If it is incompatible, we must generate the versioned resource.
-
-  // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other
-  // qualifiers, so we need to iterate through the entire list to be sure there
-  // are no higher sdk level versions of this resource.
-  ConfigDescription temp_config(config);
-  for (; iter != end_iter; ++iter) {
-    temp_config.sdkVersion = (*iter)->config.sdkVersion;
-    if (temp_config == (*iter)->config) {
-      // The two configs are the same, return the sdkVersion.
-      return (*iter)->config.sdkVersion;
-    }
-  }
-
-  // Didn't find another config with a different sdk version, so return the highest possible value.
-  return std::numeric_limits<ApiVersion>::max();
+  return FindNextApiVersionForConfigInSortedVector(iter, end_iter);
 }
 
 bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) {
diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp
index 49639f8..88a831b 100644
--- a/tools/aapt2/link/AutoVersioner_test.cpp
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -42,8 +42,8 @@
 
   ResourceEntry entry("foo");
   entry.values.push_back(util::make_unique<ResourceConfigValue>(ConfigDescription::DefaultConfig(), ""));
-  entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dp_v13_config, ""));
   entry.values.push_back(util::make_unique<ResourceConfigValue>(v21_config, ""));
+  entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dp_v13_config, ""));
 
   EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 17));
   EXPECT_FALSE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 22));
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index 5527f90..493b6b1 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -23,6 +23,7 @@
 #include "android-base/macros.h"
 
 #include "Resource.h"
+#include "ResourceTable.h"
 #include "SdkConstants.h"
 #include "process/IResourceTableConsumer.h"
 #include "xml/XmlDom.h"
@@ -41,17 +42,19 @@
   ResourceNameRef resource;
 };
 
-/**
- * Determines whether a versioned resource should be created. If a versioned
- * resource already exists, it takes precedence.
- */
+// Determines whether a versioned resource should be created. If a versioned resource already
+// exists, it takes precedence.
 bool ShouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
                                      const ApiVersion sdk_version_to_generate);
 
-// Finds the next largest ApiVersion of the config which is identical to the given config except
-// for sdkVersion.
+// Finds the next largest ApiVersion of `config` for values defined for `entry`.
 ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry, const ConfigDescription& config);
 
+// Finds the next largest ApiVersion of the config pointed to by the iterator `start`.
+ApiVersion FindNextApiVersionForConfigInSortedVector(
+    std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator start,
+    std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator end);
+
 class AutoVersioner : public IResourceTableConsumer {
  public:
   AutoVersioner() = default;
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index ebcd469..8c476fa 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -9,6 +9,7 @@
   (bug 62839863)
 - Fixed issue where Java classes referenced from fragments and menus were not added to
   the set of Proguard keep rules. (bug 62216174)
+- Automatically version XML `<adaptive-icon>` resources to v26. (bug 62316340)
 
 ## Version 2.17
 ### `aapt2 ...`
diff --git a/tools/aapt2/util/TypeTraits.h b/tools/aapt2/util/TypeTraits.h
index b6539ed..6fcb4bdb 100644
--- a/tools/aapt2/util/TypeTraits.h
+++ b/tools/aapt2/util/TypeTraits.h
@@ -38,6 +38,7 @@
 
 DEFINE_HAS_BINARY_OP_TRAIT(has_eq_op, ==);
 DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <);
+DEFINE_HAS_BINARY_OP_TRAIT(has_lte_op, <=);
 
 /**
  * Type trait that checks if two types can be equated (==) and compared (<).
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 386f74b..8bca9dd 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -48,6 +48,11 @@
 struct Range {
   T start;
   T end;
+
+  typename std::enable_if<has_lte_op<const T, const T>::value, bool>::type Contains(
+      const T& t) const {
+    return start <= t && t < end;
+  }
 };
 
 std::vector<std::string> Split(const android::StringPiece& str, char sep);
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index fb8cee8..fa1b0f0 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -80,5 +80,65 @@
   }
 }
 
+namespace {
+
+struct TagCompat {
+  ApiVersion api_version;
+
+  enum class XmlType {
+    kVector,
+    kTransition,
+    kAdaptiveIcon,
+  };
+
+  XmlType type;
+};
+
+std::unordered_map<StringPiece, TagCompat> sTagVersions = {
+    {"fade", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"changeBounds", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"slide", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"explode", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"changeImageTransform", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"changeTransform", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"changeClipBounds", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"autoTransition", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"recolor", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"changeScroll", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"transitionSet", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"transition", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+    {"transitionManager", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
+
+    {"vector", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}},
+    {"animated-vector", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}},
+    {"pathInterpolator", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}},
+    {"objectAnimator", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}},
+
+    {"adaptive-icon", {SDK_O, TagCompat::XmlType::kAdaptiveIcon}},
+};
+
+}  // namespace
+
+Maybe<TagApiVersionResult> GetXmlTagApiVersion(const StringPiece& tag_name, int options) {
+  auto iter = sTagVersions.find(tag_name);
+  if (iter == sTagVersions.end()) {
+    return {};
+  }
+
+  const TagCompat& tag_compat = iter->second;
+  if (options & kNoVersionVector) {
+    if (tag_compat.type == TagCompat::XmlType::kVector) {
+      return TagApiVersionResult{{}, true /*skip_version*/};
+    }
+  }
+
+  if (options & kNoVersionTransition) {
+    if (tag_compat.type == TagCompat::XmlType::kTransition) {
+      return TagApiVersionResult{{}, true /*skip_version*/};
+    }
+  }
+  return TagApiVersionResult{tag_compat.api_version, false /*skip_version*/};
+}
+
 }  // namespace xml
 }  // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index 1650ac2..552f42a 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -20,18 +20,16 @@
 #include <string>
 
 #include "ResourceValues.h"
+#include "SdkConstants.h"
 #include "util/Maybe.h"
 
 namespace aapt {
 namespace xml {
 
 constexpr const char* kSchemaAuto = "http://schemas.android.com/apk/res-auto";
-constexpr const char* kSchemaPublicPrefix =
-    "http://schemas.android.com/apk/res/";
-constexpr const char* kSchemaPrivatePrefix =
-    "http://schemas.android.com/apk/prv/res/";
-constexpr const char* kSchemaAndroid =
-    "http://schemas.android.com/apk/res/android";
+constexpr const char* kSchemaPublicPrefix = "http://schemas.android.com/apk/res/";
+constexpr const char* kSchemaPrivatePrefix = "http://schemas.android.com/apk/prv/res/";
+constexpr const char* kSchemaAndroid = "http://schemas.android.com/apk/res/android";
 constexpr const char* kSchemaTools = "http://schemas.android.com/tools";
 constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt";
 
@@ -102,6 +100,24 @@
 void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack,
                                      const android::StringPiece& local_package, Reference* in_ref);
 
+struct TagApiVersionResult {
+  // If set, the API version to apply.
+  Maybe<ApiVersion> api_version;
+
+  // Whether to skip any auto-versioning.
+  bool skip_version;
+};
+
+enum TagVersionOptions : int {
+  // Skip versioning XML resources that deal with vector drawables.
+  kNoVersionVector,
+
+  // Skip versioning XML resources that deal with transitions.
+  kNoVersionTransition,
+};
+
+Maybe<TagApiVersionResult> GetXmlTagApiVersion(const android::StringPiece& tag_name, int options);
+
 }  // namespace xml
 }  // namespace aapt