Merge "AAPT2: Implement attribute compat versioning" into oc-dev am: e229113d46
am: 138d2a65b3

Change-Id: Ie3502e99aefa46ee9fe62a0e821e55cb160b7142
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index b460258..bf18949 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -99,6 +99,7 @@
         "link/PrivateAttributeMover.cpp",
         "link/ReferenceLinker.cpp",
         "link/TableMerger.cpp",
+        "link/XmlCompatVersioner.cpp",
         "link/XmlNamespaceRemover.cpp",
         "link/XmlReferenceLinker.cpp",
         "optimize/ResourceDeduper.cpp",
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index 56e2e95..e2456c7 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -37,6 +37,7 @@
                               const ResourceName& target_style);
   static void DumpHex(const void* data, size_t len);
   static void DumpXml(xml::XmlResource* doc);
+  static std::string ToString(xml::XmlResource* doc);
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index e45d142..1d2e3a4 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -27,7 +27,7 @@
 static const char* sMajorVersion = "2";
 
 // Update minor version whenever a feature or flag is added.
-static const char* sMinorVersion = "15";
+static const char* sMinorVersion = "16";
 
 int PrintVersion() {
   std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 493f238..0a74c1a 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -388,13 +388,20 @@
 struct hash<aapt::ResourceName> {
   size_t operator()(const aapt::ResourceName& name) const {
     android::hash_t h = 0;
-    h = android::JenkinsHashMix(h, hash<string>()(name.package));
+    h = android::JenkinsHashMix(h, static_cast<uint32_t>(hash<string>()(name.package)));
     h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type));
-    h = android::JenkinsHashMix(h, hash<string>()(name.entry));
+    h = android::JenkinsHashMix(h, static_cast<uint32_t>(hash<string>()(name.entry)));
     return static_cast<size_t>(h);
   }
 };
 
+template <>
+struct hash<aapt::ResourceId> {
+  size_t operator()(const aapt::ResourceId& id) const {
+    return id.id;
+  }
+};
+
 }  // namespace std
 
 #endif  // AAPT_RESOURCE_H
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 0cb8c67..34bd2b4 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -333,6 +333,12 @@
   }
 }
 
+Attribute::Attribute()
+    : type_mask(0u),
+      min_int(std::numeric_limits<int32_t>::min()),
+      max_int(std::numeric_limits<int32_t>::max()) {
+}
+
 Attribute::Attribute(bool w, uint32_t t)
     : type_mask(t),
       min_int(std::numeric_limits<int32_t>::min()),
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index c71c738..06f949f 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -18,6 +18,7 @@
 #define AAPT_RESOURCE_VALUES_H
 
 #include <array>
+#include <limits>
 #include <ostream>
 #include <vector>
 
@@ -251,6 +252,7 @@
   int32_t max_int;
   std::vector<Symbol> symbols;
 
+  Attribute();
   explicit Attribute(bool w, uint32_t t = 0u);
 
   bool Equals(const Value* value) const override;
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index e806714..041cb4f 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -26,9 +26,9 @@
 namespace aapt {
 
 static const char* sDevelopmentSdkCodeName = "O";
-static int sDevelopmentSdkLevel = 26;
+static ApiVersion sDevelopmentSdkLevel = 26;
 
-static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = {
+static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
     {0x021c, 1},
     {0x021d, 2},
     {0x0269, SDK_CUPCAKE},
@@ -48,26 +48,29 @@
     {0x03f1, SDK_KITKAT},
     {0x03f6, SDK_KITKAT_WATCH},
     {0x04ce, SDK_LOLLIPOP},
+    {0x04d8, SDK_LOLLIPOP_MR1},
+    {0x04f1, SDK_MARSHMALLOW},
+    {0x0527, SDK_NOUGAT},
+    {0x0530, SDK_NOUGAT_MR1},
+    {0x0568, SDK_O},
 };
 
-static bool less_entry_id(const std::pair<uint16_t, size_t>& p,
-                        uint16_t entryId) {
+static bool less_entry_id(const std::pair<uint16_t, ApiVersion>& p, uint16_t entryId) {
   return p.first < entryId;
 }
 
-size_t FindAttributeSdkLevel(const ResourceId& id) {
-  if (id.package_id() != 0x01 && id.type_id() != 0x01) {
+ApiVersion FindAttributeSdkLevel(const ResourceId& id) {
+  if (id.package_id() != 0x01 || id.type_id() != 0x01) {
     return 0;
   }
-  auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(),
-                               id.entry_id(), less_entry_id);
+  auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entry_id(), less_entry_id);
   if (iter == sAttrIdMap.end()) {
     return SDK_LOLLIPOP_MR1;
   }
   return iter->second;
 }
 
-static const std::unordered_map<std::string, size_t> sAttrMap = {
+static const std::unordered_map<std::string, ApiVersion> sAttrMap = {
     {"marqueeRepeatLimit", 2},
     {"windowNoDisplay", 3},
     {"backgroundDimEnabled", 3},
@@ -729,7 +732,7 @@
     {"windowActivityTransitions", 21},
     {"colorEdgeEffect", 21}};
 
-size_t FindAttributeSdkLevel(const ResourceName& name) {
+ApiVersion FindAttributeSdkLevel(const ResourceName& name) {
   if (name.package != "android" && name.type != ResourceType::kAttr) {
     return 0;
   }
@@ -741,9 +744,8 @@
   return SDK_LOLLIPOP_MR1;
 }
 
-std::pair<StringPiece, int> GetDevelopmentSdkCodeNameAndVersion() {
-  return std::make_pair(StringPiece(sDevelopmentSdkCodeName),
-                        sDevelopmentSdkLevel);
+std::pair<StringPiece, ApiVersion> GetDevelopmentSdkCodeNameAndVersion() {
+  return std::make_pair(StringPiece(sDevelopmentSdkCodeName), sDevelopmentSdkLevel);
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index c2ee252..e3745e8 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -25,7 +25,9 @@
 
 namespace aapt {
 
-enum : int {
+using ApiVersion = int;
+
+enum : ApiVersion {
   SDK_CUPCAKE = 3,
   SDK_DONUT = 4,
   SDK_ECLAIR = 5,
@@ -49,12 +51,12 @@
   SDK_MARSHMALLOW = 23,
   SDK_NOUGAT = 24,
   SDK_NOUGAT_MR1 = 25,
-  SDK_O = 26,  // STOPSHIP Replace with real version
+  SDK_O = 26,
 };
 
-size_t FindAttributeSdkLevel(const ResourceId& id);
-size_t FindAttributeSdkLevel(const ResourceName& name);
-std::pair<android::StringPiece, int> GetDevelopmentSdkCodeNameAndVersion();
+ApiVersion FindAttributeSdkLevel(const ResourceId& id);
+ApiVersion FindAttributeSdkLevel(const ResourceName& name);
+std::pair<android::StringPiece, ApiVersion> GetDevelopmentSdkCodeNameAndVersion();
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp
index 716d922..61f4d71 100644
--- a/tools/aapt2/SdkConstants_test.cpp
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -21,18 +21,11 @@
 namespace aapt {
 
 TEST(SdkConstantsTest, FirstAttributeIsSdk1) {
-  EXPECT_EQ(1u, FindAttributeSdkLevel(ResourceId(0x01010000)));
+  EXPECT_EQ(1, FindAttributeSdkLevel(ResourceId(0x01010000)));
 }
 
-TEST(SdkConstantsTest, AllAttributesAfterLollipopAreLollipopMR1) {
-  EXPECT_EQ(SDK_LOLLIPOP, FindAttributeSdkLevel(ResourceId(0x010103f7)));
-  EXPECT_EQ(SDK_LOLLIPOP, FindAttributeSdkLevel(ResourceId(0x010104ce)));
-
-  EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x010104cf)));
-  EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x010104d8)));
-
-  EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x010104d9)));
-  EXPECT_EQ(SDK_LOLLIPOP_MR1, FindAttributeSdkLevel(ResourceId(0x0101ffff)));
+TEST(SdkConstantsTest, NonFrameworkAttributeIsSdk0) {
+  EXPECT_EQ(0, FindAttributeSdkLevel(ResourceId(0x7f010345)));
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 8accfa8..24a687c 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -50,6 +50,7 @@
 #include "link/ManifestFixer.h"
 #include "link/ReferenceLinker.h"
 #include "link/TableMerger.h"
+#include "link/XmlCompatVersioner.h"
 #include "optimize/ResourceDeduper.h"
 #include "optimize/VersionCollapser.h"
 #include "process/IResourceTableConsumer.h"
@@ -247,25 +248,20 @@
   IAaptContext* context_;
 };
 
-static bool FlattenXml(xml::XmlResource* xml_res, const StringPiece& path,
-                       Maybe<size_t> max_sdk_level, bool keep_raw_values, IArchiveWriter* writer,
-                       IAaptContext* context) {
+static bool FlattenXml(IAaptContext* context, xml::XmlResource* xml_res, const StringPiece& path,
+                       bool keep_raw_values, IArchiveWriter* writer) {
   BigBuffer buffer(1024);
   XmlFlattenerOptions options = {};
   options.keep_raw_values = keep_raw_values;
-  options.max_sdk_level = max_sdk_level;
   XmlFlattener flattener(&buffer, options);
   if (!flattener.Consume(context, xml_res)) {
     return false;
   }
 
   if (context->IsVerbose()) {
-    DiagMessage msg;
-    msg << "writing " << path << " to archive";
-    if (max_sdk_level) {
-      msg << " maxSdkLevel=" << max_sdk_level.value() << " keepRawValues=" << keep_raw_values;
-    }
-    context->GetDiagnostics()->Note(msg);
+    context->GetDiagnostics()->Note(DiagMessage(path) << "writing to archive (keep_raw_values="
+                                                      << (keep_raw_values ? "true" : "false")
+                                                      << ")");
   }
 
   io::BigBufferInputStream input_stream(&buffer);
@@ -311,12 +307,33 @@
   std::unordered_set<std::string> extensions_to_not_compress;
 };
 
+// A sampling of public framework resource IDs.
+struct R {
+  struct attr {
+    enum : uint32_t {
+      paddingLeft = 0x010100d6u,
+      paddingRight = 0x010100d8u,
+      paddingHorizontal = 0x0101053du,
+
+      paddingTop = 0x010100d7u,
+      paddingBottom = 0x010100d9u,
+      paddingVertical = 0x0101053eu,
+
+      layout_marginLeft = 0x010100f7u,
+      layout_marginRight = 0x010100f9u,
+      layout_marginHorizontal = 0x0101053bu,
+
+      layout_marginTop = 0x010100f8u,
+      layout_marginBottom = 0x010100fau,
+      layout_marginVertical = 0x0101053cu,
+    };
+  };
+};
+
 class ResourceFileFlattener {
  public:
   ResourceFileFlattener(const ResourceFileFlattenerOptions& options, IAaptContext* context,
-                        proguard::KeepSet* keep_set)
-      : options_(options), context_(context), keep_set_(keep_set) {
-  }
+                        proguard::KeepSet* keep_set);
 
   bool Flatten(ResourceTable* table, IArchiveWriter* archive_writer);
 
@@ -325,7 +342,7 @@
     ConfigDescription config;
 
     // The entry this file came from.
-    const ResourceEntry* entry;
+    ResourceEntry* entry;
 
     // The file to copy as-is.
     io::IFile* file_to_copy;
@@ -335,19 +352,72 @@
 
     // The destination to write this file to.
     std::string dst_path;
-    bool skip_version = false;
   };
 
   uint32_t GetCompressionFlags(const StringPiece& str);
 
-  bool LinkAndVersionXmlFile(ResourceTable* table, FileOperation* file_op,
-                             std::queue<FileOperation>* out_file_op_queue);
+  std::vector<std::unique_ptr<xml::XmlResource>> LinkAndVersionXmlFile(ResourceTable* table,
+                                                                       FileOperation* file_op);
 
   ResourceFileFlattenerOptions options_;
   IAaptContext* context_;
   proguard::KeepSet* keep_set_;
+  XmlCompatVersioner::Rules rules_;
 };
 
+ResourceFileFlattener::ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
+                                             IAaptContext* context, proguard::KeepSet* keep_set)
+    : options_(options), context_(context), keep_set_(keep_set) {
+  SymbolTable* symm = context_->GetExternalSymbols();
+
+  // Build up the rules for degrading newer attributes to older ones.
+  // NOTE(adamlesinski): These rules are hardcoded right now, but they should be
+  // generated from the attribute definitions themselves (b/62028956).
+  if (const SymbolTable::Symbol* s = symm->FindById(R::attr::paddingHorizontal)) {
+    std::vector<ReplacementAttr> replacements{
+        {"paddingLeft", R::attr::paddingLeft,
+         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+        {"paddingRight", R::attr::paddingRight,
+         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+    };
+    rules_[R::attr::paddingHorizontal] =
+        util::make_unique<DegradeToManyRule>(std::move(replacements));
+  }
+
+  if (const SymbolTable::Symbol* s = symm->FindById(R::attr::paddingVertical)) {
+    std::vector<ReplacementAttr> replacements{
+        {"paddingTop", R::attr::paddingTop,
+         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+        {"paddingBottom", R::attr::paddingBottom,
+         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+    };
+    rules_[R::attr::paddingVertical] =
+        util::make_unique<DegradeToManyRule>(std::move(replacements));
+  }
+
+  if (const SymbolTable::Symbol* s = symm->FindById(R::attr::layout_marginHorizontal)) {
+    std::vector<ReplacementAttr> replacements{
+        {"layout_marginLeft", R::attr::layout_marginLeft,
+         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+        {"layout_marginRight", R::attr::layout_marginRight,
+         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+    };
+    rules_[R::attr::layout_marginHorizontal] =
+        util::make_unique<DegradeToManyRule>(std::move(replacements));
+  }
+
+  if (const SymbolTable::Symbol* s = symm->FindById(R::attr::layout_marginVertical)) {
+    std::vector<ReplacementAttr> replacements{
+        {"layout_marginTop", R::attr::layout_marginTop,
+         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+        {"layout_marginBottom", R::attr::layout_marginBottom,
+         Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+    };
+    rules_[R::attr::layout_marginVertical] =
+        util::make_unique<DegradeToManyRule>(std::move(replacements));
+  }
+}
+
 uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) {
   if (options_.do_not_compress_anything) {
     return 0;
@@ -369,8 +439,19 @@
          name == "transitionManager";
 }
 
-bool ResourceFileFlattener::LinkAndVersionXmlFile(ResourceTable* table, FileOperation* file_op,
-                                                  std::queue<FileOperation>* out_file_op_queue) {
+static bool IsVectorElement(const std::string& name) {
+  return name == "vector" || name == "animated-vector";
+}
+
+template <typename T>
+std::vector<T> make_singleton_vec(T&& val) {
+  std::vector<T> vec;
+  vec.emplace_back(std::forward<T>(val));
+  return vec;
+}
+
+std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVersionXmlFile(
+    ResourceTable* table, FileOperation* file_op) {
   xml::XmlResource* doc = file_op->xml_to_flatten.get();
   const Source& src = doc->file.source;
 
@@ -380,107 +461,60 @@
 
   XmlReferenceLinker xml_linker;
   if (!xml_linker.Consume(context_, doc)) {
-    return false;
+    return {};
   }
 
   if (options_.update_proguard_spec && !proguard::CollectProguardRules(src, doc, keep_set_)) {
-    return false;
+    return {};
   }
 
   if (options_.no_xml_namespaces) {
     XmlNamespaceRemover namespace_remover;
     if (!namespace_remover.Consume(context_, doc)) {
-      return false;
+      return {};
     }
   }
 
-  if (!options_.no_auto_version) {
-    if (options_.no_version_vectors) {
-      // Skip this if it is a vector or animated-vector.
-      xml::Element* el = xml::FindRootElement(doc);
-      if (el && el->namespace_uri.empty()) {
-        if (el->name == "vector" || el->name == "animated-vector") {
-          // We are NOT going to version this file.
-          file_op->skip_version = true;
-          return true;
-        }
-      }
-    }
-    if (options_.no_version_transitions) {
-      // Skip this if it is a transition resource.
-      xml::Element* el = xml::FindRootElement(doc);
-      if (el && el->namespace_uri.empty()) {
-        if (IsTransitionElement(el->name)) {
-          // We are NOT going to version this file.
-          file_op->skip_version = true;
-          return true;
-        }
-      }
-    }
+  if (options_.no_auto_version) {
+    return make_singleton_vec(std::move(file_op->xml_to_flatten));
+  }
 
-    const ConfigDescription& config = file_op->config;
-
-    // Find the first SDK level used that is higher than this defined config and
-    // not superseded by a lower or equal SDK level resource.
-    const int min_sdk_version = context_->GetMinSdkVersion();
-    for (int sdk_level : xml_linker.sdk_levels()) {
-      if (sdk_level > min_sdk_version && sdk_level > config.sdkVersion) {
-        if (!ShouldGenerateVersionedResource(file_op->entry, config, sdk_level)) {
-          // If we shouldn't generate a versioned resource, stop checking.
-          break;
-        }
-
-        ResourceFile versioned_file_desc = doc->file;
-        versioned_file_desc.config.sdkVersion = (uint16_t)sdk_level;
-
-        FileOperation new_file_op;
-        new_file_op.xml_to_flatten = util::make_unique<xml::XmlResource>(
-            versioned_file_desc, StringPool{}, doc->root->Clone());
-        new_file_op.config = versioned_file_desc.config;
-        new_file_op.entry = file_op->entry;
-        new_file_op.dst_path =
-            ResourceUtils::BuildResourceFileName(versioned_file_desc, context_->GetNameMangler());
-
-        if (context_->IsVerbose()) {
-          context_->GetDiagnostics()->Note(DiagMessage(versioned_file_desc.source)
-                                           << "auto-versioning resource from config '" << config
-                                           << "' -> '" << versioned_file_desc.config << "'");
-        }
-
-        bool added = table->AddFileReferenceAllowMangled(
-            versioned_file_desc.name, versioned_file_desc.config, versioned_file_desc.source,
-            new_file_op.dst_path, nullptr, context_->GetDiagnostics());
-        if (!added) {
-          return false;
-        }
-
-        out_file_op_queue->push(std::move(new_file_op));
-        break;
+  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));
       }
     }
   }
-  return true;
+
+  const ConfigDescription& config = file_op->config;
+  ResourceEntry* entry = file_op->entry;
+
+  XmlCompatVersioner xml_compat_versioner(&rules_);
+  const util::Range<ApiVersion> api_range{config.sdkVersion,
+                                          FindNextApiVersionForConfig(entry, config)};
+  return xml_compat_versioner.Process(context_, doc, api_range);
 }
 
-/**
- * Do not insert or remove any resources while executing in this function. It
- * will
- * corrupt the iteration order.
- */
 bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archive_writer) {
   bool error = false;
   std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> config_sorted_files;
 
   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.
+      // 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) {
+          // WARNING! Do not insert or remove any resources while executing in this scope. It will
+          // corrupt the iteration order.
+
           FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
           if (!file_ref) {
             continue;
@@ -497,6 +531,7 @@
           file_op.entry = entry.get();
           file_op.dst_path = *file_ref->path;
           file_op.config = config_value->config;
+          file_op.file_to_copy = file;
 
           const StringPiece src_path = file->GetSource().path;
           if (type->type != ResourceType::kRaw &&
@@ -518,68 +553,51 @@
             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);
-
-            // Enqueue the XML files to be processed.
-            file_operations.push(std::move(file_op));
-          } else {
-            file_op.file_to_copy = file;
-
-            // NOTE(adamlesinski): Explicitly construct a StringPiece here, or
-            // else we end up copying the string in the std::make_pair() method,
-            // then creating a StringPiece from the copy, which would cause us
-            // to end up referencing garbage in the map.
-            const StringPiece entry_name(entry->name);
-            config_sorted_files[std::make_pair(config_value->config, entry_name)] =
-                std::move(file_op);
           }
+
+          // NOTE(adamlesinski): Explicitly construct a StringPiece here, or
+          // else we end up copying the string in the std::make_pair() method,
+          // then creating a StringPiece from the copy, which would cause us
+          // to end up referencing garbage in the map.
+          const StringPiece entry_name(entry->name);
+          config_sorted_files[std::make_pair(config_value->config, entry_name)] =
+              std::move(file_op);
         }
       }
 
-      // Now process the XML queue
-      for (; !file_operations.empty(); file_operations.pop()) {
-        FileOperation& file_op = file_operations.front();
-
-        if (!LinkAndVersionXmlFile(table, &file_op, &file_operations)) {
-          error = true;
-          continue;
-        }
-
-        // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
-        // we end up copying the string in the std::make_pair() method, then
-        // creating a StringPiece from the copy, which would cause us to end up
-        // referencing garbage in the map.
-        const StringPiece entry_name(file_op.entry->name);
-        config_sorted_files[std::make_pair(file_op.config, entry_name)] = std::move(file_op);
-      }
-
-      if (error) {
-        return false;
-      }
-
       // Now flatten the sorted values.
       for (auto& map_entry : config_sorted_files) {
         const ConfigDescription& config = map_entry.first.first;
-        const FileOperation& file_op = map_entry.second;
+        FileOperation& file_op = map_entry.second;
 
         if (file_op.xml_to_flatten) {
-          Maybe<size_t> max_sdk_level;
-          if (!options_.no_auto_version && !file_op.skip_version) {
-            max_sdk_level = std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u),
-                                             context_->GetMinSdkVersion());
-          }
+          std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
+              LinkAndVersionXmlFile(table, &file_op);
+          for (std::unique_ptr<xml::XmlResource>& doc : versioned_docs) {
+            std::string dst_path = file_op.dst_path;
+            if (doc->file.config != file_op.config) {
+              // Only add the new versioned configurations.
+              if (context_->IsVerbose()) {
+                context_->GetDiagnostics()->Note(DiagMessage(doc->file.source)
+                                                 << "auto-versioning resource from config '"
+                                                 << config << "' -> '" << doc->file.config << "'");
+              }
 
-          bool result = FlattenXml(file_op.xml_to_flatten.get(), file_op.dst_path, max_sdk_level,
-                                   options_.keep_raw_values, archive_writer, context_);
-          if (!result) {
-            error = true;
+              dst_path =
+                  ResourceUtils::BuildResourceFileName(doc->file, context_->GetNameMangler());
+              bool result = table->AddFileReferenceAllowMangled(doc->file.name, doc->file.config,
+                                                                doc->file.source, dst_path, nullptr,
+                                                                context_->GetDiagnostics());
+              if (!result) {
+                return false;
+              }
+            }
+            error |= !FlattenXml(context_, doc.get(), dst_path, options_.keep_raw_values,
+                                 archive_writer);
           }
         } else {
-          bool result =
-              io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path,
-                                    GetCompressionFlags(file_op.dst_path), archive_writer);
-          if (!result) {
-            error = true;
-          }
+          error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path,
+                                          GetCompressionFlags(file_op.dst_path), archive_writer);
         }
       }
     }
@@ -1358,8 +1376,7 @@
   bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
                 ResourceTable* table) {
     const bool keep_raw_values = context_->GetPackageType() == PackageType::kStaticLib;
-    bool result =
-        FlattenXml(manifest, "AndroidManifest.xml", {}, keep_raw_values, writer, context_);
+    bool result = FlattenXml(context_, manifest, "AndroidManifest.xml", keep_raw_values, writer);
     if (!result) {
       return false;
     }
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 14d4260..8741b7b 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -131,7 +131,7 @@
 }
 
 static xml::AaptAttribute CreateAttributeWithId(const ResourceId& id) {
-  return xml::AaptAttribute{id, Attribute(true)};
+  return xml::AaptAttribute(Attribute(), id);
 }
 
 std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
index e98d2d7..0711749 100644
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -194,16 +194,9 @@
 
     // Filter the attributes.
     for (xml::Attribute& attr : node->attributes) {
-      if (options_.max_sdk_level && attr.compiled_attribute && attr.compiled_attribute.value().id) {
-        size_t sdk_level = FindAttributeSdkLevel(attr.compiled_attribute.value().id.value());
-        if (sdk_level > options_.max_sdk_level.value()) {
-          continue;
-        }
+      if (attr.namespace_uri != xml::kSchemaTools) {
+        filtered_attrs_.push_back(&attr);
       }
-      if (attr.namespace_uri == xml::kSchemaTools) {
-        continue;
-      }
-      filtered_attrs_.push_back(&attr);
     }
 
     if (filtered_attrs_.empty()) {
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
index f5129fd..87557f2 100644
--- a/tools/aapt2/flatten/XmlFlattener.h
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -30,11 +30,6 @@
    * Keep attribute raw string values along with typed values.
    */
   bool keep_raw_values = false;
-
-  /**
-   * If set, the max SDK level of attribute to flatten. All others are ignored.
-   */
-  Maybe<size_t> max_sdk_level;
 };
 
 class XmlFlattener : public IXmlResourceConsumer {
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index cfa89bb..f1e903f 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -149,31 +149,6 @@
   ASSERT_EQ(android::ResXMLTree::END_DOCUMENT, tree.next());
 }
 
-TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
-  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
-            <View xmlns:android="http://schemas.android.com/apk/res/android"
-                android:paddingStart="1dp"
-                android:colorAccent="#ffffff"/>)EOF");
-
-  XmlReferenceLinker linker;
-  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
-  ASSERT_TRUE(linker.sdk_levels().count(17) == 1);
-  ASSERT_TRUE(linker.sdk_levels().count(21) == 1);
-
-  android::ResXMLTree tree;
-  XmlFlattenerOptions options;
-  options.max_sdk_level = 17;
-  ASSERT_TRUE(Flatten(doc.get(), &tree, options));
-
-  while (tree.next() != android::ResXMLTree::START_TAG) {
-    ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
-    ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
-  }
-
-  ASSERT_EQ(1u, tree.getAttributeCount());
-  EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0));
-}
-
 TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) {
   std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
             <View xmlns:tools="http://schemas.android.com/tools"
@@ -234,13 +209,11 @@
   }
 
   const StringPiece16 kPackage = u"package";
-  EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()),
-            0);
+  EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
 }
 
 TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) {
-  std::unique_ptr<xml::XmlResource> doc =
-      test::BuildXmlDom("<View package=\"\"/>");
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"\"/>");
 
   android::ResXMLTree tree;
   ASSERT_TRUE(Flatten(doc.get(), &tree));
@@ -251,8 +224,7 @@
   }
 
   const StringPiece16 kPackage = u"package";
-  ssize_t idx =
-      tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
+  ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
   ASSERT_GE(idx, 0);
 
   size_t len;
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/Android.mk b/tools/aapt2/integration-tests/AutoVersionTest/Android.mk
new file mode 100644
index 0000000..012728f
--- /dev/null
+++ b/tools/aapt2/integration-tests/AutoVersionTest/Android.mk
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_PACKAGE_NAME := AaptAutoVersionTest
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml b/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml
new file mode 100644
index 0000000..e66d709
--- /dev/null
+++ b/tools/aapt2/integration-tests/AutoVersionTest/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.aapt.autoversiontest">
+
+    <uses-sdk android:minSdkVersion="7" />
+</manifest>
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/layout-v21/layout3.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/layout-v21/layout3.xml
new file mode 100644
index 0000000..bfc5445
--- /dev/null
+++ b/tools/aapt2/integration-tests/AutoVersionTest/res/layout-v21/layout3.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:paddingVertical="24dp" />
+ 
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout.xml
new file mode 100644
index 0000000..091a8ce
--- /dev/null
+++ b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:autoStart="true"
+    android:expandableListViewWhiteStyle="@empty"
+    android:screenSize="large"
+    android:subtitle="Hello"
+    android:resizeMode="none"
+    android:largestWidthLimitDp="999"
+    android:uiOptions="none"
+    android:parentActivityName="Hello"
+    android:paddingStart="999dp"
+    android:requiredForAllUsers="true"
+    android:category="Hello"
+    android:isGame="true"
+    android:colorPrimary="#ffffff"
+    android:revisionCode="999"
+    android:autoVerify="true"
+    android:use32bitAbi="true"
+    android:shortcutId="@+id/id" />
+
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout2.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout2.xml
new file mode 100644
index 0000000..339337a
--- /dev/null
+++ b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout2.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    class="foo">
+    <View
+        android:paddingHorizontal="24dp" />
+    <View
+        android:paddingHorizontal="24dp"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp" />
+    <View
+        android:paddingHorizontal="24dp"
+        android:paddingLeft="16dp"
+        android:paddingRight="16dp" />
+    <View
+        android:paddingVertical="24dp" />
+    <View
+        android:paddingVertical="24dp"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp" />
+    <View
+        android:layout_marginHorizontal="24dp" />
+    <View
+        android:layout_marginHorizontal="24dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp" />
+    <View
+        android:layout_marginVertical="24dp" />
+    <View
+        android:layout_marginVertical="24dp"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="16dp" />
+</View>
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout3.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout3.xml
new file mode 100644
index 0000000..8025ce1
--- /dev/null
+++ b/tools/aapt2/integration-tests/AutoVersionTest/res/layout/layout3.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:paddingHorizontal="24dp" />
+ 
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/values-v21/styles.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/values-v21/styles.xml
new file mode 100644
index 0000000..ff13faa
--- /dev/null
+++ b/tools/aapt2/integration-tests/AutoVersionTest/res/values-v21/styles.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<resources>
+    <style name="Style2">
+        <!-- API 7 -->
+        <item name="android:autoStart">false</item>
+
+        <!-- API 17 -->
+        <item name="android:paddingStart">0dp</item>
+
+        <!-- API 21 -->
+        <item name="android:colorPrimary">#00ff00</item>
+    </style>
+</resources>
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/res/values/styles.xml b/tools/aapt2/integration-tests/AutoVersionTest/res/values/styles.xml
new file mode 100644
index 0000000..c4f09846
--- /dev/null
+++ b/tools/aapt2/integration-tests/AutoVersionTest/res/values/styles.xml
@@ -0,0 +1,84 @@
+<?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.
+-->
+
+<resources>
+    <style name="Style1">
+        <!-- API 7 -->
+        <item name="android:autoStart">true</item>
+
+        <!-- API 8 -->
+        <item name="android:expandableListViewWhiteStyle">@null</item>
+
+        <!-- API 9 -->
+        <item name="android:screenSize">large</item>
+
+        <!-- API 11 -->
+        <item name="android:subtitle">Hello</item>
+
+        <!-- API 12 -->
+        <item name="android:resizeMode">none</item>
+
+        <!-- API 13 -->
+        <item name="android:largestWidthLimitDp">999</item>
+
+        <!-- API 14 -->
+        <item name="android:uiOptions">none</item>
+
+        <!-- API 16 -->
+        <item name="android:parentActivityName">Hello</item>
+
+        <!-- API 17 -->
+        <item name="android:paddingStart">999dp</item>
+
+        <!-- API 18 -->
+        <item name="android:requiredForAllUsers">true</item>
+
+        <!-- API 19 -->
+        <item name="android:category">Hello</item>
+
+        <!-- API 20 -->
+        <item name="android:isGame">true</item>
+
+        <!-- API 21 -->
+        <item name="android:colorPrimary">#ffffff</item>
+
+        <!-- API 22 -->
+        <item name="android:revisionCode">999</item>
+
+        <!-- API 23 -->
+        <item name="android:autoVerify">true</item>
+
+        <!-- API 24 -->
+        <item name="android:use32bitAbi">true</item>
+
+        <!-- API 25 -->
+        <item name="android:shortcutId">@+id/id</item>
+
+        <!-- API 26 -->
+        <item name="android:paddingHorizontal">999dp</item>
+    </style>
+
+    <style name="Style2">
+        <!-- API 7 -->
+        <item name="android:autoStart">true</item>
+
+        <!-- API 17 -->
+        <item name="android:paddingStart">999dp</item>
+
+        <!-- API 21 -->
+        <item name="android:colorPrimary">#ffffff</item>
+    </style>
+</resources>
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
index 77471ea..f80c6e9 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -27,13 +27,15 @@
 
 namespace aapt {
 
-bool ShouldGenerateVersionedResource(const ResourceEntry* entry,
-                                     const ConfigDescription& config,
-                                     const int sdk_version_to_generate) {
-  // We assume the caller is trying to generate a version greater than the
-  // current configuration.
+bool ShouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
+                                     const ApiVersion sdk_version_to_generate) {
+  // We assume the caller is trying to generate a version greater than the current configuration.
   CHECK(sdk_version_to_generate > config.sdkVersion);
+  return sdk_version_to_generate < FindNextApiVersionForConfig(entry, config);
+}
 
+ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry,
+                                       const ConfigDescription& config) {
   const auto end_iter = entry->values.end();
   auto iter = entry->values.begin();
   for (; iter != end_iter; ++iter) {
@@ -46,26 +48,23 @@
   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.
+  // 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
+  // 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, check the sdk version.
-      return sdk_version_to_generate < (*iter)->config.sdkVersion;
+      // The two configs are the same, return the sdkVersion.
+      return (*iter)->config.sdkVersion;
     }
   }
 
-  // No match was found, so we should generate the versioned resource.
-  return true;
+  // Didn't find another config with a different sdk version, so return the highest possible value.
+  return std::numeric_limits<ApiVersion>::max();
 }
 
 bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) {
@@ -86,7 +85,7 @@
           }
 
           if (Style* style = ValueCast<Style>(config_value->value.get())) {
-            Maybe<size_t> min_sdk_stripped;
+            Maybe<ApiVersion> min_sdk_stripped;
             std::vector<Style::Entry> stripped;
 
             auto iter = style->entries.begin();
@@ -95,17 +94,14 @@
 
               // Find the SDK level that is higher than the configuration
               // allows.
-              const size_t sdk_level =
-                  FindAttributeSdkLevel(iter->key.id.value());
-              if (sdk_level >
-                  std::max<size_t>(config_value->config.sdkVersion, 1)) {
+              const ApiVersion sdk_level = FindAttributeSdkLevel(iter->key.id.value());
+              if (sdk_level > std::max<ApiVersion>(config_value->config.sdkVersion, 1)) {
                 // Record that we are about to strip this.
                 stripped.emplace_back(std::move(*iter));
 
                 // We use the smallest SDK level to generate the new style.
                 if (min_sdk_stripped) {
-                  min_sdk_stripped =
-                      std::min(min_sdk_stripped.value(), sdk_level);
+                  min_sdk_stripped = std::min(min_sdk_stripped.value(), sdk_level);
                 } else {
                   min_sdk_stripped = sdk_level;
                 }
@@ -126,10 +122,9 @@
                                                   min_sdk_stripped.value())) {
                 // Let's create a new Style for this versioned resource.
                 ConfigDescription new_config(config_value->config);
-                new_config.sdkVersion = min_sdk_stripped.value();
+                new_config.sdkVersion = static_cast<uint16_t>(min_sdk_stripped.value());
 
-                std::unique_ptr<Style> new_style(
-                    style->Clone(&table->string_pool));
+                std::unique_ptr<Style> new_style(style->Clone(&table->string_pool));
                 new_style->SetComment(style->GetComment());
                 new_style->SetSource(style->GetSource());
 
@@ -140,8 +135,7 @@
                     std::make_move_iterator(stripped.end()));
 
                 // Insert the new Resource into the correct place.
-                entry->FindOrCreateValue(new_config, {})->value =
-                    std::move(new_style);
+                entry->FindOrCreateValue(new_config, {})->value = std::move(new_style);
               }
             }
           }
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index d00fa73..5527f90 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 "SdkConstants.h"
 #include "process/IResourceTableConsumer.h"
 #include "xml/XmlDom.h"
 
@@ -44,9 +45,12 @@
  * 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 int sdk_version_to_generate);
+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.
+ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry, const ConfigDescription& config);
 
 class AutoVersioner : public IResourceTableConsumer {
  public:
@@ -105,11 +109,10 @@
 
 class ProductFilter : public IResourceTableConsumer {
  public:
-  using ResourceConfigValueIter =
-      std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
+  using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
 
-  explicit ProductFilter(std::unordered_set<std::string> products)
-      : products_(products) {}
+  explicit ProductFilter(std::unordered_set<std::string> products) : products_(products) {
+  }
 
   ResourceConfigValueIter SelectProductToKeep(
       const ResourceNameRef& name, const ResourceConfigValueIter begin,
@@ -118,19 +121,9 @@
   bool Consume(IAaptContext* context, ResourceTable* table) override;
 
  private:
-  std::unordered_set<std::string> products_;
-
   DISALLOW_COPY_AND_ASSIGN(ProductFilter);
-};
 
-class XmlAutoVersioner : public IXmlResourceConsumer {
- public:
-  XmlAutoVersioner() = default;
-
-  bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(XmlAutoVersioner);
+  std::unordered_set<std::string> products_;
 };
 
 /**
@@ -143,8 +136,7 @@
  */
 class XmlNamespaceRemover : public IXmlResourceConsumer {
  public:
-  explicit XmlNamespaceRemover(bool keep_uris = false)
-      : keep_uris_(keep_uris){};
+  explicit XmlNamespaceRemover(bool keep_uris = false) : keep_uris_(keep_uris){};
 
   bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
 
@@ -165,17 +157,8 @@
 
   bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
 
-  /**
-   * Once the XmlResource has been consumed, this returns the various SDK levels
-   * in which
-   * framework attributes used within the XML document were defined.
-   */
-  inline const std::set<int>& sdk_levels() const { return sdk_levels_found_; }
-
  private:
   DISALLOW_COPY_AND_ASSIGN(XmlReferenceLinker);
-
-  std::set<int> sdk_levels_found_;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 833ae69..18498e3 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -274,7 +274,7 @@
     if (out_error) *out_error = "is not an attribute";
     return {};
   }
-  return xml::AaptAttribute{symbol->id, *symbol->attribute};
+  return xml::AaptAttribute(*symbol->attribute, symbol->id);
 }
 
 void ReferenceLinker::WriteResourceName(DiagMessage* out_msg,
diff --git a/tools/aapt2/link/XmlCompatVersioner.cpp b/tools/aapt2/link/XmlCompatVersioner.cpp
new file mode 100644
index 0000000..f1f4e3b
--- /dev/null
+++ b/tools/aapt2/link/XmlCompatVersioner.cpp
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+#include "link/XmlCompatVersioner.h"
+
+#include <algorithm>
+
+#include "util/Util.h"
+
+namespace aapt {
+
+static xml::Attribute CopyAttr(const xml::Attribute& src, StringPool* out_string_pool) {
+  xml::Attribute dst{src.namespace_uri, src.name, src.value, src.compiled_attribute};
+  if (src.compiled_value != nullptr) {
+    dst.compiled_value.reset(src.compiled_value->Clone(out_string_pool));
+  }
+  return dst;
+}
+
+// Returns false if the attribute is not copied because an existing attribute takes precedence
+// (came from a rule).
+static bool CopyAttribute(const xml::Attribute& src_attr, bool generated, xml::Element* dst_el,
+                          StringPool* out_string_pool) {
+  xml::Attribute* dst_attr = dst_el->FindAttribute(src_attr.namespace_uri, src_attr.name);
+  if (dst_attr != nullptr) {
+    if (generated) {
+      // Generated attributes always take precedence.
+      dst_attr->value = src_attr.value;
+      dst_attr->compiled_attribute = src_attr.compiled_attribute;
+      if (src_attr.compiled_value != nullptr) {
+        dst_attr->compiled_value.reset(src_attr.compiled_value->Clone(out_string_pool));
+      }
+      return true;
+    }
+    return false;
+  }
+  dst_el->attributes.push_back(CopyAttr(src_attr, out_string_pool));
+  return true;
+}
+
+void XmlCompatVersioner::ProcessRule(const xml::Element& src_el, const xml::Attribute& src_attr,
+                                     const ApiVersion& src_attr_version, const IDegradeRule* rule,
+                                     const util::Range<ApiVersion>& api_range, bool generated,
+                                     xml::Element* dst_el,
+                                     std::set<ApiVersion>* out_apis_referenced,
+                                     StringPool* out_string_pool) {
+  if (src_attr_version <= api_range.start) {
+    // The API is compatible, so don't check the rule and just copy.
+    if (!CopyAttribute(src_attr, generated, dst_el, out_string_pool)) {
+      // TODO(adamlesinski): Log a warning that an attribute was overridden?
+    }
+    return;
+  }
+
+  if (api_range.start >= SDK_LOLLIPOP_MR1) {
+    // Since LOLLIPOP MR1, the framework can handle silently ignoring unknown public attributes,
+    // so we don't need to erase/version them.
+    // Copy.
+    if (!CopyAttribute(src_attr, generated, dst_el, out_string_pool)) {
+      // TODO(adamlesinski): Log a warning that an attribute was overridden?
+    }
+  } else {
+    // We are going to erase this attribute from this XML resource version, but check if
+    // we even need to move it anywhere. A developer may have effectively overwritten it with
+    // a similarly versioned XML resource.
+    if (src_attr_version < api_range.end) {
+      // There is room for another versioned XML resource between this XML resource and the next
+      // versioned XML resource defined by the developer.
+      out_apis_referenced->insert(std::min<ApiVersion>(src_attr_version, SDK_LOLLIPOP_MR1));
+    }
+  }
+
+  if (rule != nullptr) {
+    for (const DegradeResult& result : rule->Degrade(src_el, src_attr, out_string_pool)) {
+      const ResourceId attr_resid = result.attr.compiled_attribute.value().id.value();
+      const ApiVersion attr_version = FindAttributeSdkLevel(attr_resid);
+
+      auto iter = rules_->find(attr_resid);
+      ProcessRule(src_el, result.attr, attr_version,
+                  iter != rules_->end() ? iter->second.get() : nullptr, api_range,
+                  true /*generated*/, dst_el, out_apis_referenced, out_string_pool);
+    }
+  }
+}
+
+XmlCompatVersioner::XmlCompatVersioner(const Rules* rules) : rules_(rules) {
+}
+
+std::unique_ptr<xml::XmlResource> XmlCompatVersioner::ProcessDoc(
+    ApiVersion target_api, ApiVersion max_api, xml::XmlResource* doc,
+    std::set<ApiVersion>* out_apis_referenced) {
+  const util::Range<ApiVersion> api_range{target_api, max_api};
+
+  std::unique_ptr<xml::XmlResource> cloned_doc = util::make_unique<xml::XmlResource>(doc->file);
+  cloned_doc->file.config.sdkVersion = static_cast<uint16_t>(target_api);
+
+  cloned_doc->root = doc->root->Clone([&](const xml::Element& el, xml::Element* out_el) {
+    for (const auto& attr : el.attributes) {
+      if (!attr.compiled_attribute) {
+        // Just copy if this isn't a compiled attribute.
+        out_el->attributes.push_back(CopyAttr(attr, &cloned_doc->string_pool));
+        continue;
+      }
+
+      const ResourceId attr_resid = attr.compiled_attribute.value().id.value();
+      const ApiVersion attr_version = FindAttributeSdkLevel(attr_resid);
+
+      auto rule = rules_->find(attr_resid);
+      ProcessRule(el, attr, attr_version, rule != rules_->end() ? rule->second.get() : nullptr,
+                  api_range, false /*generated*/, out_el, out_apis_referenced,
+                  &cloned_doc->string_pool);
+    }
+  });
+  return cloned_doc;
+}
+
+std::vector<std::unique_ptr<xml::XmlResource>> XmlCompatVersioner::Process(
+    IAaptContext* context, xml::XmlResource* doc, util::Range<ApiVersion> api_range) {
+  // Adjust the API range so that it falls after this document and after minSdkVersion.
+  api_range.start = std::max(api_range.start, context->GetMinSdkVersion());
+  api_range.start = std::max(api_range.start, static_cast<ApiVersion>(doc->file.config.sdkVersion));
+
+  std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs;
+  std::set<ApiVersion> apis_referenced;
+  versioned_docs.push_back(ProcessDoc(api_range.start, api_range.end, doc, &apis_referenced));
+
+  // Adjust the sdkVersion of the first XML document back to its original (this only really
+  // makes a difference if the sdk version was below the minSdk to start).
+  versioned_docs.back()->file.config.sdkVersion = doc->file.config.sdkVersion;
+
+  // Iterate from smallest to largest API version.
+  for (ApiVersion api : apis_referenced) {
+    std::set<ApiVersion> dummy;
+    versioned_docs.push_back(ProcessDoc(api, api_range.end, doc, &dummy));
+  }
+  return versioned_docs;
+}
+
+DegradeToManyRule::DegradeToManyRule(std::vector<ReplacementAttr> attrs)
+    : attrs_(std::move(attrs)) {
+}
+
+static inline std::unique_ptr<Item> CloneIfNotNull(const std::unique_ptr<Item>& src,
+                                                   StringPool* out_string_pool) {
+  if (src == nullptr) {
+    return {};
+  }
+  return std::unique_ptr<Item>(src->Clone(out_string_pool));
+}
+
+std::vector<DegradeResult> DegradeToManyRule::Degrade(const xml::Element& src_el,
+                                                      const xml::Attribute& src_attr,
+                                                      StringPool* out_string_pool) const {
+  std::vector<DegradeResult> result;
+  result.reserve(attrs_.size());
+  for (const ReplacementAttr& attr : attrs_) {
+    result.push_back(
+        DegradeResult{xml::Attribute{xml::kSchemaAndroid, attr.name, src_attr.value,
+                                     xml::AaptAttribute{attr.attr, attr.id},
+                                     CloneIfNotNull(src_attr.compiled_value, out_string_pool)},
+                      FindAttributeSdkLevel(attr.id)});
+  }
+  return result;
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/link/XmlCompatVersioner.h b/tools/aapt2/link/XmlCompatVersioner.h
new file mode 100644
index 0000000..099e23c
--- /dev/null
+++ b/tools/aapt2/link/XmlCompatVersioner.h
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+#ifndef AAPT_LINKER_XMLCOMPATVERSIONER_H
+#define AAPT_LINKER_XMLCOMPATVERSIONER_H
+
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+#include "android-base/macros.h"
+
+#include "Resource.h"
+#include "SdkConstants.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
+
+namespace aapt {
+
+class IDegradeRule;
+
+struct DegradeResult {
+  xml::Attribute attr;
+  ApiVersion attr_api_version;
+};
+
+class IDegradeRule {
+ public:
+  IDegradeRule() = default;
+  virtual ~IDegradeRule() = default;
+
+  virtual std::vector<DegradeResult> Degrade(const xml::Element& src_el,
+                                             const xml::Attribute& src_attr,
+                                             StringPool* out_string_pool) const = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(IDegradeRule);
+};
+
+class XmlCompatVersioner {
+ public:
+  using Rules = std::unordered_map<ResourceId, std::unique_ptr<IDegradeRule>>;
+
+  XmlCompatVersioner(const Rules* rules);
+
+  std::vector<std::unique_ptr<xml::XmlResource>> Process(IAaptContext* context,
+                                                         xml::XmlResource* doc,
+                                                         util::Range<ApiVersion> api_range);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(XmlCompatVersioner);
+
+  std::unique_ptr<xml::XmlResource> ProcessDoc(ApiVersion target_api, ApiVersion max_api,
+                                               xml::XmlResource* doc,
+                                               std::set<ApiVersion>* out_apis_referenced);
+  void ProcessRule(const xml::Element& src_el, const xml::Attribute& src_attr,
+                   const ApiVersion& src_attr_version, const IDegradeRule* rule,
+                   const util::Range<ApiVersion>& api_range, bool generated, xml::Element* dst_el,
+                   std::set<ApiVersion>* out_apis_referenced, StringPool* out_string_pool);
+
+  const Rules* rules_;
+};
+
+struct ReplacementAttr {
+  std::string name;
+  ResourceId id;
+  Attribute attr;
+};
+
+class DegradeToManyRule : public IDegradeRule {
+ public:
+  DegradeToManyRule(std::vector<ReplacementAttr> attrs);
+  virtual ~DegradeToManyRule() = default;
+
+  std::vector<DegradeResult> Degrade(const xml::Element& src_el, const xml::Attribute& src_attr,
+                                     StringPool* out_string_pool) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DegradeToManyRule);
+
+  std::vector<ReplacementAttr> attrs_;
+};
+
+}  // namespace aapt
+
+#endif  // AAPT_LINKER_XMLCOMPATVERSIONER_H
diff --git a/tools/aapt2/link/XmlCompatVersioner_test.cpp b/tools/aapt2/link/XmlCompatVersioner_test.cpp
new file mode 100644
index 0000000..ce6605c
--- /dev/null
+++ b/tools/aapt2/link/XmlCompatVersioner_test.cpp
@@ -0,0 +1,320 @@
+/*
+ * 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.
+ */
+
+#include "link/XmlCompatVersioner.h"
+
+#include "Linkers.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+constexpr auto TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION;
+constexpr auto TYPE_STRING = android::ResTable_map::TYPE_STRING;
+
+struct R {
+  struct attr {
+    enum : uint32_t {
+      paddingLeft = 0x010100d6u,         // (API 1)
+      paddingRight = 0x010100d8u,        // (API 1)
+      progressBarPadding = 0x01010319u,  // (API 11)
+      paddingStart = 0x010103b3u,        // (API 17)
+      paddingHorizontal = 0x0101053du,   // (API 26)
+    };
+  };
+};
+
+class XmlCompatVersionerTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    context_ =
+        test::ContextBuilder()
+            .SetCompilationPackage("com.app")
+            .SetPackageId(0x7f)
+            .SetPackageType(PackageType::kApp)
+            .SetMinSdkVersion(SDK_GINGERBREAD)
+            .AddSymbolSource(
+                test::StaticSymbolSourceBuilder()
+                    .AddPublicSymbol("android:attr/paddingLeft", R::attr::paddingLeft,
+                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
+                    .AddPublicSymbol("android:attr/paddingRight", R::attr::paddingRight,
+                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
+                    .AddPublicSymbol("android:attr/progressBarPadding", R::attr::progressBarPadding,
+                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
+                    .AddPublicSymbol("android:attr/paddingStart", R::attr::paddingStart,
+                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
+                    .AddPublicSymbol("android:attr/paddingHorizontal", R::attr::paddingHorizontal,
+                                     util::make_unique<Attribute>(false, TYPE_DIMENSION))
+                    .AddSymbol("com.app:attr/foo", ResourceId(0x7f010000),
+                               util::make_unique<Attribute>(false, TYPE_STRING))
+                    .Build())
+            .Build();
+  }
+
+ protected:
+  std::unique_ptr<IAaptContext> context_;
+};
+
+TEST_F(XmlCompatVersionerTest, NoRulesOnlyStripsAndCopies) {
+  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
+      <View xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto"
+          android:paddingHorizontal="24dp"
+          app:foo="16dp"
+          foo="bar"/>)EOF");
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  XmlCompatVersioner::Rules rules;
+  const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
+
+  XmlCompatVersioner versioner(&rules);
+  std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
+      versioner.Process(context_.get(), doc.get(), api_range);
+  ASSERT_EQ(2u, versioned_docs.size());
+
+  xml::Element* el;
+
+  // Source XML file's sdkVersion == 0, so the first one must also have the same sdkVersion.
+  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion);
+  el = xml::FindRootElement(versioned_docs[0].get());
+  ASSERT_NE(nullptr, el);
+  EXPECT_EQ(2u, el->attributes.size());
+  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
+  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo"));
+  EXPECT_NE(nullptr, el->FindAttribute({}, "foo"));
+
+  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion);
+  el = xml::FindRootElement(versioned_docs[1].get());
+  ASSERT_NE(nullptr, el);
+  EXPECT_EQ(3u, el->attributes.size());
+  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
+  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo"));
+  EXPECT_NE(nullptr, el->FindAttribute({}, "foo"));
+}
+
+TEST_F(XmlCompatVersionerTest, SingleRule) {
+  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
+      <View xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto"
+          android:paddingHorizontal="24dp"
+          app:foo="16dp"
+          foo="bar"/>)EOF");
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  XmlCompatVersioner::Rules rules;
+  rules[R::attr::paddingHorizontal] =
+      util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
+          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
+           ReplacementAttr{"paddingRight", R::attr::paddingRight,
+                           Attribute(false, TYPE_DIMENSION)}}));
+
+  const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
+
+  XmlCompatVersioner versioner(&rules);
+  std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
+      versioner.Process(context_.get(), doc.get(), api_range);
+  ASSERT_EQ(2u, versioned_docs.size());
+
+  xml::Element* el;
+
+  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion);
+  el = xml::FindRootElement(versioned_docs[0].get());
+  ASSERT_NE(nullptr, el);
+  EXPECT_EQ(4u, el->attributes.size());
+  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
+  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo"));
+  EXPECT_NE(nullptr, el->FindAttribute({}, "foo"));
+
+  xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion);
+  el = xml::FindRootElement(versioned_docs[1].get());
+  ASSERT_NE(nullptr, el);
+  EXPECT_EQ(5u, el->attributes.size());
+  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
+  EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo"));
+  EXPECT_NE(nullptr, el->FindAttribute({}, "foo"));
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(attr->compiled_attribute);
+}
+
+TEST_F(XmlCompatVersionerTest, ChainedRule) {
+  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
+      <View xmlns:android="http://schemas.android.com/apk/res/android"
+          android:paddingHorizontal="24dp" />)EOF");
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  XmlCompatVersioner::Rules rules;
+  rules[R::attr::progressBarPadding] =
+      util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
+          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
+           ReplacementAttr{"paddingRight", R::attr::paddingRight,
+                           Attribute(false, TYPE_DIMENSION)}}));
+  rules[R::attr::paddingHorizontal] =
+      util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>({ReplacementAttr{
+          "progressBarPadding", R::attr::progressBarPadding, Attribute(false, TYPE_DIMENSION)}}));
+
+  const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
+
+  XmlCompatVersioner versioner(&rules);
+  std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
+      versioner.Process(context_.get(), doc.get(), api_range);
+  ASSERT_EQ(3u, versioned_docs.size());
+
+  xml::Element* el;
+
+  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion);
+  el = xml::FindRootElement(versioned_docs[0].get());
+  ASSERT_NE(nullptr, el);
+  EXPECT_EQ(2u, el->attributes.size());
+  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
+
+  xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  EXPECT_EQ(static_cast<uint16_t>(SDK_HONEYCOMB), versioned_docs[1]->file.config.sdkVersion);
+  el = xml::FindRootElement(versioned_docs[1].get());
+  ASSERT_NE(nullptr, el);
+  EXPECT_EQ(1u, el->attributes.size());
+  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
+  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"));
+  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingRight"));
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "progressBarPadding");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[2]->file.config.sdkVersion);
+  el = xml::FindRootElement(versioned_docs[2].get());
+  ASSERT_NE(nullptr, el);
+  EXPECT_EQ(2u, el->attributes.size());
+  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"));
+  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingRight"));
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "progressBarPadding");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(attr->compiled_attribute);
+}
+
+TEST_F(XmlCompatVersionerTest, DegradeRuleOverridesExistingAttribute) {
+  auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
+      <View xmlns:android="http://schemas.android.com/apk/res/android"
+          android:paddingHorizontal="24dp"
+          android:paddingLeft="16dp"
+          android:paddingRight="16dp"/>)EOF");
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  Item* padding_horizontal_value = xml::FindRootElement(doc.get())
+                                       ->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")
+                                       ->compiled_value.get();
+  ASSERT_NE(nullptr, padding_horizontal_value);
+
+  XmlCompatVersioner::Rules rules;
+  rules[R::attr::paddingHorizontal] =
+      util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
+          {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
+           ReplacementAttr{"paddingRight", R::attr::paddingRight,
+                           Attribute(false, TYPE_DIMENSION)}}));
+
+  const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
+
+  XmlCompatVersioner versioner(&rules);
+  std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
+      versioner.Process(context_.get(), doc.get(), api_range);
+  ASSERT_EQ(2u, versioned_docs.size());
+
+  xml::Element* el;
+
+  EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion);
+  el = xml::FindRootElement(versioned_docs[0].get());
+  ASSERT_NE(nullptr, el);
+  EXPECT_EQ(2u, el->attributes.size());
+  EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"));
+
+  xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get()));
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get()));
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion);
+  el = xml::FindRootElement(versioned_docs[1].get());
+  ASSERT_NE(nullptr, el);
+  EXPECT_EQ(3u, el->attributes.size());
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get()));
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get()));
+  ASSERT_TRUE(attr->compiled_attribute);
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight");
+  ASSERT_NE(nullptr, attr);
+  ASSERT_NE(nullptr, attr->compiled_value);
+  ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get()));
+  ASSERT_TRUE(attr->compiled_attribute);
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 94bdccd..721fc26 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -72,13 +72,13 @@
   using xml::PackageAwareVisitor::Visit;
 
   XmlVisitor(const Source& source, const CallSite& callsite, IAaptContext* context,
-             SymbolTable* symbols, std::set<int>* sdk_levels_found)
+             SymbolTable* symbols)
       : source_(source),
         callsite_(callsite),
         context_(context),
         symbols_(symbols),
-        sdk_levels_found_(sdk_levels_found),
-        reference_visitor_(callsite, context, symbols, this) {}
+        reference_visitor_(callsite, context, symbols, this) {
+  }
 
   void Visit(xml::Element* el) override {
     // The default Attribute allows everything except enums or flags.
@@ -118,22 +118,12 @@
           continue;
         }
 
-        // Find this compiled attribute's SDK level.
-        const xml::AaptAttribute& aapt_attr = attr.compiled_attribute.value();
-        if (aapt_attr.id) {
-          // Record all SDK levels from which the attributes were defined.
-          const size_t sdk_level = FindAttributeSdkLevel(aapt_attr.id.value());
-          if (sdk_level > 1) {
-            sdk_levels_found_->insert(sdk_level);
-          }
-        }
-        attribute = &aapt_attr.attribute;
+        attribute = &attr.compiled_attribute.value().attribute;
       }
 
       attr.compiled_value = ResourceUtils::TryParseItemForAttribute(attr.value, attribute);
       if (attr.compiled_value) {
-        // With a compiledValue, we must resolve the reference and assign it an
-        // ID.
+        // With a compiledValue, we must resolve the reference and assign it an ID.
         attr.compiled_value->SetSource(source);
         attr.compiled_value->Accept(&reference_visitor_);
       } else if ((attribute->type_mask & android::ResTable_map::TYPE_STRING) == 0) {
@@ -164,7 +154,6 @@
   IAaptContext* context_;
   SymbolTable* symbols_;
 
-  std::set<int>* sdk_levels_found_;
   ReferenceVisitor reference_visitor_;
   bool error_ = false;
 };
@@ -172,10 +161,8 @@
 }  // namespace
 
 bool XmlReferenceLinker::Consume(IAaptContext* context, xml::XmlResource* resource) {
-  sdk_levels_found_.clear();
   const CallSite callsite = {resource->file.name};
-  XmlVisitor visitor(resource->file.source, callsite, context, context->GetExternalSymbols(),
-                     &sdk_levels_found_);
+  XmlVisitor visitor(resource->file.source, callsite, context, context->GetExternalSymbols());
   if (resource->root) {
     resource->root->Accept(&visitor);
     return !visitor.HasError();
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index 66ecc15..de81e73 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -160,16 +160,6 @@
   ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
 }
 
-TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
-  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
-        <View xmlns:android="http://schemas.android.com/apk/res/android"
-              android:colorAccent="#ffffff" />)EOF");
-
-  XmlReferenceLinker linker;
-  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
-  EXPECT_TRUE(linker.sdk_levels().count(21) == 1);
-}
-
 TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
   std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
             <View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 1a648bf..882a85b 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -237,14 +237,12 @@
   }
 
   // We found a resource.
-  std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>();
-  s->id = id;
+  std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>(id);
 
   // Check to see if it is an attribute.
   for (size_t i = 0; i < (size_t)count; i++) {
     if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
-      s->attribute = std::make_shared<Attribute>(false);
-      s->attribute->type_mask = entry[i].map.value.data;
+      s->attribute = std::make_shared<Attribute>(false, entry[i].map.value.data);
       break;
     }
   }
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index bd252d2..4a2181e 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -53,16 +53,12 @@
 class SymbolTable {
  public:
   struct Symbol {
-    Symbol() : Symbol(Maybe<ResourceId>{}) {}
+    Symbol() = default;
 
-    explicit Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) {}
-
-    Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr)
-        : Symbol(i, attr, false) {}
-
-    Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr,
-           bool pub)
-        : id(i), attribute(attr), is_public(pub) {}
+    explicit Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr = {},
+                    bool pub = false)
+        : id(i), attribute(attr), is_public(pub) {
+    }
 
     Symbol(const Symbol&) = default;
     Symbol(Symbol&&) = default;
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index e92565f..7e28ae7b 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -1,5 +1,11 @@
 # Android Asset Packaging Tool 2.0 (AAPT2) release notes
 
+## Version 2.16
+### `aapt2 link ...`
+- Versioning of XML files is more intelligent, using a small set of rules to degrade
+  specific newer attributes to backwards compatible versions of them.
+  Ex: `android:paddingHorizontal` degrades to `android:paddingLeft` and `android:paddingRight`.
+
 ## Version 2.15
 ### `aapt2 compile ...`
 - Add `--no-crunch` option to avoid processing PNGs during the compile phase. Note that this
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 30b9af6..386f74b 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -44,6 +44,12 @@
 namespace aapt {
 namespace util {
 
+template <typename T>
+struct Range {
+  T start;
+  T end;
+};
+
 std::vector<std::string> Split(const android::StringPiece& str, char sep);
 std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
 
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 98f5f1d..885ab3e 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -356,17 +356,16 @@
   return util::make_unique<XmlResource>(ResourceFile{}, std::move(string_pool), std::move(root));
 }
 
-std::unique_ptr<Node> Namespace::Clone() {
+std::unique_ptr<Node> Namespace::Clone(const ElementCloneFunc& el_cloner) {
   auto ns = util::make_unique<Namespace>();
   ns->comment = comment;
   ns->line_number = line_number;
   ns->column_number = column_number;
   ns->namespace_prefix = namespace_prefix;
   ns->namespace_uri = namespace_uri;
-
   ns->children.reserve(children.size());
   for (const std::unique_ptr<xml::Node>& child : children) {
-    ns->AppendChild(child->Clone());
+    ns->AppendChild(child->Clone(el_cloner));
   }
   return std::move(ns);
 }
@@ -412,6 +411,15 @@
   return nullptr;
 }
 
+const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const {
+  for (const auto& attr : attributes) {
+    if (ns == attr.namespace_uri && name == attr.name) {
+      return &attr;
+    }
+  }
+  return nullptr;
+}
+
 Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
@@ -464,29 +472,23 @@
   return elements;
 }
 
-std::unique_ptr<Node> Element::Clone() {
+std::unique_ptr<Node> Element::Clone(const ElementCloneFunc& el_cloner) {
   auto el = util::make_unique<Element>();
   el->comment = comment;
   el->line_number = line_number;
   el->column_number = column_number;
   el->name = name;
   el->namespace_uri = namespace_uri;
-
   el->attributes.reserve(attributes.size());
-  for (xml::Attribute& attr : attributes) {
-    // Don't copy compiled values or attributes.
-    el->attributes.push_back(
-        xml::Attribute{attr.namespace_uri, attr.name, attr.value});
-  }
-
+  el_cloner(*this, el.get());
   el->children.reserve(children.size());
   for (const std::unique_ptr<xml::Node>& child : children) {
-    el->AppendChild(child->Clone());
+    el->AppendChild(child->Clone(el_cloner));
   }
   return std::move(el);
 }
 
-std::unique_ptr<Node> Text::Clone() {
+std::unique_ptr<Node> Text::Clone(const ElementCloneFunc&) {
   auto t = util::make_unique<Text>();
   t->comment = comment;
   t->line_number = line_number;
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 6950c30..2dc99d6 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -35,6 +35,8 @@
 
 class RawVisitor;
 
+class Element;
+
 /**
  * Base class for all XML nodes.
  */
@@ -51,7 +53,11 @@
   void AppendChild(std::unique_ptr<Node> child);
   void InsertChild(size_t index, std::unique_ptr<Node> child);
   virtual void Accept(RawVisitor* visitor) = 0;
-  virtual std::unique_ptr<Node> Clone() = 0;
+
+  using ElementCloneFunc = std::function<void(const Element&, Element*)>;
+
+  // Clones the Node subtree, using the given function to decide how to clone an Element.
+  virtual std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) = 0;
 };
 
 /**
@@ -72,12 +78,16 @@
   std::string namespace_prefix;
   std::string namespace_uri;
 
-  std::unique_ptr<Node> Clone() override;
+  std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override;
 };
 
 struct AaptAttribute {
-  Maybe<ResourceId> id;
+  explicit AaptAttribute(const ::aapt::Attribute& attr, const Maybe<ResourceId>& resid = {})
+      : attribute(attr), id(resid) {
+  }
+
   aapt::Attribute attribute;
+  Maybe<ResourceId> id;
 };
 
 /**
@@ -102,6 +112,8 @@
   std::vector<Attribute> attributes;
 
   Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
+  const Attribute* FindAttribute(const android::StringPiece& ns,
+                                 const android::StringPiece& name) const;
   xml::Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
   xml::Element* FindChildWithAttribute(const android::StringPiece& ns,
                                        const android::StringPiece& name,
@@ -109,7 +121,7 @@
                                        const android::StringPiece& attr_name,
                                        const android::StringPiece& attr_value);
   std::vector<xml::Element*> GetChildElements();
-  std::unique_ptr<Node> Clone() override;
+  std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override;
 };
 
 /**
@@ -119,7 +131,7 @@
  public:
   std::string text;
 
-  std::unique_ptr<Node> Clone() override;
+  std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override;
 };
 
 /**