summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt2/Android.bp3
-rw-r--r--tools/aapt2/AppInfo.h4
-rw-r--r--tools/aapt2/Configuration.proto1
-rw-r--r--tools/aapt2/Debug.cpp20
-rw-r--r--tools/aapt2/ResourceParser.cpp10
-rw-r--r--tools/aapt2/ResourceParser_test.cpp5
-rw-r--r--tools/aapt2/ResourceTable.cpp14
-rw-r--r--tools/aapt2/ResourceTable.h6
-rw-r--r--tools/aapt2/ResourceUtils.cpp2
-rw-r--r--tools/aapt2/ResourceValues.cpp4
-rw-r--r--tools/aapt2/ResourceValues.h1
-rw-r--r--tools/aapt2/ResourceValues_test.cpp52
-rw-r--r--tools/aapt2/Resources.proto4
-rw-r--r--tools/aapt2/ResourcesInternal.proto1
-rw-r--r--tools/aapt2/cmd/Compile.cpp6
-rw-r--r--tools/aapt2/cmd/Compile_test.cpp2
-rw-r--r--tools/aapt2/cmd/Convert.cpp6
-rw-r--r--tools/aapt2/cmd/Diff.cpp6
-rw-r--r--tools/aapt2/cmd/Dump.cpp6
-rw-r--r--tools/aapt2/cmd/Link.cpp188
-rw-r--r--tools/aapt2/cmd/Link.h12
-rw-r--r--tools/aapt2/cmd/Link_test.cpp244
-rw-r--r--tools/aapt2/cmd/Optimize.cpp6
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator.cpp2
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator_test.cpp21
-rw-r--r--tools/aapt2/dump/DumpManifest.cpp17
-rw-r--r--tools/aapt2/format/Archive.cpp9
-rw-r--r--tools/aapt2/format/Archive_test.cpp209
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.cpp1
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp2
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.cpp2
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize.cpp1
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize_test.cpp4
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/Android.mk2
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk29
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml23
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk28
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml17
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml25
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml43
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk28
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml28
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml25
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml25
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml29
-rw-r--r--tools/aapt2/io/Util.cpp2
-rw-r--r--tools/aapt2/io/Util.h10
-rw-r--r--tools/aapt2/java/ProguardRules.h6
-rw-r--r--tools/aapt2/java/ProguardRules_test.cpp8
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp29
-rw-r--r--tools/aapt2/link/ManifestFixer_test.cpp30
-rw-r--r--tools/aapt2/link/ReferenceLinker.cpp43
-rw-r--r--tools/aapt2/link/ReferenceLinker.h7
-rw-r--r--tools/aapt2/link/ReferenceLinker_test.cpp65
-rw-r--r--tools/aapt2/link/TableMerger.cpp37
-rw-r--r--tools/aapt2/link/TableMerger.h2
-rw-r--r--tools/aapt2/link/TableMerger_test.cpp103
-rw-r--r--tools/aapt2/link/XmlReferenceLinker.cpp2
-rw-r--r--tools/aapt2/optimize/MultiApkGenerator.cpp4
-rw-r--r--tools/aapt2/optimize/ResourcePathShortener.cpp5
-rw-r--r--tools/aapt2/optimize/ResourcePathShortener_test.cpp50
-rw-r--r--tools/aapt2/process/IResourceTableConsumer.h2
-rw-r--r--tools/aapt2/process/SymbolTable.cpp1
-rw-r--r--tools/aapt2/test/Context.h10
-rw-r--r--tools/aapt2/test/Fixture.cpp25
-rw-r--r--tools/aapt2/test/Fixture.h5
66 files changed, 1379 insertions, 240 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 53372bff3e67..1be4ea8eccda 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -56,7 +56,7 @@ cc_defaults {
"libziparchive",
"libpng",
"libbase",
- "libprotobuf-cpp-lite",
+ "libprotobuf-cpp-full",
"libz",
"libbuildversion",
],
@@ -197,6 +197,7 @@ cc_test_host {
cc_binary_host {
name: "aapt2",
srcs: ["Main.cpp"] + toolSources,
+ use_version_lib: true,
static_libs: ["libaapt2"],
defaults: ["aapt2_defaults"],
}
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h
index 75123537116f..d3ca357b0305 100644
--- a/tools/aapt2/AppInfo.h
+++ b/tools/aapt2/AppInfo.h
@@ -17,6 +17,7 @@
#ifndef AAPT_APP_INFO_H
#define AAPT_APP_INFO_H
+#include <set>
#include <string>
#include "util/Maybe.h"
@@ -42,6 +43,9 @@ struct AppInfo {
// The app's split name, if it is a split.
Maybe<std::string> split_name;
+
+ // The split names that this split depends on.
+ std::set<std::string> split_name_dependencies;
};
} // namespace aapt
diff --git a/tools/aapt2/Configuration.proto b/tools/aapt2/Configuration.proto
index fc636a43ec40..8a4644c9a219 100644
--- a/tools/aapt2/Configuration.proto
+++ b/tools/aapt2/Configuration.proto
@@ -19,7 +19,6 @@ syntax = "proto3";
package aapt.pb;
option java_package = "com.android.aapt";
-option optimize_for = LITE_RUNTIME;
// A description of the requirements a device must have in order for a
// resource to be matched and selected.
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 3da22b4fb9fa..7ffa5ffc09fe 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -178,19 +178,17 @@ class ValueBodyPrinter : public ConstValueVisitor {
void Visit(const Array* array) override {
const size_t count = array->elements.size();
printer_->Print("[");
- if (count > 0) {
- for (size_t i = 0u; i < count; i++) {
- if (i != 0u && i % 4u == 0u) {
- printer_->Println();
- printer_->Print(" ");
- }
- PrintItem(*array->elements[i]);
- if (i != count - 1) {
- printer_->Print(", ");
- }
+ for (size_t i = 0u; i < count; i++) {
+ if (i != 0u && i % 4u == 0u) {
+ printer_->Println();
+ printer_->Print(" ");
+ }
+ PrintItem(*array->elements[i]);
+ if (i != count - 1) {
+ printer_->Print(", ");
}
- printer_->Println("]");
}
+ printer_->Println("]");
}
void Visit(const Plural* plural) override {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 45cea8190844..c55765663204 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -769,16 +769,14 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
return std::move(string);
}
- // If the text is empty, and the value is not allowed to be a string, encode it as a @null.
- if (util::TrimWhitespace(raw_value).empty()) {
- return ResourceUtils::MakeNull();
- }
-
if (allow_raw_value) {
// We can't parse this so return a RawString if we are allowed.
return util::make_unique<RawString>(
table_->string_pool.MakeRef(util::TrimWhitespace(raw_value),
StringPool::Context(config_)));
+ } else if (util::TrimWhitespace(raw_value).empty()) {
+ // If the text is empty, and the value is not allowed to be a string, encode it as a @null.
+ return ResourceUtils::MakeNull();
}
return {};
}
@@ -1389,7 +1387,7 @@ Maybe<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(
return Attribute::Symbol{
Reference(ResourceNameRef({}, ResourceType::kId, maybe_name.value())),
- val.data};
+ val.data, val.dataType};
}
bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 464225fefb85..42374690d135 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -401,7 +401,7 @@ TEST_F(ResourceParserTest, ParseEnumAttr) {
std::string input = R"(
<attr name="foo">
<enum name="bar" value="0"/>
- <enum name="bat" value="1"/>
+ <enum name="bat" value="0x1"/>
<enum name="baz" value="2"/>
</attr>)";
ASSERT_TRUE(TestParse(input));
@@ -414,14 +414,17 @@ TEST_F(ResourceParserTest, ParseEnumAttr) {
ASSERT_TRUE(enum_attr->symbols[0].symbol.name);
EXPECT_THAT(enum_attr->symbols[0].symbol.name.value().entry, Eq("bar"));
EXPECT_THAT(enum_attr->symbols[0].value, Eq(0u));
+ EXPECT_THAT(enum_attr->symbols[0].type, Eq(Res_value::TYPE_INT_DEC));
ASSERT_TRUE(enum_attr->symbols[1].symbol.name);
EXPECT_THAT(enum_attr->symbols[1].symbol.name.value().entry, Eq("bat"));
EXPECT_THAT(enum_attr->symbols[1].value, Eq(1u));
+ EXPECT_THAT(enum_attr->symbols[1].type, Eq(Res_value::TYPE_INT_HEX));
ASSERT_TRUE(enum_attr->symbols[2].symbol.name);
EXPECT_THAT(enum_attr->symbols[2].symbol.name.value().entry, Eq("baz"));
EXPECT_THAT(enum_attr->symbols[2].value, Eq(2u));
+ EXPECT_THAT(enum_attr->symbols[2].type, Eq(Res_value::TYPE_INT_DEC));
}
TEST_F(ResourceParserTest, ParseFlagAttr) {
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 836e199593fc..1773b5a8addf 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -267,8 +267,7 @@ bool ResourceEntry::HasDefaultValue() const {
// A DECL will override a USE without error. Two DECLs must match in their format for there to be
// no error.
ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* existing,
- Value* incoming,
- bool overlay) {
+ Value* incoming) {
Attribute* existing_attr = ValueCast<Attribute>(existing);
Attribute* incoming_attr = ValueCast<Attribute>(incoming);
if (!incoming_attr) {
@@ -282,7 +281,7 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
}
// The existing and incoming values are strong, this is an error
// if the values are not both attributes.
- return overlay ? CollisionResult::kTakeNew : CollisionResult::kConflict;
+ return CollisionResult::kConflict;
}
if (!existing_attr) {
@@ -293,7 +292,7 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
}
// The existing value is not an attribute and it is strong,
// so the incoming attribute value is an error.
- return overlay ? CollisionResult::kTakeNew : CollisionResult::kConflict;
+ return CollisionResult::kConflict;
}
CHECK(incoming_attr != nullptr && existing_attr != nullptr);
@@ -324,9 +323,8 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
return CollisionResult::kConflict;
}
-ResourceTable::CollisionResult ResourceTable::IgnoreCollision(Value* /* existing */,
- Value* /* incoming */,
- bool /* overlay */) {
+ResourceTable::CollisionResult ResourceTable::IgnoreCollision(Value* /** existing **/,
+ Value* /** incoming **/) {
return CollisionResult::kKeepBoth;
}
@@ -442,7 +440,7 @@ bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceI
// Resource does not exist, add it now.
config_value->value = std::move(value);
} else {
- switch (conflict_resolver(config_value->value.get(), value.get(), false /* overlay */)) {
+ switch (conflict_resolver(config_value->value.get(), value.get())) {
case CollisionResult::kKeepBoth:
// Insert the value ignoring for duplicate configurations
entry->values.push_back(util::make_unique<ResourceConfigValue>(config, product));
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index e8793800b148..30ba1aed25f8 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -228,13 +228,13 @@ class ResourceTable {
enum class CollisionResult { kKeepBoth, kKeepOriginal, kConflict, kTakeNew };
- using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*, bool)>;
+ using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
// When a collision of resources occurs, this method decides which value to keep.
- static CollisionResult ResolveValueCollision(Value* existing, Value* incoming, bool overlay);
+ static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
// When a collision of resources occurs, this method keeps both values
- static CollisionResult IgnoreCollision(Value* existing, Value* incoming, bool overlay);
+ static CollisionResult IgnoreCollision(Value* existing, Value* incoming);
bool AddResource(const ResourceNameRef& name, const android::ConfigDescription& config,
const android::StringPiece& product, std::unique_ptr<Value> value,
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index e0040e486a23..bd2ab5377311 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -378,7 +378,7 @@ std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
const ResourceName& enum_symbol_resource_name = symbol.symbol.name.value();
if (trimmed_str == enum_symbol_resource_name.entry) {
android::Res_value value = {};
- value.dataType = android::Res_value::TYPE_INT_DEC;
+ value.dataType = symbol.type;
value.data = symbol.value;
return util::make_unique<BinaryPrimitive>(value);
}
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 696012786e6d..34b46c552e0c 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -574,10 +574,6 @@ bool Attribute::Equals(const Value* value) const {
}
bool Attribute::IsCompatibleWith(const Attribute& attr) const {
- if (Equals(&attr)) {
- return true;
- }
-
// If the high bits are set on any of these attribute type masks, then they are incompatible.
// We don't check that flags and enums are identical.
if ((type_mask & ~android::ResTable_map::TYPE_ANY) != 0 ||
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 168ad61784e7..fe0883be50aa 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -292,6 +292,7 @@ struct Attribute : public BaseValue<Attribute> {
struct Symbol {
Reference symbol;
uint32_t value;
+ uint8_t type;
friend std::ostream& operator<<(std::ostream& out, const Symbol& symbol);
};
diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp
index dbf51143f720..c4a1108ac62a 100644
--- a/tools/aapt2/ResourceValues_test.cpp
+++ b/tools/aapt2/ResourceValues_test.cpp
@@ -284,58 +284,8 @@ TEST(ResourcesValuesTest, AttributeIsCompatible) {
EXPECT_FALSE(attr_three.IsCompatibleWith(attr_one));
EXPECT_FALSE(attr_three.IsCompatibleWith(attr_two));
- EXPECT_TRUE(attr_three.IsCompatibleWith(attr_three));
+ EXPECT_FALSE(attr_three.IsCompatibleWith(attr_three));
EXPECT_FALSE(attr_three.IsCompatibleWith(attr_four));
-
- EXPECT_FALSE(attr_four.IsCompatibleWith(attr_one));
- EXPECT_FALSE(attr_four.IsCompatibleWith(attr_two));
- EXPECT_FALSE(attr_four.IsCompatibleWith(attr_three));
- EXPECT_TRUE(attr_four.IsCompatibleWith(attr_four));
-}
-
-TEST(ResourcesValuesTest, AttributeEnumIsCompatible) {
- Attribute attr_one(TYPE_ENUM);
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u});
-
- Attribute attr_two(TYPE_ENUM);
- attr_two.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_two.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u});
- EXPECT_TRUE(attr_one.IsCompatibleWith(attr_two));
-}
-
-TEST(ResourcesValuesTest, DifferentAttributeEnumDifferentNameIsNotCompatible) {
- Attribute attr_one(TYPE_ENUM);
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u});
-
- Attribute attr_two(TYPE_ENUM);
- attr_two.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/baz")), 0x07u});
- EXPECT_FALSE(attr_one.IsCompatibleWith(attr_two));
-}
-
-TEST(ResourcesValuesTest, DifferentAttributeEnumDifferentValueIsNotCompatible) {
- Attribute attr_one(TYPE_ENUM);
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u});
-
- Attribute attr_two(TYPE_ENUM);
- attr_two.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_two.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x09u});
- EXPECT_FALSE(attr_one.IsCompatibleWith(attr_two));
}
} // namespace aapt
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index b2fc08423d34..7498e132d943 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -21,7 +21,6 @@ import "frameworks/base/tools/aapt2/Configuration.proto";
package aapt.pb;
option java_package = "com.android.aapt";
-option optimize_for = LITE_RUNTIME;
// A string pool that wraps the binary form of the C++ class android::ResStringPool.
message StringPool {
@@ -388,6 +387,9 @@ message Attribute {
// The value of the enum/flag.
uint32 value = 4;
+
+ // The data type of the enum/flag as defined in android::Res_value.
+ uint32 type = 5;
}
// Bitmask of formats allowed for an attribute.
diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto
index 520b242ee509..b0ed3da33368 100644
--- a/tools/aapt2/ResourcesInternal.proto
+++ b/tools/aapt2/ResourcesInternal.proto
@@ -22,7 +22,6 @@ import "frameworks/base/tools/aapt2/Resources.proto";
package aapt.pb.internal;
option java_package = "android.aapt.pb.internal";
-option optimize_for = LITE_RUNTIME;
// The top level message representing an external resource file (layout XML, PNG, etc).
// This is used to represent a compiled file before it is linked. Only useful to aapt2.
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 9b81369fa9f0..c7ac438dacfb 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -629,6 +629,12 @@ class CompileContext : public IAaptContext {
return 0;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ UNIMPLEMENTED(FATAL) << "No Split Name Dependencies be needed in compile phase";
+ static std::set<std::string> empty;
+ return empty;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(CompileContext);
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index 5f637bd8d582..fb786a31360e 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -200,7 +200,7 @@ static void AssertTranslations(CommandTestFixture *ctf, std::string file_name,
const std::string compiled_files_dir = ctf->GetTestPath("/compiled_" + file_name);
const std::string out_apk = ctf->GetTestPath("/" + file_name + ".apk");
- CHECK(ctf->WriteFile(source_file, sTranslatableXmlContent));
+ ctf->WriteFile(source_file, sTranslatableXmlContent);
CHECK(file::mkdirs(compiled_files_dir.data()));
ASSERT_EQ(CompileCommand(&diag).Execute({
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 0cf86ccdd59f..22bcd8589ce9 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -243,6 +243,12 @@ class Context : public IAaptContext {
return 0u;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
+ static std::set<std::string> empty;
+ return empty;
+ }
+
bool verbose_ = false;
std::string package_;
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 262f4fc4e394..d56994e3ae24 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -65,6 +65,12 @@ class DiffContext : public IAaptContext {
return 0;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
+ static std::set<std::string> empty;
+ return empty;
+ }
+
private:
std::string empty_;
StdErrDiagnostics diagnostics_;
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index a23a6a46cf0f..429aff1ff594 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -118,6 +118,12 @@ class DumpContext : public IAaptContext {
return 0;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
+ static std::set<std::string> empty;
+ return empty;
+ }
+
private:
StdErrDiagnostics diagnostics_;
bool verbose_ = false;
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index f354bb610224..bbf71e70c396 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -140,6 +140,14 @@ class LinkContext : public IAaptContext {
min_sdk_version_ = minSdk;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ return split_name_dependencies_;
+ }
+
+ void SetSplitNameDependencies(const std::set<std::string>& split_name_dependencies) {
+ split_name_dependencies_ = split_name_dependencies;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(LinkContext);
@@ -151,6 +159,7 @@ class LinkContext : public IAaptContext {
SymbolTable symbols_;
bool verbose_ = false;
int min_sdk_version_ = 0;
+ std::set<std::string> split_name_dependencies_;
};
// A custom delegate that generates compatible pre-O IDs for use with feature splits.
@@ -269,6 +278,7 @@ struct ResourceFileFlattenerOptions {
bool keep_raw_values = false;
bool do_not_compress_anything = false;
bool update_proguard_spec = false;
+ bool do_not_fail_on_missing_resources = false;
OutputFormat output_format = OutputFormat::kApk;
std::unordered_set<std::string> extensions_to_not_compress;
Maybe<std::regex> regex_to_not_compress;
@@ -297,6 +307,25 @@ struct R {
};
};
+template <typename T>
+uint32_t GetCompressionFlags(const StringPiece& str, T options) {
+ if (options.do_not_compress_anything) {
+ return 0;
+ }
+
+ if (options.regex_to_not_compress
+ && std::regex_search(str.to_string(), options.regex_to_not_compress.value())) {
+ return 0;
+ }
+
+ for (const std::string& extension : options.extensions_to_not_compress) {
+ if (util::EndsWith(str, extension)) {
+ return 0;
+ }
+ }
+ return ArchiveEntry::kCompress;
+}
+
class ResourceFileFlattener {
public:
ResourceFileFlattener(const ResourceFileFlattenerOptions& options, IAaptContext* context,
@@ -321,8 +350,6 @@ class ResourceFileFlattener {
std::string dst_path;
};
- uint32_t GetCompressionFlags(const StringPiece& str);
-
std::vector<std::unique_ptr<xml::XmlResource>> LinkAndVersionXmlFile(ResourceTable* table,
FileOperation* file_op);
@@ -381,26 +408,6 @@ ResourceFileFlattener::ResourceFileFlattener(const ResourceFileFlattenerOptions&
}
}
-// TODO(rtmitchell): turn this function into a variable that points to a method that retrieves the
-// compression flag
-uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) {
- if (options_.do_not_compress_anything) {
- return 0;
- }
-
- if (options_.regex_to_not_compress
- && std::regex_search(str.to_string(), options_.regex_to_not_compress.value())) {
- return 0;
- }
-
- for (const std::string& extension : options_.extensions_to_not_compress) {
- if (util::EndsWith(str, extension)) {
- return 0;
- }
- }
- return ArchiveEntry::kCompress;
-}
-
static bool IsTransitionElement(const std::string& name) {
return name == "fade" || name == "changeBounds" || name == "slide" || name == "explode" ||
name == "changeImageTransform" || name == "changeTransform" ||
@@ -438,7 +445,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer
xml::StripAndroidStudioAttributes(doc->root.get());
XmlReferenceLinker xml_linker;
- if (!xml_linker.Consume(context_, doc)) {
+ if (!options_.do_not_fail_on_missing_resources && !xml_linker.Consume(context_, doc)) {
return {};
}
@@ -640,7 +647,8 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
}
} else {
error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path,
- GetCompressionFlags(file_op.dst_path), archive_writer);
+ GetCompressionFlags(file_op.dst_path, options_),
+ archive_writer);
}
}
}
@@ -965,6 +973,17 @@ class Linker {
app_info.min_sdk_version = ResourceUtils::ParseSdkVersion(min_sdk->value);
}
}
+
+ for (const xml::Element* child_el : manifest_el->GetChildElements()) {
+ if (child_el->namespace_uri.empty() && child_el->name == "uses-split") {
+ if (const xml::Attribute* split_name =
+ child_el->FindAttribute(xml::kSchemaAndroid, "name")) {
+ if (!split_name->value.empty()) {
+ app_info.split_name_dependencies.insert(split_name->value);
+ }
+ }
+ }
+ }
return app_info;
}
@@ -1547,16 +1566,7 @@ class Linker {
}
for (auto& entry : merged_assets) {
- uint32_t compression_flags = ArchiveEntry::kCompress;
- std::string extension = file::GetExtension(entry.first).to_string();
-
- if (options_.do_not_compress_anything
- || options_.extensions_to_not_compress.count(extension) > 0
- || (options_.regex_to_not_compress
- && std::regex_search(extension, options_.regex_to_not_compress.value()))) {
- compression_flags = 0u;
- }
-
+ uint32_t compression_flags = GetCompressionFlags(entry.first, options_);
if (!io::CopyFileToArchive(context_, entry.second.get(), entry.first, compression_flags,
writer)) {
return false;
@@ -1565,6 +1575,93 @@ class Linker {
return true;
}
+ void AliasAdaptiveIcon(xml::XmlResource* manifest, ResourceTable* table) {
+ xml::Element* application = manifest->root->FindChild("", "application");
+ if (!application) {
+ return;
+ }
+
+ xml::Attribute* icon = application->FindAttribute(xml::kSchemaAndroid, "icon");
+ xml::Attribute* round_icon = application->FindAttribute(xml::kSchemaAndroid, "roundIcon");
+ if (!icon || !round_icon) {
+ return;
+ }
+
+ // Find the icon resource defined within the application.
+ auto icon_reference = ValueCast<Reference>(icon->compiled_value.get());
+ if (!icon_reference || !icon_reference->name) {
+ return;
+ }
+ auto package = table->FindPackageById(icon_reference->id.value().package_id());
+ if (!package) {
+ return;
+ }
+ auto type = package->FindType(icon_reference->name.value().type);
+ if (!type) {
+ return;
+ }
+ auto icon_entry = type->FindEntry(icon_reference->name.value().entry);
+ if (!icon_entry) {
+ return;
+ }
+
+ int icon_max_sdk = 0;
+ for (auto& config_value : icon_entry->values) {
+ icon_max_sdk = (icon_max_sdk < config_value->config.sdkVersion)
+ ? config_value->config.sdkVersion : icon_max_sdk;
+ }
+ if (icon_max_sdk < SDK_O) {
+ // Adaptive icons must be versioned with v26 qualifiers, so this is not an adaptive icon.
+ return;
+ }
+
+ // Find the roundIcon resource defined within the application.
+ auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get());
+ if (!round_icon_reference || !round_icon_reference->name) {
+ return;
+ }
+ package = table->FindPackageById(round_icon_reference->id.value().package_id());
+ if (!package) {
+ return;
+ }
+ type = package->FindType(round_icon_reference->name.value().type);
+ if (!type) {
+ return;
+ }
+ auto round_icon_entry = type->FindEntry(round_icon_reference->name.value().entry);
+ if (!round_icon_entry) {
+ return;
+ }
+
+ int round_icon_max_sdk = 0;
+ for (auto& config_value : round_icon_entry->values) {
+ round_icon_max_sdk = (round_icon_max_sdk < config_value->config.sdkVersion)
+ ? config_value->config.sdkVersion : round_icon_max_sdk;
+ }
+ if (round_icon_max_sdk >= SDK_O) {
+ // The developer explicitly used a v26 compatible drawable as the roundIcon, meaning we should
+ // not generate an alias to the icon drawable.
+ return;
+ }
+
+ // Add an equivalent v26 entry to the roundIcon for each v26 variant of the regular icon.
+ for (auto& config_value : icon_entry->values) {
+ if (config_value->config.sdkVersion < SDK_O) {
+ continue;
+ }
+
+ context_->GetDiagnostics()->Note(DiagMessage() << "generating "
+ << round_icon_reference->name.value()
+ << " with config \"" << config_value->config
+ << "\" for round icon compatibility");
+
+ auto value = icon_reference->Clone(&table->string_pool);
+ auto round_config_value = round_icon_entry->FindOrCreateValue(
+ config_value->config, config_value->product);
+ round_config_value->value.reset(value);
+ }
+ }
+
// Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
// to the IArchiveWriter.
bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
@@ -1578,6 +1675,14 @@ class Linker {
return false;
}
+ // When a developer specifies an adaptive application icon, and a non-adaptive round application
+ // icon, create an alias from the round icon to the regular icon for v26 APIs and up. We do this
+ // because certain devices prefer android:roundIcon over android:icon regardless of the API
+ // levels of the drawables set for either. This auto-aliasing behaviour allows an app to prefer
+ // the android:roundIcon on API 25 devices, and prefer the adaptive icon on API 26 devices.
+ // See (b/34829129)
+ AliasAdaptiveIcon(manifest, table);
+
ResourceFileFlattenerOptions file_flattener_options;
file_flattener_options.keep_raw_values = keep_raw_values;
file_flattener_options.do_not_compress_anything = options_.do_not_compress_anything;
@@ -1590,9 +1695,9 @@ class Linker {
file_flattener_options.update_proguard_spec =
static_cast<bool>(options_.generate_proguard_rules_path);
file_flattener_options.output_format = options_.output_format;
+ file_flattener_options.do_not_fail_on_missing_resources = options_.merge_only;
ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set);
-
if (!file_flattener.Flatten(table, writer)) {
context_->GetDiagnostics()->Error(DiagMessage() << "failed linking file resources");
return false;
@@ -1687,6 +1792,7 @@ class Linker {
context_->SetMinSdkVersion(app_info_.min_sdk_version.value_or_default(0));
context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()});
+ context_->SetSplitNameDependencies(app_info_.split_name_dependencies);
// Override the package ID when it is "android".
if (context_->GetCompilationPackage() == "android") {
@@ -1702,6 +1808,8 @@ class Linker {
TableMergerOptions table_merger_options;
table_merger_options.auto_add_overlay = options_.auto_add_overlay;
+ table_merger_options.override_styles_instead_of_overlaying =
+ options_.override_styles_instead_of_overlaying;
table_merger_options.strict_visibility = options_.strict_visibility;
table_merger_ = util::make_unique<TableMerger>(context_, &final_table_, table_merger_options);
@@ -1816,7 +1924,7 @@ class Linker {
}
ReferenceLinker linker;
- if (!linker.Consume(context_, &final_table_)) {
+ if (!options_.merge_only && !linker.Consume(context_, &final_table_)) {
context_->GetDiagnostics()->Error(DiagMessage() << "failed linking references");
return 1;
}
@@ -1968,7 +2076,7 @@ class Linker {
manifest_xml->file.name.package = context_->GetCompilationPackage();
XmlReferenceLinker manifest_linker;
- if (manifest_linker.Consume(context_, manifest_xml.get())) {
+ if (options_.merge_only || manifest_linker.Consume(context_, manifest_xml.get())) {
if (options_.generate_proguard_rules_path &&
!proguard::CollectProguardRulesForManifest(manifest_xml.get(), &proguard_keep_set)) {
error = true;
@@ -2102,6 +2210,12 @@ int LinkCommand::Action(const std::vector<std::string>& args) {
return 1;
}
+ if (options_.merge_only && !static_lib_) {
+ context.GetDiagnostics()->Error(
+ DiagMessage() << "the --merge-only flag can be only used when building a static library");
+ return 1;
+ }
+
// The default build type.
context.SetPackageType(PackageType::kApp);
context.SetPackageId(kAppPackageId);
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 7c583858ee1d..324807c55215 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -42,6 +42,7 @@ struct LinkOptions {
std::vector<std::string> assets_dirs;
bool output_to_directory = false;
bool auto_add_overlay = false;
+ bool override_styles_instead_of_overlaying = false;
OutputFormat output_format = OutputFormat::kApk;
// Java/Proguard options.
@@ -70,6 +71,7 @@ struct LinkOptions {
// Static lib options.
bool no_static_lib_packages = false;
+ bool merge_only = false;
// AndroidManifest.xml massaging options.
ManifestFixerOptions manifest_fixer_options;
@@ -242,13 +244,17 @@ class LinkCommand : public Command {
"Allows the addition of new resources in overlays without\n"
"<add-resource> tags.",
&options_.auto_add_overlay);
+ AddOptionalSwitch("--override-styles-instead-of-overlaying",
+ "Causes styles defined in -R resources to replace previous definitions\n"
+ "instead of merging into them\n",
+ &options_.override_styles_instead_of_overlaying);
AddOptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml.",
&options_.manifest_fixer_options.rename_manifest_package);
AddOptionalFlag("--rename-instrumentation-target-package",
"Changes the name of the target package for instrumentation. Most useful\n"
"when used in conjunction with --rename-manifest-package.",
&options_.manifest_fixer_options.rename_instrumentation_target_package);
- AddOptionalFlagList("-0", "File extensions not to compress.",
+ AddOptionalFlagList("-0", "File suffix not to compress.",
&options_.extensions_to_not_compress);
AddOptionalSwitch("--no-compress", "Do not compress any resources.",
&options_.do_not_compress_anything);
@@ -280,6 +286,10 @@ class LinkCommand : public Command {
AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
AddOptionalFlag("--trace-folder", "Generate systrace json trace fragment to specified folder.",
&trace_folder_);
+ AddOptionalSwitch("--merge-only",
+ "Only merge the resources, without verifying resource references. This flag\n"
+ "should only be used together with the --static-lib flag.",
+ &options_.merge_only);
}
int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 9ea93f638aff..062dd8eac975 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "AppInfo.h"
#include "Link.h"
#include "LoadedApk.h"
@@ -43,10 +44,8 @@ TEST_F(LinkTest, RemoveRawXmlStrings) {
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
-
std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
ASSERT_THAT(data, Ne(nullptr));
-
AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has not been assigned
@@ -71,10 +70,8 @@ TEST_F(LinkTest, KeepRawXmlStrings) {
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
-
std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
ASSERT_THAT(data, Ne(nullptr));
-
AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has been set to the correct string pool entry
@@ -83,4 +80,241 @@ TEST_F(LinkTest, KeepRawXmlStrings) {
EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007"));
}
-} // namespace aapt \ No newline at end of file
+TEST_F(LinkTest, NoCompressAssets) {
+ StdErrDiagnostics diag;
+ std::string content(500, 'a');
+ WriteFile(GetTestPath("assets/testtxt"), content);
+ WriteFile(GetTestPath("assets/testtxt2"), content);
+ WriteFile(GetTestPath("assets/test.txt"), content);
+ WriteFile(GetTestPath("assets/test.hello.txt"), content);
+ WriteFile(GetTestPath("assets/test.hello.xml"), content);
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(),
+ "-o", out_apk,
+ "-0", ".txt",
+ "-0", "txt2",
+ "-0", ".hello.txt",
+ "-0", "hello.xml",
+ "-A", GetTestPath("assets")
+ };
+
+ ASSERT_TRUE(Link(link_args, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_THAT(apk, Ne(nullptr));
+ io::IFileCollection* zip = apk->GetFileCollection();
+ ASSERT_THAT(zip, Ne(nullptr));
+
+ auto file = zip->FindFile("assets/testtxt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_TRUE(file->WasCompressed());
+
+ file = zip->FindFile("assets/testtxt2");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("assets/test.txt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("assets/test.hello.txt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("assets/test.hello.xml");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+}
+
+TEST_F(LinkTest, NoCompressResources) {
+ StdErrDiagnostics diag;
+ std::string content(500, 'a');
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/raw/testtxt"), content, compiled_files_dir, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test.txt"), content, compiled_files_dir, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test1.hello.txt"), content, compiled_files_dir,
+ &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test2.goodbye.xml"), content, compiled_files_dir,
+ &diag));
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(),
+ "-o", out_apk,
+ "-0", ".txt",
+ "-0", ".hello.txt",
+ "-0", "goodbye.xml",
+ };
+
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_THAT(apk, Ne(nullptr));
+ io::IFileCollection* zip = apk->GetFileCollection();
+ ASSERT_THAT(zip, Ne(nullptr));
+
+ auto file = zip->FindFile("res/raw/testtxt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_TRUE(file->WasCompressed());
+
+ file = zip->FindFile("res/raw/test.txt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("res/raw/test1.hello.hello.txt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("res/raw/test2.goodbye.goodbye.xml");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+}
+
+TEST_F(LinkTest, OverlayStyles) {
+ StdErrDiagnostics diag;
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ const std::string override_files_dir = GetTestPath("compiled-override");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:textColor">#123</item>
+ </style>
+ </resources>)",
+ compiled_files_dir, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:background">#456</item>
+ </style>
+ </resources>)",
+ override_files_dir, &diag));
+
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(kDefaultPackageName),
+ "-o", out_apk,
+ };
+ const auto override_files = file::FindFiles(override_files_dir, &diag);
+ for (const auto &override_file : override_files.value()) {
+ link_args.push_back("-R");
+ link_args.push_back(file::BuildPath({override_files_dir, override_file}));
+ }
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ const Style* actual_style = test::GetValue<Style>(
+ apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle");
+ ASSERT_NE(actual_style, nullptr);
+ ASSERT_EQ(actual_style->entries.size(), 2);
+ EXPECT_EQ(actual_style->entries[0].key.id, 0x01010098); // android:textColor
+ EXPECT_EQ(actual_style->entries[1].key.id, 0x010100d4); // android:background
+}
+
+TEST_F(LinkTest, OverrideStylesInsteadOfOverlaying) {
+ StdErrDiagnostics diag;
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ const std::string override_files_dir = GetTestPath("compiled-override");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:textColor">#123</item>
+ </style>
+ </resources>)",
+ compiled_files_dir, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:background">#456</item>
+ </style>
+ </resources>)",
+ override_files_dir, &diag));
+
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(kDefaultPackageName),
+ "--override-styles-instead-of-overlaying",
+ "-o", out_apk,
+ };
+ const auto override_files = file::FindFiles(override_files_dir, &diag);
+ for (const auto &override_file : override_files.value()) {
+ link_args.push_back("-R");
+ link_args.push_back(file::BuildPath({override_files_dir, override_file}));
+ }
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ const Style* actual_style = test::GetValue<Style>(
+ apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle");
+ ASSERT_NE(actual_style, nullptr);
+ ASSERT_EQ(actual_style->entries.size(), 1);
+ EXPECT_EQ(actual_style->entries[0].key.id, 0x010100d4); // android:background
+}
+
+TEST_F(LinkTest, AppInfoWithUsesSplit) {
+ StdErrDiagnostics diag;
+ const std::string base_files_dir = GetTestPath("base");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <string name="bar">bar</string>
+ </resources>)",
+ base_files_dir, &diag));
+ const std::string base_apk = GetTestPath("base.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest("com.aapt2.app"),
+ "-o", base_apk,
+ };
+ ASSERT_TRUE(Link(link_args, base_files_dir, &diag));
+
+ const std::string feature_manifest = GetTestPath("feature_manifest.xml");
+ WriteFile(feature_manifest, android::base::StringPrintf(R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.aapt2.app" split="feature1">
+ </manifest>)"));
+ const std::string feature_files_dir = GetTestPath("feature");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <string name="foo">foo</string>
+ </resources>)",
+ feature_files_dir, &diag));
+ const std::string feature_apk = GetTestPath("feature.apk");
+ const std::string feature_package_id = "0x80";
+ link_args = {
+ "--manifest", feature_manifest,
+ "-I", base_apk,
+ "--package-id", feature_package_id,
+ "-o", feature_apk,
+ };
+ ASSERT_TRUE(Link(link_args, feature_files_dir, &diag));
+
+ const std::string feature2_manifest = GetTestPath("feature2_manifest.xml");
+ WriteFile(feature2_manifest, android::base::StringPrintf(R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.aapt2.app" split="feature2">
+ <uses-split android:name="feature1"/>
+ </manifest>)"));
+ const std::string feature2_files_dir = GetTestPath("feature2");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <string-array name="string_array">
+ <item>@string/bar</item>
+ <item>@string/foo</item>
+ </string-array>
+ </resources>)",
+ feature2_files_dir, &diag));
+ const std::string feature2_apk = GetTestPath("feature2.apk");
+ const std::string feature2_package_id = "0x81";
+ link_args = {
+ "--manifest", feature2_manifest,
+ "-I", base_apk,
+ "-I", feature_apk,
+ "--package-id", feature2_package_id,
+ "-o", feature2_apk,
+ };
+ ASSERT_TRUE(Link(link_args, feature2_files_dir, &diag));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 2e6af18c1948..5e06818d7a13 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -108,6 +108,12 @@ class OptimizeContext : public IAaptContext {
return sdk_version_;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
+ static std::set<std::string> empty;
+ return empty;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index c5de9e058907..5e0300b3071b 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -231,7 +231,7 @@ class Visitor : public ValueVisitor {
Visitor sub_visitor(pool_, method_);
if (plural->values[i]) {
plural->values[i]->Accept(&sub_visitor);
- if (sub_visitor.value) {
+ if (sub_visitor.item) {
localized->values[i] = std::move(sub_visitor.item);
} else {
localized->values[i] = std::unique_ptr<Item>(plural->values[i]->Clone(pool_));
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
index 31358020ab60..e816c86e20a8 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -234,6 +234,27 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) {
test::ParseConfigOrDie("ar-rXB")));
}
+TEST(PseudolocaleGeneratorTest, PluralsArePseudolocalized) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder().SetPackageId("com.pkg", 0x7F).Build();
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ plural->values = {util::make_unique<String>(table->string_pool.MakeRef("zero")),
+ util::make_unique<String>(table->string_pool.MakeRef("one"))};
+ ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.pkg:plurals/foo"), ConfigDescription{},
+ {}, std::move(plural), context->GetDiagnostics()));
+ std::unique_ptr<Plural> expected = util::make_unique<Plural>();
+ expected->values = {util::make_unique<String>(table->string_pool.MakeRef("[žéŕö one]")),
+ util::make_unique<String>(table->string_pool.MakeRef("[öñé one]"))};
+
+ PseudolocaleGenerator generator;
+ ASSERT_TRUE(generator.Consume(context.get(), table.get()));
+
+ const auto* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo",
+ test::ParseConfigOrDie("en-rXA"));
+ EXPECT_TRUE(actual->Equals(expected.get()));
+}
+
TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) {
std::unique_ptr<IAaptContext> context =
test::ContextBuilder().SetCompilationPackage("android").Build();
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index 92f1ddb292e1..54f0816f0398 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -291,7 +291,10 @@ class ManifestExtractor {
}
}
}
- return &attr->value;
+
+ if (!attr->value.empty()) {
+ return &attr->value;
+ }
}
return nullptr;
}
@@ -425,6 +428,8 @@ class Manifest : public ManifestExtractor::Element {
const std::string* split = nullptr;
const std::string* platformVersionName = nullptr;
const std::string* platformVersionCode = nullptr;
+ const int32_t* platformVersionNameInt = nullptr;
+ const int32_t* platformVersionCodeInt = nullptr;
const int32_t* compilesdkVersion = nullptr;
const std::string* compilesdkVersionCodename = nullptr;
const int32_t* installLocation = nullptr;
@@ -440,6 +445,10 @@ class Manifest : public ManifestExtractor::Element {
"platformBuildVersionName"));
platformVersionCode = GetAttributeString(FindAttribute(manifest, {},
"platformBuildVersionCode"));
+ platformVersionNameInt = GetAttributeInteger(FindAttribute(manifest, {},
+ "platformBuildVersionName"));
+ platformVersionCodeInt = GetAttributeInteger(FindAttribute(manifest, {},
+ "platformBuildVersionCode"));
// Extract the compile sdk info
compilesdkVersion = GetAttributeInteger(FindAttribute(manifest, COMPILE_SDK_VERSION_ATTR));
@@ -460,9 +469,15 @@ class Manifest : public ManifestExtractor::Element {
if (platformVersionName) {
printer->Print(StringPrintf(" platformBuildVersionName='%s'", platformVersionName->data()));
}
+ if (platformVersionNameInt) {
+ printer->Print(StringPrintf(" platformBuildVersionName='%d'", *platformVersionNameInt));
+ }
if (platformVersionCode) {
printer->Print(StringPrintf(" platformBuildVersionCode='%s'", platformVersionCode->data()));
}
+ if (platformVersionCodeInt) {
+ printer->Print(StringPrintf(" platformBuildVersionCode='%d'", *platformVersionCodeInt));
+ }
if (compilesdkVersion) {
printer->Print(StringPrintf(" compileSdkVersion='%d'", *compilesdkVersion));
}
diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp
index d152a9cc7e62..41f01a01ed7c 100644
--- a/tools/aapt2/format/Archive.cpp
+++ b/tools/aapt2/format/Archive.cpp
@@ -103,7 +103,13 @@ class DirectoryWriter : public IArchiveWriter {
return false;
}
}
- return !in->HadError();
+
+ if (in->HadError()) {
+ error_ = in->GetError();
+ return false;
+ }
+
+ return FinishEntry();
}
bool HadError() const override {
@@ -191,6 +197,7 @@ class ZipFileWriter : public IArchiveWriter {
}
if (in->HadError()) {
+ error_ = in->GetError();
return false;
}
diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp
new file mode 100644
index 000000000000..ceed3740f37a
--- /dev/null
+++ b/tools/aapt2/format/Archive_test.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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 "test/Test.h"
+
+namespace aapt {
+
+using ArchiveTest = TestDirectoryFixture;
+
+constexpr size_t kTestDataLength = 100;
+
+class TestData : public io::MallocData {
+ public:
+ TestData(std::unique_ptr<uint8_t[]>& data, size_t size)
+ : MallocData(std::move(data), size) {}
+
+ bool HadError() const override { return !error_.empty(); }
+
+ std::string GetError() const override { return error_; }
+
+ std::string error_;
+};
+
+std::unique_ptr<uint8_t[]> MakeTestArray() {
+ auto array = std::make_unique<uint8_t[]>(kTestDataLength);
+ for (int index = 0; index < kTestDataLength; ++index) {
+ array[index] = static_cast<uint8_t>(rand());
+ }
+ return array;
+}
+
+std::unique_ptr<IArchiveWriter> MakeDirectoryWriter(const std::string& output_path) {
+ file::mkdirs(output_path);
+
+ StdErrDiagnostics diag;
+ return CreateDirectoryArchiveWriter(&diag, output_path);
+}
+
+std::unique_ptr<IArchiveWriter> MakeZipFileWriter(const std::string& output_path) {
+ file::mkdirs(file::GetStem(output_path).to_string());
+ std::remove(output_path.c_str());
+
+ StdErrDiagnostics diag;
+ return CreateZipFileArchiveWriter(&diag, output_path);
+}
+
+void VerifyDirectory(const std::string& path, const std::string& file, const uint8_t array[]) {
+ std::string file_path = file::BuildPath({path, file});
+ auto buffer = std::make_unique<char[]>(kTestDataLength);
+ std::ifstream stream(file_path);
+ stream.read(buffer.get(), kTestDataLength);
+
+ for (int index = 0; index < kTestDataLength; ++index) {
+ ASSERT_EQ(array[index], static_cast<uint8_t>(buffer[index]));
+ }
+}
+
+void VerifyZipFile(const std::string& output_path, const std::string& file, const uint8_t array[]) {
+ std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(output_path, nullptr);
+ std::unique_ptr<io::InputStream> stream = zip->FindFile(file)->OpenInputStream();
+
+ std::vector<uint8_t> buffer;
+ const void* data;
+ size_t size;
+
+ while (stream->Next(&data, &size)) {
+ auto pointer = static_cast<const uint8_t*>(data);
+ buffer.insert(buffer.end(), pointer, pointer + size);
+ }
+
+ for (int index = 0; index < kTestDataLength; ++index) {
+ ASSERT_EQ(array[index], buffer[index]);
+ }
+}
+
+TEST_F(ArchiveTest, DirectoryWriteEntrySuccess) {
+ std::string output_path = GetTestPath("output");
+ std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path);
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+
+ ASSERT_TRUE(writer->StartEntry("test1", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ ASSERT_TRUE(writer->StartEntry("test2", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ VerifyDirectory(output_path, "test1", data1.get());
+ VerifyDirectory(output_path, "test2", data2.get());
+}
+
+TEST_F(ArchiveTest, DirectoryWriteFileSuccess) {
+ std::string output_path = GetTestPath("output");
+ std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path);
+
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ auto data1_copy = std::make_unique<uint8_t[]>(kTestDataLength);
+ std::copy(data1.get(), data1.get() + kTestDataLength, data1_copy.get());
+
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+ auto data2_copy = std::make_unique<uint8_t[]>(kTestDataLength);
+ std::copy(data2.get(), data2.get() + kTestDataLength, data2_copy.get());
+
+ auto input1 = std::make_unique<TestData>(data1_copy, kTestDataLength);
+ auto input2 = std::make_unique<TestData>(data2_copy, kTestDataLength);
+
+ ASSERT_TRUE(writer->WriteFile("test1", 0, input1.get()));
+ ASSERT_FALSE(writer->HadError());
+ ASSERT_TRUE(writer->WriteFile("test2", 0, input2.get()));
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ VerifyDirectory(output_path, "test1", data1.get());
+ VerifyDirectory(output_path, "test2", data2.get());
+}
+
+TEST_F(ArchiveTest, DirectoryWriteFileError) {
+ std::string output_path = GetTestPath("output");
+ std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path);
+ std::unique_ptr<uint8_t[]> data = MakeTestArray();
+ auto input = std::make_unique<TestData>(data, kTestDataLength);
+ input->error_ = "DirectoryWriteFileError";
+
+ ASSERT_FALSE(writer->WriteFile("test", 0, input.get()));
+ ASSERT_TRUE(writer->HadError());
+ ASSERT_EQ("DirectoryWriteFileError", writer->GetError());
+}
+
+TEST_F(ArchiveTest, ZipFileWriteEntrySuccess) {
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+
+ ASSERT_TRUE(writer->StartEntry("test1", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ ASSERT_TRUE(writer->StartEntry("test2", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ VerifyZipFile(output_path, "test1", data1.get());
+ VerifyZipFile(output_path, "test2", data2.get());
+}
+
+TEST_F(ArchiveTest, ZipFileWriteFileSuccess) {
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ auto data1_copy = std::make_unique<uint8_t[]>(kTestDataLength);
+ std::copy(data1.get(), data1.get() + kTestDataLength, data1_copy.get());
+
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+ auto data2_copy = std::make_unique<uint8_t[]>(kTestDataLength);
+ std::copy(data2.get(), data2.get() + kTestDataLength, data2_copy.get());
+
+ auto input1 = std::make_unique<TestData>(data1_copy, kTestDataLength);
+ auto input2 = std::make_unique<TestData>(data2_copy, kTestDataLength);
+
+ ASSERT_TRUE(writer->WriteFile("test1", 0, input1.get()));
+ ASSERT_FALSE(writer->HadError());
+ ASSERT_TRUE(writer->WriteFile("test2", 0, input2.get()));
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ VerifyZipFile(output_path, "test1", data1.get());
+ VerifyZipFile(output_path, "test2", data2.get());
+}
+
+TEST_F(ArchiveTest, ZipFileWriteFileError) {
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+ std::unique_ptr<uint8_t[]> data = MakeTestArray();
+ auto input = std::make_unique<TestData>(data, kTestDataLength);
+ input->error_ = "ZipFileWriteFileError";
+
+ ASSERT_FALSE(writer->WriteFile("test", 0, input.get()));
+ ASSERT_TRUE(writer->HadError());
+ ASSERT_EQ("ZipFileWriteFileError", writer->GetError());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index fd8e36ebf823..fcd6aaafba7a 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -614,6 +614,7 @@ std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(const ResourceNameRef
if (attr->type_mask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
Attribute::Symbol symbol;
symbol.value = util::DeviceToHost32(map_entry.value.data);
+ symbol.type = map_entry.value.dataType;
symbol.symbol = Reference(util::DeviceToHost32(map_entry.name.ident));
attr->symbols.push_back(std::move(symbol));
}
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f2e72da4056a..b9321174100b 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -107,7 +107,7 @@ class MapFlattenVisitor : public ValueVisitor {
}
for (Attribute::Symbol& s : attr->symbols) {
- BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
+ BinaryPrimitive val(s.type, s.value);
FlattenEntry(&s.symbol, &val);
}
}
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index bb21c1c539fb..efbf636878f2 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -708,6 +708,8 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
return {};
}
symbol.value = pb_symbol.value();
+ symbol.type = pb_symbol.type() != 0U ? pb_symbol.type()
+ : android::Res_value::TYPE_INT_DEC;
attr->symbols.push_back(std::move(symbol));
}
value = std::move(attr);
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index a54822b20302..aa6547e81624 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -552,6 +552,7 @@ class ValueSerializer : public ConstValueVisitor {
SerializeItemMetaDataToPb(symbol.symbol, pb_symbol, src_pool_);
SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name());
pb_symbol->set_value(symbol.value);
+ pb_symbol->set_type(symbol.type);
}
}
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index f252f33f44fb..e7f23302652c 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -80,7 +80,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), context->GetDiagnostics()));
ASSERT_TRUE(table->AddResource(
test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), "tablet",
- test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 321u), context->GetDiagnostics()));
+ test::BuildPrimitive(android::Res_value::TYPE_INT_HEX, 321u), context->GetDiagnostics()));
// Make a reference with both resource name and resource ID.
// The reference should point to a resource outside of this table to test that both name and id
@@ -133,11 +133,13 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
&new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), "");
ASSERT_THAT(prim, NotNull());
EXPECT_THAT(prim->value.data, Eq(123u));
+ EXPECT_THAT(prim->value.dataType, Eq(0x10));
prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
&new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet");
ASSERT_THAT(prim, NotNull());
EXPECT_THAT(prim->value.data, Eq(321u));
+ EXPECT_THAT(prim->value.dataType, Eq(0x11));
Reference* actual_ref = test::GetValue<Reference>(&new_table, "com.app.a:layout/abc");
ASSERT_THAT(actual_ref, NotNull());
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
new file mode 100644
index 000000000000..6361f9b8ae7d
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
@@ -0,0 +1,2 @@
+LOCAL_PATH := $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
new file mode 100644
index 000000000000..6bc2064c6e63
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 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_AAPT_NAMESPACES := true
+LOCAL_PACKAGE_NAME := AaptTestMergeOnly_App
+LOCAL_SDK_VERSION := current
+LOCAL_EXPORT_PACKAGE_RESOURCES := true
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ AaptTestMergeOnly_LeafLib \
+ AaptTestMergeOnly_LocalLib
+include $(BUILD_PACKAGE) \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml b/tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml
new file mode 100644
index 000000000000..bc3a7e5ebd21
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.app">
+
+ <application
+ android:label="@*com.local.lib:string/lib_string"/>
+
+</manifest> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
new file mode 100644
index 000000000000..7bf8cf84426c
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2019 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_AAPT_NAMESPACES := true
+LOCAL_MODULE := AaptTestMergeOnly_LeafLib
+LOCAL_SDK_VERSION := current
+LOCAL_MODULE_TAGS := tests
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_MIN_SDK_VERSION := 21
+LOCAL_AAPT_FLAGS := --merge-only
+include $(BUILD_STATIC_JAVA_LIBRARY) \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml
new file mode 100644
index 000000000000..9907bd98790d
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 package="com.leaf.lib" />
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml
new file mode 100644
index 000000000000..07de87fa1d33
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:leaf="http://schemas.android.com/apk/res/com.leaf.lib">
+
+ <TextView android:text="@string/leaf_string"
+ leaf:leaf_attr="hello"
+ style="@style/LeafChildStyle"/>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml
new file mode 100644
index 000000000000..7f94c26de23c
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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>
+ <attr format="string" name="leaf_attr"/>
+ <attr format="string" name="leaf_attr2"/>
+
+ <string name="leaf_string">I am a leaf</string>
+
+ <style name="LeafParentStyle">
+ <item type="attr" name="leaf_attr"/>
+ <item type="attr" name="leaf_attr2"/>
+ </style>
+
+ <style name="LeafChildStyle" parent="LeafParentStyle">
+ <item type="attr" name="leaf_attr2">hello</item>
+ </style>
+
+ <style name="LeafParentStyle.DottedChild"/>
+
+ <declare-styleable name="leaf_ds">
+ <attr name="leaf_attr">hello</attr>
+ </declare-styleable>
+
+ <public type="attr" name="leaf_attr"/>
+ <public type="attr" name="leaf_attr2"/>
+ <public type="style" name="LeafParentStyle"/>
+ <public type="style" name="LeafChildStyle"/>
+ <public type="style" name="LeafParentStyle.DottedChild"/>
+</resources> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
new file mode 100644
index 000000000000..ba781c56a913
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2019 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_AAPT_NAMESPACES := true
+LOCAL_MODULE := AaptTestMergeOnly_LocalLib
+LOCAL_SDK_VERSION := current
+LOCAL_MODULE_TAGS := tests
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_MIN_SDK_VERSION := 21
+LOCAL_AAPT_FLAGS := --merge-only
+include $(BUILD_STATIC_JAVA_LIBRARY) \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml
new file mode 100644
index 000000000000..aa0ff5dcb4b6
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.local.lib">
+
+ <application>
+
+ <activity
+ android:name="com.myapp.MyActivity"
+ android:theme="@com.leaf.lib:style/LeafParentStyle.DottedChild"/>
+
+ </application>
+
+</manifest>
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml
new file mode 100644
index 000000000000..80d2fd6bcd09
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:leaf="http://schemas.android.com/apk/res/com.leaf.lib">
+
+ <TextView android:text="@*com.leaf.lib:string/leaf_string"
+ leaf:leaf_attr="hello"
+ style="@com.leaf.lib:style/LeafChildStyle"/>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml
new file mode 100644
index 000000000000..f06371874a45
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:leaf="http://schemas.android.com/apk/res/com.leaf.lib">
+
+ <include layout="@layout/activity"/>
+
+ <include layout="@*com.leaf.lib:layout/activity"/>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml
new file mode 100644
index 000000000000..2f9704df0570
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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>
+ <string name="lib_string">@*com.leaf.lib:string/leaf_string</string>
+
+ <style name="lib_style" parent="@com.leaf.lib:style/LeafChildStyle">
+ <item name="com.leaf.lib:leaf_attr">hello</item>
+ </style>
+
+ <style name="LeafParentStyle.DottedChild.LocalLibStyle"
+ parent="@com.leaf.lib:style/LeafParentStyle.DottedChild"/>
+
+ <public type="style" name="LeafParentStyle.DottedChild.LocalLibStyle"/>
+
+</resources> \ No newline at end of file
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index ce6d9352180d..bb925c9b3f8e 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -58,7 +58,7 @@ bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file
return CopyFileToArchive(context, file, out_path, compression_flags, writer);
}
-bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg,
+bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
const std::string& out_path, uint32_t compression_flags,
IArchiveWriter* writer) {
TRACE_CALL();
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 5f978a8e2c35..5cb8206db23c 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -19,7 +19,7 @@
#include <string>
-#include "google/protobuf/message_lite.h"
+#include "google/protobuf/message.h"
#include "google/protobuf/io/coded_stream.h"
#include "format/Archive.h"
@@ -39,7 +39,7 @@ bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& ou
bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file,
const std::string& out_path, IArchiveWriter* writer);
-bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg,
+bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
const std::string& out_path, uint32_t compression_flags,
IArchiveWriter* writer);
@@ -127,13 +127,13 @@ class ProtoInputStreamReader {
public:
explicit ProtoInputStreamReader(io::InputStream* in) : in_(in) { }
- /** Deserializes a MessageLite proto from the current position in the input stream.*/
- template <typename T> bool ReadMessage(T *message_lite) {
+ /** Deserializes a Message proto from the current position in the input stream.*/
+ template <typename T> bool ReadMessage(T *message) {
ZeroCopyInputAdaptor adapter(in_);
google::protobuf::io::CodedInputStream coded_stream(&adapter);
coded_stream.SetTotalBytesLimit(std::numeric_limits<int32_t>::max(),
coded_stream.BytesUntilTotalBytesLimit());
- return message_lite->ParseFromCodedStream(&coded_stream);
+ return message->ParseFromCodedStream(&coded_stream);
}
private:
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index f9656d112b7b..b15df59f56a6 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -99,11 +99,13 @@ bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
//
inline bool operator==(const UsageLocation& lhs, const UsageLocation& rhs) {
+ // The "source" member is ignored because we only need "name" for outputting
+ // keep rules; "source" is used for comments.
return lhs.name == rhs.name;
}
-inline int operator<(const UsageLocation& lhs, const UsageLocation& rhs) {
- return lhs.name.compare(rhs.name);
+inline bool operator<(const UsageLocation& lhs, const UsageLocation& rhs) {
+ return lhs.name.compare(rhs.name) < 0;
}
//
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index 559b07af3e80..25b55ab003b0 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -364,4 +364,12 @@ TEST(ProguardRulesTest, TransitionRulesAreEmitted) {
"-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
}
+TEST(ProguardRulesTest, UsageLocationComparator) {
+ proguard::UsageLocation location1 = {{"pkg", ResourceType::kAttr, "x"}};
+ proguard::UsageLocation location2 = {{"pkg", ResourceType::kAttr, "y"}};
+
+ EXPECT_EQ(location1 < location2, true);
+ EXPECT_EQ(location2 < location1, false);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 49909f6e2b8e..11150dd8e882 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -214,6 +214,33 @@ static bool VerifyUsesFeature(xml::Element* el, SourcePathDiagnostics* diag) {
return true;
}
+// Ensure that 'ns_decls' contains a declaration for 'uri', using 'prefix' as
+// the xmlns prefix if possible.
+static void EnsureNamespaceIsDeclared(const std::string& prefix, const std::string& uri,
+ std::vector<xml::NamespaceDecl>* ns_decls) {
+ if (std::find_if(ns_decls->begin(), ns_decls->end(), [&](const xml::NamespaceDecl& ns_decl) {
+ return ns_decl.uri == uri;
+ }) != ns_decls->end()) {
+ return;
+ }
+
+ std::set<std::string> used_prefixes;
+ for (const auto& ns_decl : *ns_decls) {
+ used_prefixes.insert(ns_decl.prefix);
+ }
+
+ // Make multiple attempts in the unlikely event that 'prefix' is already taken.
+ std::string disambiguator;
+ for (int i = 0; i < used_prefixes.size() + 1; i++) {
+ std::string attempted_prefix = prefix + disambiguator;
+ if (used_prefixes.find(attempted_prefix) == used_prefixes.end()) {
+ ns_decls->push_back(xml::NamespaceDecl{attempted_prefix, uri});
+ return;
+ }
+ disambiguator = std::to_string(i);
+ }
+}
+
bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
IDiagnostics* diag) {
// First verify some options.
@@ -262,6 +289,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
manifest_action.Action(VerifyManifest);
manifest_action.Action(FixCoreAppAttribute);
manifest_action.Action([&](xml::Element* el) -> bool {
+ EnsureNamespaceIsDeclared("android", xml::kSchemaAndroid, &el->namespace_decls);
+
if (options_.version_name_default) {
if (options_.replace_version) {
el->RemoveAttribute(xml::kSchemaAndroid, "versionName");
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 3f1ee36dea4a..3af06f53d4f3 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -727,8 +727,7 @@ TEST_F(ManifestFixerTest, SupportKeySets) {
}
TEST_F(ManifestFixerTest, InsertCompileSdkVersions) {
- std::string input = R"(
- <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" />)";
+ std::string input = R"(<manifest package="com.pkg" />)";
ManifestFixerOptions options;
options.compile_sdk_version = {"28"};
options.compile_sdk_version_codename = {"P"};
@@ -736,6 +735,12 @@ TEST_F(ManifestFixerTest, InsertCompileSdkVersions) {
std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
ASSERT_THAT(manifest, NotNull());
+ // There should be a declaration of kSchemaAndroid, even when the input
+ // didn't have one.
+ EXPECT_EQ(manifest->root->namespace_decls.size(), 1);
+ EXPECT_EQ(manifest->root->namespace_decls[0].prefix, "android");
+ EXPECT_EQ(manifest->root->namespace_decls[0].uri, xml::kSchemaAndroid);
+
xml::Attribute* attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersion");
ASSERT_THAT(attr, NotNull());
EXPECT_THAT(attr->value, StrEq("28"));
@@ -782,6 +787,27 @@ TEST_F(ManifestFixerTest, OverrideCompileSdkVersions) {
EXPECT_THAT(attr->value, StrEq("P"));
}
+TEST_F(ManifestFixerTest, AndroidPrefixAlreadyUsed) {
+ std::string input =
+ R"(<manifest package="com.pkg"
+ xmlns:android="http://schemas.android.com/apk/prv/res/android"
+ android:private_attr="foo" />)";
+ ManifestFixerOptions options;
+ options.compile_sdk_version = {"28"};
+ options.compile_sdk_version_codename = {"P"};
+
+ std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+ ASSERT_THAT(manifest, NotNull());
+
+ // Make sure that we don't redefine "android".
+ EXPECT_EQ(manifest->root->namespace_decls.size(), 2);
+ EXPECT_EQ(manifest->root->namespace_decls[0].prefix, "android");
+ EXPECT_EQ(manifest->root->namespace_decls[0].uri,
+ "http://schemas.android.com/apk/prv/res/android");
+ EXPECT_EQ(manifest->root->namespace_decls[1].prefix, "android0");
+ EXPECT_EQ(manifest->root->namespace_decls[1].uri, xml::kSchemaAndroid);
+}
+
TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) {
std::string input = R"(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 28f09aa48365..8e49fabe6a5c 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -17,6 +17,7 @@
#include "link/ReferenceLinker.h"
#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
#include "androidfw/ResourceTypes.h"
#include "Diagnostics.h"
@@ -33,6 +34,7 @@
using ::aapt::ResourceUtils::StringBuilder;
using ::android::StringPiece;
+using ::android::base::StringPrintf;
namespace aapt {
@@ -81,7 +83,7 @@ class ReferenceLinkerVisitor : public DescendingValueVisitor {
// Find the attribute in the symbol table and check if it is visible from this callsite.
const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveAttributeCheckVisibility(
- transformed_reference, callsite_, symbols_, &err_str);
+ transformed_reference, callsite_, context_, symbols_, &err_str);
if (symbol) {
// Assign our style key the correct ID. The ID may not exist.
entry.key.id = symbol->id;
@@ -203,12 +205,35 @@ bool IsSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref,
const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols) {
if (reference.name) {
const ResourceName& name = reference.name.value();
if (name.package.empty()) {
// Use the callsite's package name if no package name was defined.
- return symbols->FindByName(ResourceName(callsite.package, name.type, name.entry));
+ const SymbolTable::Symbol* symbol = symbols->FindByName(
+ ResourceName(callsite.package, name.type, name.entry));
+ if (symbol) {
+ return symbol;
+ }
+
+ // If the callsite package is the same as the current compilation package,
+ // check the feature split dependencies as well. Feature split resources
+ // can be referenced without a namespace, just like the base package.
+ // TODO: modify the package name of included splits instead of having the
+ // symbol table look up the resource in in every package. b/136105066
+ if (callsite.package == context->GetCompilationPackage()) {
+ const auto& split_name_dependencies = context->GetSplitNameDependencies();
+ for (const std::string& split_name : split_name_dependencies) {
+ std::string split_package =
+ StringPrintf("%s.%s", callsite.package.c_str(), split_name.c_str());
+ symbol = symbols->FindByName(ResourceName(split_package, name.type, name.entry));
+ if (symbol) {
+ return symbol;
+ }
+ }
+ }
+ return nullptr;
}
return symbols->FindByName(name);
} else if (reference.id) {
@@ -220,9 +245,10 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& refer
const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols,
std::string* out_error) {
- const SymbolTable::Symbol* symbol = ResolveSymbol(reference, callsite, symbols);
+ const SymbolTable::Symbol* symbol = ResolveSymbol(reference, callsite, context, symbols);
if (!symbol) {
if (out_error) *out_error = "not found";
return nullptr;
@@ -236,10 +262,10 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const R
}
const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility(
- const Reference& reference, const CallSite& callsite, SymbolTable* symbols,
- std::string* out_error) {
+ const Reference& reference, const CallSite& callsite, IAaptContext* context,
+ SymbolTable* symbols, std::string* out_error) {
const SymbolTable::Symbol* symbol =
- ResolveSymbolCheckVisibility(reference, callsite, symbols, out_error);
+ ResolveSymbolCheckVisibility(reference, callsite, context, symbols, out_error);
if (!symbol) {
return nullptr;
}
@@ -253,10 +279,11 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility(
Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols,
std::string* out_error) {
const SymbolTable::Symbol* symbol =
- ResolveAttributeCheckVisibility(reference, callsite, symbols, out_error);
+ ResolveAttributeCheckVisibility(reference, callsite, context, symbols, out_error);
if (!symbol) {
return {};
}
@@ -335,7 +362,7 @@ bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* referen
std::string err_str;
const SymbolTable::Symbol* s =
- ResolveSymbolCheckVisibility(transformed_reference, callsite, symbols, &err_str);
+ ResolveSymbolCheckVisibility(transformed_reference, callsite, context, symbols, &err_str);
if (s) {
// The ID may not exist. This is fine because of the possibility of building
// against libraries without assigned IDs.
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
index b0b49457e5dd..1256709edbf4 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -39,13 +39,16 @@ class ReferenceLinker : public IResourceTableConsumer {
// package if the reference has no package name defined (implicit).
// Returns nullptr if the symbol was not found.
static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference,
- const CallSite& callsite, SymbolTable* symbols);
+ const CallSite& callsite,
+ IAaptContext* context,
+ SymbolTable* symbols);
// Performs name mangling and looks up the resource in the symbol table. If the symbol is not
// visible by the reference at the callsite, nullptr is returned.
// `out_error` holds the error message.
static const SymbolTable::Symbol* ResolveSymbolCheckVisibility(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols,
std::string* out_error);
@@ -53,6 +56,7 @@ class ReferenceLinker : public IResourceTableConsumer {
// That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute.
static const SymbolTable::Symbol* ResolveAttributeCheckVisibility(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols,
std::string* out_error);
@@ -60,6 +64,7 @@ class ReferenceLinker : public IResourceTableConsumer {
// If resolution fails, outError holds the error message.
static Maybe<xml::AaptAttribute> CompileXmlAttribute(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols,
std::string* out_error);
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
index be38b967c986..a31ce9496d0c 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -266,8 +266,13 @@ TEST(ReferenceLinkerTest, AppsWithSamePackageButDifferentIdAreVisibleNonPublic)
std::string error;
const CallSite call_site{"com.app.test"};
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .Build();
const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveSymbolCheckVisibility(
- *test::BuildReference("com.app.test:string/foo"), call_site, &table, &error);
+ *test::BuildReference("com.app.test:string/foo"), call_site, context.get(), &table, &error);
ASSERT_THAT(symbol, NotNull());
EXPECT_TRUE(error.empty());
}
@@ -281,17 +286,23 @@ TEST(ReferenceLinkerTest, AppsWithDifferentPackageCanNotUseEachOthersAttribute)
.AddPublicSymbol("com.app.test:attr/public_foo", ResourceId(0x7f010001),
test::AttributeBuilder().Build())
.Build());
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.ext")
+ .SetPackageId(0x7f)
+ .Build();
std::string error;
const CallSite call_site{"com.app.ext"};
EXPECT_FALSE(ReferenceLinker::CompileXmlAttribute(
- *test::BuildReference("com.app.test:attr/foo"), call_site, &table, &error));
+ *test::BuildReference("com.app.test:attr/foo"), call_site, context.get(), &table, &error));
EXPECT_FALSE(error.empty());
error = "";
ASSERT_TRUE(ReferenceLinker::CompileXmlAttribute(
- *test::BuildReference("com.app.test:attr/public_foo"), call_site, &table, &error));
+ *test::BuildReference("com.app.test:attr/public_foo"), call_site, context.get(), &table,
+ &error));
EXPECT_TRUE(error.empty());
}
@@ -302,20 +313,62 @@ TEST(ReferenceLinkerTest, ReferenceWithNoPackageUsesCallSitePackage) {
.AddSymbol("com.app.test:string/foo", ResourceId(0x7f010000))
.AddSymbol("com.app.lib:string/foo", ResourceId(0x7f010001))
.Build());
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .Build();
const SymbolTable::Symbol* s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"),
- CallSite{"com.app.test"}, &table);
+ CallSite{"com.app.test"},
+ context.get(), &table);
ASSERT_THAT(s, NotNull());
EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010000)));
s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.lib"},
- &table);
+ context.get(), &table);
ASSERT_THAT(s, NotNull());
EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010001)));
EXPECT_THAT(ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"),
- CallSite{"com.app.bad"}, &table),
+ CallSite{"com.app.bad"}, context.get(), &table),
IsNull());
}
+TEST(ReferenceLinkerTest, ReferenceSymbolFromOtherSplit) {
+ NameMangler mangler(NameManglerPolicy{"com.app.test"});
+ SymbolTable table(&mangler);
+ table.AppendSource(test::StaticSymbolSourceBuilder()
+ .AddSymbol("com.app.test.feature:string/bar", ResourceId(0x80010000))
+ .Build());
+ std::set<std::string> split_name_dependencies;
+ split_name_dependencies.insert("feature");
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x81)
+ .SetSplitNameDependencies(split_name_dependencies)
+ .Build();
+
+ const SymbolTable::Symbol* s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/bar"),
+ CallSite{"com.app.test"},
+ context.get(), &table);
+ ASSERT_THAT(s, NotNull());
+ EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x80010000)));
+
+ s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.lib"},
+ context.get(), &table);
+ EXPECT_THAT(s, IsNull());
+
+ context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x81)
+ .Build();
+ s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/bar"),CallSite{"com.app.test"},
+ context.get(), &table);
+
+ EXPECT_THAT(s, IsNull());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 3f65e868505d..c25e4503a208 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -172,28 +172,32 @@ static bool MergeEntry(IAaptContext* context, const Source& src,
//
// Styleables and Styles don't simply overlay each other, their definitions merge and accumulate.
// If both values are Styleables/Styles, we just merge them into the existing value.
-static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Value* incoming,
- StringPool* pool) {
+static ResourceTable::CollisionResult ResolveMergeCollision(
+ bool override_styles_instead_of_overlaying, Value* existing, Value* incoming,
+ StringPool* pool) {
if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) {
if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) {
// Styleables get merged.
existing_styleable->MergeWith(incoming_styleable);
return ResourceTable::CollisionResult::kKeepOriginal;
}
- } else if (Style* existing_style = ValueCast<Style>(existing)) {
- if (Style* incoming_style = ValueCast<Style>(incoming)) {
- // Styles get merged.
- existing_style->MergeWith(incoming_style, pool);
- return ResourceTable::CollisionResult::kKeepOriginal;
+ } else if (!override_styles_instead_of_overlaying) {
+ if (Style* existing_style = ValueCast<Style>(existing)) {
+ if (Style* incoming_style = ValueCast<Style>(incoming)) {
+ // Styles get merged.
+ existing_style->MergeWith(incoming_style, pool);
+ return ResourceTable::CollisionResult::kKeepOriginal;
+ }
}
}
// Delegate to the default handler.
- return ResourceTable::ResolveValueCollision(existing, incoming, true /* overlay */);
+ return ResourceTable::ResolveValueCollision(existing, incoming);
}
static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
const ResourceNameRef& res_name,
bool overlay,
+ bool override_styles_instead_of_overlaying,
ResourceConfigValue* dst_config_value,
ResourceConfigValue* src_config_value,
StringPool* pool) {
@@ -204,13 +208,18 @@ static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
CollisionResult collision_result;
if (overlay) {
- collision_result = ResolveMergeCollision(dst_value, src_value, pool);
+ collision_result =
+ ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
} else {
- collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value,
- false /* overlay */);
+ collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
}
if (collision_result == CollisionResult::kConflict) {
+ if (overlay) {
+ return CollisionResult::kTakeNew;
+ }
+
+ // Error!
context->GetDiagnostics()->Error(DiagMessage(src_value->GetSource())
<< "resource '" << res_name << "' has a conflicting value for "
<< "configuration (" << src_config_value->config << ")");
@@ -268,9 +277,9 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package,
ResourceConfigValue* dst_config_value = dst_entry->FindValue(
src_config_value->config, src_config_value->product);
if (dst_config_value) {
- CollisionResult collision_result =
- MergeConfigValue(context_, res_name, overlay, dst_config_value,
- src_config_value.get(), &master_table_->string_pool);
+ CollisionResult collision_result = MergeConfigValue(
+ context_, res_name, overlay, options_.override_styles_instead_of_overlaying,
+ dst_config_value, src_config_value.get(), &master_table_->string_pool);
if (collision_result == CollisionResult::kConflict) {
error = true;
continue;
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 51305cfcdd25..a35a134a887d 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -37,6 +37,8 @@ struct TableMergerOptions {
bool auto_add_overlay = false;
// If true, resource overlays with conflicting visibility are not allowed.
bool strict_visibility = false;
+ // If true, styles specified via "aapt2 link -R" completely replace any previously-seen resources.
+ bool override_styles_instead_of_overlaying = false;
};
// TableMerger takes resource tables and merges all packages within the tables that have the same
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 78d42a160e21..0be4ccf9ae85 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -352,62 +352,6 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
}
-TEST_F(TableMergerTest, OverrideAttributeSameFormatsWithOverlay) {
- std::unique_ptr<ResourceTable> base =
- test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
- .AddValue("attr/foo", test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_STRING)
- .SetWeak(false)
- .Build())
- .Build();
-
- std::unique_ptr<ResourceTable> overlay =
- test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
- .AddValue("attr/foo", test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_STRING)
- .SetWeak(false)
- .Build())
- .Build();
-
- ResourceTable final_table;
- TableMergerOptions options;
- options.auto_add_overlay = false;
- TableMerger merger(context_.get(), &final_table, options);
-
- ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
- ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/));
-}
-
-TEST_F(TableMergerTest, FailToOverrideConflictingAttributeFormatsWithOverlay) {
- std::unique_ptr<ResourceTable> base =
- test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
- .AddValue("attr/foo", test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_ANY)
- .SetWeak(false)
- .Build())
- .Build();
-
- std::unique_ptr<ResourceTable> overlay =
- test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
- .AddValue("attr/foo", test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_STRING)
- .SetWeak(false)
- .Build())
- .Build();
-
- ResourceTable final_table;
- TableMergerOptions options;
- options.auto_add_overlay = false;
- TableMerger merger(context_.get(), &final_table, options);
-
- ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
- ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
-}
-
TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
@@ -492,6 +436,53 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent")))));
}
+TEST_F(TableMergerTest, OverrideStyleInsteadOfOverlaying) {
+ std::unique_ptr<ResourceTable> table_a =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddValue(
+ "com.app.a:styleable/MyWidget",
+ test::StyleableBuilder().AddItem("com.app.a:attr/foo", ResourceId(0x1234)).Build())
+ .AddValue("com.app.a:style/Theme",
+ test::StyleBuilder()
+ .AddItem("com.app.a:attr/foo", ResourceUtils::MakeBool(false))
+ .Build())
+ .Build();
+ std::unique_ptr<ResourceTable> table_b =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddValue(
+ "com.app.a:styleable/MyWidget",
+ test::StyleableBuilder().AddItem("com.app.a:attr/bar", ResourceId(0x5678)).Build())
+ .AddValue(
+ "com.app.a:style/Theme",
+ test::StyleBuilder().AddItem("com.app.a:attr/bat", util::make_unique<Id>()).Build())
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = true;
+ options.override_styles_instead_of_overlaying = true;
+ TableMerger merger(context_.get(), &final_table, options);
+ ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+ ASSERT_TRUE(merger.Merge({}, table_b.get(), true /*overlay*/));
+
+ // Styleables are always overlaid
+ std::unique_ptr<Styleable> expected_styleable = test::StyleableBuilder()
+ // The merged Styleable has its entries ordered by name.
+ .AddItem("com.app.a:attr/bar", ResourceId(0x5678))
+ .AddItem("com.app.a:attr/foo", ResourceId(0x1234))
+ .Build();
+ const Styleable* actual_styleable =
+ test::GetValue<Styleable>(&final_table, "com.app.a:styleable/MyWidget");
+ ASSERT_NE(actual_styleable, nullptr);
+ EXPECT_TRUE(actual_styleable->Equals(expected_styleable.get()));
+ // Style should be overridden
+ const Style* actual_style = test::GetValue<Style>(&final_table, "com.app.a:style/Theme");
+ ASSERT_NE(actual_style, nullptr);
+ EXPECT_TRUE(actual_style->Equals(test::GetValue<Style>(table_b.get(), "com.app.a:style/Theme")));
+}
+
TEST_F(TableMergerTest, SetOverlayable) {
auto overlayable = std::make_shared<Overlayable>("CustomizableResources",
"overlay://customization");
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index d68f7dd44c9f..f3be483ab728 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -99,7 +99,7 @@ class XmlVisitor : public xml::PackageAwareVisitor {
std::string err_str;
attr.compiled_attribute =
- ReferenceLinker::CompileXmlAttribute(attr_ref, callsite_, symbols_, &err_str);
+ ReferenceLinker::CompileXmlAttribute(attr_ref, callsite_, context_, symbols_, &err_str);
if (!attr.compiled_attribute) {
DiagMessage error_msg(source);
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 8c9c43409569..c686a10a3fa9 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -101,6 +101,10 @@ class ContextWrapper : public IAaptContext {
util::make_unique<SourcePathDiagnostics>(Source{source}, context_->GetDiagnostics());
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ return context_->GetSplitNameDependencies();
+ }
+
private:
IAaptContext* context_;
std::unique_ptr<SourcePathDiagnostics> source_diag_;
diff --git a/tools/aapt2/optimize/ResourcePathShortener.cpp b/tools/aapt2/optimize/ResourcePathShortener.cpp
index c5df3dd00db9..6b11de759d2d 100644
--- a/tools/aapt2/optimize/ResourcePathShortener.cpp
+++ b/tools/aapt2/optimize/ResourcePathShortener.cpp
@@ -23,6 +23,7 @@
#include "ResourceTable.h"
#include "ValueVisitor.h"
+#include "util/Util.h"
static const std::string base64_chars =
@@ -95,6 +96,10 @@ bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table)
android::StringPiece res_subdir, actual_filename, extension;
util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension);
+ // Android detects ColorStateLists via pathname, skip res/color*
+ if (util::StartsWith(res_subdir, "res/color"))
+ continue;
+
std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars);
int collision_count = 0;
std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
diff --git a/tools/aapt2/optimize/ResourcePathShortener_test.cpp b/tools/aapt2/optimize/ResourcePathShortener_test.cpp
index 88cadc76c336..1f4569495186 100644
--- a/tools/aapt2/optimize/ResourcePathShortener_test.cpp
+++ b/tools/aapt2/optimize/ResourcePathShortener_test.cpp
@@ -24,6 +24,11 @@ using ::testing::Not;
using ::testing::NotNull;
using ::testing::Eq;
+android::StringPiece GetExtension(android::StringPiece path) {
+ auto iter = std::find(path.begin(), path.end(), '.');
+ return android::StringPiece(iter, path.end() - iter);
+}
+
namespace aapt {
TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) {
@@ -64,4 +69,49 @@ TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) {
EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end()));
}
+TEST(ResourcePathShortenerTest, SkipColorFileRefPaths) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:color/colorlist", "res/color/colorlist.xml")
+ .AddFileReference("android:color/colorlist",
+ "res/color-mdp-v21/colorlist.xml",
+ test::ParseConfigOrDie("mdp-v21"))
+ .Build();
+
+ std::map<std::string, std::string> path_map;
+ ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+
+ // Expect that the path map to not contain the ColorStateList
+ ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
+ ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end()));
+}
+
+TEST(ResourcePathShortenerTest, KeepExtensions) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ std::string original_xml_path = "res/drawable/xmlfile.xml";
+ std::string original_png_path = "res/drawable/pngfile.png";
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:color/xmlfile", original_xml_path)
+ .AddFileReference("android:color/pngfile", original_png_path)
+ .Build();
+
+ std::map<std::string, std::string> path_map;
+ ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+
+ // Expect that the path map is populated
+ ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find("res/drawable/pngfile.png"), Not(Eq(path_map.end())));
+
+ auto shortend_xml_path = path_map[original_xml_path];
+ auto shortend_png_path = path_map[original_png_path];
+
+ EXPECT_THAT(GetExtension(path_map[original_xml_path]), Eq(android::StringPiece(".xml")));
+ EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png")));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index 30dad8025900..9c4b323db433 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -19,6 +19,7 @@
#include <iostream>
#include <list>
+#include <set>
#include <sstream>
#include "Diagnostics.h"
@@ -50,6 +51,7 @@ struct IAaptContext {
virtual NameMangler* GetNameMangler() = 0;
virtual bool IsVerbose() = 0;
virtual int GetMinSdkVersion() = 0;
+ virtual const std::set<std::string>& GetSplitNameDependencies() = 0;
};
struct IResourceTableConsumer {
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 61a8fbbb7f52..bc09f19b94d5 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -313,6 +313,7 @@ static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable(
symbol.symbol.name = parsed_name.value();
symbol.symbol.id = ResourceId(map_entry.key);
symbol.value = map_entry.value.data;
+ symbol.type = map_entry.value.dataType;
s->attribute->symbols.push_back(std::move(symbol));
}
}
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 0564db063b9a..7e10a59df877 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -81,6 +81,10 @@ class Context : public IAaptContext {
return min_sdk_version_;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ return split_name_dependencies_;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(Context);
@@ -93,6 +97,7 @@ class Context : public IAaptContext {
NameMangler name_mangler_;
SymbolTable symbols_;
int min_sdk_version_;
+ std::set<std::string> split_name_dependencies_;
};
class ContextBuilder {
@@ -127,6 +132,11 @@ class ContextBuilder {
return *this;
}
+ ContextBuilder& SetSplitNameDependencies(const std::set<std::string>& split_name_dependencies) {
+ context_->split_name_dependencies_ = split_name_dependencies;
+ return *this;
+ }
+
std::unique_ptr<Context> Build() { return std::move(context_); }
private:
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index a51b4a4649f1..5386802dbc8e 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -80,7 +80,7 @@ void TestDirectoryFixture::TearDown() {
ClearDirectory(temp_dir_);
}
-bool TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) {
+void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) {
CHECK(util::StartsWith(path, temp_dir_))
<< "Attempting to create a file outside of test temporary directory.";
@@ -91,16 +91,31 @@ bool TestDirectoryFixture::WriteFile(const std::string& path, const std::string&
file::mkdirs(dirs);
}
- return android::base::WriteStringToFile(contents, path);
+ CHECK(android::base::WriteStringToFile(contents, path));
}
bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents,
const android::StringPiece& out_dir, IDiagnostics* diag) {
- CHECK(WriteFile(path, contents));
+ WriteFile(path, contents);
CHECK(file::mkdirs(out_dir.data()));
return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0;
}
+bool CommandTestFixture::Link(const std::vector<std::string>& args, IDiagnostics* diag) {
+ std::vector<android::StringPiece> link_args;
+ for(const std::string& arg : args) {
+ link_args.emplace_back(arg);
+ }
+
+ // Link against the android SDK
+ std::string android_sdk = file::BuildPath({android::base::GetExecutableDirectory(),
+ "integration-tests", "CommandTests",
+ "android-28.jar"});
+ link_args.insert(link_args.end(), {"-I", android_sdk});
+
+ return LinkCommand(diag).Execute(link_args, &std::cerr) == 0;
+}
+
bool CommandTestFixture::Link(const std::vector<std::string>& args,
const android::StringPiece& flat_dir, IDiagnostics* diag) {
std::vector<android::StringPiece> link_args;
@@ -128,10 +143,10 @@ bool CommandTestFixture::Link(const std::vector<std::string>& args,
std::string CommandTestFixture::GetDefaultManifest(const char* package_name) {
const std::string manifest_file = GetTestPath("AndroidManifest.xml");
- CHECK(WriteFile(manifest_file, android::base::StringPrintf(R"(
+ WriteFile(manifest_file, android::base::StringPrintf(R"(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="%s">
- </manifest>)", package_name)));
+ </manifest>)", package_name));
return manifest_file;
}
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index fce2aebfecaa..457d65e30b65 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -58,7 +58,7 @@ class TestDirectoryFixture : public ::testing::Test {
// Creates a file with the specified contents, creates any intermediate directories in the
// process. The file path must be an absolute path within the test directory.
- bool WriteFile(const std::string& path, const std::string& contents);
+ void WriteFile(const std::string& path, const std::string& contents);
private:
std::string temp_dir_;
@@ -75,6 +75,9 @@ class CommandTestFixture : public TestDirectoryFixture {
bool CompileFile(const std::string& path, const std::string& contents,
const android::StringPiece& flat_out_dir, IDiagnostics* diag);
+ // Executes the link command with the specified arguments.
+ bool Link(const std::vector<std::string>& args, IDiagnostics* diag);
+
// Executes the link command with the specified arguments. The flattened files residing in the
// flat directory will be added to the link command as file arguments.
bool Link(const std::vector<std::string>& args, const android::StringPiece& flat_dir,