Merge "AAPT2: Auto-version adaptive-icon XML"

This commit is contained in:
Adam Lesinski
2017-06-30 01:12:35 +00:00
committed by Android (Google) Code Review
13 changed files with 233 additions and 119 deletions

View File

@@ -1901,8 +1901,6 @@ int ResTable_config::compare(const ResTable_config& o) const {
if (diff != 0) return diff;
diff = (int32_t)(screenSize - o.screenSize);
if (diff != 0) return diff;
diff = (int32_t)(version - o.version);
if (diff != 0) return diff;
diff = (int32_t)(screenLayout - o.screenLayout);
if (diff != 0) return diff;
diff = (int32_t)(screenLayout2 - o.screenLayout2);
@@ -1914,6 +1912,11 @@ int ResTable_config::compare(const ResTable_config& o) const {
diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp);
if (diff != 0) return diff;
diff = (int32_t)(screenSizeDp - o.screenSizeDp);
if (diff != 0) return diff;
// Version MUST be last to ensure that a sorted list of configurations will always have the
// versions beside each other.
diff = (int32_t)(version - o.version);
return (int)diff;
}

View File

@@ -987,4 +987,8 @@ bool ConfigDescription::IsCompatibleWith(const ConfigDescription& o) const {
return !ConflictsWith(o) && !Dominates(o) && !o.Dominates(*this);
}
::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) {
return out << o.toString().string();
}
} // namespace aapt

View File

@@ -24,30 +24,22 @@
namespace aapt {
/*
* Subclass of ResTable_config that adds convenient
* initialization and comparison methods.
*/
// Subclass of ResTable_config that adds convenient
// initialization and comparison methods.
struct ConfigDescription : public android::ResTable_config {
/**
* Returns an immutable default config.
*/
// Returns an immutable default config.
static const ConfigDescription& DefaultConfig();
/*
* Parse a string of the form 'fr-sw600dp-land' and fill in the
* given ResTable_config with resulting configuration parameters.
*
* The resulting configuration has the appropriate sdkVersion defined
* for backwards compatibility.
*/
// Parse a string of the form 'fr-sw600dp-land' and fill in the
// given ResTable_config with resulting configuration parameters.
//
// The resulting configuration has the appropriate sdkVersion defined
// for backwards compatibility.
static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr);
/**
* If the configuration uses an axis that was added after
* the original Android release, make sure the SDK version
* is set accordingly.
*/
// If the configuration uses an axis that was added after
// the original Android release, make sure the SDK version
// is set accordingly.
static void ApplyVersionForCompatibility(ConfigDescription* config);
ConfigDescription();
@@ -61,38 +53,30 @@ struct ConfigDescription : public android::ResTable_config {
ConfigDescription CopyWithoutSdkVersion() const;
/**
* A configuration X dominates another configuration Y, if X has at least the
* precedence of Y and X is strictly more general than Y: for any type defined
* by X, the same type is defined by Y with a value equal to or, in the case
* of ranges, more specific than that of X.
*
* For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It
* does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'.
*/
// A configuration X dominates another configuration Y, if X has at least the
// precedence of Y and X is strictly more general than Y: for any type defined
// by X, the same type is defined by Y with a value equal to or, in the case
// of ranges, more specific than that of X.
//
// For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It
// does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'.
bool Dominates(const ConfigDescription& o) const;
/**
* Returns true if this configuration defines a more important configuration
* parameter than o. For example, "en" has higher precedence than "v23",
* whereas "en" has the same precedence as "en-v23".
*/
// Returns true if this configuration defines a more important configuration
// parameter than o. For example, "en" has higher precedence than "v23",
// whereas "en" has the same precedence as "en-v23".
bool HasHigherPrecedenceThan(const ConfigDescription& o) const;
/**
* A configuration conflicts with another configuration if both
* configurations define an incompatible configuration parameter. An
* incompatible configuration parameter is a non-range, non-density parameter
* that is defined in both configurations as a different, non-default value.
*/
// A configuration conflicts with another configuration if both
// configurations define an incompatible configuration parameter. An
// incompatible configuration parameter is a non-range, non-density parameter
// that is defined in both configurations as a different, non-default value.
bool ConflictsWith(const ConfigDescription& o) const;
/**
* A configuration is compatible with another configuration if both
* configurations can match a common concrete device configuration and are
* unrelated by domination. For example, land-v11 conflicts with port-v21
* but is compatible with v21 (both land-v11 and v21 would match en-land-v23).
*/
// A configuration is compatible with another configuration if both
// configurations can match a common concrete device configuration and are
// unrelated by domination. For example, land-v11 conflicts with port-v21
// but is compatible with v21 (both land-v11 and v21 would match en-land-v23).
bool IsCompatibleWith(const ConfigDescription& o) const;
bool MatchWithDensity(const ConfigDescription& o) const;
@@ -105,6 +89,8 @@ struct ConfigDescription : public android::ResTable_config {
bool operator>(const ConfigDescription& o) const;
};
::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o);
inline ConfigDescription::ConfigDescription() {
memset(this, 0, sizeof(*this));
size = sizeof(android::ResTable_config);
@@ -123,15 +109,13 @@ inline ConfigDescription::ConfigDescription(ConfigDescription&& o) {
*this = o;
}
inline ConfigDescription& ConfigDescription::operator=(
const android::ResTable_config& o) {
inline ConfigDescription& ConfigDescription::operator=(const android::ResTable_config& o) {
*static_cast<android::ResTable_config*>(this) = o;
size = sizeof(android::ResTable_config);
return *this;
}
inline ConfigDescription& ConfigDescription::operator=(
const ConfigDescription& o) {
inline ConfigDescription& ConfigDescription::operator=(const ConfigDescription& o) {
*static_cast<android::ResTable_config*>(this) = o;
return *this;
}
@@ -141,8 +125,7 @@ inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) {
return *this;
}
inline bool ConfigDescription::MatchWithDensity(
const ConfigDescription& o) const {
inline bool ConfigDescription::MatchWithDensity(const ConfigDescription& o) const {
return match(o) && (density == 0 || density == o.density);
}
@@ -170,11 +153,6 @@ inline bool ConfigDescription::operator>(const ConfigDescription& o) const {
return compare(o) > 0;
}
inline ::std::ostream& operator<<(::std::ostream& out,
const ConfigDescription& o) {
return out << o.toString().string();
}
} // namespace aapt
#endif // AAPT_CONFIG_DESCRIPTION_H

View File

@@ -60,6 +60,7 @@
#include "unflatten/BinaryResourceParser.h"
#include "util/Files.h"
#include "xml/XmlDom.h"
#include "xml/XmlUtil.h"
using android::StringPiece;
using android::base::StringPrintf;
@@ -342,16 +343,18 @@ class ResourceFileFlattener {
ConfigDescription config;
// The entry this file came from.
ResourceEntry* entry;
ResourceEntry* entry = nullptr;
// The file to copy as-is.
io::IFile* file_to_copy;
io::IFile* file_to_copy = nullptr;
// The XML to process and flatten.
std::unique_ptr<xml::XmlResource> xml_to_flatten;
// The destination to write this file to.
std::string dst_path;
bool skip_versioning = false;
};
uint32_t GetCompressionFlags(const StringPiece& str);
@@ -431,19 +434,6 @@ uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) {
return ArchiveEntry::kCompress;
}
static bool IsTransitionElement(const std::string& name) {
return name == "fade" || name == "changeBounds" || name == "slide" || name == "explode" ||
name == "changeImageTransform" || name == "changeTransform" ||
name == "changeClipBounds" || name == "autoTransition" || name == "recolor" ||
name == "changeScroll" || name == "transitionSet" || name == "transition" ||
name == "transitionManager";
}
static bool IsVectorElement(const std::string& name) {
return name == "vector" || name == "animated-vector" || name == "pathInterpolator" ||
name == "objectAnimator";
}
template <typename T>
std::vector<T> make_singleton_vec(T&& val) {
std::vector<T> vec;
@@ -476,21 +466,10 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer
}
}
if (options_.no_auto_version) {
if (options_.no_auto_version || file_op->skip_versioning) {
return make_singleton_vec(std::move(file_op->xml_to_flatten));
}
if (options_.no_version_vectors || options_.no_version_transitions) {
// Skip this if it is a vector or animated-vector.
xml::Element* el = xml::FindRootElement(doc);
if (el && el->namespace_uri.empty()) {
if ((options_.no_version_vectors && IsVectorElement(el->name)) ||
(options_.no_version_transitions && IsTransitionElement(el->name))) {
return make_singleton_vec(std::move(file_op->xml_to_flatten));
}
}
}
const ConfigDescription& config = file_op->config;
ResourceEntry* entry = file_op->entry;
@@ -504,15 +483,26 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
bool error = false;
std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> config_sorted_files;
int tag_version_options = 0;
if (options_.no_version_vectors) {
tag_version_options |= xml::kNoVersionVector;
}
if (options_.no_version_transitions) {
tag_version_options |= xml::kNoVersionTransition;
}
for (auto& pkg : table->packages) {
for (auto& type : pkg->types) {
// Sort by config and name, so that we get better locality in the zip file.
config_sorted_files.clear();
std::queue<FileOperation> file_operations;
// Populate the queue with all files in the ResourceTable.
for (auto& entry : type->entries) {
for (auto& config_value : entry->values) {
const auto values_end = entry->values.end();
for (auto values_iter = entry->values.begin(); values_iter != values_end; ++values_iter) {
ResourceConfigValue* config_value = values_iter->get();
// WARNING! Do not insert or remove any resources while executing in this scope. It will
// corrupt the iteration order.
@@ -554,6 +544,44 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
file_op.xml_to_flatten->file.config = config_value->config;
file_op.xml_to_flatten->file.source = file_ref->GetSource();
file_op.xml_to_flatten->file.name = ResourceName(pkg->name, type->type, entry->name);
// Check if this file needs to be versioned based on tag rules.
xml::Element* root_el = xml::FindRootElement(file_op.xml_to_flatten.get());
if (root_el == nullptr) {
context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
<< "failed to find the root XML element");
return false;
}
if (root_el->namespace_uri.empty()) {
if (Maybe<xml::TagApiVersionResult> result =
xml::GetXmlTagApiVersion(root_el->name, tag_version_options)) {
file_op.skip_versioning = result.value().skip_version;
if (result.value().api_version && config_value->config.sdkVersion == 0u) {
const ApiVersion min_tag_version = result.value().api_version.value();
// Only version it if it doesn't specify its own version and the version is
// greater than the minSdk.
const util::Range<ApiVersion> valid_range{
context_->GetMinSdkVersion() + 1,
FindNextApiVersionForConfigInSortedVector(values_iter, values_end)};
if (valid_range.Contains(min_tag_version)) {
// Update the configurations. The iteration order will not be affected
// since sdkVersions in ConfigDescriptions are the last property compared
// in the sort function.
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage(config_value->value->GetSource())
<< "auto-versioning XML resource to API "
<< min_tag_version);
}
const_cast<ConfigDescription&>(config_value->config).sdkVersion =
static_cast<uint16_t>(min_tag_version);
file_op.config.sdkVersion = static_cast<uint16_t>(min_tag_version);
file_op.xml_to_flatten->file.config.sdkVersion =
static_cast<uint16_t>(min_tag_version);
}
}
}
}
}
// NOTE(adamlesinski): Explicitly construct a StringPiece here, or

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<adaptive-icon xmlns:android="http://schema.android.com/apk/res/android">
<background android:drawable="@android:color/white" />
<foreground android:drawable="@drawable/image" />
</adaptive-icon>

View File

@@ -34,6 +34,19 @@ bool ShouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDes
return sdk_version_to_generate < FindNextApiVersionForConfig(entry, config);
}
ApiVersion FindNextApiVersionForConfigInSortedVector(
std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator start,
std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator end) {
const ConfigDescription start_config = (*start)->config.CopyWithoutSdkVersion();
++start;
if (start != end) {
if ((*start)->config.CopyWithoutSdkVersion() == start_config) {
return static_cast<ApiVersion>((*start)->config.sdkVersion);
}
}
return std::numeric_limits<ApiVersion>::max();
}
ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry,
const ConfigDescription& config) {
const auto end_iter = entry->values.end();
@@ -46,25 +59,7 @@ ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry,
// The source config came from this list, so it should be here.
CHECK(iter != entry->values.end());
++iter;
// The next configuration either only varies in sdkVersion, or it is completely different
// and therefore incompatible. If it is incompatible, we must generate the versioned resource.
// NOTE: The ordering of configurations takes sdkVersion as higher precedence than other
// qualifiers, so we need to iterate through the entire list to be sure there
// are no higher sdk level versions of this resource.
ConfigDescription temp_config(config);
for (; iter != end_iter; ++iter) {
temp_config.sdkVersion = (*iter)->config.sdkVersion;
if (temp_config == (*iter)->config) {
// The two configs are the same, return the sdkVersion.
return (*iter)->config.sdkVersion;
}
}
// Didn't find another config with a different sdk version, so return the highest possible value.
return std::numeric_limits<ApiVersion>::max();
return FindNextApiVersionForConfigInSortedVector(iter, end_iter);
}
bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) {

View File

@@ -42,8 +42,8 @@ TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) {
ResourceEntry entry("foo");
entry.values.push_back(util::make_unique<ResourceConfigValue>(ConfigDescription::DefaultConfig(), ""));
entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dp_v13_config, ""));
entry.values.push_back(util::make_unique<ResourceConfigValue>(v21_config, ""));
entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dp_v13_config, ""));
EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 17));
EXPECT_FALSE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 22));

View File

@@ -23,6 +23,7 @@
#include "android-base/macros.h"
#include "Resource.h"
#include "ResourceTable.h"
#include "SdkConstants.h"
#include "process/IResourceTableConsumer.h"
#include "xml/XmlDom.h"
@@ -41,17 +42,19 @@ struct CallSite {
ResourceNameRef resource;
};
/**
* Determines whether a versioned resource should be created. If a versioned
* resource already exists, it takes precedence.
*/
// Determines whether a versioned resource should be created. If a versioned resource already
// exists, it takes precedence.
bool ShouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
const ApiVersion sdk_version_to_generate);
// Finds the next largest ApiVersion of the config which is identical to the given config except
// for sdkVersion.
// Finds the next largest ApiVersion of `config` for values defined for `entry`.
ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry, const ConfigDescription& config);
// Finds the next largest ApiVersion of the config pointed to by the iterator `start`.
ApiVersion FindNextApiVersionForConfigInSortedVector(
std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator start,
std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator end);
class AutoVersioner : public IResourceTableConsumer {
public:
AutoVersioner() = default;

View File

@@ -9,6 +9,7 @@
(bug 62839863)
- Fixed issue where Java classes referenced from fragments and menus were not added to
the set of Proguard keep rules. (bug 62216174)
- Automatically version XML `<adaptive-icon>` resources to v26. (bug 62316340)
## Version 2.17
### `aapt2 ...`

View File

@@ -38,6 +38,7 @@ namespace aapt {
DEFINE_HAS_BINARY_OP_TRAIT(has_eq_op, ==);
DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <);
DEFINE_HAS_BINARY_OP_TRAIT(has_lte_op, <=);
/**
* Type trait that checks if two types can be equated (==) and compared (<).

View File

@@ -48,6 +48,11 @@ template <typename T>
struct Range {
T start;
T end;
typename std::enable_if<has_lte_op<const T, const T>::value, bool>::type Contains(
const T& t) const {
return start <= t && t < end;
}
};
std::vector<std::string> Split(const android::StringPiece& str, char sep);

View File

@@ -80,5 +80,65 @@ void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack,
}
}
namespace {
struct TagCompat {
ApiVersion api_version;
enum class XmlType {
kVector,
kTransition,
kAdaptiveIcon,
};
XmlType type;
};
std::unordered_map<StringPiece, TagCompat> sTagVersions = {
{"fade", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"changeBounds", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"slide", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"explode", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"changeImageTransform", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"changeTransform", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"changeClipBounds", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"autoTransition", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"recolor", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"changeScroll", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"transitionSet", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"transition", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"transitionManager", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}},
{"vector", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}},
{"animated-vector", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}},
{"pathInterpolator", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}},
{"objectAnimator", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}},
{"adaptive-icon", {SDK_O, TagCompat::XmlType::kAdaptiveIcon}},
};
} // namespace
Maybe<TagApiVersionResult> GetXmlTagApiVersion(const StringPiece& tag_name, int options) {
auto iter = sTagVersions.find(tag_name);
if (iter == sTagVersions.end()) {
return {};
}
const TagCompat& tag_compat = iter->second;
if (options & kNoVersionVector) {
if (tag_compat.type == TagCompat::XmlType::kVector) {
return TagApiVersionResult{{}, true /*skip_version*/};
}
}
if (options & kNoVersionTransition) {
if (tag_compat.type == TagCompat::XmlType::kTransition) {
return TagApiVersionResult{{}, true /*skip_version*/};
}
}
return TagApiVersionResult{tag_compat.api_version, false /*skip_version*/};
}
} // namespace xml
} // namespace aapt

View File

@@ -20,18 +20,16 @@
#include <string>
#include "ResourceValues.h"
#include "SdkConstants.h"
#include "util/Maybe.h"
namespace aapt {
namespace xml {
constexpr const char* kSchemaAuto = "http://schemas.android.com/apk/res-auto";
constexpr const char* kSchemaPublicPrefix =
"http://schemas.android.com/apk/res/";
constexpr const char* kSchemaPrivatePrefix =
"http://schemas.android.com/apk/prv/res/";
constexpr const char* kSchemaAndroid =
"http://schemas.android.com/apk/res/android";
constexpr const char* kSchemaPublicPrefix = "http://schemas.android.com/apk/res/";
constexpr const char* kSchemaPrivatePrefix = "http://schemas.android.com/apk/prv/res/";
constexpr const char* kSchemaAndroid = "http://schemas.android.com/apk/res/android";
constexpr const char* kSchemaTools = "http://schemas.android.com/tools";
constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt";
@@ -102,6 +100,24 @@ struct IPackageDeclStack {
void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack,
const android::StringPiece& local_package, Reference* in_ref);
struct TagApiVersionResult {
// If set, the API version to apply.
Maybe<ApiVersion> api_version;
// Whether to skip any auto-versioning.
bool skip_version;
};
enum TagVersionOptions : int {
// Skip versioning XML resources that deal with vector drawables.
kNoVersionVector,
// Skip versioning XML resources that deal with transitions.
kNoVersionTransition,
};
Maybe<TagApiVersionResult> GetXmlTagApiVersion(const android::StringPiece& tag_name, int options);
} // namespace xml
} // namespace aapt