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;
};
/**