AAPT2: Implement attribute compat versioning
This change defines some hardcoded rules to degrade
attributes in newer SDKs to specific older attributes.
An attribute with a degrade rule will generate a new XML for the API
in which the attribute resulting from the degradation was introduced.
Since API 22 (Lollipop MR1), attributes are correctly ignored and do
not need to be versioned. In XML files defined for APIs 22+, the
original and degraded attributes coexist in the same XML file.
One such example is paddingHorizontal, introduced in API 26.
paddingHorizontal degrades to paddingLeft and paddingRight, which
were both introduced in API 1.
Bug: 35763493
Test: make aapt2_tests
Change-Id: I4aa8755a9ee2c0cc5afdc55c3d30093fd3a47f3d
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;
};
/**