Files
frameworks_base/tools/aapt2/ResourceValues.cpp
Adam Lesinski c744ae8aca 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
2017-05-24 15:14:29 -07:00

824 lines
22 KiB
C++

/*
* Copyright (C) 2015 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 "ResourceValues.h"
#include <algorithm>
#include <limits>
#include <set>
#include "androidfw/ResourceTypes.h"
#include "Resource.h"
#include "ResourceUtils.h"
#include "ValueVisitor.h"
#include "util/Util.h"
namespace aapt {
template <typename Derived>
void BaseValue<Derived>::Accept(RawValueVisitor* visitor) {
visitor->Visit(static_cast<Derived*>(this));
}
template <typename Derived>
void BaseItem<Derived>::Accept(RawValueVisitor* visitor) {
visitor->Visit(static_cast<Derived*>(this));
}
RawString::RawString(const StringPool::Ref& ref) : value(ref) {}
bool RawString::Equals(const Value* value) const {
const RawString* other = ValueCast<RawString>(value);
if (!other) {
return false;
}
return *this->value == *other->value;
}
RawString* RawString::Clone(StringPool* new_pool) const {
RawString* rs = new RawString(new_pool->MakeRef(*value));
rs->comment_ = comment_;
rs->source_ = source_;
return rs;
}
bool RawString::Flatten(android::Res_value* out_value) const {
out_value->dataType = android::Res_value::TYPE_STRING;
out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
return true;
}
void RawString::Print(std::ostream* out) const {
*out << "(raw string) " << *value;
}
Reference::Reference() : reference_type(Type::kResource) {}
Reference::Reference(const ResourceNameRef& n, Type t)
: name(n.ToResourceName()), reference_type(t) {}
Reference::Reference(const ResourceId& i, Type type)
: id(i), reference_type(type) {}
Reference::Reference(const ResourceNameRef& n, const ResourceId& i)
: name(n.ToResourceName()), id(i), reference_type(Type::kResource) {}
bool Reference::Equals(const Value* value) const {
const Reference* other = ValueCast<Reference>(value);
if (!other) {
return false;
}
return reference_type == other->reference_type &&
private_reference == other->private_reference && id == other->id &&
name == other->name;
}
bool Reference::Flatten(android::Res_value* out_value) const {
const ResourceId resid = id.value_or_default(ResourceId(0));
const bool dynamic =
(resid.package_id() != kFrameworkPackageId && resid.package_id() != kAppPackageId);
if (reference_type == Reference::Type::kResource) {
if (dynamic) {
out_value->dataType = android::Res_value::TYPE_DYNAMIC_REFERENCE;
} else {
out_value->dataType = android::Res_value::TYPE_REFERENCE;
}
} else {
if (dynamic) {
out_value->dataType = android::Res_value::TYPE_DYNAMIC_ATTRIBUTE;
} else {
out_value->dataType = android::Res_value::TYPE_ATTRIBUTE;
}
}
out_value->data = util::HostToDevice32(resid.id);
return true;
}
Reference* Reference::Clone(StringPool* /*new_pool*/) const {
return new Reference(*this);
}
void Reference::Print(std::ostream* out) const {
*out << "(reference) ";
if (reference_type == Reference::Type::kResource) {
*out << "@";
if (private_reference) {
*out << "*";
}
} else {
*out << "?";
}
if (name) {
*out << name.value();
}
if (id && !Res_INTERNALID(id.value().id)) {
*out << " " << id.value();
}
}
bool Id::Equals(const Value* value) const {
return ValueCast<Id>(value) != nullptr;
}
bool Id::Flatten(android::Res_value* out) const {
out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
out->data = util::HostToDevice32(0);
return true;
}
Id* Id::Clone(StringPool* /*new_pool*/) const { return new Id(*this); }
void Id::Print(std::ostream* out) const { *out << "(id)"; }
String::String(const StringPool::Ref& ref) : value(ref) {}
bool String::Equals(const Value* value) const {
const String* other = ValueCast<String>(value);
if (!other) {
return false;
}
if (this->value != other->value) {
return false;
}
if (untranslatable_sections.size() != other->untranslatable_sections.size()) {
return false;
}
auto other_iter = other->untranslatable_sections.begin();
for (const UntranslatableSection& this_section : untranslatable_sections) {
if (this_section != *other_iter) {
return false;
}
++other_iter;
}
return true;
}
bool String::Flatten(android::Res_value* out_value) const {
// Verify that our StringPool index is within encode-able limits.
if (value.index() > std::numeric_limits<uint32_t>::max()) {
return false;
}
out_value->dataType = android::Res_value::TYPE_STRING;
out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
return true;
}
String* String::Clone(StringPool* new_pool) const {
String* str = new String(new_pool->MakeRef(*value));
str->comment_ = comment_;
str->source_ = source_;
str->untranslatable_sections = untranslatable_sections;
return str;
}
void String::Print(std::ostream* out) const {
*out << "(string) \"" << *value << "\"";
}
StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {}
bool StyledString::Equals(const Value* value) const {
const StyledString* other = ValueCast<StyledString>(value);
if (!other) {
return false;
}
if (this->value != other->value) {
return false;
}
if (untranslatable_sections.size() != other->untranslatable_sections.size()) {
return false;
}
auto other_iter = other->untranslatable_sections.begin();
for (const UntranslatableSection& this_section : untranslatable_sections) {
if (this_section != *other_iter) {
return false;
}
++other_iter;
}
return true;
}
bool StyledString::Flatten(android::Res_value* out_value) const {
if (value.index() > std::numeric_limits<uint32_t>::max()) {
return false;
}
out_value->dataType = android::Res_value::TYPE_STRING;
out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
return true;
}
StyledString* StyledString::Clone(StringPool* new_pool) const {
StyledString* str = new StyledString(new_pool->MakeRef(value));
str->comment_ = comment_;
str->source_ = source_;
str->untranslatable_sections = untranslatable_sections;
return str;
}
void StyledString::Print(std::ostream* out) const {
*out << "(styled string) \"" << *value->str << "\"";
for (const StringPool::Span& span : value->spans) {
*out << " " << *span.name << ":" << span.first_char << ","
<< span.last_char;
}
}
FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {}
bool FileReference::Equals(const Value* value) const {
const FileReference* other = ValueCast<FileReference>(value);
if (!other) {
return false;
}
return path == other->path;
}
bool FileReference::Flatten(android::Res_value* out_value) const {
if (path.index() > std::numeric_limits<uint32_t>::max()) {
return false;
}
out_value->dataType = android::Res_value::TYPE_STRING;
out_value->data = util::HostToDevice32(static_cast<uint32_t>(path.index()));
return true;
}
FileReference* FileReference::Clone(StringPool* new_pool) const {
FileReference* fr = new FileReference(new_pool->MakeRef(*path));
fr->file = file;
fr->comment_ = comment_;
fr->source_ = source_;
return fr;
}
void FileReference::Print(std::ostream* out) const {
*out << "(file) " << *path;
}
BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {}
BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
value.dataType = dataType;
value.data = data;
}
bool BinaryPrimitive::Equals(const Value* value) const {
const BinaryPrimitive* other = ValueCast<BinaryPrimitive>(value);
if (!other) {
return false;
}
return this->value.dataType == other->value.dataType &&
this->value.data == other->value.data;
}
bool BinaryPrimitive::Flatten(android::Res_value* out_value) const {
out_value->dataType = value.dataType;
out_value->data = util::HostToDevice32(value.data);
return true;
}
BinaryPrimitive* BinaryPrimitive::Clone(StringPool* /*new_pool*/) const {
return new BinaryPrimitive(*this);
}
void BinaryPrimitive::Print(std::ostream* out) const {
switch (value.dataType) {
case android::Res_value::TYPE_NULL:
*out << "(null)";
break;
case android::Res_value::TYPE_INT_DEC:
*out << "(integer) " << static_cast<int32_t>(value.data);
break;
case android::Res_value::TYPE_INT_HEX:
*out << "(integer) 0x" << std::hex << value.data << std::dec;
break;
case android::Res_value::TYPE_INT_BOOLEAN:
*out << "(boolean) " << (value.data != 0 ? "true" : "false");
break;
case android::Res_value::TYPE_INT_COLOR_ARGB8:
case android::Res_value::TYPE_INT_COLOR_RGB8:
case android::Res_value::TYPE_INT_COLOR_ARGB4:
case android::Res_value::TYPE_INT_COLOR_RGB4:
*out << "(color) #" << std::hex << value.data << std::dec;
break;
default:
*out << "(unknown 0x" << std::hex << (int)value.dataType << ") 0x"
<< std::hex << value.data << std::dec;
break;
}
}
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()),
max_int(std::numeric_limits<int32_t>::max()) {
weak_ = w;
}
template <typename T>
constexpr T* add_pointer(T& val) {
return &val;
}
bool Attribute::Equals(const Value* value) const {
const Attribute* other = ValueCast<Attribute>(value);
if (!other) {
return false;
}
if (symbols.size() != other->symbols.size()) {
return false;
}
if (type_mask != other->type_mask || min_int != other->min_int ||
max_int != other->max_int) {
return false;
}
std::vector<const Symbol*> sorted_a;
std::transform(symbols.begin(), symbols.end(), std::back_inserter(sorted_a),
add_pointer<const Symbol>);
std::sort(sorted_a.begin(), sorted_a.end(),
[](const Symbol* a, const Symbol* b) -> bool {
return a->symbol.name < b->symbol.name;
});
std::vector<const Symbol*> sorted_b;
std::transform(other->symbols.begin(), other->symbols.end(),
std::back_inserter(sorted_b), add_pointer<const Symbol>);
std::sort(sorted_b.begin(), sorted_b.end(),
[](const Symbol* a, const Symbol* b) -> bool {
return a->symbol.name < b->symbol.name;
});
return std::equal(sorted_a.begin(), sorted_a.end(), sorted_b.begin(),
[](const Symbol* a, const Symbol* b) -> bool {
return a->symbol.Equals(&b->symbol) &&
a->value == b->value;
});
}
Attribute* Attribute::Clone(StringPool* /*new_pool*/) const {
return new Attribute(*this);
}
void Attribute::PrintMask(std::ostream* out) const {
if (type_mask == android::ResTable_map::TYPE_ANY) {
*out << "any";
return;
}
bool set = false;
if ((type_mask & android::ResTable_map::TYPE_REFERENCE) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
}
*out << "reference";
}
if ((type_mask & android::ResTable_map::TYPE_STRING) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
}
*out << "string";
}
if ((type_mask & android::ResTable_map::TYPE_INTEGER) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
}
*out << "integer";
}
if ((type_mask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
}
*out << "boolean";
}
if ((type_mask & android::ResTable_map::TYPE_COLOR) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
}
*out << "color";
}
if ((type_mask & android::ResTable_map::TYPE_FLOAT) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
}
*out << "float";
}
if ((type_mask & android::ResTable_map::TYPE_DIMENSION) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
}
*out << "dimension";
}
if ((type_mask & android::ResTable_map::TYPE_FRACTION) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
}
*out << "fraction";
}
if ((type_mask & android::ResTable_map::TYPE_ENUM) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
}
*out << "enum";
}
if ((type_mask & android::ResTable_map::TYPE_FLAGS) != 0) {
if (!set) {
set = true;
} else {
*out << "|";
}
*out << "flags";
}
}
void Attribute::Print(std::ostream* out) const {
*out << "(attr) ";
PrintMask(out);
if (!symbols.empty()) {
*out << " [" << util::Joiner(symbols, ", ") << "]";
}
if (min_int != std::numeric_limits<int32_t>::min()) {
*out << " min=" << min_int;
}
if (max_int != std::numeric_limits<int32_t>::max()) {
*out << " max=" << max_int;
}
if (IsWeak()) {
*out << " [weak]";
}
}
static void BuildAttributeMismatchMessage(DiagMessage* msg,
const Attribute* attr,
const Item* value) {
*msg << "expected";
if (attr->type_mask & android::ResTable_map::TYPE_BOOLEAN) {
*msg << " boolean";
}
if (attr->type_mask & android::ResTable_map::TYPE_COLOR) {
*msg << " color";
}
if (attr->type_mask & android::ResTable_map::TYPE_DIMENSION) {
*msg << " dimension";
}
if (attr->type_mask & android::ResTable_map::TYPE_ENUM) {
*msg << " enum";
}
if (attr->type_mask & android::ResTable_map::TYPE_FLAGS) {
*msg << " flags";
}
if (attr->type_mask & android::ResTable_map::TYPE_FLOAT) {
*msg << " float";
}
if (attr->type_mask & android::ResTable_map::TYPE_FRACTION) {
*msg << " fraction";
}
if (attr->type_mask & android::ResTable_map::TYPE_INTEGER) {
*msg << " integer";
}
if (attr->type_mask & android::ResTable_map::TYPE_REFERENCE) {
*msg << " reference";
}
if (attr->type_mask & android::ResTable_map::TYPE_STRING) {
*msg << " string";
}
*msg << " but got " << *value;
}
bool Attribute::Matches(const Item* item, DiagMessage* out_msg) const {
android::Res_value val = {};
item->Flatten(&val);
// Always allow references.
const uint32_t mask = type_mask | android::ResTable_map::TYPE_REFERENCE;
if (!(mask & ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType))) {
if (out_msg) {
BuildAttributeMismatchMessage(out_msg, this, item);
}
return false;
} else if (ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType) &
android::ResTable_map::TYPE_INTEGER) {
if (static_cast<int32_t>(util::DeviceToHost32(val.data)) < min_int) {
if (out_msg) {
*out_msg << *item << " is less than minimum integer " << min_int;
}
return false;
} else if (static_cast<int32_t>(util::DeviceToHost32(val.data)) > max_int) {
if (out_msg) {
*out_msg << *item << " is greater than maximum integer " << max_int;
}
return false;
}
}
return true;
}
bool Style::Equals(const Value* value) const {
const Style* other = ValueCast<Style>(value);
if (!other) {
return false;
}
if (bool(parent) != bool(other->parent) ||
(parent && other->parent &&
!parent.value().Equals(&other->parent.value()))) {
return false;
}
if (entries.size() != other->entries.size()) {
return false;
}
std::vector<const Entry*> sorted_a;
std::transform(entries.begin(), entries.end(), std::back_inserter(sorted_a),
add_pointer<const Entry>);
std::sort(sorted_a.begin(), sorted_a.end(),
[](const Entry* a, const Entry* b) -> bool {
return a->key.name < b->key.name;
});
std::vector<const Entry*> sorted_b;
std::transform(other->entries.begin(), other->entries.end(),
std::back_inserter(sorted_b), add_pointer<const Entry>);
std::sort(sorted_b.begin(), sorted_b.end(),
[](const Entry* a, const Entry* b) -> bool {
return a->key.name < b->key.name;
});
return std::equal(sorted_a.begin(), sorted_a.end(), sorted_b.begin(),
[](const Entry* a, const Entry* b) -> bool {
return a->key.Equals(&b->key) &&
a->value->Equals(b->value.get());
});
}
Style* Style::Clone(StringPool* new_pool) const {
Style* style = new Style();
style->parent = parent;
style->parent_inferred = parent_inferred;
style->comment_ = comment_;
style->source_ = source_;
for (auto& entry : entries) {
style->entries.push_back(
Entry{entry.key, std::unique_ptr<Item>(entry.value->Clone(new_pool))});
}
return style;
}
void Style::Print(std::ostream* out) const {
*out << "(style) ";
if (parent && parent.value().name) {
if (parent.value().private_reference) {
*out << "*";
}
*out << parent.value().name.value();
}
*out << " [" << util::Joiner(entries, ", ") << "]";
}
static ::std::ostream& operator<<(::std::ostream& out,
const Style::Entry& value) {
if (value.key.name) {
out << value.key.name.value();
} else if (value.key.id) {
out << value.key.id.value();
} else {
out << "???";
}
out << " = ";
value.value->Print(&out);
return out;
}
bool Array::Equals(const Value* value) const {
const Array* other = ValueCast<Array>(value);
if (!other) {
return false;
}
if (items.size() != other->items.size()) {
return false;
}
return std::equal(items.begin(), items.end(), other->items.begin(),
[](const std::unique_ptr<Item>& a,
const std::unique_ptr<Item>& b) -> bool {
return a->Equals(b.get());
});
}
Array* Array::Clone(StringPool* new_pool) const {
Array* array = new Array();
array->comment_ = comment_;
array->source_ = source_;
for (auto& item : items) {
array->items.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool)));
}
return array;
}
void Array::Print(std::ostream* out) const {
*out << "(array) [" << util::Joiner(items, ", ") << "]";
}
bool Plural::Equals(const Value* value) const {
const Plural* other = ValueCast<Plural>(value);
if (!other) {
return false;
}
auto one_iter = values.begin();
auto one_end_iter = values.end();
auto two_iter = other->values.begin();
for (; one_iter != one_end_iter; ++one_iter, ++two_iter) {
const std::unique_ptr<Item>& a = *one_iter;
const std::unique_ptr<Item>& b = *two_iter;
if (a != nullptr && b != nullptr) {
if (!a->Equals(b.get())) {
return false;
}
} else if (a != b) {
return false;
}
}
return true;
}
Plural* Plural::Clone(StringPool* new_pool) const {
Plural* p = new Plural();
p->comment_ = comment_;
p->source_ = source_;
const size_t count = values.size();
for (size_t i = 0; i < count; i++) {
if (values[i]) {
p->values[i] = std::unique_ptr<Item>(values[i]->Clone(new_pool));
}
}
return p;
}
void Plural::Print(std::ostream* out) const {
*out << "(plural)";
if (values[Zero]) {
*out << " zero=" << *values[Zero];
}
if (values[One]) {
*out << " one=" << *values[One];
}
if (values[Two]) {
*out << " two=" << *values[Two];
}
if (values[Few]) {
*out << " few=" << *values[Few];
}
if (values[Many]) {
*out << " many=" << *values[Many];
}
if (values[Other]) {
*out << " other=" << *values[Other];
}
}
static ::std::ostream& operator<<(::std::ostream& out,
const std::unique_ptr<Item>& item) {
return out << *item;
}
bool Styleable::Equals(const Value* value) const {
const Styleable* other = ValueCast<Styleable>(value);
if (!other) {
return false;
}
if (entries.size() != other->entries.size()) {
return false;
}
return std::equal(entries.begin(), entries.end(), other->entries.begin(),
[](const Reference& a, const Reference& b) -> bool {
return a.Equals(&b);
});
}
Styleable* Styleable::Clone(StringPool* /*new_pool*/) const {
return new Styleable(*this);
}
void Styleable::Print(std::ostream* out) const {
*out << "(styleable) "
<< " [" << util::Joiner(entries, ", ") << "]";
}
bool operator<(const Reference& a, const Reference& b) {
int cmp = a.name.value_or_default({}).compare(b.name.value_or_default({}));
if (cmp != 0) return cmp < 0;
return a.id < b.id;
}
bool operator==(const Reference& a, const Reference& b) {
return a.name == b.name && a.id == b.id;
}
bool operator!=(const Reference& a, const Reference& b) {
return a.name != b.name || a.id != b.id;
}
struct NameOnlyComparator {
bool operator()(const Reference& a, const Reference& b) const {
return a.name < b.name;
}
};
void Styleable::MergeWith(Styleable* other) {
// Compare only names, because some References may already have their IDs
// assigned
// (framework IDs that don't change).
std::set<Reference, NameOnlyComparator> references;
references.insert(entries.begin(), entries.end());
references.insert(other->entries.begin(), other->entries.end());
entries.clear();
entries.reserve(references.size());
entries.insert(entries.end(), references.begin(), references.end());
}
} // namespace aapt