summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/Android.bp3
-rw-r--r--tools/aapt/Command.cpp55
-rw-r--r--tools/aapt2/Android.bp15
-rw-r--r--tools/aapt2/Debug.cpp67
-rw-r--r--tools/aapt2/LoadedApk.cpp12
-rw-r--r--tools/aapt2/Resource.cpp3
-rw-r--r--tools/aapt2/Resource.h18
-rw-r--r--tools/aapt2/ResourceParser.cpp264
-rw-r--r--tools/aapt2/ResourceParser.h22
-rw-r--r--tools/aapt2/ResourceParser_test.cpp121
-rw-r--r--tools/aapt2/ResourceTable.cpp784
-rw-r--r--tools/aapt2/ResourceTable.h214
-rw-r--r--tools/aapt2/ResourceTable_test.cpp206
-rw-r--r--tools/aapt2/ResourceUtils.cpp11
-rw-r--r--tools/aapt2/ResourceUtils.h4
-rw-r--r--tools/aapt2/ResourceValues.cpp234
-rw-r--r--tools/aapt2/ResourceValues.h100
-rw-r--r--tools/aapt2/ResourceValues_test.cpp12
-rw-r--r--tools/aapt2/Resources.proto51
-rw-r--r--tools/aapt2/StringPool.h4
-rw-r--r--tools/aapt2/ValueTransformer.cpp51
-rw-r--r--tools/aapt2/ValueTransformer.h130
-rw-r--r--tools/aapt2/ValueTransformer_inline.h47
-rw-r--r--tools/aapt2/ValueVisitor.h6
-rw-r--r--tools/aapt2/cmd/Compile.cpp31
-rw-r--r--tools/aapt2/cmd/Compile.h4
-rw-r--r--tools/aapt2/cmd/Compile_test.cpp91
-rw-r--r--tools/aapt2/cmd/Diff.cpp229
-rw-r--r--tools/aapt2/cmd/Link.cpp245
-rw-r--r--tools/aapt2/cmd/Link.h5
-rw-r--r--tools/aapt2/cmd/Link_test.cpp468
-rw-r--r--tools/aapt2/cmd/Optimize.cpp6
-rw-r--r--tools/aapt2/cmd/Optimize.h4
-rw-r--r--tools/aapt2/compile/IdAssigner.cpp402
-rw-r--r--tools/aapt2/compile/IdAssigner_test.cpp202
-rw-r--r--tools/aapt2/compile/PngChunkFilter.cpp6
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator.cpp3
-rw-r--r--tools/aapt2/compile/PseudolocaleGenerator_test.cpp21
-rw-r--r--tools/aapt2/configuration/ConfigurationParser_test.cpp2
-rw-r--r--tools/aapt2/dump/DumpManifest.cpp315
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.cpp107
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.h7
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp243
-rw-r--r--tools/aapt2/format/binary/TableFlattener_test.cpp41
-rw-r--r--tools/aapt2/format/binary/XmlFlattener_test.cpp2
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.cpp81
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize.cpp97
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize.h9
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize_test.cpp333
-rw-r--r--tools/aapt2/java/ClassDefinition.h50
-rw-r--r--tools/aapt2/java/JavaClassGenerator.cpp80
-rw-r--r--tools/aapt2/java/JavaClassGenerator.h2
-rw-r--r--tools/aapt2/java/JavaClassGenerator_test.cpp76
-rw-r--r--tools/aapt2/java/ProguardRules_test.cpp8
-rw-r--r--tools/aapt2/jni/aapt2_jni.cpp2
-rw-r--r--tools/aapt2/link/AutoVersioner.cpp3
-rw-r--r--tools/aapt2/link/AutoVersioner_test.cpp1
-rw-r--r--tools/aapt2/link/Linkers.h4
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp65
-rw-r--r--tools/aapt2/link/ManifestFixer.h4
-rw-r--r--tools/aapt2/link/ManifestFixer_test.cpp134
-rw-r--r--tools/aapt2/link/NoDefaultResourceRemover_test.cpp15
-rw-r--r--tools/aapt2/link/ProductFilter_test.cpp77
-rw-r--r--tools/aapt2/link/ReferenceLinker.cpp366
-rw-r--r--tools/aapt2/link/ReferenceLinker.h60
-rw-r--r--tools/aapt2/link/ReferenceLinker_test.cpp24
-rw-r--r--tools/aapt2/link/TableMerger.cpp51
-rw-r--r--tools/aapt2/link/TableMerger.h4
-rw-r--r--tools/aapt2/link/TableMerger_test.cpp49
-rw-r--r--tools/aapt2/link/XmlCompatVersioner.cpp13
-rw-r--r--tools/aapt2/link/XmlCompatVersioner_test.cpp8
-rw-r--r--tools/aapt2/link/XmlReferenceLinker.cpp46
-rw-r--r--tools/aapt2/link/XmlReferenceLinker_test.cpp20
-rw-r--r--tools/aapt2/optimize/ResourceFilter.cpp6
-rw-r--r--tools/aapt2/optimize/ResourceFilter.h6
-rw-r--r--tools/aapt2/optimize/ResourceFilter_test.cpp46
-rw-r--r--tools/aapt2/process/SymbolTable.cpp28
-rw-r--r--tools/aapt2/split/TableSplitter.cpp11
-rw-r--r--tools/aapt2/split/TableSplitter_test.cpp26
-rw-r--r--tools/aapt2/test/Builders.cpp36
-rw-r--r--tools/aapt2/test/Builders.h2
-rw-r--r--tools/aapt2/test/Common.cpp4
-rw-r--r--tools/aapt2/test/Context.h3
-rw-r--r--tools/aapt2/test/Fixture.cpp88
-rw-r--r--tools/aapt2/test/Fixture.h35
-rw-r--r--tools/aapt2/trace/TraceBuffer.h2
-rw-r--r--tools/aapt2/util/Maybe_test.cpp34
-rw-r--r--tools/aapt2/util/Util.cpp17
-rw-r--r--tools/aapt2/util/Util.h10
-rw-r--r--tools/aapt2/util/Util_test.cpp33
-rw-r--r--tools/aapt2/xml/XmlActionExecutor.cpp37
-rw-r--r--tools/aapt2/xml/XmlActionExecutor.h9
-rw-r--r--tools/aapt2/xml/XmlActionExecutor_test.cpp4
-rw-r--r--tools/aapt2/xml/XmlDom.cpp3
-rw-r--r--tools/aapt2/xml/XmlPullParser.cpp4
-rw-r--r--tools/aapt2/xml/XmlPullParser.h12
-rwxr-xr-xtools/aosp/aosp_sha.sh2
-rw-r--r--tools/codegen/src/com/android/codegen/ClassInfo.kt2
-rw-r--r--tools/codegen/src/com/android/codegen/ClassPrinter.kt1
-rw-r--r--tools/codegen/src/com/android/codegen/Debug.kt39
-rw-r--r--tools/codegen/src/com/android/codegen/FieldInfo.kt3
-rw-r--r--tools/codegen/src/com/android/codegen/FileInfo.kt2
-rw-r--r--tools/codegen/src/com/android/codegen/Generators.kt17
-rw-r--r--tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt10
-rw-r--r--tools/codegen/src/com/android/codegen/SharedConstants.kt2
-rw-r--r--tools/codegen/src/com/android/codegen/Utils.kt4
-rw-r--r--tools/fonts/Android.bp12
-rwxr-xr-xtools/fonts/fontchain_linter.py95
-rwxr-xr-xtools/fonts/update_font_metadata.py27
-rw-r--r--tools/powerstats/Android.bp19
-rw-r--r--tools/powerstats/OWNERS2
-rw-r--r--tools/powerstats/PowerStatsServiceProtoParser.java196
-rw-r--r--tools/powerstats/PowerStatsServiceProtoParser_manifest.txt1
-rw-r--r--tools/processors/intdef_mappings/Android.bp42
-rw-r--r--tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt189
-rw-r--r--tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt162
-rw-r--r--tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt14
-rw-r--r--tools/protologtool/Android.bp2
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/LogParser.kt9
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt2
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt8
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt2
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt20
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt4
-rw-r--r--tools/validatekeymaps/Android.bp11
-rw-r--r--tools/validatekeymaps/Main.cpp106
-rw-r--r--tools/xmlpersistence/Android.bp20
-rw-r--r--tools/xmlpersistence/OWNERS1
-rw-r--r--tools/xmlpersistence/manifest.txt1
-rw-r--r--tools/xmlpersistence/src/main/kotlin/Generator.kt576
-rw-r--r--tools/xmlpersistence/src/main/kotlin/Main.kt45
-rw-r--r--tools/xmlpersistence/src/main/kotlin/Parser.kt248
-rw-r--r--tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt44
133 files changed, 6678 insertions, 2268 deletions
diff --git a/tools/aapt/Android.bp b/tools/aapt/Android.bp
index c75ba71c4432..a19d183d617b 100644
--- a/tools/aapt/Android.bp
+++ b/tools/aapt/Android.bp
@@ -124,6 +124,9 @@ cc_binary_host {
srcs: ["Main.cpp"],
use_version_lib: true,
static_libs: ["libaapt"],
+ dist: {
+ targets: ["aapt2_artifacts"],
+ },
}
// ==========================================================
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 21386b88ce2c..812e2087f36b 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -969,6 +969,8 @@ int doDump(Bundle* bundle)
densities.add(dens);
}
+ std::vector<ResXMLParser::ResXMLPosition> tagsToSkip;
+
size_t len;
ResXMLTree::event_code_t code;
int depth = 0;
@@ -1091,6 +1093,42 @@ int doDump(Bundle* bundle)
Vector<FeatureGroup> featureGroups;
KeyedVector<String8, ImpliedFeature> impliedFeatures;
+ {
+ int curDepth = 0;
+ ResXMLParser::ResXMLPosition initialPos;
+ tree.getPosition(&initialPos);
+
+ // Find all of the "uses-sdk" tags within the "manifest" tag.
+ std::vector<ResXMLParser::ResXMLPosition> usesSdkTagPositions;
+ ResXMLParser::ResXMLPosition curPos;
+ while ((code = tree.next()) != ResXMLTree::END_DOCUMENT &&
+ code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ curDepth--;
+ continue;
+ }
+ if (code == ResXMLTree::START_TAG) {
+ curDepth++;
+ }
+ const char16_t* ctag16 = tree.getElementName(&len);
+ if (ctag16 == NULL || String8(ctag16) != "uses-sdk" || curDepth != 2) {
+ continue;
+ }
+
+ tree.getPosition(&curPos);
+ usesSdkTagPositions.emplace_back(curPos);
+ }
+
+ // Skip all "uses-sdk" tags besides the very last tag. The android runtime only uses
+ // the attribute values from the last defined tag.
+ for (size_t i = 1; i < usesSdkTagPositions.size(); i++) {
+ tagsToSkip.emplace_back(usesSdkTagPositions[i - 1]);
+ }
+
+ // Reset the position before parsing.
+ tree.setPosition(initialPos);
+ }
+
while ((code=tree.next()) != ResXMLTree::END_DOCUMENT &&
code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::END_TAG) {
@@ -1202,8 +1240,25 @@ int doDump(Bundle* bundle)
if (code != ResXMLTree::START_TAG) {
continue;
}
+
depth++;
+ // If this tag should be skipped, skip to the end of this tag.
+ ResXMLParser::ResXMLPosition curPos;
+ tree.getPosition(&curPos);
+ if (std::find(tagsToSkip.begin(), tagsToSkip.end(), curPos) != tagsToSkip.end()) {
+ const int breakDepth = depth - 1;
+ while ((code = tree.next()) != ResXMLTree::END_DOCUMENT &&
+ code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG && --depth == breakDepth) {
+ break;
+ } else if (code == ResXMLTree::START_TAG) {
+ depth++;
+ }
+ }
+ continue;
+ }
+
const char16_t* ctag16 = tree.getElementName(&len);
if (ctag16 == NULL) {
SourcePos(manifestFile, tree.getLineNumber()).error(
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 5c5b3c32876c..12dc156f75be 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -162,6 +162,7 @@ cc_library_host_static {
"Configuration.proto",
"Resources.proto",
"ResourcesInternal.proto",
+ "ValueTransformer.cpp",
],
proto: {
export_proto_headers: true,
@@ -196,9 +197,9 @@ cc_test_host {
],
defaults: ["aapt2_defaults"],
data: [
- "integration-tests/CompileTest/**/*",
- "integration-tests/CommandTests/**/*",
- "integration-tests/ConvertTest/**/*"
+ "integration-tests/CompileTest/**/*",
+ "integration-tests/CommandTests/**/*",
+ "integration-tests/ConvertTest/**/*",
],
}
@@ -211,6 +212,9 @@ cc_binary_host {
use_version_lib: true,
static_libs: ["libaapt2"],
defaults: ["aapt2_defaults"],
+ dist: {
+ targets: ["aapt2_artifacts"],
+ },
}
// ==========================================================
@@ -229,6 +233,9 @@ genrule {
"cp $(in) $(genDir)/protos && " +
"$(location :soong_zip) -o $(out) -C $(genDir)/protos -D $(genDir)/protos",
dist: {
- targets: ["sdk_repo"],
+ targets: [
+ "sdk_repo",
+ "aapt2_artifacts",
+ ],
},
}
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 82da24959521..ef3a62f4efcc 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -256,57 +256,41 @@ class ValueBodyPrinter : public ConstValueVisitor {
void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
Printer* printer) {
- for (const auto& package : table.packages) {
- ValueHeadlinePrinter headline_printer(package->name, printer);
- ValueBodyPrinter body_printer(package->name, printer);
+ const auto table_view = table.GetPartitionedView();
+ for (const auto& package : table_view.packages) {
+ ValueHeadlinePrinter headline_printer(package.name, printer);
+ ValueBodyPrinter body_printer(package.name, printer);
printer->Print("Package name=");
- printer->Print(package->name);
- if (package->id) {
- printer->Print(StringPrintf(" id=%02x", package->id.value()));
+ printer->Print(package.name);
+ if (package.id) {
+ printer->Print(StringPrintf(" id=%02x", package.id.value()));
}
printer->Println();
printer->Indent();
- for (const auto& type : package->types) {
+ for (const auto& type : package.types) {
printer->Print("type ");
- printer->Print(to_string(type->type));
- if (type->id) {
- printer->Print(StringPrintf(" id=%02x", type->id.value()));
- }
- printer->Println(StringPrintf(" entryCount=%zd", type->entries.size()));
-
- std::vector<const ResourceEntry*> sorted_entries;
- for (const auto& entry : type->entries) {
- auto iter = std::lower_bound(
- sorted_entries.begin(), sorted_entries.end(), entry.get(),
- [](const ResourceEntry* a, const ResourceEntry* b) -> bool {
- if (a->id && b->id) {
- return a->id.value() < b->id.value();
- } else if (a->id) {
- return true;
- } else {
- return false;
- }
- });
- sorted_entries.insert(iter, entry.get());
+ printer->Print(to_string(type.type));
+ if (type.id) {
+ printer->Print(StringPrintf(" id=%02x", type.id.value()));
}
+ printer->Println(StringPrintf(" entryCount=%zd", type.entries.size()));
printer->Indent();
- for (const ResourceEntry* entry : sorted_entries) {
- const ResourceId id(package->id.value_or_default(0), type->id.value_or_default(0),
- entry->id.value_or_default(0));
-
+ for (const ResourceTableEntryView& entry : type.entries) {
printer->Print("resource ");
- printer->Print(id.to_string());
+ printer->Print(ResourceId(package.id.value_or_default(0), type.id.value_or_default(0),
+ entry.id.value_or_default(0))
+ .to_string());
printer->Print(" ");
// Write the name without the package (this is obvious and too verbose).
- printer->Print(to_string(type->type));
+ printer->Print(to_string(type.type));
printer->Print("/");
- printer->Print(entry->name);
+ printer->Print(entry.name);
- switch (entry->visibility.level) {
+ switch (entry.visibility.level) {
case Visibility::Level::kPublic:
printer->Print(" PUBLIC");
break;
@@ -318,15 +302,24 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
break;
}
- if (entry->overlayable_item) {
+ if (entry.visibility.staged_api) {
+ printer->Print(" STAGED");
+ }
+
+ if (entry.overlayable_item) {
printer->Print(" OVERLAYABLE");
}
+ if (entry.staged_id) {
+ printer->Print(" STAGED_ID=");
+ printer->Print(entry.staged_id.value().id.to_string());
+ }
+
printer->Println();
if (options.show_values) {
printer->Indent();
- for (const auto& value : entry->values) {
+ for (const auto& value : entry.values) {
printer->Print("(");
printer->Print(value->config.to_string());
printer->Print(") ");
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 45719ef474cd..830bc5fa36aa 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -113,7 +113,7 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
}
std::string error;
- table = util::make_unique<ResourceTable>(/** validate_resources **/ false);
+ table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled);
if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
diag->Error(DiagMessage(source)
<< "failed to deserialize " << kProtoResourceTablePath << ": " << error);
@@ -157,7 +157,7 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
if (table_file != nullptr) {
- table = util::make_unique<ResourceTable>(/** validate_resources **/ false);
+ table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled);
std::unique_ptr<io::IData> data = table_file->OpenAsData();
if (data == nullptr) {
diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
@@ -267,8 +267,14 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table
return false;
}
} else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) {
+ SerializeTableOptions proto_serialize_options;
+ proto_serialize_options.collapse_key_stringpool =
+ options.collapse_key_stringpool;
+ proto_serialize_options.name_collapse_exemptions =
+ options.name_collapse_exemptions;
pb::ResourceTable pb_table;
- SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics());
+ SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics(),
+ proto_serialize_options);
if (!io::CopyProtoToArchive(context,
&pb_table,
path,
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index b78f48ce7f17..6364ccdd09e5 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -78,6 +78,8 @@ StringPiece to_string(ResourceType type) {
return "interpolator";
case ResourceType::kLayout:
return "layout";
+ case ResourceType::kMacro:
+ return "macro";
case ResourceType::kMenu:
return "menu";
case ResourceType::kMipmap:
@@ -119,6 +121,7 @@ static const std::map<StringPiece, ResourceType> sResourceTypeMap{
{"integer", ResourceType::kInteger},
{"interpolator", ResourceType::kInterpolator},
{"layout", ResourceType::kLayout},
+ {"macro", ResourceType::kMacro},
{"menu", ResourceType::kMenu},
{"mipmap", ResourceType::kMipmap},
{"navigation", ResourceType::kNavigation},
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 4e051a37f3ed..307c21d9dc96 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -57,6 +57,7 @@ enum class ResourceType {
kInteger,
kInterpolator,
kLayout,
+ kMacro,
kMenu,
kMipmap,
kNavigation,
@@ -138,7 +139,7 @@ struct ResourceId {
uint32_t id;
ResourceId();
- ResourceId(const ResourceId& rhs);
+ ResourceId(const ResourceId& rhs) = default;
ResourceId(uint32_t res_id); // NOLINT(google-explicit-constructor)
ResourceId(uint8_t p, uint8_t t, uint16_t e);
@@ -222,8 +223,6 @@ bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b);
inline ResourceId::ResourceId() : id(0) {}
-inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {}
-
inline ResourceId::ResourceId(uint32_t res_id) : id(res_id) {}
inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e)
@@ -274,6 +273,19 @@ inline std::string to_string(const ResourceId& id) {
return id.to_string();
}
+// Helper to compare resource IDs, moving dynamic IDs after framework IDs.
+inline bool cmp_ids_dynamic_after_framework(const ResourceId& a, const ResourceId& b) {
+ // If one of a and b is from the framework package (package ID 0x01), and the
+ // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the
+ // framework ID. This ensures that when AssetManager resolves the dynamic IDs,
+ // they will be in sorted order as expected by AssetManager.
+ if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) ||
+ (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) {
+ return b < a;
+ }
+ return a < b;
+}
+
//
// ResourceType implementation.
//
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 931a14b1f650..f1e2da9f41e2 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -42,6 +42,11 @@ using ::android::StringPiece;
using android::idmap2::policy::kPolicyStringToFlag;
namespace aapt {
+namespace {
+constexpr const char* kPublicGroupTag = "public-group";
+constexpr const char* kStagingPublicGroupTag = "staging-public-group";
+constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final";
+} // namespace
constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
@@ -102,8 +107,10 @@ struct ParsedResource {
ResourceId id;
Visibility::Level visibility_level = Visibility::Level::kUndefined;
+ bool staged_api = false;
bool allow_new = false;
Maybe<OverlayableItem> overlayable_item;
+ Maybe<StagedId> staged_alias;
std::string comment;
std::unique_ptr<Value> value;
@@ -118,43 +125,48 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed
res->comment = trimmed_comment.to_string();
}
+ NewResourceBuilder res_builder(res->name);
if (res->visibility_level != Visibility::Level::kUndefined) {
Visibility visibility;
visibility.level = res->visibility_level;
+ visibility.staged_api = res->staged_api;
visibility.source = res->source;
visibility.comment = res->comment;
- if (!table->SetVisibilityWithId(res->name, visibility, res->id, diag)) {
- return false;
- }
+ res_builder.SetVisibility(visibility);
+ }
+
+ if (res->id.is_valid()) {
+ res_builder.SetId(res->id);
}
if (res->allow_new) {
AllowNew allow_new;
allow_new.source = res->source;
allow_new.comment = res->comment;
- if (!table->SetAllowNew(res->name, allow_new, diag)) {
- return false;
- }
+ res_builder.SetAllowNew(allow_new);
}
if (res->overlayable_item) {
- if (!table->SetOverlayable(res->name, res->overlayable_item.value(), diag)) {
- return false;
- }
+ res_builder.SetOverlayable(res->overlayable_item.value());
}
if (res->value != nullptr) {
// Attach the comment, source and config to the value.
res->value->SetComment(std::move(res->comment));
res->value->SetSource(std::move(res->source));
+ res_builder.SetValue(std::move(res->value), res->config, res->product);
+ }
- if (!table->AddResourceWithId(res->name, res->id, res->config, res->product,
- std::move(res->value), diag)) {
- return false;
- }
+ if (res->staged_alias) {
+ res_builder.SetStagedId(res->staged_alias.value());
}
bool error = false;
+ if (!res->name.entry.empty()) {
+ if (!table->AddResource(res_builder.Build(), diag)) {
+ return false;
+ }
+ }
for (ParsedResource& child : res->child_resources) {
error |= !AddResourcesToTable(table, diag, &child);
}
@@ -342,7 +354,7 @@ bool ResourceParser::FlattenXmlSubtree(
}
}
- // Sanity check to make sure we processed all the nodes.
+ // Validity check to make sure we processed all the nodes.
CHECK(node_stack.size() == 1u);
CHECK(node_stack.back() == &root);
@@ -525,6 +537,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
{"plurals", std::mem_fn(&ResourceParser::ParsePlural)},
{"public", std::mem_fn(&ResourceParser::ParsePublic)},
{"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
+ {"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)},
+ {"staging-public-group-final", std::mem_fn(&ResourceParser::ParseStagingPublicGroupFinal)},
{"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
{"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle,
std::placeholders::_2, std::placeholders::_3)},
@@ -620,6 +634,16 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
}
return true;
+ } else if (resource_type == "macro") {
+ if (!maybe_name) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << parser->element_name() << "> missing 'name' attribute");
+ return false;
+ }
+
+ out_resource->name.type = ResourceType::kMacro;
+ out_resource->name.entry = maybe_name.value().to_string();
+ return ParseMacro(parser, out_resource);
}
if (can_be_item) {
@@ -653,7 +677,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
const auto bag_iter = elToBagMap.find(resource_type);
if (bag_iter != elToBagMap.end()) {
// Ensure we have a name (unless this is a <public-group> or <overlayable>).
- if (resource_type != "public-group" && resource_type != "overlayable") {
+ if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag &&
+ resource_type != kStagingPublicGroupFinalTag && resource_type != "overlayable") {
if (!maybe_name) {
diag_->Error(DiagMessage(out_resource->source)
<< "<" << parser->element_name() << "> missing 'name' attribute");
@@ -718,6 +743,24 @@ bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
return true;
}
+std::optional<FlattenedXmlSubTree> ResourceParser::CreateFlattenSubTree(
+ xml::XmlPullParser* parser) {
+ const size_t begin_xml_line = parser->line_number();
+
+ std::string raw_value;
+ StyleString style_string;
+ std::vector<UntranslatableSection> untranslatable_sections;
+ if (!FlattenXmlSubtree(parser, &raw_value, &style_string, &untranslatable_sections)) {
+ return {};
+ }
+
+ return FlattenedXmlSubTree{.raw_value = raw_value,
+ .style_string = style_string,
+ .untranslatable_sections = untranslatable_sections,
+ .namespace_resolver = parser,
+ .source = source_.WithLine(begin_xml_line)};
+}
+
/**
* Reads the entire XML subtree and attempts to parse it as some Item,
* with typeMask denoting which items it can be. If allowRawValue is
@@ -725,42 +768,46 @@ bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
* an Item. If allowRawValue is false, nullptr is returned in this
* case.
*/
-std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
- const uint32_t type_mask,
+std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, const uint32_t type_mask,
const bool allow_raw_value) {
- const size_t begin_xml_line = parser->line_number();
-
- std::string raw_value;
- StyleString style_string;
- std::vector<UntranslatableSection> untranslatable_sections;
- if (!FlattenXmlSubtree(parser, &raw_value, &style_string, &untranslatable_sections)) {
+ auto sub_tree = CreateFlattenSubTree(parser);
+ if (!sub_tree.has_value()) {
return {};
}
+ return ParseXml(sub_tree.value(), type_mask, allow_raw_value, *table_, config_, *diag_);
+}
- if (!style_string.spans.empty()) {
+std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub_tree,
+ const uint32_t type_mask, const bool allow_raw_value,
+ ResourceTable& table,
+ const android::ConfigDescription& config,
+ IDiagnostics& diag) {
+ if (!xmlsub_tree.style_string.spans.empty()) {
// This can only be a StyledString.
std::unique_ptr<StyledString> styled_string =
- util::make_unique<StyledString>(table_->string_pool.MakeRef(
- style_string, StringPool::Context(StringPool::Context::kNormalPriority, config_)));
- styled_string->untranslatable_sections = std::move(untranslatable_sections);
+ util::make_unique<StyledString>(table.string_pool.MakeRef(
+ xmlsub_tree.style_string,
+ StringPool::Context(StringPool::Context::kNormalPriority, config)));
+ styled_string->untranslatable_sections = xmlsub_tree.untranslatable_sections;
return std::move(styled_string);
}
auto on_create_reference = [&](const ResourceName& name) {
// name.package can be empty here, as it will assume the package name of the
// table.
- std::unique_ptr<Id> id = util::make_unique<Id>();
- id->SetSource(source_.WithLine(begin_xml_line));
- table_->AddResource(name, {}, {}, std::move(id), diag_);
+ auto id = util::make_unique<Id>();
+ id->SetSource(xmlsub_tree.source);
+ return table.AddResource(NewResourceBuilder(name).SetValue(std::move(id)).Build(), &diag);
};
// Process the raw value.
- std::unique_ptr<Item> processed_item =
- ResourceUtils::TryParseItemForAttribute(raw_value, type_mask, on_create_reference);
+ std::unique_ptr<Item> processed_item = ResourceUtils::TryParseItemForAttribute(
+ xmlsub_tree.raw_value, type_mask, on_create_reference);
if (processed_item) {
// Fix up the reference.
- if (Reference* ref = ValueCast<Reference>(processed_item.get())) {
- ResolvePackage(parser, ref);
+ if (auto ref = ValueCast<Reference>(processed_item.get())) {
+ ref->allow_raw = allow_raw_value;
+ ResolvePackage(xmlsub_tree.namespace_resolver, ref);
}
return processed_item;
}
@@ -769,17 +816,16 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
if (type_mask & android::ResTable_map::TYPE_STRING) {
// Use the trimmed, escaped string.
std::unique_ptr<String> string = util::make_unique<String>(
- table_->string_pool.MakeRef(style_string.str, StringPool::Context(config_)));
- string->untranslatable_sections = std::move(untranslatable_sections);
+ table.string_pool.MakeRef(xmlsub_tree.style_string.str, StringPool::Context(config)));
+ string->untranslatable_sections = xmlsub_tree.untranslatable_sections;
return std::move(string);
}
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()) {
+ return util::make_unique<RawString>(table.string_pool.MakeRef(
+ util::TrimWhitespace(xmlsub_tree.raw_value), StringPool::Context(config)));
+ } else if (util::TrimWhitespace(xmlsub_tree.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();
}
@@ -842,6 +888,35 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser,
return true;
}
+bool ResourceParser::ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+ auto sub_tree = CreateFlattenSubTree(parser);
+ if (!sub_tree) {
+ return false;
+ }
+
+ if (out_resource->config != ConfigDescription::DefaultConfig()) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<macro> tags cannot be declared in configurations other than the default "
+ "configuration'");
+ return false;
+ }
+
+ auto macro = std::make_unique<Macro>();
+ macro->raw_value = std::move(sub_tree->raw_value);
+ macro->style_string = std::move(sub_tree->style_string);
+ macro->untranslatable_sections = std::move(sub_tree->untranslatable_sections);
+
+ for (const auto& decl : parser->package_decls()) {
+ macro->alias_namespaces.emplace_back(
+ Macro::Namespace{.alias = decl.prefix,
+ .package_name = decl.package.package,
+ .is_private = decl.package.private_namespace});
+ }
+
+ out_resource->value = std::move(macro);
+ return true;
+}
+
bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
if (options_.visibility) {
diag_->Error(DiagMessage(out_resource->source)
@@ -890,54 +965,45 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out
return true;
}
-bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
- if (options_.visibility) {
- diag_->Error(DiagMessage(out_resource->source)
- << "<public-group> tag not allowed with --visibility flag");
- return false;
- }
-
+template <typename Func>
+bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
+ const char* tag_name, IDiagnostics* diag, Func&& func) {
if (out_resource->config != ConfigDescription::DefaultConfig()) {
- diag_->Warn(DiagMessage(out_resource->source)
- << "ignoring configuration '" << out_resource->config
- << "' for <public-group> tag");
+ diag->Warn(DiagMessage(out_resource->source)
+ << "ignoring configuration '" << out_resource->config << "' for <" << tag_name
+ << "> tag");
}
Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
if (!maybe_type) {
- diag_->Error(DiagMessage(out_resource->source)
- << "<public-group> must have a 'type' attribute");
+ diag->Error(DiagMessage(out_resource->source)
+ << "<" << tag_name << "> must have a 'type' attribute");
return false;
}
const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
if (!parsed_type) {
- diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '"
- << maybe_type.value()
- << "' in <public-group>");
+ diag->Error(DiagMessage(out_resource->source)
+ << "invalid resource type '" << maybe_type.value() << "' in <" << tag_name << ">");
return false;
}
- Maybe<StringPiece> maybe_id_str =
- xml::FindNonEmptyAttribute(parser, "first-id");
+ Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id");
if (!maybe_id_str) {
- diag_->Error(DiagMessage(out_resource->source)
- << "<public-group> must have a 'first-id' attribute");
+ diag->Error(DiagMessage(out_resource->source)
+ << "<" << tag_name << "> must have a 'first-id' attribute");
return false;
}
- Maybe<ResourceId> maybe_id =
- ResourceUtils::ParseResourceId(maybe_id_str.value());
+ Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value());
if (!maybe_id) {
- diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '"
- << maybe_id_str.value()
- << "' in <public-group>");
+ diag->Error(DiagMessage(out_resource->source)
+ << "invalid resource ID '" << maybe_id_str.value() << "' in <" << tag_name << ">");
return false;
}
- ResourceId next_id = maybe_id.value();
-
std::string comment;
+ ResourceId next_id = maybe_id.value();
bool error = false;
const size_t depth = parser->depth();
while (xml::XmlPullParser::NextChildNode(parser, depth)) {
@@ -949,53 +1015,79 @@ bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource
continue;
}
- const Source item_source = source_.WithLine(parser->line_number());
+ const Source item_source = out_resource->source.WithLine(parser->line_number());
const std::string& element_namespace = parser->element_namespace();
const std::string& element_name = parser->element_name();
if (element_namespace.empty() && element_name == "public") {
- Maybe<StringPiece> maybe_name =
- xml::FindNonEmptyAttribute(parser, "name");
+ auto maybe_name = xml::FindNonEmptyAttribute(parser, "name");
if (!maybe_name) {
- diag_->Error(DiagMessage(item_source)
- << "<public> must have a 'name' attribute");
+ diag->Error(DiagMessage(item_source) << "<public> must have a 'name' attribute");
error = true;
continue;
}
if (xml::FindNonEmptyAttribute(parser, "id")) {
- diag_->Error(DiagMessage(item_source)
- << "'id' is ignored within <public-group>");
+ diag->Error(DiagMessage(item_source) << "'id' is ignored within <" << tag_name << ">");
error = true;
continue;
}
if (xml::FindNonEmptyAttribute(parser, "type")) {
- diag_->Error(DiagMessage(item_source)
- << "'type' is ignored within <public-group>");
+ diag->Error(DiagMessage(item_source) << "'type' is ignored within <" << tag_name << ">");
error = true;
continue;
}
- ParsedResource child_resource;
- child_resource.name.type = *parsed_type;
- child_resource.name.entry = maybe_name.value().to_string();
- child_resource.id = next_id;
- // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment
- child_resource.comment = std::move(comment);
- child_resource.source = item_source;
- child_resource.visibility_level = Visibility::Level::kPublic;
- out_resource->child_resources.push_back(std::move(child_resource));
+ ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
+ .name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()},
+ .source = item_source,
+ .comment = std::move(comment),
+ });
- next_id.id += 1;
+ // Execute group specific code.
+ func(entry_res, next_id);
+ next_id.id++;
} else if (!ShouldIgnoreElement(element_namespace, element_name)) {
- diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
+ diag->Error(DiagMessage(item_source) << ":" << element_name << ">");
error = true;
}
}
return !error;
}
+bool ResourceParser::ParseStagingPublicGroup(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ return ParseGroupImpl(parser, out_resource, kStagingPublicGroupTag, diag_,
+ [](ParsedResource& parsed_entry, ResourceId id) {
+ parsed_entry.id = id;
+ parsed_entry.staged_api = true;
+ parsed_entry.visibility_level = Visibility::Level::kPublic;
+ });
+}
+
+bool ResourceParser::ParseStagingPublicGroupFinal(xml::XmlPullParser* parser,
+ ParsedResource* out_resource) {
+ return ParseGroupImpl(parser, out_resource, kStagingPublicGroupFinalTag, diag_,
+ [](ParsedResource& parsed_entry, ResourceId id) {
+ parsed_entry.staged_alias = StagedId{id, parsed_entry.source};
+ });
+}
+
+bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+ if (options_.visibility) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<" << kPublicGroupTag << "> tag not allowed with --visibility flag");
+ return false;
+ }
+
+ return ParseGroupImpl(parser, out_resource, kPublicGroupTag, diag_,
+ [](ParsedResource& parsed_entry, ResourceId id) {
+ parsed_entry.id = id;
+ parsed_entry.visibility_level = Visibility::Level::kPublic;
+ });
+}
+
bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser,
ParsedResource* out_resource) {
Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 9d3ecc866c5d..261499781638 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -57,6 +57,14 @@ struct ResourceParserOptions {
Maybe<Visibility::Level> visibility;
};
+struct FlattenedXmlSubTree {
+ std::string raw_value;
+ StyleString style_string;
+ std::vector<UntranslatableSection> untranslatable_sections;
+ xml::IPackageDeclStack* namespace_resolver;
+ Source source;
+};
+
/*
* Parses an XML file for resources and adds them to a ResourceTable.
*/
@@ -67,9 +75,16 @@ class ResourceParser {
const ResourceParserOptions& options = {});
bool Parse(xml::XmlPullParser* parser);
+ static std::unique_ptr<Item> ParseXml(const FlattenedXmlSubTree& xmlsub_tree, uint32_t type_mask,
+ bool allow_raw_value, ResourceTable& table,
+ const android::ConfigDescription& config,
+ IDiagnostics& diag);
+
private:
DISALLOW_COPY_AND_ASSIGN(ResourceParser);
+ std::optional<FlattenedXmlSubTree> CreateFlattenSubTree(xml::XmlPullParser* parser);
+
// Parses the XML subtree as a StyleString (flattened XML representation for strings with
// formatting). If parsing fails, false is returned and the out parameters are left in an
// unspecified state. Otherwise,
@@ -96,9 +111,11 @@ class ResourceParser {
bool ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource, uint32_t format);
bool ParseString(xml::XmlPullParser* parser, ParsedResource* out_resource);
-
+ bool ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
+ bool ParseStagingPublicGroupFinal(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);
@@ -107,8 +124,7 @@ class ResourceParser {
bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
Maybe<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
const android::StringPiece& tag);
- bool ParseStyle(const ResourceType type, xml::XmlPullParser* parser,
- ParsedResource* out_resource);
+ bool ParseStyle(ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 9b70079a98c9..279ebcba2f71 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -336,6 +336,90 @@ TEST_F(ResourceParserTest, ParseAttr) {
EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_ANY));
}
+TEST_F(ResourceParserTest, ParseMacro) {
+ std::string input = R"(<macro name="foo">12345</macro>)";
+ ASSERT_TRUE(TestParse(input));
+
+ Macro* macro = test::GetValue<Macro>(&table_, "macro/foo");
+ ASSERT_THAT(macro, NotNull());
+ EXPECT_THAT(macro->raw_value, Eq("12345"));
+ EXPECT_THAT(macro->style_string.str, Eq("12345"));
+ EXPECT_THAT(macro->style_string.spans, IsEmpty());
+ EXPECT_THAT(macro->untranslatable_sections, IsEmpty());
+ EXPECT_THAT(macro->alias_namespaces, IsEmpty());
+}
+
+TEST_F(ResourceParserTest, ParseMacroUntranslatableSection) {
+ std::string input = R"(<macro name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+This being <b><xliff:g>human</xliff:g></b> is a guest house.</macro>)";
+ ASSERT_TRUE(TestParse(input));
+
+ Macro* macro = test::GetValue<Macro>(&table_, "macro/foo");
+ ASSERT_THAT(macro, NotNull());
+ EXPECT_THAT(macro->raw_value, Eq("\nThis being human is a guest house."));
+ EXPECT_THAT(macro->style_string.str, Eq(" This being human is a guest house."));
+ EXPECT_THAT(macro->style_string.spans.size(), Eq(1));
+ EXPECT_THAT(macro->style_string.spans[0].name, Eq("b"));
+ EXPECT_THAT(macro->style_string.spans[0].first_char, Eq(12));
+ EXPECT_THAT(macro->style_string.spans[0].last_char, Eq(16));
+ ASSERT_THAT(macro->untranslatable_sections.size(), Eq(1));
+ EXPECT_THAT(macro->untranslatable_sections[0].start, Eq(12));
+ EXPECT_THAT(macro->untranslatable_sections[0].end, Eq(17));
+ EXPECT_THAT(macro->alias_namespaces, IsEmpty());
+}
+
+TEST_F(ResourceParserTest, ParseMacroNamespaces) {
+ std::string input = R"(<macro name="foo" xmlns:app="http://schemas.android.com/apk/res/android">
+@app:string/foo</macro>)";
+ ASSERT_TRUE(TestParse(input));
+
+ Macro* macro = test::GetValue<Macro>(&table_, "macro/foo");
+ ASSERT_THAT(macro, NotNull());
+ EXPECT_THAT(macro->raw_value, Eq("\n@app:string/foo"));
+ EXPECT_THAT(macro->style_string.str, Eq("@app:string/foo"));
+ EXPECT_THAT(macro->style_string.spans, IsEmpty());
+ EXPECT_THAT(macro->untranslatable_sections, IsEmpty());
+ EXPECT_THAT(macro->alias_namespaces.size(), Eq(1));
+ EXPECT_THAT(macro->alias_namespaces[0].alias, Eq("app"));
+ EXPECT_THAT(macro->alias_namespaces[0].package_name, Eq("android"));
+ EXPECT_THAT(macro->alias_namespaces[0].is_private, Eq(false));
+}
+
+TEST_F(ResourceParserTest, ParseMacroReference) {
+ std::string input = R"(<string name="res_string">@macro/foo</string>)";
+ ASSERT_TRUE(TestParse(input));
+
+ Reference* macro = test::GetValue<Reference>(&table_, "string/res_string");
+ ASSERT_THAT(macro, NotNull());
+ EXPECT_THAT(macro->type_flags, Eq(ResTable_map::TYPE_STRING));
+ EXPECT_THAT(macro->allow_raw, Eq(false));
+
+ input = R"(<style name="foo">
+ <item name="bar">@macro/foo</item>
+ </style>)";
+
+ ASSERT_TRUE(TestParse(input));
+ Style* style = test::GetValue<Style>(&table_, "style/foo");
+ ASSERT_THAT(style, NotNull());
+ EXPECT_THAT(style->entries.size(), Eq(1));
+
+ macro = ValueCast<Reference>(style->entries[0].value.get());
+ ASSERT_THAT(macro, NotNull());
+ EXPECT_THAT(macro->type_flags, Eq(0U));
+ EXPECT_THAT(macro->allow_raw, Eq(true));
+}
+
+TEST_F(ResourceParserTest, ParseMacroNoNameFail) {
+ std::string input = R"(<macro>12345</macro>)";
+ ASSERT_FALSE(TestParse(input));
+}
+
+TEST_F(ResourceParserTest, ParseMacroNonDefaultConfigurationFail) {
+ const ConfigDescription watch_config = test::ParseConfigOrDie("watch");
+ std::string input = R"(<macro name="foo">12345</macro>)";
+ ASSERT_FALSE(TestParse(input, watch_config));
+}
+
// Old AAPT allowed attributes to be defined under different configurations, but ultimately
// stored them with the default configuration. Check that we have the same behavior.
TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
@@ -831,25 +915,38 @@ TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("attr/foo"));
ASSERT_TRUE(result);
+ ASSERT_TRUE(result.value().entry->id);
+ EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01010040)));
- ASSERT_TRUE(result.value().package->id);
- ASSERT_TRUE(result.value().type->id);
+ result = table_.FindResource(test::ParseNameOrDie("attr/bar"));
+ ASSERT_TRUE(result);
ASSERT_TRUE(result.value().entry->id);
- ResourceId actual_id(result.value().package->id.value(),
- result.value().type->id.value(),
- result.value().entry->id.value());
- EXPECT_THAT(actual_id, Eq(ResourceId(0x01010040)));
+ EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01010041)));
+}
+
+TEST_F(ResourceParserTest, StagingPublicGroup) {
+ std::string input = R"(
+ <staging-public-group type="attr" first-id="0x01ff0049">
+ <public name="foo" />
+ <public name="bar" />
+ </staging-public-group>)";
+ ASSERT_TRUE(TestParse(input));
+
+ Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("attr/foo"));
+ ASSERT_TRUE(result);
+
+ ASSERT_TRUE(result.value().entry->id);
+ EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff0049)));
+ EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
+ EXPECT_TRUE(result.value().entry->visibility.staged_api);
result = table_.FindResource(test::ParseNameOrDie("attr/bar"));
ASSERT_TRUE(result);
- ASSERT_TRUE(result.value().package->id);
- ASSERT_TRUE(result.value().type->id);
ASSERT_TRUE(result.value().entry->id);
- actual_id = ResourceId(result.value().package->id.value(),
- result.value().type->id.value(),
- result.value().entry->id.value());
- EXPECT_THAT(actual_id, Eq(ResourceId(0x01010041)));
+ EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff004a)));
+ EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
+ EXPECT_TRUE(result.value().entry->visibility.staged_api);
}
TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) {
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index e0a9a31eee8b..8ab1493c6ab3 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -18,20 +18,18 @@
#include <algorithm>
#include <memory>
-#include <string>
#include <tuple>
#include "android-base/logging.h"
-#include "android-base/stringprintf.h"
#include "androidfw/ConfigDescription.h"
#include "androidfw/ResourceTypes.h"
-#include "Debug.h"
#include "NameMangler.h"
+#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-#include "trace/TraceBuffer.h"
#include "text/Unicode.h"
+#include "trace/TraceBuffer.h"
#include "util/Util.h"
using ::aapt::text::IsValidResourceEntryName;
@@ -43,154 +41,130 @@ namespace aapt {
const char* Overlayable::kActorScheme = "overlay";
-static bool less_than_type_and_id(const std::unique_ptr<ResourceTableType>& lhs,
- const std::pair<ResourceType, Maybe<uint8_t>>& rhs) {
- return lhs->type < rhs.first || (lhs->type == rhs.first && rhs.second && lhs->id < rhs.second);
+namespace {
+bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
+ return lhs->type < rhs;
}
template <typename T>
-static bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) {
+bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) {
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
}
template <typename T>
-static bool less_than_struct_with_name_and_id(const std::unique_ptr<T>& lhs,
- const std::pair<StringPiece, Maybe<uint16_t>>& rhs) {
- int name_cmp = lhs->name.compare(0, lhs->name.size(), rhs.first.data(), rhs.first.size());
- return name_cmp < 0 || (name_cmp == 0 && rhs.second && lhs->id < rhs.second);
+bool greater_than_struct_with_name(const StringPiece& lhs, const std::unique_ptr<T>& rhs) {
+ return rhs->name.compare(0, rhs->name.size(), lhs.data(), lhs.size()) > 0;
}
-ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) const {
- const auto last = packages.end();
- auto iter = std::lower_bound(packages.begin(), last, name,
- less_than_struct_with_name<ResourceTablePackage>);
- if (iter != last && name == (*iter)->name) {
- return iter->get();
+template <typename T>
+struct NameEqualRange {
+ bool operator()(const std::unique_ptr<T>& lhs, const StringPiece& rhs) const {
+ return less_than_struct_with_name<T>(lhs, rhs);
}
- return nullptr;
-}
-
-ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) const {
- for (auto& package : packages) {
- if (package->id && package->id.value() == id) {
- return package.get();
- }
+ bool operator()(const StringPiece& lhs, const std::unique_ptr<T>& rhs) const {
+ return greater_than_struct_with_name<T>(lhs, rhs);
}
- return nullptr;
-}
+};
-ResourceTablePackage* ResourceTable::CreatePackage(const StringPiece& name, Maybe<uint8_t> id) {
- TRACE_CALL();
- ResourceTablePackage* package = FindOrCreatePackage(name);
- if (id && !package->id) {
- package->id = id;
- return package;
+template <typename T, typename U>
+bool less_than_struct_with_name_and_id(const T& lhs,
+ const std::pair<std::string_view, Maybe<U>>& rhs) {
+ if (lhs.id != rhs.second) {
+ return lhs.id < rhs.second;
}
+ return lhs.name.compare(0, lhs.name.size(), rhs.first.data(), rhs.first.size()) < 0;
+}
- if (id && package->id && package->id.value() != id.value()) {
- return nullptr;
- }
- return package;
+template <typename T, typename Func, typename Elements>
+T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) {
+ const auto iter =
+ std::lower_bound(entries.begin(), entries.end(), name, less_than_struct_with_name<T>);
+ const bool found = iter != entries.end() && name == (*iter)->name;
+ return action(found, iter);
}
-ResourceTablePackage* ResourceTable::CreatePackageAllowingDuplicateNames(const StringPiece& name,
- const Maybe<uint8_t> id) {
- const auto last = packages.end();
- auto iter = std::lower_bound(packages.begin(), last, std::make_pair(name, id),
- less_than_struct_with_name_and_id<ResourceTablePackage>);
+struct ConfigKey {
+ const ConfigDescription* config;
+ const StringPiece& product;
+};
- if (iter != last && name == (*iter)->name && id == (*iter)->id) {
- return iter->get();
+template <typename T>
+bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) {
+ int cmp = lhs->config.compare(*rhs.config);
+ if (cmp == 0) {
+ cmp = StringPiece(lhs->product).compare(rhs.product);
}
-
- std::unique_ptr<ResourceTablePackage> new_package = util::make_unique<ResourceTablePackage>();
- new_package->name = name.to_string();
- new_package->id = id;
- return packages.emplace(iter, std::move(new_package))->get();
+ return cmp < 0;
}
-ResourceTablePackage* ResourceTable::FindOrCreatePackage(const StringPiece& name) {
- const auto last = packages.end();
- auto iter = std::lower_bound(packages.begin(), last, name,
- less_than_struct_with_name<ResourceTablePackage>);
- if (iter != last && name == (*iter)->name) {
- return iter->get();
- }
+} // namespace
- std::unique_ptr<ResourceTablePackage> new_package = util::make_unique<ResourceTablePackage>();
- new_package->name = name.to_string();
- return packages.emplace(iter, std::move(new_package))->get();
+ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
}
-ResourceTableType* ResourceTablePackage::FindType(ResourceType type, const Maybe<uint8_t> id) {
- const auto last = types.end();
- auto iter = std::lower_bound(types.begin(), last, std::make_pair(type, id),
- less_than_type_and_id);
- if (iter != last && (*iter)->type == type && (!id || id == (*iter)->id)) {
- return iter->get();
- }
- return nullptr;
+ResourceTablePackage* ResourceTable::FindPackage(const android::StringPiece& name) const {
+ return FindElementsRunAction<ResourceTablePackage>(
+ name, packages, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
}
-ResourceTableType* ResourceTablePackage::FindOrCreateType(ResourceType type,
- const Maybe<uint8_t> id) {
- const auto last = types.end();
- auto iter = std::lower_bound(types.begin(), last, std::make_pair(type, id),
- less_than_type_and_id);
- if (iter != last && (*iter)->type == type && (!id || id == (*iter)->id)) {
- return iter->get();
- }
+ResourceTablePackage* ResourceTable::FindOrCreatePackage(const android::StringPiece& name) {
+ return FindElementsRunAction<ResourceTablePackage>(name, packages, [&](bool found, auto& iter) {
+ return found ? iter->get() : packages.emplace(iter, new ResourceTablePackage(name))->get();
+ });
+}
- auto new_type = new ResourceTableType(type);
- new_type->id = id;
- return types.emplace(iter, std::move(new_type))->get();
+template <typename Func, typename Elements>
+static ResourceTableType* FindTypeRunAction(ResourceType type, Elements& entries, Func action) {
+ const auto iter = std::lower_bound(entries.begin(), entries.end(), type, less_than_type);
+ const bool found = iter != entries.end() && type == (*iter)->type;
+ return action(found, iter);
}
-ResourceEntry* ResourceTableType::FindEntry(const StringPiece& name, const Maybe<uint16_t> id) {
- const auto last = entries.end();
- auto iter = std::lower_bound(entries.begin(), last, std::make_pair(name, id),
- less_than_struct_with_name_and_id<ResourceEntry>);
- if (iter != last && name == (*iter)->name && (!id || id == (*iter)->id)) {
- return iter->get();
- }
- return nullptr;
+ResourceTableType* ResourceTablePackage::FindType(ResourceType type) const {
+ return FindTypeRunAction(type, types,
+ [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
}
-ResourceEntry* ResourceTableType::FindOrCreateEntry(const StringPiece& name,
- const Maybe<uint16_t > id) {
- auto last = entries.end();
- auto iter = std::lower_bound(entries.begin(), last, std::make_pair(name, id),
- less_than_struct_with_name_and_id<ResourceEntry>);
- if (iter != last && name == (*iter)->name && (!id || id == (*iter)->id)) {
- return iter->get();
- }
+ResourceTableType* ResourceTablePackage::FindOrCreateType(ResourceType type) {
+ return FindTypeRunAction(type, types, [&](bool found, auto& iter) {
+ return found ? iter->get() : types.emplace(iter, new ResourceTableType(type))->get();
+ });
+}
- auto new_entry = new ResourceEntry(name);
- new_entry->id = id;
- return entries.emplace(iter, std::move(new_entry))->get();
+ResourceEntry* ResourceTableType::CreateEntry(const android::StringPiece& name) {
+ return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) {
+ return entries.emplace(iter, new ResourceEntry(name))->get();
+ });
}
-ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config) {
- return FindValue(config, StringPiece());
+ResourceEntry* ResourceTableType::FindEntry(const android::StringPiece& name) const {
+ return FindElementsRunAction<ResourceEntry>(
+ name, entries, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
}
-struct ConfigKey {
- const ConfigDescription* config;
- const StringPiece& product;
-};
+ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece& name) {
+ return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) {
+ return found ? iter->get() : entries.emplace(iter, new ResourceEntry(name))->get();
+ });
+}
-bool lt_config_key_ref(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
- int cmp = lhs->config.compare(*rhs.config);
- if (cmp == 0) {
- cmp = StringPiece(lhs->product).compare(rhs.product);
+ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
+ android::StringPiece product) {
+ auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
+ lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
+ if (iter != values.end()) {
+ ResourceConfigValue* value = iter->get();
+ if (value->config == config && StringPiece(value->product) == product) {
+ return value;
+ }
}
- return cmp < 0;
+ return nullptr;
}
-ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
- const StringPiece& product) {
+const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescription& config,
+ android::StringPiece product) const {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
- lt_config_key_ref);
+ lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
if (iter != values.end()) {
ResourceConfigValue* value = iter->get();
if (value->config == config && StringPiece(value->product) == product) {
@@ -203,7 +177,7 @@ ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
const StringPiece& product) {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
- lt_config_key_ref);
+ lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
if (iter != values.end()) {
ResourceConfigValue* value = iter->get();
if (value->config == config && StringPiece(value->product) == product) {
@@ -323,307 +297,335 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
return CollisionResult::kConflict;
}
-ResourceTable::CollisionResult ResourceTable::IgnoreCollision(Value* /** existing **/,
- Value* /** incoming **/) {
- return CollisionResult::kKeepBoth;
-}
-
-static StringPiece ResourceNameValidator(const StringPiece& name) {
- if (!IsValidResourceEntryName(name)) {
- return name;
+namespace {
+template <typename T, typename Comparer>
+struct SortedVectorInserter : public Comparer {
+ std::pair<bool, typename std::vector<T>::iterator> LowerBound(std::vector<T>& el,
+ const T& value) {
+ auto it = std::lower_bound(el.begin(), el.end(), value, [&](auto& lhs, auto& rhs) {
+ return Comparer::operator()(lhs, rhs);
+ });
+ bool found =
+ it != el.end() && !Comparer::operator()(*it, value) && !Comparer::operator()(value, *it);
+ return std::make_pair(found, it);
}
- return {};
-}
-static StringPiece SkipNameValidator(const StringPiece& /*name*/) {
- return {};
-}
-
-bool ResourceTable::AddResource(const ResourceNameRef& name,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value,
- IDiagnostics* diag) {
- return AddResourceImpl(name, ResourceId{}, config, product, std::move(value),
- (validate_resources_ ? ResourceNameValidator : SkipNameValidator),
- (validate_resources_ ? ResolveValueCollision : IgnoreCollision), diag);
-}
-
-bool ResourceTable::AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id,
- const ConfigDescription& config, const StringPiece& product,
- std::unique_ptr<Value> value, IDiagnostics* diag) {
- return AddResourceImpl(name, res_id, config, product, std::move(value),
- (validate_resources_ ? ResourceNameValidator : SkipNameValidator),
- (validate_resources_ ? ResolveValueCollision : IgnoreCollision), diag);
-}
-
-bool ResourceTable::AddResourceMangled(const ResourceNameRef& name, const ConfigDescription& config,
- const StringPiece& product, std::unique_ptr<Value> value,
- IDiagnostics* diag) {
- return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipNameValidator,
- (validate_resources_ ? ResolveValueCollision : IgnoreCollision), diag);
-}
-
-bool ResourceTable::AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id,
- const ConfigDescription& config,
- const StringPiece& product,
- std::unique_ptr<Value> value, IDiagnostics* diag) {
- return AddResourceImpl(name, id, config, product, std::move(value), SkipNameValidator,
- (validate_resources_ ? ResolveValueCollision : IgnoreCollision), diag);
-}
-
-bool ResourceTable::ValidateName(NameValidator name_validator, const ResourceNameRef& name,
- const Source& source, IDiagnostics* diag) {
- const StringPiece bad_char = name_validator(name.entry);
- if (!bad_char.empty()) {
- diag->Error(DiagMessage(source) << "resource '" << name << "' has invalid entry name '"
- << name.entry << "'. Invalid character '" << bad_char << "'");
- return false;
- }
- return true;
-}
-
-bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
- const ConfigDescription& config, const StringPiece& product,
- std::unique_ptr<Value> value, NameValidator name_validator,
- const CollisionResolverFunc& conflict_resolver,
- IDiagnostics* diag) {
- CHECK(value != nullptr);
- CHECK(diag != nullptr);
-
- const Source& source = value->GetSource();
- if (!ValidateName(name_validator, name, source, diag)) {
- return false;
+ T* Insert(std::vector<T>& el, T&& value) {
+ auto [found, it] = LowerBound(el, value);
+ if (found) {
+ return &*it;
+ }
+ return &*el.insert(it, std::forward<T>(value));
}
+};
- // Check for package names appearing twice with two different package ids
- ResourceTablePackage* package = FindOrCreatePackage(name.package);
- if (res_id.is_valid() && package->id && package->id.value() != res_id.package_id()) {
- diag->Error(DiagMessage(source)
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but package '" << package->name << "' already has ID "
- << StringPrintf("%02x", package->id.value()));
- return false;
+struct PackageViewComparer {
+ bool operator()(const ResourceTablePackageView& lhs, const ResourceTablePackageView& rhs) {
+ return less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>(
+ lhs, std::make_pair(rhs.name, rhs.id));
}
+};
- // Whether or not to error on duplicate resources
- bool check_id = validate_resources_ && res_id.is_valid();
- // Whether or not to create a duplicate resource if the id does not match
- bool use_id = !validate_resources_ && res_id.is_valid();
-
- ResourceTableType* type = package->FindOrCreateType(name.type, use_id ? res_id.type_id()
- : Maybe<uint8_t>());
-
- // Check for types appearing twice with two different type ids
- if (check_id && type->id && type->id.value() != res_id.type_id()) {
- diag->Error(DiagMessage(source)
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but type '" << type->type << "' already has ID "
- << StringPrintf("%02x", type->id.value()));
- return false;
+struct TypeViewComparer {
+ bool operator()(const ResourceTableTypeView& lhs, const ResourceTableTypeView& rhs) {
+ return lhs.id != rhs.id ? lhs.id < rhs.id : lhs.type < rhs.type;
}
+};
- ResourceEntry* entry = type->FindOrCreateEntry(name.entry, use_id ? res_id.entry_id()
- : Maybe<uint16_t>());
-
- // Check for entries appearing twice with two different entry ids
- if (check_id && entry->id && entry->id.value() != res_id.entry_id()) {
- diag->Error(DiagMessage(source)
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but resource already has ID "
- << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
- return false;
+struct EntryViewComparer {
+ bool operator()(const ResourceTableEntryView& lhs, const ResourceTableEntryView& rhs) {
+ return less_than_struct_with_name_and_id<ResourceTableEntryView, uint16_t>(
+ lhs, std::make_pair(rhs.name, rhs.id));
}
+};
- ResourceConfigValue* config_value = entry->FindOrCreateValue(config, product);
- if (!config_value->value) {
- // Resource does not exist, add it now.
- config_value->value = std::move(value);
- } else {
- 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));
- entry->values.back()->value = std::move(value);
- break;
+void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package,
+ const ResourceTableType* type, const std::string& entry_name,
+ const Maybe<ResourceId>& id, const Visibility& visibility,
+ const Maybe<AllowNew>& allow_new,
+ const Maybe<OverlayableItem>& overlayable_item,
+ const Maybe<StagedId>& staged_id,
+ const std::vector<std::unique_ptr<ResourceConfigValue>>& values) {
+ SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
+ SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
+ SortedVectorInserter<ResourceTableEntryView, EntryViewComparer> entry_inserter;
- case CollisionResult::kTakeNew:
- // Take the incoming value.
- config_value->value = std::move(value);
- break;
+ ResourceTablePackageView new_package{package->name,
+ id ? id.value().package_id() : Maybe<uint8_t>{}};
+ auto view_package = package_inserter.Insert(table.packages, std::move(new_package));
- case CollisionResult::kConflict:
- diag->Error(DiagMessage(source) << "duplicate value for resource '" << name << "' "
- << "with config '" << config << "'");
- diag->Error(DiagMessage(source) << "resource previously defined here");
- return false;
+ ResourceTableTypeView new_type{type->type, id ? id.value().type_id() : Maybe<uint8_t>{}};
+ auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));
- case CollisionResult::kKeepOriginal:
- break;
- }
+ if (visibility.level == Visibility::Level::kPublic) {
+ // Only mark the type visibility level as public, it doesn't care about being private.
+ view_type->visibility_level = Visibility::Level::kPublic;
}
- if (res_id.is_valid()) {
- package->id = res_id.package_id();
- type->id = res_id.type_id();
- entry->id = res_id.entry_id();
+ ResourceTableEntryView new_entry{.name = entry_name,
+ .id = id ? id.value().entry_id() : Maybe<uint16_t>{},
+ .visibility = visibility,
+ .allow_new = allow_new,
+ .overlayable_item = overlayable_item,
+ .staged_id = staged_id};
+ for (auto& value : values) {
+ new_entry.values.emplace_back(value.get());
}
- return true;
-}
-
-bool ResourceTable::GetValidateResources() {
- return validate_resources_;
+ entry_inserter.Insert(view_type->entries, std::move(new_entry));
}
+} // namespace
-bool ResourceTable::SetVisibility(const ResourceNameRef& name, const Visibility& visibility,
- IDiagnostics* diag) {
- return SetVisibilityImpl(name, visibility, {}, ResourceNameValidator, diag);
+const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescription& config,
+ android::StringPiece product) const {
+ auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
+ lt_config_key_ref<const ResourceConfigValue*>);
+ if (iter != values.end()) {
+ const ResourceConfigValue* value = *iter;
+ if (value->config == config && StringPiece(value->product) == product) {
+ return value;
+ }
+ }
+ return nullptr;
}
-bool ResourceTable::SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility,
- const ResourceId& res_id, IDiagnostics* diag) {
- return SetVisibilityImpl(name, visibility, res_id, ResourceNameValidator, diag);
-}
+ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptions& options) const {
+ ResourceTableView view;
+ for (const auto& package : packages) {
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, entry->id,
+ entry->visibility, entry->allow_new, entry->overlayable_item,
+ entry->staged_id, entry->values);
+
+ if (options.create_alias_entries && entry->staged_id) {
+ auto alias_id = entry->staged_id.value().id;
+ InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, alias_id,
+ entry->visibility, entry->allow_new, entry->overlayable_item, {},
+ entry->values);
+ }
+ }
+ }
+ }
-bool ResourceTable::SetVisibilityWithIdMangled(const ResourceNameRef& name,
- const Visibility& visibility,
- const ResourceId& res_id, IDiagnostics* diag) {
- return SetVisibilityImpl(name, visibility, res_id, SkipNameValidator, diag);
-}
+ // The android runtime does not support querying resources when the there are multiple type ids
+ // for the same resource type within the same package. For this reason, if there are types with
+ // multiple type ids, each type needs to exist in its own package in order to be queried by name.
+ std::vector<ResourceTablePackageView> new_packages;
+ SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
+ SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
+ for (auto& package : view.packages) {
+ // If a new package was already created for a different type within this package, then
+ // we can reuse those packages for other types that need to be extracted from this package.
+ // `start_index` is the index of the first newly created package that can be reused.
+ const size_t start_index = new_packages.size();
+ std::map<ResourceType, size_t> type_new_package_index;
+ for (auto type_it = package.types.begin(); type_it != package.types.end();) {
+ auto& type = *type_it;
+ auto type_index_iter = type_new_package_index.find(type.type);
+ if (type_index_iter == type_new_package_index.end()) {
+ // First occurrence of the resource type in this package. Keep it in this package.
+ type_new_package_index.insert(type_index_iter, std::make_pair(type.type, start_index));
+ ++type_it;
+ continue;
+ }
-bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility,
- const ResourceId& res_id, NameValidator name_validator,
- IDiagnostics* diag) {
- CHECK(diag != nullptr);
+ // The resource type has already been seen for this package, so this type must be extracted to
+ // a new separate package.
+ const size_t index = type_index_iter->second;
+ if (new_packages.size() == index) {
+ new_packages.emplace_back(ResourceTablePackageView{package.name, package.id});
+ type_new_package_index[type.type] = index + 1;
+ }
- const Source& source = visibility.source;
- if (!ValidateName(name_validator, name, source, diag)) {
- return false;
+ // Move the type into a new package
+ auto& other_package = new_packages[index];
+ type_inserter.Insert(other_package.types, std::move(type));
+ type_it = package.types.erase(type_it);
+ }
}
- // Check for package names appearing twice with two different package ids
- ResourceTablePackage* package = FindOrCreatePackage(name.package);
- if (res_id.is_valid() && package->id && package->id.value() != res_id.package_id()) {
- diag->Error(DiagMessage(source)
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but package '" << package->name << "' already has ID "
- << StringPrintf("%02x", package->id.value()));
- return false;
+ for (auto& new_package : new_packages) {
+ // Insert newly created packages after their original packages
+ auto [_, it] = package_inserter.LowerBound(view.packages, new_package);
+ view.packages.insert(++it, std::move(new_package));
}
- // Whether or not to error on duplicate resources
- bool check_id = validate_resources_ && res_id.is_valid();
- // Whether or not to create a duplicate resource if the id does not match
- bool use_id = !validate_resources_ && res_id.is_valid();
+ return view;
+}
- ResourceTableType* type = package->FindOrCreateType(name.type, use_id ? res_id.type_id()
- : Maybe<uint8_t>());
+bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) {
+ CHECK(diag != nullptr) << "Diagnostic pointer is null";
- // Check for types appearing twice with two different type ids
- if (check_id && type->id && type->id.value() != res_id.type_id()) {
+ const bool validate = validation_ == Validation::kEnabled;
+ const Source source = res.value ? res.value->GetSource() : Source{};
+ if (validate && !res.allow_mangled && !IsValidResourceEntryName(res.name.entry)) {
diag->Error(DiagMessage(source)
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but type '" << type->type << "' already has ID "
- << StringPrintf("%02x", type->id.value()));
+ << "resource '" << res.name << "' has invalid entry name '" << res.name.entry);
return false;
}
- ResourceEntry* entry = type->FindOrCreateEntry(name.entry, use_id ? res_id.entry_id()
- : Maybe<uint16_t>());
-
- // Check for entries appearing twice with two different entry ids
- if (check_id && entry->id && entry->id.value() != res_id.entry_id()) {
- diag->Error(DiagMessage(source)
- << "trying to add resource '" << name << "' with ID " << res_id
- << " but resource already has ID "
- << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
+ if (res.id.has_value() && !res.id->first.is_valid()) {
+ diag->Error(DiagMessage(source) << "trying to add resource '" << res.name << "' with ID "
+ << res.id->first << " but that ID is invalid");
return false;
}
- if (res_id.is_valid()) {
- package->id = res_id.package_id();
- type->id = res_id.type_id();
- entry->id = res_id.entry_id();
- }
-
- // Only mark the type visibility level as public, it doesn't care about being private.
- if (visibility.level == Visibility::Level::kPublic) {
- type->visibility_level = Visibility::Level::kPublic;
- }
+ auto package = FindOrCreatePackage(res.name.package);
+ auto type = package->FindOrCreateType(res.name.type);
+ auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), res.name.entry,
+ NameEqualRange<ResourceEntry>{});
+ const size_t entry_count = std::distance(entry_it.first, entry_it.second);
- if (visibility.level == Visibility::Level::kUndefined &&
- entry->visibility.level != Visibility::Level::kUndefined) {
- // We can't undefine a symbol (remove its visibility). Ignore.
- return true;
+ ResourceEntry* entry;
+ if (entry_count == 0) {
+ // Adding a new resource
+ entry = type->CreateEntry(res.name.entry);
+ } else if (entry_count == 1) {
+ // Assume that the existing resource is being modified
+ entry = entry_it.first->get();
+ } else {
+ // Multiple resources with the same name exist in the resource table. The only way to
+ // distinguish between them is using resource id since each resource should have a unique id.
+ CHECK(res.id.has_value()) << "ambiguous modification of resource entry '" << res.name
+ << "' without specifying a resource id.";
+ entry = entry_it.first->get();
+ for (auto it = entry_it.first; it != entry_it.second; ++it) {
+ CHECK((bool)(*it)->id) << "ambiguous modification of resource entry '" << res.name
+ << "' with multiple entries without resource ids";
+ if ((*it)->id == res.id->first) {
+ entry = it->get();
+ break;
+ }
+ }
}
- if (visibility.level < entry->visibility.level) {
- // We can't downgrade public to private. Ignore.
- return true;
+ if (res.id.has_value()) {
+ if (entry->id && entry->id.value() != res.id->first) {
+ if (res.id->second != OnIdConflict::CREATE_ENTRY) {
+ diag->Error(DiagMessage(source)
+ << "trying to add resource '" << res.name << "' with ID " << res.id->first
+ << " but resource already has ID " << entry->id.value());
+ return false;
+ }
+ entry = type->CreateEntry(res.name.entry);
+ }
+ entry->id = res.id->first;
}
- // This symbol definition takes precedence, replace.
- entry->visibility = visibility;
- return true;
-}
-
-bool ResourceTable::SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new,
- IDiagnostics* diag) {
- return SetAllowNewImpl(name, allow_new, ResourceNameValidator, diag);
-}
+ if (res.visibility.has_value()) {
+ // Only mark the type visibility level as public, it doesn't care about being private.
+ if (res.visibility->level == Visibility::Level::kPublic) {
+ type->visibility_level = Visibility::Level::kPublic;
+ }
-bool ResourceTable::SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new,
- IDiagnostics* diag) {
- return SetAllowNewImpl(name, allow_new, SkipNameValidator, diag);
-}
+ if (res.visibility->level > entry->visibility.level) {
+ // This symbol definition takes precedence, replace.
+ entry->visibility = res.visibility.value();
+ }
-bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
- NameValidator name_validator, IDiagnostics* diag) {
- CHECK(diag != nullptr);
+ if (res.visibility->staged_api) {
+ entry->visibility.staged_api = entry->visibility.staged_api;
+ }
+ }
- if (!ValidateName(name_validator, name, allow_new.source, diag)) {
- return false;
+ if (res.overlayable.has_value()) {
+ if (entry->overlayable_item) {
+ diag->Error(DiagMessage(res.overlayable->source)
+ << "duplicate overlayable declaration for resource '" << res.name << "'");
+ diag->Error(DiagMessage(entry->overlayable_item.value().source)
+ << "previous declaration here");
+ return false;
+ }
+ entry->overlayable_item = res.overlayable.value();
+ }
+
+ if (res.allow_new.has_value()) {
+ entry->allow_new = res.allow_new.value();
+ }
+
+ if (res.staged_id.has_value()) {
+ entry->staged_id = res.staged_id.value();
+ }
+
+ if (res.value != nullptr) {
+ auto config_value = entry->FindOrCreateValue(res.config, res.product);
+ if (!config_value->value) {
+ // Resource does not exist, add it now.
+ config_value->value = std::move(res.value);
+ } else {
+ // When validation is enabled, ensure that a resource cannot have multiple values defined for
+ // the same configuration.
+ auto result = validate ? ResolveValueCollision(config_value->value.get(), res.value.get())
+ : CollisionResult::kKeepBoth;
+ switch (result) {
+ case CollisionResult::kKeepBoth:
+ // Insert the value ignoring for duplicate configurations
+ entry->values.push_back(util::make_unique<ResourceConfigValue>(res.config, res.product));
+ entry->values.back()->value = std::move(res.value);
+ break;
+
+ case CollisionResult::kTakeNew:
+ // Take the incoming value.
+ config_value->value = std::move(res.value);
+ break;
+
+ case CollisionResult::kConflict:
+ diag->Error(DiagMessage(source) << "duplicate value for resource '" << res.name << "' "
+ << "with config '" << res.config << "'");
+ diag->Error(DiagMessage(source) << "resource previously defined here");
+ return false;
+
+ case CollisionResult::kKeepOriginal:
+ break;
+ }
+ }
}
- ResourceTablePackage* package = FindOrCreatePackage(name.package);
- ResourceTableType* type = package->FindOrCreateType(name.type);
- ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
- entry->allow_new = allow_new;
return true;
}
-bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const OverlayableItem& overlayable,
- IDiagnostics* diag) {
- return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
-}
+Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) const {
+ ResourceTablePackage* package = FindPackage(name.package);
+ if (package == nullptr) {
+ return {};
+ }
-bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name,
- const OverlayableItem& overlayable,
- NameValidator name_validator, IDiagnostics *diag) {
- CHECK(diag != nullptr);
+ ResourceTableType* type = package->FindType(name.type);
+ if (type == nullptr) {
+ return {};
+ }
- if (!ValidateName(name_validator, name, overlayable.source, diag)) {
- return false;
+ ResourceEntry* entry = type->FindEntry(name.entry);
+ if (entry == nullptr) {
+ return {};
}
+ return SearchResult{package, type, entry};
+}
- ResourceTablePackage* package = FindOrCreatePackage(name.package);
- ResourceTableType* type = package->FindOrCreateType(name.type);
- ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name,
+ ResourceId id) const {
+ ResourceTablePackage* package = FindPackage(name.package);
+ if (package == nullptr) {
+ return {};
+ }
- if (entry->overlayable_item) {
- diag->Error(DiagMessage(overlayable.source)
- << "duplicate overlayable declaration for resource '" << name << "'");
- diag->Error(DiagMessage(entry->overlayable_item.value().source)
- << "previous declaration here");
- return false;
+ ResourceTableType* type = package->FindType(name.type);
+ if (type == nullptr) {
+ return {};
}
- entry->overlayable_item = overlayable;
- return true;
+ auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), name.entry,
+ NameEqualRange<ResourceEntry>{});
+ for (auto it = entry_it.first; it != entry_it.second; ++it) {
+ if ((*it)->id == id) {
+ return SearchResult{package, type, it->get()};
+ }
+ }
+ return {};
}
-Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) const {
+bool ResourceTable::RemoveResource(const ResourceNameRef& name, ResourceId id) const {
ResourceTablePackage* package = FindPackage(name.package);
if (package == nullptr) {
return {};
@@ -634,24 +636,28 @@ Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNam
return {};
}
- ResourceEntry* entry = type->FindEntry(name.entry);
- if (entry == nullptr) {
- return {};
+ auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), name.entry,
+ NameEqualRange<ResourceEntry>{});
+ for (auto it = entry_it.first; it != entry_it.second; ++it) {
+ if ((*it)->id == id) {
+ type->entries.erase(it);
+ return true;
+ }
}
- return SearchResult{package, type, entry};
+ return false;
}
std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
std::unique_ptr<ResourceTable> new_table = util::make_unique<ResourceTable>();
+ CloningValueTransformer cloner(&new_table->string_pool);
for (const auto& pkg : packages) {
- ResourceTablePackage* new_pkg = new_table->CreatePackage(pkg->name, pkg->id);
+ ResourceTablePackage* new_pkg = new_table->FindOrCreatePackage(pkg->name);
for (const auto& type : pkg->types) {
ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type);
- new_type->id = type->id;
new_type->visibility_level = type->visibility_level;
for (const auto& entry : type->entries) {
- ResourceEntry* new_entry = new_type->FindOrCreateEntry(entry->name);
+ ResourceEntry* new_entry = new_type->CreateEntry(entry->name);
new_entry->id = entry->id;
new_entry->visibility = entry->visibility;
new_entry->allow_new = entry->allow_new;
@@ -660,7 +666,7 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
for (const auto& config_value : entry->values) {
ResourceConfigValue* new_value =
new_entry->FindOrCreateValue(config_value->config, config_value->product);
- new_value->value.reset(config_value->value->Clone(&new_table->string_pool));
+ new_value->value = config_value->value->Transform(cloner);
}
}
}
@@ -668,4 +674,56 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
return new_table;
}
+NewResourceBuilder::NewResourceBuilder(const ResourceNameRef& name) {
+ res_.name = name.ToResourceName();
+}
+
+NewResourceBuilder::NewResourceBuilder(const std::string& name) {
+ ResourceNameRef ref;
+ CHECK(ResourceUtils::ParseResourceName(name, &ref)) << "invalid resource name: " << name;
+ res_.name = ref.ToResourceName();
+}
+
+NewResourceBuilder& NewResourceBuilder::SetValue(std::unique_ptr<Value> value,
+ android::ConfigDescription config,
+ std::string product) {
+ res_.value = std::move(value);
+ res_.config = std::move(config);
+ res_.product = std::move(product);
+ return *this;
+}
+
+NewResourceBuilder& NewResourceBuilder::SetId(ResourceId id, OnIdConflict on_conflict) {
+ res_.id = std::make_pair(id, on_conflict);
+ return *this;
+}
+
+NewResourceBuilder& NewResourceBuilder::SetVisibility(Visibility visibility) {
+ res_.visibility = std::move(visibility);
+ return *this;
+}
+
+NewResourceBuilder& NewResourceBuilder::SetOverlayable(OverlayableItem overlayable) {
+ res_.overlayable = std::move(overlayable);
+ return *this;
+}
+NewResourceBuilder& NewResourceBuilder::SetAllowNew(AllowNew allow_new) {
+ res_.allow_new = std::move(allow_new);
+ return *this;
+}
+
+NewResourceBuilder& NewResourceBuilder::SetStagedId(StagedId staged_alias) {
+ res_.staged_id = std::move(staged_alias);
+ return *this;
+}
+
+NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) {
+ res_.allow_mangled = allow_mangled;
+ return *this;
+}
+
+NewResource NewResourceBuilder::Build() {
+ return std::move(res_);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 93a7a314d6ba..bae1d827a841 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -51,6 +51,11 @@ struct Visibility {
Level level = Level::kUndefined;
Source source;
std::string comment;
+
+ // Indicates that the resource id may change across builds and that the public R.java identifier
+ // for this resource should not be final. This is set to `true` for resources in `staging-group`
+ // tags.
+ bool staged_api = false;
};
// Represents <add-resource> in an overlay.
@@ -59,6 +64,12 @@ struct AllowNew {
std::string comment;
};
+// Represents the staged resource id of a finalized resource.
+struct StagedId {
+ ResourceId id;
+ Source source;
+};
+
struct Overlayable {
Overlayable() = default;
Overlayable(const android::StringPiece& name, const android::StringPiece& actor)
@@ -109,7 +120,7 @@ class ResourceEntry {
const std::string name;
// The entry ID for this resource (the EEEE in 0xPPTTEEEE).
- Maybe<uint16_t> id;
+ Maybe<ResourceId> id;
// Whether this resource is public (and must maintain the same entry ID across builds).
Visibility visibility;
@@ -119,15 +130,18 @@ class ResourceEntry {
// The declarations of this resource as overlayable for RROs
Maybe<OverlayableItem> overlayable_item;
+ // The staged resource id for a finalized resource.
+ Maybe<StagedId> staged_id;
+
// The resource's values for each configuration.
std::vector<std::unique_ptr<ResourceConfigValue>> values;
explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {}
- ResourceConfigValue* FindValue(const android::ConfigDescription& config);
-
ResourceConfigValue* FindValue(const android::ConfigDescription& config,
- const android::StringPiece& product);
+ android::StringPiece product = {});
+ const ResourceConfigValue* FindValue(const android::ConfigDescription& config,
+ android::StringPiece product = {}) const;
ResourceConfigValue* FindOrCreateValue(const android::ConfigDescription& config,
const android::StringPiece& product);
@@ -156,9 +170,6 @@ class ResourceTableType {
// The logical type of resource (string, drawable, layout, etc.).
const ResourceType type;
- // The type ID for this resource (the TT in 0xPPTTEEEE).
- Maybe<uint8_t> id;
-
// Whether this type is public (and must maintain the same type ID across builds).
Visibility::Level visibility_level = Visibility::Level::kUndefined;
@@ -167,10 +178,9 @@ class ResourceTableType {
explicit ResourceTableType(const ResourceType type) : type(type) {}
- ResourceEntry* FindEntry(const android::StringPiece& name,
- Maybe<uint16_t> id = Maybe<uint16_t>());
- ResourceEntry* FindOrCreateEntry(const android::StringPiece& name,
- Maybe<uint16_t> id = Maybe<uint16_t>());
+ ResourceEntry* CreateEntry(const android::StringPiece& name);
+ ResourceEntry* FindEntry(const android::StringPiece& name) const;
+ ResourceEntry* FindOrCreateEntry(const android::StringPiece& name);
private:
DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
@@ -180,70 +190,118 @@ class ResourceTablePackage {
public:
std::string name;
- // The package ID (the PP in 0xPPTTEEEE).
- Maybe<uint8_t> id;
-
std::vector<std::unique_ptr<ResourceTableType>> types;
+ explicit ResourceTablePackage(const android::StringPiece& name) : name(name.to_string()) {
+ }
+
ResourceTablePackage() = default;
- ResourceTableType* FindType(ResourceType type, Maybe<uint8_t> id = Maybe<uint8_t>());
- ResourceTableType* FindOrCreateType(const ResourceType type,
- Maybe<uint8_t> id = Maybe<uint8_t>());
+ ResourceTableType* FindType(ResourceType type) const;
+ ResourceTableType* FindOrCreateType(ResourceType type);
private:
DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
};
-// The container and index for all resources defined for an app.
-class ResourceTable {
- public:
- ResourceTable() = default;
- explicit ResourceTable(bool validate_resources) : validate_resources_(validate_resources) {}
+struct ResourceTableEntryView {
+ std::string name;
+ Maybe<uint16_t> id;
+ Visibility visibility;
+ Maybe<AllowNew> allow_new;
+ Maybe<OverlayableItem> overlayable_item;
+ Maybe<StagedId> staged_id;
+ std::vector<const ResourceConfigValue*> values;
- enum class CollisionResult { kKeepBoth, kKeepOriginal, kConflict, kTakeNew };
+ const ResourceConfigValue* FindValue(const android::ConfigDescription& config,
+ android::StringPiece product = {}) const;
+};
+
+struct ResourceTableTypeView {
+ ResourceType type;
+ Maybe<uint8_t> id;
+ Visibility::Level visibility_level = Visibility::Level::kUndefined;
- using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
+ // Entries sorted in ascending entry id order. If ids have not been assigned, the entries are
+ // sorted lexicographically.
+ std::vector<ResourceTableEntryView> entries;
+};
- // When a collision of resources occurs, this method decides which value to keep.
- static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
+struct ResourceTablePackageView {
+ std::string name;
+ Maybe<uint8_t> id;
+ // Types sorted in ascending type id order. If ids have not been assigned, the types are sorted by
+ // their declaration order in the ResourceType enum.
+ std::vector<ResourceTableTypeView> types;
+};
- // When a collision of resources occurs, this method keeps both values
- static CollisionResult IgnoreCollision(Value* existing, Value* incoming);
+struct ResourceTableViewOptions {
+ bool create_alias_entries = false;
+};
- bool AddResource(const ResourceNameRef& name, const android::ConfigDescription& config,
- const android::StringPiece& product, std::unique_ptr<Value> value,
- IDiagnostics* diag);
+struct ResourceTableView {
+ // Packages sorted in ascending package id order. If ids have not been assigned, the packages are
+ // sorted lexicographically.
+ std::vector<ResourceTablePackageView> packages;
+};
- bool AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id,
- const android::ConfigDescription& config,
- const android::StringPiece& product, std::unique_ptr<Value> value,
- IDiagnostics* diag);
+enum class OnIdConflict {
+ // If the resource entry already exists but has a different resource id, the resource value will
+ // not be added to the table.
+ ERROR,
- // Same as AddResource, but doesn't verify the validity of the name. This is used
- // when loading resources from an existing binary resource table that may have mangled names.
- bool AddResourceMangled(const ResourceNameRef& name, const android::ConfigDescription& config,
- const android::StringPiece& product, std::unique_ptr<Value> value,
- IDiagnostics* diag);
+ // If the resource entry already exists but has a different resource id, create a new resource
+ // with this resource name and id combination.
+ CREATE_ENTRY,
+};
- bool AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id,
- const android::ConfigDescription& config,
- const android::StringPiece& product, std::unique_ptr<Value> value,
- IDiagnostics* diag);
+struct NewResource {
+ ResourceName name;
+ std::unique_ptr<Value> value;
+ android::ConfigDescription config;
+ std::string product;
+ std::optional<std::pair<ResourceId, OnIdConflict>> id;
+ std::optional<Visibility> visibility;
+ std::optional<OverlayableItem> overlayable;
+ std::optional<AllowNew> allow_new;
+ std::optional<StagedId> staged_id;
+ bool allow_mangled = false;
+};
- bool GetValidateResources();
+struct NewResourceBuilder {
+ explicit NewResourceBuilder(const ResourceNameRef& name);
+ explicit NewResourceBuilder(const std::string& name);
+ NewResourceBuilder& SetValue(std::unique_ptr<Value> value, android::ConfigDescription config = {},
+ std::string product = {});
+ NewResourceBuilder& SetId(ResourceId id, OnIdConflict on_conflict = OnIdConflict::ERROR);
+ NewResourceBuilder& SetVisibility(Visibility id);
+ NewResourceBuilder& SetOverlayable(OverlayableItem overlayable);
+ NewResourceBuilder& SetAllowNew(AllowNew allow_new);
+ NewResourceBuilder& SetStagedId(StagedId id);
+ NewResourceBuilder& SetAllowMangled(bool allow_mangled);
+ NewResource Build();
- bool SetVisibility(const ResourceNameRef& name, const Visibility& visibility, IDiagnostics* diag);
- bool SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility,
- const ResourceId& res_id, IDiagnostics* diag);
- bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility,
- const ResourceId& res_id, IDiagnostics* diag);
+ private:
+ NewResource res_;
+};
- bool SetOverlayable(const ResourceNameRef& name, const OverlayableItem& overlayable,
- IDiagnostics *diag);
+// The container and index for all resources defined for an app.
+class ResourceTable {
+ public:
+ enum class Validation {
+ kEnabled,
+ kDisabled,
+ };
+
+ enum class CollisionResult { kKeepBoth, kKeepOriginal, kConflict, kTakeNew };
- bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag);
- bool SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new,
- IDiagnostics* diag);
+ ResourceTable() = default;
+ explicit ResourceTable(Validation validation);
+
+ bool AddResource(NewResource&& res, IDiagnostics* diag);
+
+ // Retrieves a sorted a view of the packages, types, and entries sorted in ascending resource id
+ // order.
+ ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const;
struct SearchResult {
ResourceTablePackage* package;
@@ -252,23 +310,20 @@ class ResourceTable {
};
Maybe<SearchResult> FindResource(const ResourceNameRef& name) const;
+ Maybe<SearchResult> FindResource(const ResourceNameRef& name, ResourceId id) const;
+ bool RemoveResource(const ResourceNameRef& name, ResourceId id) const;
// Returns the package struct with the given name, or nullptr if such a package does not
// exist. The empty string is a valid package and typically is used to represent the
// 'current' package before it is known to the ResourceTable.
ResourceTablePackage* FindPackage(const android::StringPiece& name) const;
-
- ResourceTablePackage* FindPackageById(uint8_t id) const;
-
- ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {});
-
- // Attempts to find a package having the specified name and ID. If not found, a new package
- // of the specified parameters is created and returned.
- ResourceTablePackage* CreatePackageAllowingDuplicateNames(const android::StringPiece& name,
- const Maybe<uint8_t> id);
+ ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
std::unique_ptr<ResourceTable> Clone() const;
+ // When a collision of resources occurs, this method decides which value to keep.
+ static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
+
// The string pool used by this resource table. Values that reference strings must use
// this pool to create their strings.
// NOTE: `string_pool` must come before `packages` so that it is destroyed after.
@@ -286,36 +341,9 @@ class ResourceTable {
std::map<size_t, std::string> included_packages_;
private:
- // The function type that validates a symbol name. Returns a non-empty StringPiece representing
- // the offending character (which may be more than one byte in UTF-8). Returns an empty string
- // if the name was valid.
- using NameValidator = android::StringPiece(const android::StringPiece&);
-
- ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
-
- bool ValidateName(NameValidator validator, const ResourceNameRef& name, const Source& source,
- IDiagnostics* diag);
-
- bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
- const android::ConfigDescription& config,
- const android::StringPiece& product, std::unique_ptr<Value> value,
- NameValidator name_validator, const CollisionResolverFunc& conflict_resolver,
- IDiagnostics* diag);
-
- bool SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility,
- const ResourceId& res_id, NameValidator name_validator,
- IDiagnostics* diag);
-
- bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
- NameValidator name_validator, IDiagnostics* diag);
-
- bool SetOverlayableImpl(const ResourceNameRef &name, const OverlayableItem& overlayable,
- NameValidator name_validator, IDiagnostics *diag);
-
- // Controls whether the table validates resource names and prevents duplicate resource names
- bool validate_resources_ = true;
-
DISALLOW_COPY_AND_ASSIGN(ResourceTable);
+
+ Validation validation_ = Validation::kEnabled;
};
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 9271a7e6bae1..38391c99f55a 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -37,31 +37,32 @@ namespace aapt {
TEST(ResourceTableTest, FailToAddResourceWithBadName) {
ResourceTable table;
- EXPECT_FALSE(table.AddResource(
- test::ParseNameOrDie("android:id/hey,there"), ConfigDescription{}, "",
- test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(),
- test::GetDiagnostics()));
+ EXPECT_FALSE(
+ table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:id/hey,there")).Build(),
+ test::GetDiagnostics()));
- EXPECT_FALSE(table.AddResource(
- test::ParseNameOrDie("android:id/hey:there"), ConfigDescription{}, "",
- test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(),
- test::GetDiagnostics()));
+ EXPECT_FALSE(
+ table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:id/hey:there")).Build(),
+ test::GetDiagnostics()));
}
TEST(ResourceTableTest, AddResourceWithWeirdNameWhenAddingMangledResources) {
ResourceTable table;
- EXPECT_TRUE(table.AddResourceMangled(
- test::ParseNameOrDie("android:id/heythere "), ConfigDescription{}, "",
- test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), test::GetDiagnostics()));
+ EXPECT_TRUE(
+ table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:id/heythere "))
+ .SetAllowMangled(true)
+ .Build(),
+ test::GetDiagnostics()));
}
TEST(ResourceTableTest, AddOneResource) {
ResourceTable table;
EXPECT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:attr/id"), ConfigDescription{}, "",
- test::ValueBuilder<Id>().SetSource("test/path/file.xml", 23u).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:attr/id"))
+ .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 23u).Build())
+ .Build(),
test::GetDiagnostics()));
EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/id"), NotNull());
@@ -70,32 +71,36 @@ TEST(ResourceTableTest, AddOneResource) {
TEST(ResourceTableTest, AddMultipleResources) {
ResourceTable table;
- ConfigDescription config;
ConfigDescription language_config;
memcpy(language_config.language, "pl", sizeof(language_config.language));
EXPECT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:attr/layout_width"), config, "",
- test::ValueBuilder<Id>().SetSource("test/path/file.xml", 10u).Build(),
- test::GetDiagnostics()));
-
- EXPECT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:attr/id"), config, "",
- test::ValueBuilder<Id>().SetSource("test/path/file.xml", 12u).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:attr/layout_width"))
+ .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 10u).Build())
+ .Build(),
test::GetDiagnostics()));
EXPECT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/ok"), config, "",
- test::ValueBuilder<Id>().SetSource("test/path/file.xml", 14u).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:attr/id"))
+ .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 12u).Build())
+ .Build(),
test::GetDiagnostics()));
EXPECT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/ok"), language_config, "",
- test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
- .SetSource("test/path/file.xml", 20u)
+ NewResourceBuilder(test::ParseNameOrDie("android:string/ok"))
+ .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 14u).Build())
.Build(),
test::GetDiagnostics()));
+ EXPECT_TRUE(
+ table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/ok"))
+ .SetValue(test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
+ .SetSource("test/path/file.xml", 20u)
+ .Build(),
+ language_config)
+ .Build(),
+ test::GetDiagnostics()));
+
EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/layout_width"), NotNull());
EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/id"), NotNull());
EXPECT_THAT(test::GetValue<Id>(&table, "android:string/ok"), NotNull());
@@ -104,17 +109,19 @@ TEST(ResourceTableTest, AddMultipleResources) {
TEST(ResourceTableTest, OverrideWeakResourceValue) {
ResourceTable table;
-
- ASSERT_TRUE(table.AddResource(test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
- test::AttributeBuilder().SetWeak(true).Build(),
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:attr/foo"))
+ .SetValue(test::AttributeBuilder().SetWeak(true).Build())
+ .Build(),
test::GetDiagnostics()));
Attribute* attr = test::GetValue<Attribute>(&table, "android:attr/foo");
ASSERT_THAT(attr, NotNull());
EXPECT_TRUE(attr->IsWeak());
- ASSERT_TRUE(table.AddResource(test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
- util::make_unique<Attribute>(), test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:attr/foo"))
+ .SetValue(util::make_unique<Attribute>())
+ .Build(),
+ test::GetDiagnostics()));
attr = test::GetValue<Attribute>(&table, "android:attr/foo");
ASSERT_THAT(attr, NotNull());
@@ -130,23 +137,27 @@ TEST(ResourceTableTest, AllowCompatibleDuplicateAttributes) {
Attribute attr_two(android::ResTable_map::TYPE_STRING | android::ResTable_map::TYPE_REFERENCE);
attr_two.SetWeak(true);
- ASSERT_TRUE(table.AddResource(name, ConfigDescription{}, "",
- util::make_unique<Attribute>(attr_one), test::GetDiagnostics()));
- ASSERT_TRUE(table.AddResource(name, ConfigDescription{}, "",
- util::make_unique<Attribute>(attr_two), test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(name).SetValue(util::make_unique<Attribute>(attr_one)).Build(),
+ test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(name).SetValue(util::make_unique<Attribute>(attr_two)).Build(),
+ test::GetDiagnostics()));
}
TEST(ResourceTableTest, ProductVaryingValues) {
ResourceTable table;
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/foo"))
+ .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land"), "tablet")
+ .Build(),
+ test::GetDiagnostics()));
- EXPECT_TRUE(table.AddResource(test::ParseNameOrDie("android:string/foo"),
- test::ParseConfigOrDie("land"), "tablet",
- util::make_unique<Id>(),
- test::GetDiagnostics()));
- EXPECT_TRUE(table.AddResource(test::ParseNameOrDie("android:string/foo"),
- test::ParseConfigOrDie("land"), "phone",
- util::make_unique<Id>(),
- test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/foo"))
+ .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land"), "phone")
+ .Build(),
+ test::GetDiagnostics()));
EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "tablet"), NotNull());
EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "phone"), NotNull());
@@ -203,22 +214,26 @@ TEST(ResourceTableTest, SetVisibility) {
Visibility visibility;
visibility.level = Visibility::Level::kPrivate;
visibility.comment = "private";
- ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(),
+ test::GetDiagnostics()));
ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
visibility.level = Visibility::Level::kUndefined;
visibility.comment = "undefined";
- ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(),
+ test::GetDiagnostics()));
ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
visibility.level = Visibility::Level::kPublic;
visibility.comment = "public";
- ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(),
+ test::GetDiagnostics()));
ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
visibility.level = Visibility::Level::kPrivate;
visibility.comment = "private";
- ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(),
+ test::GetDiagnostics()));
ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
}
@@ -230,14 +245,16 @@ TEST(ResourceTableTest, SetAllowNew) {
Maybe<ResourceTable::SearchResult> result;
allow_new.comment = "first";
- ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetAllowNew(allow_new).Build(),
+ test::GetDiagnostics()));
result = table.FindResource(name);
ASSERT_TRUE(result);
ASSERT_TRUE(result.value().entry->allow_new);
ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("first"));
allow_new.comment = "second";
- ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetAllowNew(allow_new).Build(),
+ test::GetDiagnostics()));
result = table.FindResource(name);
ASSERT_TRUE(result);
ASSERT_TRUE(result.value().entry->allow_new);
@@ -255,7 +272,8 @@ TEST(ResourceTableTest, SetOverlayable) {
overlayable_item.source = Source("res/values/overlayable.xml", 42);
const ResourceName name = test::ParseNameOrDie("android:string/foo");
- ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(),
+ test::GetDiagnostics()));
Maybe<ResourceTable::SearchResult> search_result = table.FindResource(name);
ASSERT_TRUE(search_result);
@@ -280,17 +298,20 @@ TEST(ResourceTableTest, SetMultipleOverlayableResources) {
auto group = std::make_shared<Overlayable>("Name", "overlay://theme");
OverlayableItem overlayable(group);
overlayable.policies = PolicyFlags::PRODUCT_PARTITION;
- ASSERT_TRUE(table.SetOverlayable(foo, overlayable, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(foo).SetOverlayable(overlayable).Build(),
+ test::GetDiagnostics()));
const ResourceName bar = test::ParseNameOrDie("android:string/bar");
OverlayableItem overlayable2(group);
overlayable2.policies = PolicyFlags::PRODUCT_PARTITION;
- ASSERT_TRUE(table.SetOverlayable(bar, overlayable2, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(bar).SetOverlayable(overlayable2).Build(),
+ test::GetDiagnostics()));
const ResourceName baz = test::ParseNameOrDie("android:string/baz");
OverlayableItem overlayable3(group);
overlayable3.policies = PolicyFlags::VENDOR_PARTITION;
- ASSERT_TRUE(table.SetOverlayable(baz, overlayable3, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(baz).SetOverlayable(overlayable3).Build(),
+ test::GetDiagnostics()));
}
TEST(ResourceTableTest, SetOverlayableDifferentResourcesDifferentName) {
@@ -299,12 +320,14 @@ TEST(ResourceTableTest, SetOverlayableDifferentResourcesDifferentName) {
const ResourceName foo = test::ParseNameOrDie("android:string/foo");
OverlayableItem overlayable_item(std::make_shared<Overlayable>("Name", "overlay://theme"));
overlayable_item.policies = PolicyFlags::PRODUCT_PARTITION;
- ASSERT_TRUE(table.SetOverlayable(foo, overlayable_item, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(foo).SetOverlayable(overlayable_item).Build(),
+ test::GetDiagnostics()));
const ResourceName bar = test::ParseNameOrDie("android:string/bar");
OverlayableItem overlayable_item2(std::make_shared<Overlayable>("Name2", "overlay://theme"));
overlayable_item2.policies = PolicyFlags::PRODUCT_PARTITION;
- ASSERT_TRUE(table.SetOverlayable(bar, overlayable_item2, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(bar).SetOverlayable(overlayable_item2).Build(),
+ test::GetDiagnostics()));
}
TEST(ResourceTableTest, SetOverlayableSameResourcesFail) {
@@ -313,10 +336,12 @@ TEST(ResourceTableTest, SetOverlayableSameResourcesFail) {
auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme");
OverlayableItem overlayable_item(overlayable);
- ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(),
+ test::GetDiagnostics()));
OverlayableItem overlayable_item2(overlayable);
- ASSERT_FALSE(table.SetOverlayable(name, overlayable_item2, test::GetDiagnostics()));
+ ASSERT_FALSE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item2).Build(),
+ test::GetDiagnostics()));
}
TEST(ResourceTableTest, SetOverlayableSameResourcesDifferentNameFail) {
@@ -325,51 +350,38 @@ TEST(ResourceTableTest, SetOverlayableSameResourcesDifferentNameFail) {
auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme");
OverlayableItem overlayable_item(overlayable);
- ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(),
+ test::GetDiagnostics()));
auto overlayable2 = std::make_shared<Overlayable>("Other", "overlay://theme");
OverlayableItem overlayable_item2(overlayable2);
- ASSERT_FALSE(table.SetOverlayable(name, overlayable_item2, test::GetDiagnostics()));
+ ASSERT_FALSE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item2).Build(),
+ test::GetDiagnostics()));
}
-TEST(ResourceTableTest, AllowDuplictaeResourcesNames) {
- ResourceTable table(/* validate_resources */ false);
-
- const ResourceName foo_name = test::ParseNameOrDie("android:bool/foo");
- ASSERT_TRUE(table.AddResourceWithId(foo_name, ResourceId(0x7f0100ff), ConfigDescription{} , "",
- test::BuildPrimitive(android::Res_value::TYPE_INT_BOOLEAN, 0),
- test::GetDiagnostics()));
- ASSERT_TRUE(table.AddResourceWithId(foo_name, ResourceId(0x7f010100), ConfigDescription{} , "",
- test::BuildPrimitive(android::Res_value::TYPE_INT_BOOLEAN, 1),
- test::GetDiagnostics()));
-
- ASSERT_TRUE(table.SetVisibilityWithId(foo_name, Visibility{Visibility::Level::kPublic},
- ResourceId(0x7f0100ff), test::GetDiagnostics()));
- ASSERT_TRUE(table.SetVisibilityWithId(foo_name, Visibility{Visibility::Level::kPrivate},
- ResourceId(0x7f010100), test::GetDiagnostics()));
-
- auto package = table.FindPackageById(0x7f);
- ASSERT_THAT(package, NotNull());
- auto type = package->FindType(ResourceType::kBool);
- ASSERT_THAT(type, NotNull());
-
- auto entry1 = type->FindEntry("foo", 0x00ff);
- ASSERT_THAT(entry1, NotNull());
- ASSERT_THAT(entry1->id, Eq(0x00ff));
- ASSERT_THAT(entry1->values[0], NotNull());
- ASSERT_THAT(entry1->values[0]->value, NotNull());
- ASSERT_THAT(ValueCast<BinaryPrimitive>(entry1->values[0]->value.get()), NotNull());
- ASSERT_THAT(ValueCast<BinaryPrimitive>(entry1->values[0]->value.get())->value.data, Eq(0u));
- ASSERT_THAT(entry1->visibility.level, Visibility::Level::kPublic);
-
- auto entry2 = type->FindEntry("foo", 0x0100);
- ASSERT_THAT(entry2, NotNull());
- ASSERT_THAT(entry2->id, Eq(0x0100));
- ASSERT_THAT(entry2->values[0], NotNull());
- ASSERT_THAT(entry1->values[0]->value, NotNull());
- ASSERT_THAT(ValueCast<BinaryPrimitive>(entry2->values[0]->value.get()), NotNull());
- ASSERT_THAT(ValueCast<BinaryPrimitive>(entry2->values[0]->value.get())->value.data, Eq(1u));
- ASSERT_THAT(entry2->visibility.level, Visibility::Level::kPrivate);
+TEST(ResourceTableTest, ConflictingIds) {
+ ResourceTable table;
+ const ResourceName name = test::ParseNameOrDie("android:string/foo");
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetId(0x01010000).Build(),
+ test::GetDiagnostics()));
+ ASSERT_FALSE(table.AddResource(NewResourceBuilder(name).SetId(0x01010001).Build(),
+ test::GetDiagnostics()));
+}
+
+TEST(ResourceTableTest, ConflictingIdsCreateEntry) {
+ ResourceTable table;
+ const ResourceName name = test::ParseNameOrDie("android:string/foo");
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(name).SetId(0x01010000, OnIdConflict::CREATE_ENTRY).Build(),
+ test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(name).SetId(0x01010001, OnIdConflict::CREATE_ENTRY).Build(),
+ test::GetDiagnostics()));
+
+ // Non-ambiguous query
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(name).SetId(0x01010001).SetValue(std::make_unique<Id>()).Build(),
+ test::GetDiagnostics()));
}
} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index f26e995aa4f9..e0e80ac02dea 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -628,7 +628,7 @@ uint32_t AndroidTypeToAttributeTypeMask(uint16_t type) {
std::unique_ptr<Item> TryParseItemForAttribute(
const StringPiece& value, uint32_t type_mask,
- const std::function<void(const ResourceName&)>& on_create_reference) {
+ const std::function<bool(const ResourceName&)>& on_create_reference) {
using android::ResTable_map;
auto null_or_empty = TryParseNullOrEmpty(value);
@@ -639,8 +639,11 @@ std::unique_ptr<Item> TryParseItemForAttribute(
bool create = false;
auto reference = TryParseReference(value, &create);
if (reference) {
+ reference->type_flags = type_mask;
if (create && on_create_reference) {
- on_create_reference(reference->name.value());
+ if (!on_create_reference(reference->name.value())) {
+ return {};
+ }
}
return std::move(reference);
}
@@ -689,7 +692,7 @@ std::unique_ptr<Item> TryParseItemForAttribute(
*/
std::unique_ptr<Item> TryParseItemForAttribute(
const StringPiece& str, const Attribute* attr,
- const std::function<void(const ResourceName&)>& on_create_reference) {
+ const std::function<bool(const ResourceName&)>& on_create_reference) {
using android::ResTable_map;
const uint32_t type_mask = attr->type_mask;
@@ -740,7 +743,7 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config
if (type == ResourceType::kId) {
if (res_value.dataType != android::Res_value::TYPE_REFERENCE &&
res_value.dataType != android::Res_value::TYPE_DYNAMIC_REFERENCE) {
- // plain "id" resources are actually encoded as dummy values (aapt1 uses an empty string,
+ // plain "id" resources are actually encoded as unused values (aapt1 uses an empty string,
// while aapt2 uses a false boolean).
return util::make_unique<Id>();
}
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index f77766ee9061..be493db8cee0 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -204,11 +204,11 @@ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
*/
std::unique_ptr<Item> TryParseItemForAttribute(
const android::StringPiece& value, const Attribute* attr,
- const std::function<void(const ResourceName&)>& on_create_reference = {});
+ const std::function<bool(const ResourceName&)>& on_create_reference = {});
std::unique_ptr<Item> TryParseItemForAttribute(
const android::StringPiece& value, uint32_t type_mask,
- const std::function<void(const ResourceName&)>& on_create_reference = {});
+ const std::function<bool(const ResourceName&)>& on_create_reference = {});
uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 4f0fa8ae29ba..2a90f267f185 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -47,6 +47,14 @@ std::ostream& operator<<(std::ostream& out, const Value& value) {
return out;
}
+std::unique_ptr<Value> Value::Transform(ValueTransformer& transformer) const {
+ return std::unique_ptr<Value>(this->TransformValueImpl(transformer));
+}
+
+std::unique_ptr<Item> Item::Transform(ValueTransformer& transformer) const {
+ return std::unique_ptr<Item>(this->TransformItemImpl(transformer));
+}
+
template <typename Derived>
void BaseValue<Derived>::Accept(ValueVisitor* visitor) {
visitor->Visit(static_cast<Derived*>(this));
@@ -77,13 +85,6 @@ bool RawString::Equals(const Value* value) const {
return *this->value == *other->value;
}
-RawString* RawString::Clone(StringPool* new_pool) const {
- RawString* rs = new RawString(new_pool->MakeRef(value));
- rs->comment_ = comment_;
- rs->source_ = source_;
- return rs;
-}
-
bool RawString::Flatten(android::Res_value* out_value) const {
out_value->dataType = android::Res_value::TYPE_STRING;
out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index()));
@@ -110,12 +111,15 @@ bool Reference::Equals(const Value* value) const {
if (!other) {
return false;
}
- return reference_type == other->reference_type &&
- private_reference == other->private_reference && id == other->id &&
- name == other->name;
+ return reference_type == other->reference_type && private_reference == other->private_reference &&
+ id == other->id && name == other->name && type_flags == other->type_flags;
}
bool Reference::Flatten(android::Res_value* out_value) const {
+ if (name && name.value().type == ResourceType::kMacro) {
+ return false;
+ }
+
const ResourceId resid = id.value_or_default(ResourceId(0));
const bool dynamic = resid.is_valid() && is_dynamic;
@@ -136,10 +140,6 @@ bool Reference::Flatten(android::Res_value* out_value) const {
return true;
}
-Reference* Reference::Clone(StringPool* /*new_pool*/) const {
- return new Reference(*this);
-}
-
void Reference::Print(std::ostream* out) const {
if (reference_type == Type::kResource) {
*out << "(reference) @";
@@ -220,10 +220,6 @@ bool Id::Flatten(android::Res_value* out) const {
return true;
}
-Id* Id::Clone(StringPool* /*new_pool*/) const {
- return new Id(*this);
-}
-
void Id::Print(std::ostream* out) const {
*out << "(id)";
}
@@ -266,14 +262,6 @@ bool String::Flatten(android::Res_value* out_value) const {
return true;
}
-String* String::Clone(StringPool* new_pool) const {
- String* str = new String(new_pool->MakeRef(value));
- str->comment_ = comment_;
- str->source_ = source_;
- str->untranslatable_sections = untranslatable_sections;
- return str;
-}
-
void String::Print(std::ostream* out) const {
*out << "(string) \"" << *value << "\"";
}
@@ -321,14 +309,6 @@ bool StyledString::Flatten(android::Res_value* out_value) const {
return true;
}
-StyledString* StyledString::Clone(StringPool* new_pool) const {
- StyledString* str = new StyledString(new_pool->MakeRef(value));
- str->comment_ = comment_;
- str->source_ = source_;
- str->untranslatable_sections = untranslatable_sections;
- return str;
-}
-
void StyledString::Print(std::ostream* out) const {
*out << "(styled string) \"" << value->value << "\"";
for (const StringPool::Span& span : value->spans) {
@@ -357,15 +337,6 @@ bool FileReference::Flatten(android::Res_value* out_value) const {
return true;
}
-FileReference* FileReference::Clone(StringPool* new_pool) const {
- FileReference* fr = new FileReference(new_pool->MakeRef(path));
- fr->file = file;
- fr->type = type;
- fr->comment_ = comment_;
- fr->source_ = source_;
- return fr;
-}
-
void FileReference::Print(std::ostream* out) const {
*out << "(file) " << *path;
switch (type) {
@@ -406,10 +377,6 @@ bool BinaryPrimitive::Flatten(::android::Res_value* out_value) const {
return true;
}
-BinaryPrimitive* BinaryPrimitive::Clone(StringPool* /*new_pool*/) const {
- return new BinaryPrimitive(*this);
-}
-
void BinaryPrimitive::Print(std::ostream* out) const {
*out << StringPrintf("(primitive) type=0x%02x data=0x%08x", value.dataType, value.data);
}
@@ -587,11 +554,7 @@ bool Attribute::IsCompatibleWith(const Attribute& attr) const {
return this_type_mask == that_type_mask;
}
-Attribute* Attribute::Clone(StringPool* /*new_pool*/) const {
- return new Attribute(*this);
-}
-
-std::string Attribute::MaskString() const {
+std::string Attribute::MaskString(uint32_t type_mask) {
if (type_mask == android::ResTable_map::TYPE_ANY) {
return "any";
}
@@ -690,6 +653,10 @@ std::string Attribute::MaskString() const {
return out.str();
}
+std::string Attribute::MaskString() const {
+ return MaskString(type_mask);
+}
+
void Attribute::Print(std::ostream* out) const {
*out << "(attr) " << MaskString();
@@ -893,18 +860,6 @@ bool Style::Equals(const Value* value) const {
});
}
-Style* Style::Clone(StringPool* new_pool) const {
- Style* style = new Style();
- style->parent = parent;
- style->parent_inferred = parent_inferred;
- style->comment_ = comment_;
- style->source_ = source_;
- for (auto& entry : entries) {
- style->entries.push_back(Entry{entry.key, std::unique_ptr<Item>(entry.value->Clone(new_pool))});
- }
- return style;
-}
-
void Style::Print(std::ostream* out) const {
*out << "(style) ";
if (parent && parent.value().name) {
@@ -920,7 +875,8 @@ void Style::Print(std::ostream* out) const {
Style::Entry CloneEntry(const Style::Entry& entry, StringPool* pool) {
Style::Entry cloned_entry{entry.key};
if (entry.value != nullptr) {
- cloned_entry.value.reset(entry.value->Clone(pool));
+ CloningValueTransformer cloner(pool);
+ cloned_entry.value = entry.value->Transform(cloner);
}
return cloned_entry;
}
@@ -993,16 +949,6 @@ bool Array::Equals(const Value* value) const {
});
}
-Array* Array::Clone(StringPool* new_pool) const {
- Array* array = new Array();
- array->comment_ = comment_;
- array->source_ = source_;
- for (auto& item : elements) {
- array->elements.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool)));
- }
- return array;
-}
-
void Array::Print(std::ostream* out) const {
*out << "(array) [" << util::Joiner(elements, ", ") << "]";
}
@@ -1030,19 +976,6 @@ bool Plural::Equals(const Value* value) const {
return true;
}
-Plural* Plural::Clone(StringPool* new_pool) const {
- Plural* p = new Plural();
- p->comment_ = comment_;
- p->source_ = source_;
- const size_t count = values.size();
- for (size_t i = 0; i < count; i++) {
- if (values[i]) {
- p->values[i] = std::unique_ptr<Item>(values[i]->Clone(new_pool));
- }
- }
- return p;
-}
-
void Plural::Print(std::ostream* out) const {
*out << "(plural)";
if (values[Zero]) {
@@ -1086,15 +1019,26 @@ bool Styleable::Equals(const Value* value) const {
});
}
-Styleable* Styleable::Clone(StringPool* /*new_pool*/) const {
- return new Styleable(*this);
-}
-
void Styleable::Print(std::ostream* out) const {
*out << "(styleable) "
<< " [" << util::Joiner(entries, ", ") << "]";
}
+bool Macro::Equals(const Value* value) const {
+ const Macro* other = ValueCast<Macro>(value);
+ if (!other) {
+ return false;
+ }
+ return other->raw_value == raw_value && other->style_string.spans == style_string.spans &&
+ other->style_string.str == style_string.str &&
+ other->untranslatable_sections == untranslatable_sections &&
+ other->alias_namespaces == alias_namespaces;
+}
+
+void Macro::Print(std::ostream* out) const {
+ *out << "(macro) ";
+}
+
bool operator<(const Reference& a, const Reference& b) {
int cmp = a.name.value_or_default({}).compare(b.name.value_or_default({}));
if (cmp != 0) return cmp < 0;
@@ -1126,4 +1070,110 @@ void Styleable::MergeWith(Styleable* other) {
entries.insert(entries.end(), references.begin(), references.end());
}
+template <typename T>
+std::unique_ptr<T> CopyValueFields(std::unique_ptr<T> new_value, const T* value) {
+ new_value->SetSource(value->GetSource());
+ new_value->SetComment(value->GetComment());
+ return new_value;
+}
+
+CloningValueTransformer::CloningValueTransformer(StringPool* new_pool)
+ : ValueTransformer(new_pool) {
+}
+
+std::unique_ptr<Reference> CloningValueTransformer::TransformDerived(const Reference* value) {
+ return std::make_unique<Reference>(*value);
+}
+
+std::unique_ptr<Id> CloningValueTransformer::TransformDerived(const Id* value) {
+ return std::make_unique<Id>(*value);
+}
+
+std::unique_ptr<RawString> CloningValueTransformer::TransformDerived(const RawString* value) {
+ auto new_value = std::make_unique<RawString>(pool_->MakeRef(value->value));
+ return CopyValueFields(std::move(new_value), value);
+}
+
+std::unique_ptr<String> CloningValueTransformer::TransformDerived(const String* value) {
+ auto new_value = std::make_unique<String>(pool_->MakeRef(value->value));
+ new_value->untranslatable_sections = value->untranslatable_sections;
+ return CopyValueFields(std::move(new_value), value);
+}
+
+std::unique_ptr<StyledString> CloningValueTransformer::TransformDerived(const StyledString* value) {
+ auto new_value = std::make_unique<StyledString>(pool_->MakeRef(value->value));
+ new_value->untranslatable_sections = value->untranslatable_sections;
+ return CopyValueFields(std::move(new_value), value);
+}
+
+std::unique_ptr<FileReference> CloningValueTransformer::TransformDerived(
+ const FileReference* value) {
+ auto new_value = std::make_unique<FileReference>(pool_->MakeRef(value->path));
+ new_value->file = value->file;
+ new_value->type = value->type;
+ return CopyValueFields(std::move(new_value), value);
+}
+
+std::unique_ptr<BinaryPrimitive> CloningValueTransformer::TransformDerived(
+ const BinaryPrimitive* value) {
+ return std::make_unique<BinaryPrimitive>(*value);
+}
+
+std::unique_ptr<Attribute> CloningValueTransformer::TransformDerived(const Attribute* value) {
+ auto new_value = std::make_unique<Attribute>();
+ new_value->type_mask = value->type_mask;
+ new_value->min_int = value->min_int;
+ new_value->max_int = value->max_int;
+ for (const Attribute::Symbol& s : value->symbols) {
+ new_value->symbols.emplace_back(Attribute::Symbol{
+ .symbol = *s.symbol.Transform(*this),
+ .value = s.value,
+ .type = s.type,
+ });
+ }
+ return CopyValueFields(std::move(new_value), value);
+}
+
+std::unique_ptr<Style> CloningValueTransformer::TransformDerived(const Style* value) {
+ auto new_value = std::make_unique<Style>();
+ new_value->parent = value->parent;
+ new_value->parent_inferred = value->parent_inferred;
+ for (auto& entry : value->entries) {
+ new_value->entries.push_back(Style::Entry{entry.key, entry.value->Transform(*this)});
+ }
+ return CopyValueFields(std::move(new_value), value);
+}
+
+std::unique_ptr<Array> CloningValueTransformer::TransformDerived(const Array* value) {
+ auto new_value = std::make_unique<Array>();
+ for (auto& item : value->elements) {
+ new_value->elements.emplace_back(item->Transform(*this));
+ }
+ return CopyValueFields(std::move(new_value), value);
+}
+
+std::unique_ptr<Plural> CloningValueTransformer::TransformDerived(const Plural* value) {
+ auto new_value = std::make_unique<Plural>();
+ const size_t count = value->values.size();
+ for (size_t i = 0; i < count; i++) {
+ if (value->values[i]) {
+ new_value->values[i] = value->values[i]->Transform(*this);
+ }
+ }
+ return CopyValueFields(std::move(new_value), value);
+}
+
+std::unique_ptr<Styleable> CloningValueTransformer::TransformDerived(const Styleable* value) {
+ auto new_value = std::make_unique<Styleable>();
+ for (const Reference& s : value->entries) {
+ new_value->entries.emplace_back(*s.Transform(*this));
+ }
+ return CopyValueFields(std::move(new_value), value);
+}
+
+std::unique_ptr<Macro> CloningValueTransformer::TransformDerived(const Macro* value) {
+ auto new_value = std::make_unique<Macro>(*value);
+ return CopyValueFields(std::move(new_value), value);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index fe0883be50aa..d903b7e1b8b3 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -28,6 +28,7 @@
#include "Diagnostics.h"
#include "Resource.h"
#include "StringPool.h"
+#include "ValueTransformer.h"
#include "io/File.h"
#include "text/Printer.h"
#include "util/Maybe.h"
@@ -100,9 +101,8 @@ class Value {
// Calls the appropriate overload of ConstValueVisitor.
virtual void Accept(ConstValueVisitor* visitor) const = 0;
- // Clone the value. `new_pool` is the new StringPool that
- // any resources with strings should use when copying their string.
- virtual Value* Clone(StringPool* new_pool) const = 0;
+ // Transform this Value into another Value using the transformer.
+ std::unique_ptr<Value> Transform(ValueTransformer& transformer) const;
// Human readable printout of this value.
virtual void Print(std::ostream* out) const = 0;
@@ -118,6 +118,9 @@ class Value {
std::string comment_;
bool weak_ = false;
bool translatable_ = true;
+
+ private:
+ virtual Value* TransformValueImpl(ValueTransformer& transformer) const = 0;
};
// Inherit from this to get visitor accepting implementations for free.
@@ -129,12 +132,15 @@ struct BaseValue : public Value {
// A resource item with a single value. This maps to android::ResTable_entry.
struct Item : public Value {
- // Clone the Item.
- virtual Item* Clone(StringPool* new_pool) const override = 0;
-
// Fills in an android::Res_value structure with this Item's binary representation.
// Returns false if an error occurred.
virtual bool Flatten(android::Res_value* out_value) const = 0;
+
+ // Transform this Item into another Item using the transformer.
+ std::unique_ptr<Item> Transform(ValueTransformer& transformer) const;
+
+ private:
+ virtual Item* TransformItemImpl(ValueTransformer& transformer) const = 0;
};
// Inherit from this to get visitor accepting implementations for free.
@@ -147,17 +153,19 @@ struct BaseItem : public Item {
// A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE.
// A reference can be symbolic (with the name set to a valid resource name) or be
// numeric (the id is set to a valid resource ID).
-struct Reference : public BaseItem<Reference> {
- enum class Type {
+struct Reference : public TransformableItem<Reference, BaseItem<Reference>> {
+ enum class Type : uint8_t {
kResource,
kAttribute,
};
Maybe<ResourceName> name;
Maybe<ResourceId> id;
+ std::optional<uint32_t> type_flags;
Reference::Type reference_type;
bool private_reference = false;
bool is_dynamic = false;
+ bool allow_raw = false;
Reference();
explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
@@ -166,7 +174,6 @@ struct Reference : public BaseItem<Reference> {
bool Equals(const Value* value) const override;
bool Flatten(android::Res_value* out_value) const override;
- Reference* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
void PrettyPrint(text::Printer* printer) const override;
@@ -178,27 +185,25 @@ bool operator<(const Reference&, const Reference&);
bool operator==(const Reference&, const Reference&);
// An ID resource. Has no real value, just a place holder.
-struct Id : public BaseItem<Id> {
+struct Id : public TransformableItem<Id, BaseItem<Id>> {
Id() {
weak_ = true;
}
bool Equals(const Value* value) const override;
bool Flatten(android::Res_value* out) const override;
- Id* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
};
// A raw, unprocessed string. This may contain quotations, escape sequences, and whitespace.
// This shall *NOT* end up in the final resource table.
-struct RawString : public BaseItem<RawString> {
+struct RawString : public TransformableItem<RawString, BaseItem<RawString>> {
StringPool::Ref value;
explicit RawString(const StringPool::Ref& ref);
bool Equals(const Value* value) const override;
bool Flatten(android::Res_value* out_value) const override;
- RawString* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
};
@@ -220,7 +225,7 @@ inline bool operator!=(const UntranslatableSection& a, const UntranslatableSecti
return a.start != b.start || a.end != b.end;
}
-struct String : public BaseItem<String> {
+struct String : public TransformableItem<String, BaseItem<String>> {
StringPool::Ref value;
// Sections of the string to NOT translate. Mainly used
@@ -232,12 +237,11 @@ struct String : public BaseItem<String> {
bool Equals(const Value* value) const override;
bool Flatten(android::Res_value* out_value) const override;
- String* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
void PrettyPrint(text::Printer* printer) const override;
};
-struct StyledString : public BaseItem<StyledString> {
+struct StyledString : public TransformableItem<StyledString, BaseItem<StyledString>> {
StringPool::StyleRef value;
// Sections of the string to NOT translate. Mainly used
@@ -249,11 +253,10 @@ struct StyledString : public BaseItem<StyledString> {
bool Equals(const Value* value) const override;
bool Flatten(android::Res_value* out_value) const override;
- StyledString* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
};
-struct FileReference : public BaseItem<FileReference> {
+struct FileReference : public TransformableItem<FileReference, BaseItem<FileReference>> {
StringPool::Ref path;
// A handle to the file object from which this file can be read.
@@ -269,12 +272,11 @@ struct FileReference : public BaseItem<FileReference> {
bool Equals(const Value* value) const override;
bool Flatten(android::Res_value* out_value) const override;
- FileReference* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
};
// Represents any other android::Res_value.
-struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
+struct BinaryPrimitive : public TransformableItem<BinaryPrimitive, BaseItem<BinaryPrimitive>> {
android::Res_value value;
BinaryPrimitive() = default;
@@ -283,12 +285,11 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
bool Equals(const Value* value) const override;
bool Flatten(android::Res_value* out_value) const override;
- BinaryPrimitive* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
void PrettyPrint(text::Printer* printer) const override;
};
-struct Attribute : public BaseValue<Attribute> {
+struct Attribute : public TransformableValue<Attribute, BaseValue<Attribute>> {
struct Symbol {
Reference symbol;
uint32_t value;
@@ -311,13 +312,14 @@ struct Attribute : public BaseValue<Attribute> {
// TYPE_ENUMS are never compatible.
bool IsCompatibleWith(const Attribute& attr) const;
- Attribute* Clone(StringPool* new_pool) const override;
std::string MaskString() const;
+ static std::string MaskString(uint32_t type_mask);
+
void Print(std::ostream* out) const override;
bool Matches(const Item& item, DiagMessage* out_msg = nullptr) const;
};
-struct Style : public BaseValue<Style> {
+struct Style : public TransformableValue<Style, BaseValue<Style>> {
struct Entry {
Reference key;
std::unique_ptr<Item> value;
@@ -333,7 +335,6 @@ struct Style : public BaseValue<Style> {
std::vector<Entry> entries;
bool Equals(const Value* value) const override;
- Style* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
// Merges `style` into this Style. All identical attributes of `style` take precedence, including
@@ -341,33 +342,52 @@ struct Style : public BaseValue<Style> {
void MergeWith(Style* style, StringPool* pool);
};
-struct Array : public BaseValue<Array> {
+struct Array : public TransformableValue<Array, BaseValue<Array>> {
std::vector<std::unique_ptr<Item>> elements;
bool Equals(const Value* value) const override;
- Array* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
};
-struct Plural : public BaseValue<Plural> {
+struct Plural : public TransformableValue<Plural, BaseValue<Plural>> {
enum { Zero = 0, One, Two, Few, Many, Other, Count };
std::array<std::unique_ptr<Item>, Count> values;
bool Equals(const Value* value) const override;
- Plural* Clone(StringPool* new_pool) const override;
void Print(std::ostream* out) const override;
};
-struct Styleable : public BaseValue<Styleable> {
+struct Styleable : public TransformableValue<Styleable, BaseValue<Styleable>> {
std::vector<Reference> entries;
bool Equals(const Value* value) const override;
- Styleable* Clone(StringPool* newPool) const override;
void Print(std::ostream* out) const override;
void MergeWith(Styleable* styleable);
};
+struct Macro : public TransformableValue<Macro, BaseValue<Macro>> {
+ std::string raw_value;
+ StyleString style_string;
+ std::vector<UntranslatableSection> untranslatable_sections;
+
+ struct Namespace {
+ std::string alias;
+ std::string package_name;
+ bool is_private;
+
+ bool operator==(const Namespace& right) const {
+ return alias == right.alias && package_name == right.package_name &&
+ is_private == right.is_private;
+ }
+ };
+
+ std::vector<Namespace> alias_namespaces;
+
+ bool Equals(const Value* value) const override;
+ void Print(std::ostream* out) const override;
+};
+
template <typename T>
typename std::enable_if<std::is_base_of<Value, T>::value, std::ostream&>::type operator<<(
std::ostream& out, const std::unique_ptr<T>& value) {
@@ -379,6 +399,24 @@ typename std::enable_if<std::is_base_of<Value, T>::value, std::ostream&>::type o
return out;
}
+struct CloningValueTransformer : public ValueTransformer {
+ explicit CloningValueTransformer(StringPool* new_pool);
+
+ std::unique_ptr<Reference> TransformDerived(const Reference* value) override;
+ std::unique_ptr<Id> TransformDerived(const Id* value) override;
+ std::unique_ptr<RawString> TransformDerived(const RawString* value) override;
+ std::unique_ptr<String> TransformDerived(const String* value) override;
+ std::unique_ptr<StyledString> TransformDerived(const StyledString* value) override;
+ std::unique_ptr<FileReference> TransformDerived(const FileReference* value) override;
+ std::unique_ptr<BinaryPrimitive> TransformDerived(const BinaryPrimitive* value) override;
+ std::unique_ptr<Attribute> TransformDerived(const Attribute* value) override;
+ std::unique_ptr<Style> TransformDerived(const Style* value) override;
+ std::unique_ptr<Array> TransformDerived(const Array* value) override;
+ std::unique_ptr<Plural> TransformDerived(const Plural* value) override;
+ std::unique_ptr<Styleable> TransformDerived(const Styleable* value) override;
+ std::unique_ptr<Macro> TransformDerived(const Macro* value) override;
+};
+
} // namespace aapt
#endif // AAPT_RESOURCE_VALUES_H
diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp
index c4a1108ac62a..c75a4b99e138 100644
--- a/tools/aapt2/ResourceValues_test.cpp
+++ b/tools/aapt2/ResourceValues_test.cpp
@@ -62,7 +62,8 @@ TEST(ResourceValuesTest, PluralClone) {
a.values[Plural::One] = util::make_unique<String>(pool.MakeRef("one"));
a.values[Plural::Other] = util::make_unique<String>(pool.MakeRef("other"));
- std::unique_ptr<Plural> b(a.Clone(&pool));
+ CloningValueTransformer cloner(&pool);
+ std::unique_ptr<Plural> b(a.Transform(cloner));
EXPECT_TRUE(a.Equals(b.get()));
}
@@ -97,7 +98,8 @@ TEST(ResourceValuesTest, ArrayClone) {
a.elements.push_back(util::make_unique<String>(pool.MakeRef("one")));
a.elements.push_back(util::make_unique<String>(pool.MakeRef("two")));
- std::unique_ptr<Array> b(a.Clone(&pool));
+ CloningValueTransformer cloner(&pool);
+ std::unique_ptr<Array> b(a.Transform(cloner));
EXPECT_TRUE(a.Equals(b.get()));
}
@@ -160,7 +162,8 @@ TEST(ResourceValuesTest, StyleClone) {
.AddItem("android:attr/bar", ResourceUtils::TryParseInt("2"))
.Build();
- std::unique_ptr<Style> b(a->Clone(nullptr));
+ CloningValueTransformer cloner(nullptr);
+ std::unique_ptr<Style> b(a->Transform(cloner));
EXPECT_TRUE(a->Equals(b.get()));
}
@@ -174,7 +177,8 @@ TEST(ResourcesValuesTest, StringClones) {
EXPECT_THAT(pool_a.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en")));
EXPECT_THAT(pool_a.strings()[0]->value, StrEq("hello"));
- std::unique_ptr<String> str_b(str_a.Clone(&pool_b));
+ CloningValueTransformer cloner(&pool_b);
+ str_a.Transform(cloner);
ASSERT_THAT(pool_b, SizeIs(1u));
EXPECT_THAT(pool_b.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en")));
EXPECT_THAT(pool_b.strings()[0]->value, StrEq("hello"));
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index b1e1a77e1224..95b794964068 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -132,6 +132,11 @@ message Visibility {
// The comment associated with the <public> tag.
string comment = 3;
+
+ // Indicates that the resource id may change across builds and that the public R.java identifier
+ // for this resource should not be final. This is set to `true` for resources in `staging-group`
+ // tags.
+ bool staged_api = 4;
}
// Whether a resource comes from a compile-time overlay and is explicitly allowed to not overlay an
@@ -185,6 +190,12 @@ message OverlayableItem {
uint32 overlayable_idx = 4;
}
+// The staged resource ID definition of a finalized resource.
+message StagedId {
+ Source source = 1;
+ uint32 staged_id = 2;
+}
+
// An entry ID in the range [0x0000, 0xffff].
message EntryId {
uint32 id = 1;
@@ -217,6 +228,9 @@ message Entry {
// The set of values defined for this entry, each corresponding to a different
// configuration/variant.
repeated ConfigValue config_value = 6;
+
+ // The staged resource ID of this finalized resource.
+ StagedId staged_id = 7;
}
// A Configuration/Value pair.
@@ -268,6 +282,7 @@ message CompoundValue {
Styleable styleable = 3;
Array array = 4;
Plural plural = 5;
+ MacroBody macro = 6;
}
}
@@ -299,6 +314,13 @@ message Reference {
// Whether this reference is dynamic.
Boolean is_dynamic = 5;
+
+ // The type flags used when compiling the reference. Used for substituting the contents of macros.
+ uint32 type_flags = 6;
+
+ // Whether raw string values would have been accepted in place of this reference definition. Used
+ // for substituting the contents of macros.
+ bool allow_raw = 7;
}
// A value that represents an ID. This is just a placeholder, as ID values are used to occupy a
@@ -586,3 +608,32 @@ message XmlAttribute {
// The optional interpreted/compiled version of the `value` string.
Item compiled_item = 6;
}
+
+message MacroBody {
+ string raw_string = 1;
+ StyleString style_string = 2;
+ repeated UntranslatableSection untranslatable_sections = 3;
+ repeated NamespaceAlias namespace_stack = 4;
+ SourcePosition source = 5;
+}
+
+message NamespaceAlias {
+ string prefix = 1;
+ string package_name = 2;
+ bool is_private = 3;
+}
+
+message StyleString {
+ message Span {
+ string name = 1;
+ uint32 start_index = 2;
+ uint32 end_index = 3;
+ }
+ string str = 1;
+ repeated Span spans = 2;
+}
+
+message UntranslatableSection {
+ uint64 start_index = 1;
+ uint64 end_index = 2;
+} \ No newline at end of file
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 1006ca970dc5..3457e0b41859 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -36,6 +36,10 @@ struct Span {
std::string name;
uint32_t first_char;
uint32_t last_char;
+
+ bool operator==(const Span& right) const {
+ return name == right.name && first_char == right.first_char && last_char == right.last_char;
+ }
};
struct StyleString {
diff --git a/tools/aapt2/ValueTransformer.cpp b/tools/aapt2/ValueTransformer.cpp
new file mode 100644
index 000000000000..2d7996b9d880
--- /dev/null
+++ b/tools/aapt2/ValueTransformer.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 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 "ValueTransformer.h"
+
+#include "ResourceValues.h"
+
+namespace aapt {
+
+#define VALUE_CREATE_VALUE_DECL(T) \
+ std::unique_ptr<Value> ValueTransformer::TransformValue(const T* value) { \
+ return TransformDerived(value); \
+ }
+
+#define VALUE_CREATE_ITEM_DECL(T) \
+ std::unique_ptr<Item> ValueTransformer::TransformItem(const T* value) { \
+ return TransformDerived(value); \
+ } \
+ std::unique_ptr<Value> ValueTransformer::TransformValue(const T* value) { \
+ return TransformItem(value); \
+ }
+
+VALUE_CREATE_ITEM_DECL(Id);
+VALUE_CREATE_ITEM_DECL(Reference);
+VALUE_CREATE_ITEM_DECL(RawString);
+VALUE_CREATE_ITEM_DECL(String);
+VALUE_CREATE_ITEM_DECL(StyledString);
+VALUE_CREATE_ITEM_DECL(FileReference);
+VALUE_CREATE_ITEM_DECL(BinaryPrimitive);
+
+VALUE_CREATE_VALUE_DECL(Attribute);
+VALUE_CREATE_VALUE_DECL(Style);
+VALUE_CREATE_VALUE_DECL(Array);
+VALUE_CREATE_VALUE_DECL(Plural);
+VALUE_CREATE_VALUE_DECL(Styleable);
+VALUE_CREATE_VALUE_DECL(Macro);
+
+} // namespace aapt \ No newline at end of file
diff --git a/tools/aapt2/ValueTransformer.h b/tools/aapt2/ValueTransformer.h
new file mode 100644
index 000000000000..6fc4a191b04b
--- /dev/null
+++ b/tools/aapt2/ValueTransformer.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_VALUE_TRANSFORMER_H
+#define AAPT_VALUE_TRANSFORMER_H
+
+#include <memory>
+
+#include "StringPool.h"
+
+namespace aapt {
+
+class Value;
+struct Item;
+struct Reference;
+struct Id;
+struct RawString;
+struct String;
+struct StyledString;
+struct FileReference;
+struct BinaryPrimitive;
+struct Attribute;
+struct Style;
+struct Array;
+struct Plural;
+struct Styleable;
+struct Macro;
+
+#define AAPT_TRANSFORM_VALUE(T) \
+ virtual std::unique_ptr<T> TransformDerived(const T* value) = 0; \
+ virtual std::unique_ptr<Value> TransformValue(const T* value);
+
+#define AAPT_TRANSFORM_ITEM(T) \
+ virtual std::unique_ptr<Item> TransformItem(const T* value); \
+ AAPT_TRANSFORM_VALUE(T)
+
+/**
+ * An interface for consuming a Value type and transforming it into another Value.
+ *
+ * The interface defines 2 methods for each type (T) that inherits from TransformableValue:
+ * std::unique_ptr<T> TransformDerived(const T*)
+ * std::unique_ptr<Value> TransformValue(const T*)
+ *
+ * The interface defines 3 method for each type (T) that inherits from TransformableItem:
+ * std::unique_ptr<T> TransformDerived(const T*)
+ * std::unique_ptr<Item> TransformItem(const T*)
+ * std::unique_ptr<Value> TransformValue(const T*)
+ *
+ * TransformDerived is invoked when Transform is invoked on the derived type T.
+ * TransformItem is invoked when Transform is invoked on an Item type.
+ * TransformValue is invoked when Transform is invoked on a Value type.
+ *
+ * ValueTransformerImpl transformer(&string_pool);
+ * T* derived = ...;
+ * std::unique_ptr<T> new_type = derived->Transform(transformer); // Invokes TransformDerived
+ *
+ * Item* item = derived;
+ * std::unique_ptr<Item> new_item = item->TransformItem(transformer); // Invokes TransformItem
+ *
+ * Value* value = item;
+ * std::unique_ptr<Value> new_value = value->TransformValue(transformer); // Invokes TransformValue
+ *
+ * For types that inherit from AbstractTransformableItem, the default implementation of
+ * TransformValue invokes TransformItem which invokes TransformDerived.
+ *
+ * For types that inherit from AbstractTransformableValue, the default implementation of
+ * TransformValue invokes TransformDerived.
+ */
+struct ValueTransformer {
+ // `new_pool` is the new StringPool that newly created Values should use for string storing string
+ // values.
+ explicit ValueTransformer(StringPool* new_pool);
+ virtual ~ValueTransformer() = default;
+
+ AAPT_TRANSFORM_ITEM(Id);
+ AAPT_TRANSFORM_ITEM(Reference);
+ AAPT_TRANSFORM_ITEM(RawString);
+ AAPT_TRANSFORM_ITEM(String);
+ AAPT_TRANSFORM_ITEM(StyledString);
+ AAPT_TRANSFORM_ITEM(FileReference);
+ AAPT_TRANSFORM_ITEM(BinaryPrimitive);
+
+ AAPT_TRANSFORM_VALUE(Attribute);
+ AAPT_TRANSFORM_VALUE(Style);
+ AAPT_TRANSFORM_VALUE(Array);
+ AAPT_TRANSFORM_VALUE(Plural);
+ AAPT_TRANSFORM_VALUE(Styleable);
+ AAPT_TRANSFORM_VALUE(Macro);
+
+ protected:
+ StringPool* const pool_;
+};
+
+#undef AAPT_TRANSFORM_VALUE
+#undef AAPT_TRANSFORM_ITEM
+
+template <typename Derived, typename Base>
+struct TransformableValue : public Base {
+ // Transform this Derived into another Derived using the transformer.
+ std::unique_ptr<Derived> Transform(ValueTransformer& transformer) const;
+
+ private:
+ Value* TransformValueImpl(ValueTransformer& transformer) const override;
+};
+
+template <typename Derived, typename Base>
+struct TransformableItem : public TransformableValue<Derived, Base> {
+ private:
+ Item* TransformItemImpl(ValueTransformer& transformer) const override;
+};
+
+} // namespace aapt
+
+// Implementation
+#include "ValueTransformer_inline.h"
+
+#endif // AAPT_VALUE_TRANSFORMER_H \ No newline at end of file
diff --git a/tools/aapt2/ValueTransformer_inline.h b/tools/aapt2/ValueTransformer_inline.h
new file mode 100644
index 000000000000..c6c07c0fd6f9
--- /dev/null
+++ b/tools/aapt2/ValueTransformer_inline.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_VALUE_TRANSFORMER_IMPL_H
+#define AAPT_VALUE_TRANSFORMER_IMPL_H
+
+namespace aapt {
+
+inline ValueTransformer::ValueTransformer(StringPool* new_pool) : pool_(new_pool) {
+}
+
+template <typename Derived, typename Base>
+inline std::unique_ptr<Derived> TransformableValue<Derived, Base>::Transform(
+ ValueTransformer& transformer) const {
+ return transformer.TransformDerived(static_cast<const Derived*>(this));
+}
+
+template <typename Derived, typename Base>
+Value* TransformableValue<Derived, Base>::TransformValueImpl(ValueTransformer& transformer) const {
+ auto self = static_cast<const Derived*>(this);
+ auto transformed = transformer.TransformValue(self);
+ return transformed.release();
+}
+
+template <typename Derived, typename Base>
+Item* TransformableItem<Derived, Base>::TransformItemImpl(ValueTransformer& transformer) const {
+ auto self = static_cast<const Derived*>(this);
+ auto transformed = transformer.TransformItem(self);
+ return transformed.release();
+}
+
+} // namespace aapt
+
+#endif // AAPT_VALUE_TRANSFORMER_IMPL_H \ No newline at end of file
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index 4e74ec366dab..d0c9d89b6f0c 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -43,6 +43,9 @@ class ValueVisitor {
virtual void Visit(Array* value) { VisitAny(value); }
virtual void Visit(Plural* value) { VisitAny(value); }
virtual void Visit(Styleable* value) { VisitAny(value); }
+ virtual void Visit(Macro* value) {
+ VisitAny(value);
+ }
};
// Const version of ValueVisitor.
@@ -92,6 +95,9 @@ class ConstValueVisitor {
virtual void Visit(const Styleable* value) {
VisitAny(value);
}
+ virtual void Visit(const Macro* value) {
+ VisitAny(value);
+ }
};
// NOLINT, do not add parentheses around T.
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 32686538c10d..cd5015e81203 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -75,8 +75,10 @@ struct ResourcePathData {
};
// Resource file paths are expected to look like: [--/res/]type[-config]/name
-static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, const char dir_sep,
- std::string* out_error) {
+static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path,
+ const char dir_sep,
+ std::string* out_error,
+ const CompileOptions& options) {
std::vector<std::string> parts = util::Split(path, dir_sep);
if (parts.size() < 2) {
if (out_error) *out_error = "bad resource path";
@@ -121,7 +123,11 @@ static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path,
}
}
- return ResourcePathData{Source(path), dir_str.to_string(), name.to_string(),
+ const Source res_path = options.source_path
+ ? StringPiece(options.source_path.value())
+ : StringPiece(path);
+
+ return ResourcePathData{res_path, dir_str.to_string(), name.to_string(),
extension.to_string(), config_str.to_string(), config};
}
@@ -184,17 +190,6 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,
}
}
- // Ensure we have the compilation package at least.
- table.CreatePackage(context->GetCompilationPackage());
-
- // Assign an ID to any package that has resources.
- for (auto& pkg : table.packages) {
- if (!pkg->id) {
- // If no package ID was set while parsing (public identifiers), auto assign an ID.
- pkg->id = context->GetPackageId();
- }
- }
-
// Create the file/zip entry.
if (!writer->StartEntry(output_path, 0)) {
context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open");
@@ -667,7 +662,8 @@ int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter*
// Extract resource type information from the full path
std::string err_str;
ResourcePathData path_data;
- if (auto maybe_path_data = ExtractResourcePathData(path, inputs->GetDirSeparator(), &err_str)) {
+ if (auto maybe_path_data = ExtractResourcePathData(
+ path, inputs->GetDirSeparator(), &err_str, options)) {
path_data = maybe_path_data.value();
} else {
context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str);
@@ -747,6 +743,11 @@ int CompileCommand::Action(const std::vector<std::string>& args) {
context.GetDiagnostics()->Error(DiagMessage()
<< "only one of --dir and --zip can be specified");
return 1;
+ } else if ((options_.res_dir || options_.res_zip) &&
+ options_.source_path && args.size() > 1) {
+ context.GetDiagnostics()->Error(DiagMessage(kPath)
+ << "Cannot use an overriding source path with multiple files.");
+ return 1;
} else if (options_.res_dir) {
if (!args.empty()) {
context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index 1752a1adac24..1bc1f6651f85 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -28,6 +28,7 @@ namespace aapt {
struct CompileOptions {
std::string output_path;
+ Maybe<std::string> source_path;
Maybe<std::string> res_dir;
Maybe<std::string> res_zip;
Maybe<std::string> generate_text_symbols_path;
@@ -69,6 +70,9 @@ class CompileCommand : public Command {
AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose);
AddOptionalFlag("--trace-folder", "Generate systrace json trace fragment to specified folder.",
&trace_folder_);
+ AddOptionalFlag("--source-path",
+ "Sets the compiled resource file source file path to the given string.",
+ &options_.source_path);
}
int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index ae9f7926cf09..89757134f3a7 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -24,6 +24,7 @@
#include "io/ZipArchive.h"
#include "java/AnnotationProcessor.h"
#include "test/Test.h"
+#include "format/proto/ProtoDeserialize.h"
namespace aapt {
@@ -216,6 +217,7 @@ static void AssertTranslations(CommandTestFixture *ctf, std::string file_name,
}, compiled_files_dir, &diag));
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_NE(apk, nullptr);
ResourceTable* table = apk->GetResourceTable();
ASSERT_NE(table, nullptr);
@@ -252,4 +254,93 @@ TEST_F(CompilerTest, DoNotTranslateTest) {
AssertTranslations(this, "donottranslate", expected_not_translatable);
AssertTranslations(this, "donottranslate_foo", expected_not_translatable);
}
+
+TEST_F(CompilerTest, RelativePathTest) {
+ StdErrDiagnostics diag;
+ const std::string res_path = BuildPath(
+ {android::base::Dirname(android::base::GetExecutablePath()),
+ "integration-tests", "CompileTest", "res"});
+
+ const std::string path_values_colors = GetTestPath("values/colors.xml");
+ WriteFile(path_values_colors, "<resources>"
+ "<color name=\"color_one\">#008577</color>"
+ "</resources>");
+
+ const std::string path_layout_layout_one = GetTestPath("layout/layout_one.xml");
+ WriteFile(path_layout_layout_one, "<LinearLayout "
+ "xmlns:android=\"http://schemas.android.com/apk/res/android\">"
+ "<TextBox android:id=\"@+id/text_one\" android:background=\"@color/color_one\"/>"
+ "</LinearLayout>");
+
+ const std::string compiled_files_dir = BuildPath(
+ {android::base::Dirname(android::base::GetExecutablePath()),
+ "integration-tests", "CompileTest", "compiled"});
+ CHECK(file::mkdirs(compiled_files_dir.data()));
+
+ const std::string path_values_colors_out =
+ BuildPath({compiled_files_dir,"values_colors.arsc.flat"});
+ const std::string path_layout_layout_one_out =
+ BuildPath({compiled_files_dir, "layout_layout_one.flat"});
+ ::android::base::utf8::unlink(path_values_colors_out.c_str());
+ ::android::base::utf8::unlink(path_layout_layout_one_out.c_str());
+ const std::string apk_path = BuildPath(
+ {android::base::Dirname(android::base::GetExecutablePath()),
+ "integration-tests", "CompileTest", "out.apk"});
+
+ const std::string source_set_res = BuildPath({"main", "res"});
+ const std::string relative_path_values_colors =
+ BuildPath({source_set_res, "values", "colors.xml"});
+ const std::string relative_path_layout_layout_one =
+ BuildPath({source_set_res, "layout", "layout_one.xml"});
+
+ CompileCommand(&diag).Execute({
+ path_values_colors,
+ "-o",
+ compiled_files_dir,
+ "--source-path",
+ relative_path_values_colors},
+ &std::cerr);
+
+ CompileCommand(&diag).Execute({
+ path_layout_layout_one,
+ "-o",
+ compiled_files_dir,
+ "--source-path",
+ relative_path_layout_layout_one},
+ &std::cerr);
+
+ std::ifstream ifs_values(path_values_colors_out);
+ std::string content_values((std::istreambuf_iterator<char>(ifs_values)),
+ (std::istreambuf_iterator<char>()));
+ ASSERT_NE(content_values.find(relative_path_values_colors), -1);
+ ASSERT_EQ(content_values.find(path_values_colors), -1);
+
+ ASSERT_TRUE(Link({"-o", apk_path,
+ "--manifest", GetDefaultManifest(),
+ "--proto-format"},
+ compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, &diag);
+ ResourceTable* resource_table = apk.get()->GetResourceTable();
+ const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings =
+ resource_table->string_pool.strings();
+
+ ASSERT_EQ(pool_strings.size(), 2);
+ ASSERT_EQ(pool_strings[0]->value, "res/layout/layout_one.xml");
+ ASSERT_EQ(pool_strings[1]->value, "res/layout-v1/layout_one.xml");
+
+ // Check resources.pb contains relative sources.
+ io::IFile* proto_file =
+ apk.get()->GetFileCollection()->FindFile("resources.pb");
+ std::unique_ptr<io::InputStream> proto_stream = proto_file->OpenInputStream();
+ io::ProtoInputStreamReader proto_reader(proto_stream.get());
+ pb::ResourceTable pb_table;
+ proto_reader.ReadMessage(&pb_table);
+
+ const std::string pool_strings_proto = pb_table.source_pool().data();
+
+ ASSERT_NE(pool_strings_proto.find(relative_path_values_colors), -1);
+ ASSERT_NE(pool_strings_proto.find(relative_path_layout_layout_one), -1);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index d56994e3ae24..3950f337b575 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -83,7 +83,7 @@ static void EmitDiffLine(const Source& source, const StringPiece& message) {
}
static bool IsSymbolVisibilityDifferent(const Visibility& vis_a, const Visibility& vis_b) {
- return vis_a.level != vis_b.level;
+ return vis_a.level != vis_b.level || vis_a.staged_api != vis_b.staged_api;
}
template <typename Id>
@@ -95,17 +95,17 @@ static bool IsIdDiff(const Visibility::Level& level_a, const Maybe<Id>& id_a,
return false;
}
-static bool EmitResourceConfigValueDiff(IAaptContext* context, LoadedApk* apk_a,
- ResourceTablePackage* pkg_a, ResourceTableType* type_a,
- ResourceEntry* entry_a, ResourceConfigValue* config_value_a,
- LoadedApk* apk_b, ResourceTablePackage* pkg_b,
- ResourceTableType* type_b, ResourceEntry* entry_b,
- ResourceConfigValue* config_value_b) {
+static bool EmitResourceConfigValueDiff(
+ IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a,
+ const ResourceTableTypeView& type_a, const ResourceTableEntryView& entry_a,
+ const ResourceConfigValue* config_value_a, LoadedApk* apk_b,
+ const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b,
+ const ResourceTableEntryView& entry_b, const ResourceConfigValue* config_value_b) {
Value* value_a = config_value_a->value.get();
Value* value_b = config_value_b->value.get();
if (!value_a->Equals(value_b)) {
std::stringstream str_stream;
- str_stream << "value " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
+ str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " config=" << config_value_a->config << " does not match:\n";
value_a->Print(&str_stream);
str_stream << "\n vs \n";
@@ -117,64 +117,82 @@ static bool EmitResourceConfigValueDiff(IAaptContext* context, LoadedApk* apk_a,
}
static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
- ResourceTablePackage* pkg_a, ResourceTableType* type_a,
- ResourceEntry* entry_a, LoadedApk* apk_b,
- ResourceTablePackage* pkg_b, ResourceTableType* type_b,
- ResourceEntry* entry_b) {
+ const ResourceTablePackageView& pkg_a,
+ const ResourceTableTypeView& type_a,
+ const ResourceTableEntryView& entry_a, LoadedApk* apk_b,
+ const ResourceTablePackageView& pkg_b,
+ const ResourceTableTypeView& type_b,
+ const ResourceTableEntryView& entry_b) {
bool diff = false;
- for (std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
- ResourceConfigValue* config_value_b = entry_b->FindValue(config_value_a->config);
+ for (const ResourceConfigValue* config_value_a : entry_a.values) {
+ auto config_value_b = entry_b.FindValue(config_value_a->config);
if (!config_value_b) {
std::stringstream str_stream;
- str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
+ str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " config=" << config_value_a->config;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
- diff |=
- EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(),
- apk_b, pkg_b, type_b, entry_b, config_value_b);
+ diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a,
+ apk_b, pkg_b, type_b, entry_b, config_value_b);
}
}
// Check for any newly added config values.
- for (std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
- ResourceConfigValue* config_value_a = entry_a->FindValue(config_value_b->config);
+ for (const ResourceConfigValue* config_value_b : entry_b.values) {
+ auto config_value_a = entry_a.FindValue(config_value_b->config);
if (!config_value_a) {
std::stringstream str_stream;
- str_stream << "new config " << pkg_b->name << ":" << type_b->type << "/" << entry_b->name
+ str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b.name
<< " config=" << config_value_b->config;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
}
}
- return false;
+ return diff;
}
static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
- ResourceTablePackage* pkg_a, ResourceTableType* type_a,
- LoadedApk* apk_b, ResourceTablePackage* pkg_b,
- ResourceTableType* type_b) {
+ const ResourceTablePackageView& pkg_a,
+ const ResourceTableTypeView& type_a, LoadedApk* apk_b,
+ const ResourceTablePackageView& pkg_b,
+ const ResourceTableTypeView& type_b) {
bool diff = false;
- for (std::unique_ptr<ResourceEntry>& entry_a : type_a->entries) {
- ResourceEntry* entry_b = type_b->FindEntry(entry_a->name);
- if (!entry_b) {
+ auto entry_a_iter = type_a.entries.begin();
+ auto entry_b_iter = type_b.entries.begin();
+ while (entry_a_iter != type_a.entries.end() || entry_b_iter != type_b.entries.end()) {
+ if (entry_b_iter == type_b.entries.end()) {
+ // Type A contains a type that type B does not have.
+ std::stringstream str_stream;
+ str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a_iter->name;
+ EmitDiffLine(apk_a->GetSource(), str_stream.str());
+ diff = true;
+ } else if (entry_a_iter == type_a.entries.end()) {
+ // Type B contains a type that type A does not have.
std::stringstream str_stream;
- str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name;
+ str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/" << entry_b_iter->name;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
- if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) {
+ const auto& entry_a = *entry_a_iter;
+ const auto& entry_b = *entry_b_iter;
+ if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) {
std::stringstream str_stream;
- str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
+ str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " has different visibility (";
- if (entry_b->visibility.level == Visibility::Level::kPublic) {
+ if (entry_b.visibility.staged_api) {
+ str_stream << "STAGED ";
+ }
+ if (entry_b.visibility.level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
}
str_stream << " vs ";
- if (entry_a->visibility.level == Visibility::Level::kPublic) {
+ if (entry_a.visibility.staged_api) {
+ str_stream << "STAGED ";
+ }
+ if (entry_a.visibility.level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
@@ -182,19 +200,19 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
- } else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level,
- entry_b->id)) {
+ } else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level,
+ entry_b.id)) {
std::stringstream str_stream;
- str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
+ str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " has different public ID (";
- if (entry_b->id) {
- str_stream << "0x" << std::hex << entry_b->id.value();
+ if (entry_b.id) {
+ str_stream << "0x" << std::hex << entry_b.id.value();
} else {
str_stream << "none";
}
str_stream << " vs ";
- if (entry_a->id) {
- str_stream << "0x " << std::hex << entry_a->id.value();
+ if (entry_a.id) {
+ str_stream << "0x " << std::hex << entry_a.id.value();
} else {
str_stream << "none";
}
@@ -202,46 +220,51 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
}
- diff |= EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a.get(), apk_b, pkg_b,
- type_b, entry_b);
+ diff |= EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a, apk_b, pkg_b, type_b,
+ entry_b);
}
- }
-
- // Check for any newly added entries.
- for (std::unique_ptr<ResourceEntry>& entry_b : type_b->entries) {
- ResourceEntry* entry_a = type_a->FindEntry(entry_b->name);
- if (!entry_a) {
- std::stringstream str_stream;
- str_stream << "new entry " << pkg_b->name << ":" << type_b->type << "/" << entry_b->name;
- EmitDiffLine(apk_b->GetSource(), str_stream.str());
- diff = true;
+ if (entry_a_iter != type_a.entries.end()) {
+ ++entry_a_iter;
+ }
+ if (entry_b_iter != type_b.entries.end()) {
+ ++entry_b_iter;
}
}
return diff;
}
static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
- ResourceTablePackage* pkg_a, LoadedApk* apk_b,
- ResourceTablePackage* pkg_b) {
+ const ResourceTablePackageView& pkg_a, LoadedApk* apk_b,
+ const ResourceTablePackageView& pkg_b) {
bool diff = false;
- for (std::unique_ptr<ResourceTableType>& type_a : pkg_a->types) {
- ResourceTableType* type_b = pkg_b->FindType(type_a->type);
- if (!type_b) {
+ auto type_a_iter = pkg_a.types.begin();
+ auto type_b_iter = pkg_b.types.begin();
+ while (type_a_iter != pkg_a.types.end() || type_b_iter != pkg_b.types.end()) {
+ if (type_b_iter == pkg_b.types.end()) {
+ // Type A contains a type that type B does not have.
std::stringstream str_stream;
- str_stream << "missing " << pkg_a->name << ":" << type_a->type;
+ str_stream << "missing " << pkg_a.name << ":" << type_a_iter->type;
EmitDiffLine(apk_a->GetSource(), str_stream.str());
diff = true;
+ } else if (type_a_iter == pkg_a.types.end()) {
+ // Type B contains a type that type A does not have.
+ std::stringstream str_stream;
+ str_stream << "new type " << pkg_b.name << ":" << type_b_iter->type;
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
} else {
- if (type_a->visibility_level != type_b->visibility_level) {
+ const auto& type_a = *type_a_iter;
+ const auto& type_b = *type_b_iter;
+ if (type_a.visibility_level != type_b.visibility_level) {
std::stringstream str_stream;
- str_stream << pkg_a->name << ":" << type_a->type << " has different visibility (";
- if (type_b->visibility_level == Visibility::Level::kPublic) {
+ str_stream << pkg_a.name << ":" << type_a.type << " has different visibility (";
+ if (type_b.visibility_level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
}
str_stream << " vs ";
- if (type_a->visibility_level == Visibility::Level::kPublic) {
+ if (type_a.visibility_level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
@@ -249,18 +272,17 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
- } else if (IsIdDiff(type_a->visibility_level, type_a->id, type_b->visibility_level,
- type_b->id)) {
+ } else if (IsIdDiff(type_a.visibility_level, type_a.id, type_b.visibility_level, type_b.id)) {
std::stringstream str_stream;
- str_stream << pkg_a->name << ":" << type_a->type << " has different public ID (";
- if (type_b->id) {
- str_stream << "0x" << std::hex << type_b->id.value();
+ str_stream << pkg_a.name << ":" << type_a.type << " has different public ID (";
+ if (type_b.id) {
+ str_stream << "0x" << std::hex << type_b.id.value();
} else {
str_stream << "none";
}
str_stream << " vs ";
- if (type_a->id) {
- str_stream << "0x " << std::hex << type_a->id.value();
+ if (type_a.id) {
+ str_stream << "0x " << std::hex << type_a.id.value();
} else {
str_stream << "none";
}
@@ -268,47 +290,52 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
}
- diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b, pkg_b, type_b);
+ diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a, apk_b, pkg_b, type_b);
}
- }
-
- // Check for any newly added types.
- for (std::unique_ptr<ResourceTableType>& type_b : pkg_b->types) {
- ResourceTableType* type_a = pkg_a->FindType(type_b->type);
- if (!type_a) {
- std::stringstream str_stream;
- str_stream << "new type " << pkg_b->name << ":" << type_b->type;
- EmitDiffLine(apk_b->GetSource(), str_stream.str());
- diff = true;
+ if (type_a_iter != pkg_a.types.end()) {
+ ++type_a_iter;
+ }
+ if (type_b_iter != pkg_b.types.end()) {
+ ++type_b_iter;
}
}
return diff;
}
static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, LoadedApk* apk_b) {
- ResourceTable* table_a = apk_a->GetResourceTable();
- ResourceTable* table_b = apk_b->GetResourceTable();
+ const auto table_a = apk_a->GetResourceTable()->GetPartitionedView();
+ const auto table_b = apk_b->GetResourceTable()->GetPartitionedView();
bool diff = false;
- for (std::unique_ptr<ResourceTablePackage>& pkg_a : table_a->packages) {
- ResourceTablePackage* pkg_b = table_b->FindPackage(pkg_a->name);
- if (!pkg_b) {
+ auto package_a_iter = table_a.packages.begin();
+ auto package_b_iter = table_b.packages.begin();
+ while (package_a_iter != table_a.packages.end() || package_b_iter != table_b.packages.end()) {
+ if (package_b_iter == table_b.packages.end()) {
+ // Table A contains a package that table B does not have.
+ std::stringstream str_stream;
+ str_stream << "missing package " << package_a_iter->name;
+ EmitDiffLine(apk_a->GetSource(), str_stream.str());
+ diff = true;
+ } else if (package_a_iter == table_a.packages.end()) {
+ // Table B contains a package that table A does not have.
std::stringstream str_stream;
- str_stream << "missing package " << pkg_a->name;
+ str_stream << "new package " << package_b_iter->name;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
- if (pkg_a->id != pkg_b->id) {
+ const auto& package_a = *package_a_iter;
+ const auto& package_b = *package_b_iter;
+ if (package_a.id != package_b.id) {
std::stringstream str_stream;
- str_stream << "package '" << pkg_a->name << "' has different id (";
- if (pkg_b->id) {
- str_stream << "0x" << std::hex << pkg_b->id.value();
+ str_stream << "package '" << package_a.name << "' has different id (";
+ if (package_b.id) {
+ str_stream << "0x" << std::hex << package_b.id.value();
} else {
str_stream << "none";
}
str_stream << " vs ";
- if (pkg_a->id) {
- str_stream << "0x" << std::hex << pkg_a->id.value();
+ if (package_a.id) {
+ str_stream << "0x" << std::hex << package_b.id.value();
} else {
str_stream << "none";
}
@@ -316,20 +343,16 @@ static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, Loade
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
}
- diff |= EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b);
+ diff |= EmitResourcePackageDiff(context, apk_a, package_a, apk_b, package_b);
}
- }
-
- // Check for any newly added packages.
- for (std::unique_ptr<ResourceTablePackage>& pkg_b : table_b->packages) {
- ResourceTablePackage* pkg_a = table_a->FindPackage(pkg_b->name);
- if (!pkg_a) {
- std::stringstream str_stream;
- str_stream << "new package " << pkg_b->name;
- EmitDiffLine(apk_b->GetSource(), str_stream.str());
- diff = true;
+ if (package_a_iter != table_a.packages.end()) {
+ ++package_a_iter;
+ }
+ if (package_b_iter != table_b.packages.end()) {
+ ++package_b_iter;
}
}
+
return diff;
}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index faabe461648a..e4d0f3b6bd23 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -25,6 +25,7 @@
#include <vector>
#include "android-base/errors.h"
+#include "android-base/expected.h"
#include "android-base/file.h"
#include "android-base/stringprintf.h"
#include "androidfw/Locale.h"
@@ -74,10 +75,27 @@
using ::aapt::io::FileInputStream;
using ::android::ConfigDescription;
using ::android::StringPiece;
+using ::android::base::expected;
using ::android::base::StringPrintf;
+using ::android::base::unexpected;
namespace aapt {
+namespace {
+
+expected<ResourceTablePackage*, const char*> GetStaticLibraryPackage(ResourceTable* table) {
+ // Resource tables built by aapt2 always contain one package. This is a post condition of
+ // VerifyNoExternalPackages.
+ if (table->packages.size() != 1u) {
+ return unexpected("static library contains more than one package");
+ }
+ return table->packages.back().get();
+}
+
+} // namespace
+
+constexpr uint8_t kAndroidPackageId = 0x01;
+
class LinkContext : public IAaptContext {
public:
explicit LinkContext(IDiagnostics* diagnostics)
@@ -444,7 +462,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer
// that existing projects have out-of-date references which pass compilation.
xml::StripAndroidStudioAttributes(doc->root.get());
- XmlReferenceLinker xml_linker;
+ XmlReferenceLinker xml_linker(table);
if (!options_.do_not_fail_on_missing_resources && !xml_linker.Consume(context_, doc)) {
return {};
}
@@ -631,13 +649,18 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
const ResourceFile& file = doc->file;
dst_path = ResourceUtils::BuildResourceFileName(file, context_->GetNameMangler());
- std::unique_ptr<FileReference> file_ref =
+ auto file_ref =
util::make_unique<FileReference>(table->string_pool.MakeRef(dst_path));
file_ref->SetSource(doc->file.source);
+
// Update the output format of this XML file.
file_ref->type = XmlFileTypeForOutputFormat(options_.output_format);
- if (!table->AddResourceMangled(file.name, file.config, {}, std::move(file_ref),
- context_->GetDiagnostics())) {
+ bool result = table->AddResource(NewResourceBuilder(file.name)
+ .SetValue(std::move(file_ref), file.config)
+ .SetAllowMangled(true)
+ .Build(),
+ context_->GetDiagnostics());
+ if (!result) {
return false;
}
}
@@ -840,18 +863,15 @@ class Linker {
ResourceTable* table = static_apk->GetResourceTable();
// If we are using --no-static-lib-packages, we need to rename the package of this table to
- // our compilation package.
- if (options_.no_static_lib_packages) {
- // Since package names can differ, and multiple packages can exist in a ResourceTable,
- // we place the requirement that all static libraries are built with the package
- // ID 0x7f. So if one is not found, this is an error.
- if (ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId)) {
- pkg->name = context_->GetCompilationPackage();
- } else {
- context_->GetDiagnostics()->Error(DiagMessage(path)
- << "no package with ID 0x7f found in static library");
+ // our compilation package so the symbol package name does not get mangled into the entry
+ // name.
+ if (options_.no_static_lib_packages && !table->packages.empty()) {
+ auto lib_package_result = GetStaticLibraryPackage(table);
+ if (!lib_package_result.has_value()) {
+ context_->GetDiagnostics()->Error(DiagMessage(path) << lib_package_result.error());
return false;
}
+ lib_package_result.value()->name = context_->GetCompilationPackage();
}
context_->GetExternalSymbols()->AppendSource(
@@ -980,8 +1000,7 @@ class Linker {
// stripped, or there is an error and false is returned.
bool VerifyNoExternalPackages() {
auto is_ext_package_func = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
- return context_->GetCompilationPackage() != pkg->name || !pkg->id ||
- pkg->id.value() != context_->GetPackageId();
+ return context_->GetCompilationPackage() != pkg->name;
};
bool error = false;
@@ -1025,19 +1044,11 @@ class Linker {
bool VerifyNoIdsSet() {
for (const auto& package : final_table_.packages) {
for (const auto& type : package->types) {
- if (type->id) {
- context_->GetDiagnostics()->Error(DiagMessage() << "type " << type->type << " has ID "
- << StringPrintf("%02x", type->id.value())
- << " assigned");
- return false;
- }
-
for (const auto& entry : type->entries) {
if (entry->id) {
ResourceNameRef res_name(package->name, type->type, entry->name);
- context_->GetDiagnostics()->Error(
- DiagMessage() << "entry " << res_name << " has ID "
- << StringPrintf("%02x", entry->id.value()) << " assigned");
+ context_->GetDiagnostics()->Error(DiagMessage() << "resource " << res_name << " has ID "
+ << entry->id.value() << " assigned");
return false;
}
}
@@ -1311,12 +1322,17 @@ class Linker {
}
ResourceTable* table = apk->GetResourceTable();
- ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId);
- if (!pkg) {
- context_->GetDiagnostics()->Error(DiagMessage(input) << "static library has no package");
+ if (table->packages.empty()) {
+ return true;
+ }
+
+ auto lib_package_result = GetStaticLibraryPackage(table);
+ if (!lib_package_result.has_value()) {
+ context_->GetDiagnostics()->Error(DiagMessage(input) << lib_package_result.error());
return false;
}
+ ResourceTablePackage* pkg = lib_package_result.value();
bool result;
if (options_.no_static_lib_packages) {
// Merge all resources as if they were in the compilation package. This is the old behavior
@@ -1363,11 +1379,11 @@ class Linker {
res_name = mangled_name.value();
}
- std::unique_ptr<Id> id = util::make_unique<Id>();
+ auto id = util::make_unique<Id>();
id->SetSource(source.WithLine(exported_symbol.line));
- bool result =
- final_table_.AddResourceMangled(res_name, ConfigDescription::DefaultConfig(),
- std::string(), std::move(id), context_->GetDiagnostics());
+ bool result = final_table_.AddResource(
+ NewResourceBuilder(res_name).SetValue(std::move(id)).SetAllowMangled(true).Build(),
+ context_->GetDiagnostics());
if (!result) {
return false;
}
@@ -1389,7 +1405,7 @@ class Linker {
return MergeExportedSymbols(compiled_file.source, compiled_file.exported_symbols);
}
- // Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
+ // Takes a path to load as a ZIP file and merges the files within into the main ResourceTable.
// If override is true, conflicting resources are allowed to override each other, in order of last
// seen.
// An io::IFileCollection is created from the ZIP file and added to the set of
@@ -1420,7 +1436,7 @@ class Linker {
return !error;
}
- // Takes a path to load and merge into the master ResourceTable. If override is true,
+ // Takes a path to load and merge into the main ResourceTable. If override is true,
// conflicting resources are allowed to override each other, in order of last seen.
// If the file path ends with .flata, .jar, .jack, or .zip the file is treated
// as ZIP archive and the files within are merged individually.
@@ -1437,7 +1453,7 @@ class Linker {
return MergeFile(file, override);
}
- // Takes an AAPT Container file (.apc/.flat) to load and merge into the master ResourceTable.
+ // Takes an AAPT Container file (.apc/.flat) to load and merge into the main ResourceTable.
// If override is true, conflicting resources are allowed to override each other, in order of last
// seen.
// All other file types are ignored. This is because these files could be coming from a zip,
@@ -1566,32 +1582,37 @@ class Linker {
return true;
}
+ ResourceEntry* ResolveTableEntry(LinkContext* context, ResourceTable* table,
+ Reference* reference) {
+ if (!reference || !reference->name) {
+ return nullptr;
+ }
+ auto name_ref = ResourceNameRef(reference->name.value());
+ if (name_ref.package.empty()) {
+ name_ref.package = context->GetCompilationPackage();
+ }
+ const auto search_result = table->FindResource(name_ref);
+ if (!search_result) {
+ return nullptr;
+ }
+ return search_result.value().entry;
+ }
+
void AliasAdaptiveIcon(xml::XmlResource* manifest, ResourceTable* table) {
- xml::Element* application = manifest->root->FindChild("", "application");
+ const 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");
+ const xml::Attribute* icon = application->FindAttribute(xml::kSchemaAndroid, "icon");
+ const 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);
+ const auto icon_reference = ValueCast<Reference>(icon->compiled_value.get());
+ const auto icon_entry = ResolveTableEntry(context_, table, icon_reference);
if (!icon_entry) {
return;
}
@@ -1607,19 +1628,8 @@ class Linker {
}
// 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);
+ const auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get());
+ const auto round_icon_entry = ResolveTableEntry(context_, table, round_icon_reference);
if (!round_icon_entry) {
return;
}
@@ -1646,13 +1656,64 @@ class Linker {
<< " 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);
+ CloningValueTransformer cloner(&table->string_pool);
+ auto value = icon_reference->Transform(cloner);
+ auto round_config_value =
+ round_icon_entry->FindOrCreateValue(config_value->config, config_value->product);
+ round_config_value->value = std::move(value);
}
}
+ bool VerifySharedUserId(xml::XmlResource* manifest, ResourceTable* table) {
+ const xml::Element* manifest_el = xml::FindRootElement(manifest->root.get());
+ if (manifest_el == nullptr) {
+ return true;
+ }
+ if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
+ return true;
+ }
+ const xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "sharedUserId");
+ if (!attr) {
+ return true;
+ }
+ const auto validate = [&](const std::string& shared_user_id) -> bool {
+ if (util::IsAndroidSharedUserId(context_->GetCompilationPackage(), shared_user_id)) {
+ return true;
+ }
+ DiagMessage error_msg(manifest_el->line_number);
+ error_msg << "attribute 'sharedUserId' in <manifest> tag is not a valid shared user id: '"
+ << shared_user_id << "'";
+ if (options_.manifest_fixer_options.warn_validation) {
+ // Treat the error only as a warning.
+ context_->GetDiagnostics()->Warn(error_msg);
+ return true;
+ }
+ context_->GetDiagnostics()->Error(error_msg);
+ return false;
+ };
+ // If attr->compiled_value is not null, check if it is a ref
+ if (attr->compiled_value) {
+ const auto ref = ValueCast<Reference>(attr->compiled_value.get());
+ if (ref == nullptr) {
+ return true;
+ }
+ const auto shared_user_id_entry = ResolveTableEntry(context_, table, ref);
+ if (!shared_user_id_entry) {
+ return true;
+ }
+ for (const auto& value : shared_user_id_entry->values) {
+ const auto str_value = ValueCast<String>(value->value.get());
+ if (str_value != nullptr && !validate(*str_value->value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Fallback to checking the raw value
+ return validate(attr->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,
@@ -1674,6 +1735,11 @@ class Linker {
// See (b/34829129)
AliasAdaptiveIcon(manifest, table);
+ // Verify the shared user id here to handle the case of reference value.
+ if (!VerifySharedUserId(manifest, table)) {
+ return false;
+ }
+
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;
@@ -1708,15 +1774,13 @@ class Linker {
context_->GetPackageId() != kAppPackageId &&
context_->GetPackageId() != kFrameworkPackageId)
|| (!options_.allow_reserved_package_id && context_->GetPackageId() > kAppPackageId);
- if (isSplitPackage &&
- included_feature_base_ == make_value(context_->GetCompilationPackage())) {
+ if (isSplitPackage && included_feature_base_ == context_->GetCompilationPackage()) {
// The base APK is included, and this is a feature split. If the base package is
// the same as this package, then we are building an old style Android Instant Apps feature
// split and must apply this workaround to avoid requiring namespaces support.
- package_to_rewrite = table->FindPackage(context_->GetCompilationPackage());
- if (package_to_rewrite != nullptr) {
- CHECK_EQ(1u, table->packages.size()) << "can't change name of package when > 1 package";
-
+ if (!table->packages.empty() &&
+ table->packages.back()->name == context_->GetCompilationPackage()) {
+ package_to_rewrite = table->packages.back().get();
std::string new_package_name =
StringPrintf("%s.%s", package_to_rewrite->name.c_str(),
app_info_.split_name.value_or_default("feature").c_str());
@@ -1735,9 +1799,12 @@ class Linker {
if (package_to_rewrite != nullptr) {
// Change the name back.
package_to_rewrite->name = context_->GetCompilationPackage();
- if (package_to_rewrite->id) {
- table->included_packages_.erase(package_to_rewrite->id.value());
- }
+
+ // TableFlattener creates an `included_packages_` mapping entry for each package with a
+ // non-standard package id (not 0x01 or 0x7f). Since this is a feature split and not a shared
+ // library, do not include a mapping from the feature package name to the feature package id
+ // in the feature's dynamic reference table.
+ table->included_packages_.erase(context_->GetPackageId());
}
if (!success) {
@@ -1797,7 +1864,7 @@ class Linker {
// Override the package ID when it is "android".
if (context_->GetCompilationPackage() == "android") {
- context_->SetPackageId(0x01);
+ context_->SetPackageId(kAndroidPackageId);
// Verify we're building a regular app.
if (context_->GetPackageType() != PackageType::kApp) {
@@ -1854,7 +1921,8 @@ class Linker {
if (context_->GetPackageType() != PackageType::kStaticLib) {
PrivateAttributeMover mover;
- if (!mover.Consume(context_, &final_table_)) {
+ if (context_->GetPackageId() == kAndroidPackageId &&
+ !mover.Consume(context_, &final_table_)) {
context_->GetDiagnostics()->Error(DiagMessage() << "failed moving private attributes");
return 1;
}
@@ -1873,8 +1941,7 @@ class Linker {
for (auto& entry : type->entries) {
ResourceName name(package->name, type->type, entry->name);
// The IDs are guaranteed to exist.
- options_.stable_id_map[std::move(name)] =
- ResourceId(package->id.value(), type->id.value(), entry->id.value());
+ options_.stable_id_map[std::move(name)] = entry->id.value();
}
}
}
@@ -2045,7 +2112,7 @@ class Linker {
std::unique_ptr<xml::XmlResource> split_manifest =
GenerateSplitManifest(app_info_, *split_constraints_iter);
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(&final_table_);
if (!linker.Consume(context_, split_manifest.get())) {
context_->GetDiagnostics()->Error(DiagMessage()
<< "failed to create Split AndroidManifest.xml");
@@ -2076,7 +2143,7 @@ class Linker {
// So we give it a package name so it can see local resources.
manifest_xml->file.name.package = context_->GetCompilationPackage();
- XmlReferenceLinker manifest_linker;
+ XmlReferenceLinker manifest_linker(&final_table_);
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)) {
@@ -2211,6 +2278,16 @@ int LinkCommand::Action(const std::vector<std::string>& args) {
return 1;
}
+ if (shared_lib_ && options_.private_symbols) {
+ // If a shared library styleable in a public R.java uses a private attribute, attempting to
+ // reference the private attribute within the styleable array will cause a link error because
+ // the private attribute will not be emitted in the public R.java.
+ context.GetDiagnostics()->Error(DiagMessage()
+ << "--shared-lib cannot currently be used in combination with"
+ << " --private-symbols");
+ 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");
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 852b1244cd6e..768b4b2c7bfd 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -190,8 +190,11 @@ class LinkCommand : public Command {
AddOptionalFlag("--version-name",
"Version name to inject into the AndroidManifest.xml if none is present.",
&options_.manifest_fixer_options.version_name_default);
+ AddOptionalFlag("--revision-code",
+ "Revision code (integer) to inject into the AndroidManifest.xml if none is\n"
+ "present.", &options_.manifest_fixer_options.revision_code_default);
AddOptionalSwitch("--replace-version",
- "If --version-code and/or --version-name are specified, these\n"
+ "If --version-code, --version-name, and/or --revision-code are specified, these\n"
"values will replace any value already in the manifest. By\n"
"default, nothing is changed if the manifest already defines\n"
"these attributes.",
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 062dd8eac975..430c184ef87d 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -14,14 +14,19 @@
* limitations under the License.
*/
-#include "AppInfo.h"
#include "Link.h"
+#include <android-base/file.h>
+
+#include "AppInfo.h"
#include "LoadedApk.h"
#include "test/Test.h"
using testing::Eq;
+using testing::HasSubstr;
+using testing::IsNull;
using testing::Ne;
+using testing::NotNull;
namespace aapt {
@@ -44,6 +49,8 @@ TEST_F(LinkTest, RemoveRawXmlStrings) {
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_THAT(apk, Ne(nullptr));
+
std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
ASSERT_THAT(data, Ne(nullptr));
AssertLoadXml(apk.get(), data.get(), &tree);
@@ -70,6 +77,8 @@ TEST_F(LinkTest, KeepRawXmlStrings) {
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_THAT(apk, Ne(nullptr));
+
std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
ASSERT_THAT(data, Ne(nullptr));
AssertLoadXml(apk.get(), data.get(), &tree);
@@ -205,6 +214,8 @@ TEST_F(LinkTest, OverlayStyles) {
ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_THAT(apk, Ne(nullptr));
+
const Style* actual_style = test::GetValue<Style>(
apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle");
ASSERT_NE(actual_style, nullptr);
@@ -247,6 +258,8 @@ TEST_F(LinkTest, OverrideStylesInsteadOfOverlaying) {
ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_THAT(apk, Ne(nullptr));
+
const Style* actual_style = test::GetValue<Style>(
apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle");
ASSERT_NE(actual_style, nullptr);
@@ -317,4 +330,457 @@ TEST_F(LinkTest, AppInfoWithUsesSplit) {
ASSERT_TRUE(Link(link_args, feature2_files_dir, &diag));
}
+TEST_F(LinkTest, SharedLibraryAttributeRJava) {
+ StdErrDiagnostics diag;
+ const std::string lib_values =
+ R"(<resources>
+ <attr name="foo"/>
+ <public type="attr" name="foo" id="0x00010001"/>
+ <declare-styleable name="LibraryStyleable">
+ <attr name="foo" />
+ </declare-styleable>
+ </resources>)";
+
+ const std::string client_values =
+ R"(<resources>
+ <attr name="bar" />
+ <declare-styleable name="ClientStyleable">
+ <attr name="com.example.lib:foo" />
+ <attr name="bar" />
+ </declare-styleable>
+ </resources>)";
+
+ // Build a library with a public attribute
+ const std::string lib_res = GetTestPath("library-res");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), lib_values, lib_res, &diag));
+
+ const std::string lib_apk = GetTestPath("library.apk");
+ const std::string lib_java = GetTestPath("library_java");
+ // clang-format off
+ auto lib_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.lib")
+ .Build();
+
+ auto lib_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(lib_manifest)
+ .AddFlag("--shared-lib")
+ .AddParameter("--java", lib_java)
+ .AddCompiledResDir(lib_res, &diag)
+ .Build(lib_apk);
+ // clang-format on
+ ASSERT_TRUE(Link(lib_link_args, &diag));
+
+ const std::string lib_r_java = lib_java + "/com/example/lib/R.java";
+ std::string lib_r_contents;
+ ASSERT_TRUE(android::base::ReadFileToString(lib_r_java, &lib_r_contents));
+ EXPECT_THAT(lib_r_contents, HasSubstr(" public static int foo=0x00010001;"));
+ EXPECT_THAT(lib_r_contents, HasSubstr(" com.example.lib.R.attr.foo"));
+
+ // Build a client that uses the library attribute in a declare-styleable
+ const std::string client_res = GetTestPath("client-res");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), client_values, client_res, &diag));
+
+ const std::string client_apk = GetTestPath("client.apk");
+ const std::string client_java = GetTestPath("client_java");
+ // clang-format off
+ auto client_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.client")
+ .Build();
+
+ auto client_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(client_manifest)
+ .AddParameter("--java", client_java)
+ .AddParameter("-I", lib_apk)
+ .AddCompiledResDir(client_res, &diag)
+ .Build(client_apk);
+ // clang-format on
+ ASSERT_TRUE(Link(client_link_args, &diag));
+
+ const std::string client_r_java = client_java + "/com/example/client/R.java";
+ std::string client_r_contents;
+ ASSERT_TRUE(android::base::ReadFileToString(client_r_java, &client_r_contents));
+ EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000"));
+}
+
+struct SourceXML {
+ std::string res_file_path;
+ std::string file_contents;
+};
+
+static void BuildApk(const std::vector<SourceXML>& source_files, const std::string& apk_path,
+ LinkCommandBuilder&& link_args, CommandTestFixture* fixture,
+ IDiagnostics* diag) {
+ TemporaryDir res_dir;
+ TemporaryDir compiled_res_dir;
+ for (auto& source_file : source_files) {
+ ASSERT_TRUE(fixture->CompileFile(res_dir.path + source_file.res_file_path,
+ source_file.file_contents, compiled_res_dir.path, diag));
+ }
+ ASSERT_TRUE(fixture->Link(
+ link_args.AddCompiledResDir(compiled_res_dir.path, diag).Build(apk_path), diag));
+}
+
+static void BuildSDK(const std::vector<SourceXML>& source_files, const std::string& apk_path,
+ const std::string& java_root_path, CommandTestFixture* fixture,
+ IDiagnostics* diag) {
+ auto android_manifest = ManifestBuilder(fixture).SetPackageName("android").Build();
+
+ auto android_link_args = LinkCommandBuilder(fixture)
+ .SetManifestFile(android_manifest)
+ .AddParameter("--private-symbols", "com.android.internal")
+ .AddParameter("--java", java_root_path);
+
+ BuildApk(source_files, apk_path, std::move(android_link_args), fixture, diag);
+}
+
+static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& java_path,
+ CommandTestFixture* fixture, IDiagnostics* diag) {
+ const std::string android_values =
+ R"(<resources>
+ <public type="attr" name="finalized_res" id="0x01010001"/>
+
+ <!-- S staged attributes (support staged resources in the same type id) -->
+ <staging-public-group type="attr" first-id="0x01010050">
+ <public name="staged_s_res" />
+ </staging-public-group>
+
+ <staging-public-group type="string" first-id="0x01fd0080">
+ <public name="staged_s_string" />
+ </staging-public-group>
+
+ <!-- SV2 staged attributes (support staged resources in a separate type id) -->
+ <staging-public-group type="attr" first-id="0x01ff0049">
+ <public name="staged_s2_res" />
+ </staging-public-group>
+
+ <!-- T staged attributes (support staged resources in multiple separate type ids) -->
+ <staging-public-group type="attr" first-id="0x01fe0063">
+ <public name="staged_t_res" />
+ </staging-public-group>
+
+ <attr name="finalized_res" />
+ <attr name="staged_s_res" />
+ <attr name="staged_s2_res" />
+ <attr name="staged_t_res" />
+ <string name="staged_s_string">Hello</string>
+ </resources>)";
+
+ SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+ BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+static void BuildFinalizedSDK(const std::string& apk_path, const std::string& java_path,
+ CommandTestFixture* fixture, IDiagnostics* diag) {
+ const std::string android_values =
+ R"(<resources>
+ <public type="attr" name="finalized_res" id="0x01010001"/>
+ <public type="attr" name="staged_s_res" id="0x01010002"/>
+ <public type="attr" name="staged_s2_res" id="0x01010003"/>
+ <public type="string" name="staged_s_string" id="0x01020000"/>
+
+ <!-- S staged attributes (support staged resources in the same type id) -->
+ <staging-public-group-final type="attr" first-id="0x01010050">
+ <public name="staged_s_res" />
+ </staging-public-group-final>
+
+ <staging-public-group-final type="string" first-id="0x01fd0080">
+ <public name="staged_s_string" />
+ </staging-public-group-final>
+
+ <!-- SV2 staged attributes (support staged resources in a separate type id) -->
+ <staging-public-group-final type="attr" first-id="0x01ff0049">
+ <public name="staged_s2_res" />
+ </staging-public-group-final>
+
+ <!-- T staged attributes (support staged resources in multiple separate type ids) -->
+ <staging-public-group type="attr" first-id="0x01fe0063">
+ <public name="staged_t_res" />
+ </staging-public-group>
+
+ <attr name="finalized_res" />
+ <attr name="staged_s_res" />
+ <attr name="staged_s2_res" />
+ <attr name="staged_t_res" />
+ <string name="staged_s_string">Hello</string>
+ </resources>)";
+
+ SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+ BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+static void BuildAppAgainstSDK(const std::string& apk_path, const std::string& java_path,
+ const std::string& sdk_path, CommandTestFixture* fixture,
+ IDiagnostics* diag) {
+ const std::string app_values =
+ R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <attr name="bar" />
+ <style name="MyStyle">
+ <item name="android:staged_s_res">@android:string/staged_s_string</item>
+ </style>
+ <declare-styleable name="ClientStyleable">
+ <attr name="android:finalized_res" />
+ <attr name="android:staged_s_res" />
+ <attr name="bar" />
+ </declare-styleable>
+ <public name="MyStyle" type="style" id="0x7f020000" />
+ </resources>)";
+
+ SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = app_values};
+
+ auto app_manifest = ManifestBuilder(fixture).SetPackageName("com.example.app").Build();
+
+ auto app_link_args = LinkCommandBuilder(fixture)
+ .SetManifestFile(app_manifest)
+ .AddParameter("--java", java_path)
+ .AddParameter("-I", sdk_path);
+
+ BuildApk({source_xml}, apk_path, std::move(app_link_args), fixture, diag);
+}
+
+TEST_F(LinkTest, StagedAndroidApi) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildNonFinalizedSDK(android_apk, android_java, this, &diag);
+
+ const std::string android_r_java = android_java + "/android/R.java";
+ std::string android_r_contents;
+ ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents));
+ EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
+ EXPECT_THAT(
+ android_r_contents,
+ HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }"));
+ EXPECT_THAT(
+ android_r_contents,
+ HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }"));
+ EXPECT_THAT(
+ android_r_contents,
+ HasSubstr("public static final int staged_s2_res; static { staged_s2_res=0x01ff0049; }"));
+ EXPECT_THAT(
+ android_r_contents,
+ HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
+
+ const std::string app_apk = GetTestPath("app.apk");
+ const std::string app_java = GetTestPath("app-java");
+ BuildAppAgainstSDK(app_apk, app_java, android_apk, this, &diag);
+
+ const std::string client_r_java = app_java + "/com/example/app/R.java";
+ std::string client_r_contents;
+ ASSERT_TRUE(android::base::ReadFileToString(client_r_java, &client_r_contents));
+ EXPECT_THAT(client_r_contents, HasSubstr(" 0x01010001, android.R.attr.staged_s_res, 0x7f010000"));
+
+ // Test that the resource ids of staged and non-staged resource can be retrieved
+ android::AssetManager2 am;
+ auto android_asset = android::ApkAssets::Load(android_apk);
+ ASSERT_THAT(android_asset, NotNull());
+ ASSERT_TRUE(am.SetApkAssets({android_asset.get()}));
+
+ auto result = am.GetResourceId("android:attr/finalized_res");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01010001));
+
+ result = am.GetResourceId("android:attr/staged_s_res");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01010050));
+
+ result = am.GetResourceId("android:string/staged_s_string");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01fd0080));
+
+ result = am.GetResourceId("android:attr/staged_s2_res");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01ff0049));
+
+ result = am.GetResourceId("android:attr/staged_t_res");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01fe0063));
+}
+
+TEST_F(LinkTest, FinalizedAndroidApi) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildFinalizedSDK(android_apk, android_java, this, &diag);
+
+ const std::string android_r_java = android_java + "/android/R.java";
+ std::string android_r_contents;
+ ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents));
+ EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
+ EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_res=0x01010002;"));
+ EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_string=0x01020000;"));
+ EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s2_res=0x01010003;"));
+ EXPECT_THAT(
+ android_r_contents,
+ HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
+ ;
+
+ // Build an application against the non-finalized SDK and then load it into an AssetManager with
+ // the finalized SDK.
+ const std::string non_finalized_android_apk = GetTestPath("non-finalized-android.apk");
+ const std::string non_finalized_android_java = GetTestPath("non-finalized-android-java");
+ BuildNonFinalizedSDK(non_finalized_android_apk, non_finalized_android_java, this, &diag);
+
+ const std::string app_apk = GetTestPath("app.apk");
+ const std::string app_java = GetTestPath("app-java");
+ BuildAppAgainstSDK(app_apk, app_java, non_finalized_android_apk, this, &diag);
+
+ android::AssetManager2 am;
+ auto android_asset = android::ApkAssets::Load(android_apk);
+ auto app_against_non_final = android::ApkAssets::Load(app_apk);
+ ASSERT_THAT(android_asset, NotNull());
+ ASSERT_THAT(app_against_non_final, NotNull());
+ ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()}));
+
+ auto result = am.GetResourceId("android:attr/finalized_res");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01010001));
+
+ result = am.GetResourceId("android:attr/staged_s_res");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01010002));
+
+ result = am.GetResourceId("android:string/staged_s_string");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01020000));
+
+ result = am.GetResourceId("android:attr/staged_s2_res");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01010003));
+
+ {
+ auto style = am.GetBag(0x7f020000);
+ ASSERT_TRUE(style.has_value());
+
+ auto& entry = (*style)->entries[0];
+ EXPECT_THAT(entry.key, Eq(0x01010002));
+ EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
+ EXPECT_THAT(entry.value.data, Eq(0x01020000));
+ }
+
+ // Re-compile the application against the finalized SDK and then load it into an AssetManager with
+ // the finalized SDK.
+ const std::string app_apk_respin = GetTestPath("app-respin.apk");
+ const std::string app_java_respin = GetTestPath("app-respin-java");
+ BuildAppAgainstSDK(app_apk_respin, app_java_respin, android_apk, this, &diag);
+
+ auto app_against_final = android::ApkAssets::Load(app_apk_respin);
+ ASSERT_THAT(app_against_final, NotNull());
+ ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()}));
+
+ {
+ auto style = am.GetBag(0x7f020000);
+ ASSERT_TRUE(style.has_value());
+
+ auto& entry = (*style)->entries[0];
+ EXPECT_THAT(entry.key, Eq(0x01010002));
+ EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
+ EXPECT_THAT(entry.value.data, Eq(0x01020000));
+ }
+}
+
+TEST_F(LinkTest, MacroSubstitution) {
+ StdErrDiagnostics diag;
+ const std::string values =
+ R"(<resources xmlns:an="http://schemas.android.com/apk/res/android">
+ <macro name="is_enabled">true</macro>
+ <macro name="deep_is_enabled">@macro/is_enabled</macro>
+ <macro name="attr_ref">?is_enabled_attr</macro>
+ <macro name="raw_string">Hello World!</macro>
+ <macro name="android_ref">@an:color/primary_text_dark</macro>
+
+ <attr name="is_enabled_attr" />
+ <public type="attr" name="is_enabled_attr" id="0x7f010000"/>
+
+ <string name="is_enabled_str">@macro/is_enabled</string>
+ <bool name="is_enabled_bool">@macro/deep_is_enabled</bool>
+
+ <array name="my_array">
+ <item>@macro/is_enabled</item>
+ </array>
+
+ <style name="MyStyle">
+ <item name="android:background">@macro/attr_ref</item>
+ <item name="android:fontFamily">@macro/raw_string</item>
+ </style>
+ </resources>)";
+
+ const std::string xml_values =
+ R"(<SomeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="@macro/android_ref"
+ android:fontFamily="@macro/raw_string">
+ </SomeLayout>)";
+
+ // Build a library with a public attribute
+ const std::string lib_res = GetTestPath("test-res");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), values, lib_res, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/layout/layout.xml"), xml_values, lib_res, &diag));
+
+ const std::string lib_apk = GetTestPath("test.apk");
+ // clang-format off
+ auto lib_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(ManifestBuilder(this).SetPackageName("com.test").Build())
+ .AddCompiledResDir(lib_res, &diag)
+ .AddFlag("--no-auto-version")
+ .Build(lib_apk);
+ // clang-format on
+ ASSERT_TRUE(Link(lib_link_args, &diag));
+
+ auto apk = LoadedApk::LoadApkFromPath(lib_apk, &diag);
+ ASSERT_THAT(apk, NotNull());
+
+ // Test that the type flags determines the value type
+ auto actual_bool =
+ test::GetValue<BinaryPrimitive>(apk->GetResourceTable(), "com.test:bool/is_enabled_bool");
+ ASSERT_THAT(actual_bool, NotNull());
+ EXPECT_EQ(android::Res_value::TYPE_INT_BOOLEAN, actual_bool->value.dataType);
+ EXPECT_EQ(0xffffffffu, actual_bool->value.data);
+
+ auto actual_str =
+ test::GetValue<String>(apk->GetResourceTable(), "com.test:string/is_enabled_str");
+ ASSERT_THAT(actual_str, NotNull());
+ EXPECT_EQ(*actual_str->value, "true");
+
+ // Test nested data structures
+ auto actual_array = test::GetValue<Array>(apk->GetResourceTable(), "com.test:array/my_array");
+ ASSERT_THAT(actual_array, NotNull());
+ EXPECT_THAT(actual_array->elements.size(), Eq(1));
+
+ auto array_el_ref = ValueCast<BinaryPrimitive>(actual_array->elements[0].get());
+ ASSERT_THAT(array_el_ref, NotNull());
+ EXPECT_THAT(array_el_ref->value.dataType, Eq(android::Res_value::TYPE_INT_BOOLEAN));
+ EXPECT_THAT(array_el_ref->value.data, Eq(0xffffffffu));
+
+ auto actual_style = test::GetValue<Style>(apk->GetResourceTable(), "com.test:style/MyStyle");
+ ASSERT_THAT(actual_style, NotNull());
+ EXPECT_THAT(actual_style->entries.size(), Eq(2));
+
+ {
+ auto style_el = ValueCast<Reference>(actual_style->entries[0].value.get());
+ ASSERT_THAT(style_el, NotNull());
+ EXPECT_THAT(style_el->reference_type, Eq(Reference::Type::kAttribute));
+ EXPECT_THAT(style_el->id, Eq(0x7f010000));
+ }
+
+ {
+ auto style_el = ValueCast<String>(actual_style->entries[1].value.get());
+ ASSERT_THAT(style_el, NotNull());
+ EXPECT_THAT(*style_el->value, Eq("Hello World!"));
+ }
+
+ // Test substitution in compiled xml files
+ auto xml = apk->LoadXml("res/layout/layout.xml", &diag);
+ ASSERT_THAT(xml, NotNull());
+
+ auto& xml_attrs = xml->root->attributes;
+ ASSERT_THAT(xml_attrs.size(), Eq(2));
+
+ auto attr_value = ValueCast<Reference>(xml_attrs[0].compiled_value.get());
+ ASSERT_THAT(attr_value, NotNull());
+ EXPECT_THAT(attr_value->reference_type, Eq(Reference::Type::kResource));
+ EXPECT_THAT(attr_value->id, Eq(0x01060001));
+
+ EXPECT_THAT(xml_attrs[1].compiled_value.get(), IsNull());
+ EXPECT_THAT(xml_attrs[1].value, Eq("Hello World!"));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index e36668e5a043..5b18a3789d76 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -132,8 +132,8 @@ class Optimizer {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
}
- if (!options_.resources_blacklist.empty()) {
- ResourceFilter filter(options_.resources_blacklist);
+ if (!options_.resources_exclude_list.empty()) {
+ ResourceFilter filter(options_.resources_exclude_list);
if (!filter.Consume(context_, apk->GetResourceTable())) {
context_->GetDiagnostics()->Error(DiagMessage() << "failed filtering resources");
return 1;
@@ -328,7 +328,7 @@ bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOpti
}
for (StringPiece directive : util::Tokenize(directives, ',')) {
if (directive == "remove") {
- options->resources_blacklist.insert(resource_name.ToResourceName());
+ options->resources_exclude_list.insert(resource_name.ToResourceName());
} else if (directive == "no_collapse" || directive == "no_obfuscate") {
options->table_flattener_options.name_collapse_exemptions.insert(
resource_name.ToResourceName());
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 5070ccc8afbf..3afc46b04af6 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -36,8 +36,8 @@ struct OptimizeOptions {
// Details of the app extracted from the AndroidManifest.xml
AppInfo app_info;
- // Blacklist of unused resources that should be removed from the apk.
- std::unordered_set<ResourceName> resources_blacklist;
+ // Exclude list of unused resources that should be removed from the apk.
+ std::unordered_set<ResourceName> resources_exclude_list;
// Split APK options.
TableSplitterOptions table_splitter_options;
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index 17c22c5aac6b..339b8af5d536 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -17,76 +17,140 @@
#include "compile/IdAssigner.h"
#include <map>
+#include <unordered_map>
+#include "android-base/expected.h"
#include "android-base/logging.h"
#include "ResourceTable.h"
#include "process/IResourceTableConsumer.h"
#include "util/Util.h"
+using android::base::expected;
+using android::base::unexpected;
+
namespace aapt {
-/**
- * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and
- * ResourceEntry,
- * as long as there is no existing ID or the ID is the same.
- */
-static bool AssignId(IDiagnostics* diag, const ResourceId& id,
- const ResourceName& name, ResourceTablePackage* pkg,
- ResourceTableType* type, ResourceEntry* entry) {
- if (pkg->id.value() == id.package_id()) {
- if (!type->id || type->id.value() == id.type_id()) {
- type->id = id.type_id();
-
- if (!entry->id || entry->id.value() == id.entry_id()) {
- entry->id = id.entry_id();
- return true;
- }
- }
+namespace {
+template <typename T>
+using Result = expected<T, std::string>;
+
+template <typename Id, typename Key>
+struct NextIdFinder {
+ explicit NextIdFinder(Id start_id = 0u) : next_id_(start_id){};
+
+ // Attempts to reserve an identifier for the specified key.
+ // If the identifier is already reserved by a different key, an error message is returned.
+ // Reserving identifiers must be completed before `NextId` is called for the first time.
+ Result<Id> ReserveId(Key key, Id id);
+
+ // Retrieves the next available identifier that has not been reserved.
+ std::optional<Id> NextId();
+
+ private:
+ // Attempts to set `next_id_` to the next available identifier that has not been reserved.
+ // Returns whether there were any available identifiers.
+ std::optional<Id> SkipToNextAvailableId();
+
+ Id next_id_;
+ bool next_id_called_ = false;
+ bool exhausted_ = false;
+ std::map<Id, Key> pre_assigned_ids_;
+ typename std::map<Id, Key>::iterator next_preassigned_id_;
+};
+
+struct TypeGroup {
+ explicit TypeGroup(uint8_t package_id, uint8_t type_id)
+ : package_id_(package_id), type_id_(type_id){};
+
+ // Attempts to reserve the resource id for the specified resource name.
+ // If the id is already reserved by a different name, an error message is returned.
+ // Reserving identifiers must be completed before `NextId` is called for the first time.
+ Result<std::monostate> ReserveId(const ResourceName& name, ResourceId id);
+
+ // Retrieves the next available resource id that has not been reserved.
+ Result<ResourceId> NextId();
+
+ private:
+ uint8_t package_id_;
+ uint8_t type_id_;
+ NextIdFinder<uint16_t, ResourceName> next_entry_id_;
+};
+
+struct ResourceTypeKey {
+ ResourceType type;
+ uint8_t id;
+
+ bool operator<(const ResourceTypeKey& other) const {
+ return (type != other.type) ? type < other.type : id < other.id;
+ }
+
+ bool operator==(const ResourceTypeKey& other) const {
+ return type == other.type && id == other.id;
+ }
+
+ bool operator!=(const ResourceTypeKey& other) const {
+ return !(*this == other);
}
+};
- const ResourceId existing_id(pkg->id.value(), type->id ? type->id.value() : 0,
- entry->id ? entry->id.value() : 0);
- diag->Error(DiagMessage() << "can't assign ID " << id << " to resource "
- << name << " with conflicting ID " << existing_id);
- return false;
+::std::ostream& operator<<(::std::ostream& out, const ResourceTypeKey& type) {
+ return out << type.type;
}
-bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
- std::map<ResourceId, ResourceName> assigned_ids;
+struct IdAssignerContext {
+ IdAssignerContext(std::string package_name, uint8_t package_id)
+ : package_name_(std::move(package_name)), package_id_(package_id) {
+ }
- for (auto& package : table->packages) {
- CHECK(bool(package->id)) << "packages must have manually assigned IDs";
+ // Attempts to reserve the resource id for the specified resource name.
+ // Returns whether the id was reserved successfully.
+ // Reserving identifiers must be completed before `NextId` is called for the first time.
+ bool ReserveId(const ResourceName& name, ResourceId id, const Visibility& visibility,
+ IDiagnostics* diag);
+
+ // Retrieves the next available resource id that has not been reserved.
+ std::optional<ResourceId> NextId(const ResourceName& name, IDiagnostics* diag);
+
+ private:
+ std::string package_name_;
+ uint8_t package_id_;
+ std::map<ResourceTypeKey, TypeGroup> types_;
+ std::map<ResourceType, uint8_t> non_staged_type_ids_;
+ NextIdFinder<uint8_t, ResourceTypeKey> type_id_finder_ =
+ NextIdFinder<uint8_t, ResourceTypeKey>(1);
+};
+} // namespace
+
+bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
+ IdAssignerContext assigned_ids(context->GetCompilationPackage(), context->GetPackageId());
+ for (auto& package : table->packages) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
const ResourceName name(package->name, type->type, entry->name);
+ if (entry->id && !assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
+ context->GetDiagnostics())) {
+ return false;
+ }
+
+ auto v = entry->visibility;
+ v.staged_api = true;
+ if (entry->staged_id && !assigned_ids.ReserveId(name, entry->staged_id.value().id, v,
+ context->GetDiagnostics())) {
+ return false;
+ }
if (assigned_id_map_) {
// Assign the pre-assigned stable ID meant for this resource.
const auto iter = assigned_id_map_->find(name);
if (iter != assigned_id_map_->end()) {
const ResourceId assigned_id = iter->second;
- const bool result =
- AssignId(context->GetDiagnostics(), assigned_id, name,
- package.get(), type.get(), entry.get());
- if (!result) {
+ if (!assigned_ids.ReserveId(name, assigned_id, entry->visibility,
+ context->GetDiagnostics())) {
return false;
}
- }
- }
-
- if (package->id && type->id && entry->id) {
- // If the ID is set for this resource, then reserve it.
- ResourceId resource_id(package->id.value(), type->id.value(),
- entry->id.value());
- auto result = assigned_ids.insert({resource_id, name});
- const ResourceName& existing_name = result.first->second;
- if (!result.second) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "resource " << name << " has same ID "
- << resource_id << " as " << existing_name);
- return false;
+ entry->id = assigned_id;
}
}
}
@@ -94,125 +158,185 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
}
if (assigned_id_map_) {
- // Reserve all the IDs mentioned in the stable ID map. That way we won't
- // assign
- // IDs that were listed in the map if they don't exist in the table.
+ // Reserve all the IDs mentioned in the stable ID map. That way we won't assig IDs that were
+ // listed in the map if they don't exist in the table.
for (const auto& stable_id_entry : *assigned_id_map_) {
const ResourceName& pre_assigned_name = stable_id_entry.first;
const ResourceId& pre_assigned_id = stable_id_entry.second;
- auto result = assigned_ids.insert({pre_assigned_id, pre_assigned_name});
- const ResourceName& existing_name = result.first->second;
- if (!result.second && existing_name != pre_assigned_name) {
- context->GetDiagnostics()->Error(
- DiagMessage() << "stable ID " << pre_assigned_id << " for resource "
- << pre_assigned_name
- << " is already taken by resource " << existing_name);
+ if (!assigned_ids.ReserveId(pre_assigned_name, pre_assigned_id, {},
+ context->GetDiagnostics())) {
return false;
}
}
}
- // Assign any resources without IDs the next available ID. Gaps will be filled
- // if possible,
+ // Assign any resources without IDs the next available ID. Gaps will be filled if possible,
// unless those IDs have been reserved.
-
- const auto assigned_ids_iter_end = assigned_ids.end();
for (auto& package : table->packages) {
- CHECK(bool(package->id)) << "packages must have manually assigned IDs";
-
- // Build a half filled ResourceId object, which will be used to find the
- // closest matching
- // reserved ID in the assignedId map. From that point the next available
- // type ID can be
- // found.
- ResourceId resource_id(package->id.value(), 0, 0);
- uint8_t next_expected_type_id = 1;
-
- // Find the closest matching ResourceId that is <= the one with only the
- // package set.
- auto next_type_iter = assigned_ids.lower_bound(resource_id);
for (auto& type : package->types) {
- if (!type->id) {
- // We need to assign a type ID. Iterate over the reserved IDs until we
- // find
- // some type ID that is a distance of 2 greater than the last one we've
- // seen.
- // That means there is an available type ID between these reserved IDs.
- while (next_type_iter != assigned_ids_iter_end) {
- if (next_type_iter->first.package_id() != package->id.value()) {
- break;
- }
-
- const uint8_t type_id = next_type_iter->first.type_id();
- if (type_id > next_expected_type_id) {
- // There is a gap in the type IDs, so use the missing one.
- type->id = next_expected_type_id++;
- break;
- }
+ for (auto& entry : type->entries) {
+ const ResourceName name(package->name, type->type, entry->name);
+ if (entry->id) {
+ continue;
+ }
+ auto id = assigned_ids.NextId(name, context->GetDiagnostics());
+ if (!id.has_value()) {
+ return false;
+ }
+ entry->id = id.value();
+ }
+ }
+ }
+ return true;
+}
- // Set our expectation to be the next type ID after the reserved one
- // we
- // just saw.
- next_expected_type_id = type_id + 1;
+namespace {
+template <typename Id, typename Key>
+Result<Id> NextIdFinder<Id, Key>::ReserveId(Key key, Id id) {
+ CHECK(!next_id_called_) << "ReserveId cannot be called after NextId";
+ auto assign_result = pre_assigned_ids_.emplace(id, key);
+ if (!assign_result.second && assign_result.first->second != key) {
+ std::stringstream error;
+ error << "ID is already assigned to " << assign_result.first->second;
+ return unexpected(error.str());
+ }
+ return id;
+}
- // Move to the next reserved ID.
- ++next_type_iter;
- }
+template <typename Id, typename Key>
+std::optional<Id> NextIdFinder<Id, Key>::NextId() {
+ if (!next_id_called_) {
+ next_id_called_ = true;
+ next_preassigned_id_ = pre_assigned_ids_.begin();
+ }
+ return SkipToNextAvailableId();
+}
- if (!type->id) {
- // We must have hit the end of the reserved IDs and not found a gap.
- // That means the next ID is available.
- type->id = next_expected_type_id++;
- }
+template <typename Id, typename Key>
+std::optional<Id> NextIdFinder<Id, Key>::SkipToNextAvailableId() {
+ if (exhausted_) {
+ return {};
+ }
+ while (next_preassigned_id_ != pre_assigned_ids_.end()) {
+ if (next_preassigned_id_->first == next_id_) {
+ if (next_id_ == std::numeric_limits<Id>::max()) {
+ // The last identifier was reserved so there are no more available identifiers.
+ exhausted_ = true;
+ return {};
}
+ ++next_id_;
+ ++next_preassigned_id_;
+ continue;
+ }
+ CHECK(next_preassigned_id_->first > next_id_) << "Preassigned IDs are not in sorted order";
+ break;
+ }
+ if (next_id_ == std::numeric_limits<Id>::max()) {
+ // There are no more identifiers after this one, but this one is still available so return it.
+ exhausted_ = true;
+ }
+ return next_id_++;
+}
- resource_id = ResourceId(package->id.value(), type->id.value(), 0);
- uint16_t next_expected_entry_id = 0;
+Result<std::monostate> TypeGroup::ReserveId(const ResourceName& name, ResourceId id) {
+ if (type_id_ != id.type_id()) {
+ // Currently there cannot be multiple type ids for a single type.
+ std::stringstream error;
+ error << "type '" << name.type << "' already has ID " << std::hex << (int)type_id_;
+ return unexpected(error.str());
+ }
- // Find the closest matching ResourceId that is <= the one with only the
- // package
- // and type set.
- auto next_entry_iter = assigned_ids.lower_bound(resource_id);
- for (auto& entry : type->entries) {
- if (!entry->id) {
- // We need to assign an entry ID. Iterate over the reserved IDs until
- // we find
- // some entry ID that is a distance of 2 greater than the last one
- // we've seen.
- // That means there is an available entry ID between these reserved
- // IDs.
- while (next_entry_iter != assigned_ids_iter_end) {
- if (next_entry_iter->first.package_id() != package->id.value() ||
- next_entry_iter->first.type_id() != type->id.value()) {
- break;
- }
+ auto assign_result = next_entry_id_.ReserveId(name, id.entry_id());
+ if (!assign_result.has_value()) {
+ std::stringstream error;
+ error << "entry " << assign_result.error();
+ return unexpected(error.str());
+ }
+ return {};
+}
- const uint16_t entry_id = next_entry_iter->first.entry_id();
- if (entry_id > next_expected_entry_id) {
- // There is a gap in the entry IDs, so use the missing one.
- entry->id = next_expected_entry_id++;
- break;
- }
+Result<ResourceId> TypeGroup::NextId() {
+ auto entry_id = next_entry_id_.NextId();
+ if (!entry_id.has_value()) {
+ std::stringstream error;
+ error << "resource type ID has exceeded the maximum number of resource entries ("
+ << (std::numeric_limits<uint16_t>::max() + 1u) << ")";
+ return unexpected(error.str());
+ }
+ return ResourceId(package_id_, type_id_, entry_id.value());
+}
- // Set our expectation to be the next type ID after the reserved one
- // we
- // just saw.
- next_expected_entry_id = entry_id + 1;
+bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id,
+ const Visibility& visibility, IDiagnostics* diag) {
+ if (package_id_ != id.package_id()) {
+ diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name
+ << " because package already has ID " << std::hex
+ << (int)id.package_id());
+ return false;
+ }
- // Move to the next reserved entry ID.
- ++next_entry_iter;
- }
+ auto key = ResourceTypeKey{name.type, id.type_id()};
+ auto type = types_.find(key);
+ if (type == types_.end()) {
+ // The type has not been assigned an id yet. Ensure that the specified id is not being used by
+ // another type.
+ auto assign_result = type_id_finder_.ReserveId(key, id.type_id());
+ if (!assign_result.has_value()) {
+ diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name
+ << " because type " << assign_result.error());
+ return false;
+ }
+ type = types_.emplace(key, TypeGroup(package_id_, id.type_id())).first;
+ }
- if (!entry->id) {
- // We must have hit the end of the reserved IDs and not found a gap.
- // That means the next ID is available.
- entry->id = next_expected_entry_id++;
- }
- }
- }
+ if (!visibility.staged_api) {
+ // Ensure that non-staged resources can only exist in one type ID.
+ auto non_staged_type = non_staged_type_ids_.emplace(name.type, id.type_id());
+ if (!non_staged_type.second && non_staged_type.first->second != id.type_id()) {
+ diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name
+ << " because type already has ID " << std::hex
+ << (int)id.type_id());
+ return false;
}
}
+
+ auto assign_result = type->second.ReserveId(name, id);
+ if (!assign_result.has_value()) {
+ diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name << " because "
+ << assign_result.error());
+ return false;
+ }
+
return true;
}
+std::optional<ResourceId> IdAssignerContext::NextId(const ResourceName& name, IDiagnostics* diag) {
+ // The package name is not known during the compile stage.
+ // Resources without a package name are considered a part of the app being linked.
+ CHECK(name.package.empty() || name.package == package_name_);
+
+ // Find the type id for non-staged resources of this type.
+ auto non_staged_type = non_staged_type_ids_.find(name.type);
+ if (non_staged_type == non_staged_type_ids_.end()) {
+ auto next_type_id = type_id_finder_.NextId();
+ CHECK(next_type_id.has_value()) << "resource type IDs allocated have exceeded maximum (256)";
+ non_staged_type = non_staged_type_ids_.emplace(name.type, *next_type_id).first;
+ }
+
+ ResourceTypeKey key{name.type, non_staged_type->second};
+ auto type = types_.find(key);
+ if (type == types_.end()) {
+ type = types_.emplace(key, TypeGroup(package_id_, key.id)).first;
+ }
+
+ auto assign_result = type->second.NextId();
+ if (!assign_result.has_value()) {
+ diag->Error(DiagMessage() << "can't assign resource ID to resource " << name << " because "
+ << assign_result.error());
+ return {};
+ }
+ return assign_result.value();
+}
+} // namespace
+
} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
index 5cff0048c062..663776645990 100644
--- a/tools/aapt2/compile/IdAssigner_test.cpp
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -20,42 +20,40 @@
namespace aapt {
-::testing::AssertionResult VerifyIds(ResourceTable* table);
+struct IdAssignerTests : public ::testing::Test {
+ void SetUp() override {
+ context = test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build();
+ }
+ std::unique_ptr<IAaptContext> context;
+};
-TEST(IdAssignerTest, AssignIds) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .AddSimple("android:attr/foo")
- .AddSimple("android:attr/bar")
- .AddSimple("android:id/foo")
- .SetPackageId("android", 0x01)
- .Build();
+::testing::AssertionResult VerifyIds(ResourceTable* table);
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+TEST_F(IdAssignerTests, AssignIds) {
+ auto table = test::ResourceTableBuilder()
+ .AddSimple("android:attr/foo")
+ .AddSimple("android:attr/bar")
+ .AddSimple("android:id/foo")
+ .Build();
IdAssigner assigner;
ASSERT_TRUE(assigner.Consume(context.get(), table.get()));
ASSERT_TRUE(VerifyIds(table.get()));
}
-TEST(IdAssignerTest, AssignIdsWithReservedIds) {
- std::unique_ptr<ResourceTable> table =
- test::ResourceTableBuilder()
- .AddSimple("android:id/foo", ResourceId(0x01010000))
- .AddSimple("android:dimen/two")
- .AddSimple("android:integer/three")
- .AddSimple("android:string/five")
- .AddSimple("android:attr/fun", ResourceId(0x01040000))
- .AddSimple("android:attr/foo", ResourceId(0x01040006))
- .AddSimple("android:attr/bar")
- .AddSimple("android:attr/baz")
- .AddSimple("app:id/biz")
- .SetPackageId("android", 0x01)
- .SetPackageId("app", 0x7f)
- .Build();
-
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
- IdAssigner assigner;
+TEST_F(IdAssignerTests, AssignIdsWithReservedIds) {
+ auto table = test::ResourceTableBuilder()
+ .AddSimple("android:id/foo", ResourceId(0x01010000))
+ .AddSimple("android:dimen/two")
+ .AddSimple("android:integer/three")
+ .AddSimple("android:string/five")
+ .AddSimple("android:attr/fun", ResourceId(0x01040000))
+ .AddSimple("android:attr/foo", ResourceId(0x01040006))
+ .AddSimple("android:attr/bar")
+ .AddSimple("android:attr/baz")
+ .Build();
+ IdAssigner assigner;
ASSERT_TRUE(assigner.Consume(context.get(), table.get()));
ASSERT_TRUE(VerifyIds(table.get()));
@@ -65,12 +63,12 @@ TEST(IdAssignerTest, AssignIdsWithReservedIds) {
maybe_result = table->FindResource(test::ParseNameOrDie("android:dimen/two"));
ASSERT_TRUE(maybe_result);
- EXPECT_EQ(make_value<uint8_t>(2), maybe_result.value().type->id);
+ EXPECT_EQ(make_value<ResourceId>(0x01020000), maybe_result.value().entry->id);
maybe_result =
table->FindResource(test::ParseNameOrDie("android:integer/three"));
ASSERT_TRUE(maybe_result);
- EXPECT_EQ(make_value<uint8_t>(3), maybe_result.value().type->id);
+ EXPECT_EQ(make_value<ResourceId>(0x01030000), maybe_result.value().entry->id);
// Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX
// IDs.
@@ -78,107 +76,133 @@ TEST(IdAssignerTest, AssignIdsWithReservedIds) {
maybe_result =
table->FindResource(test::ParseNameOrDie("android:string/five"));
ASSERT_TRUE(maybe_result);
- EXPECT_EQ(make_value<uint8_t>(5), maybe_result.value().type->id);
+ EXPECT_EQ(make_value<ResourceId>(0x01050000), maybe_result.value().entry->id);
// Expect to fill in the gaps between 0x01040000 and 0x01040006.
maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/bar"));
ASSERT_TRUE(maybe_result);
- EXPECT_EQ(make_value<uint16_t>(1), maybe_result.value().entry->id);
+ EXPECT_EQ(make_value<ResourceId>(0x01040001), maybe_result.value().entry->id);
maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/baz"));
ASSERT_TRUE(maybe_result);
- EXPECT_EQ(make_value<uint16_t>(2), maybe_result.value().entry->id);
+ EXPECT_EQ(make_value<ResourceId>(0x01040002), maybe_result.value().entry->id);
}
-TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) {
- std::unique_ptr<ResourceTable> table =
- test::ResourceTableBuilder()
- .AddSimple("android:attr/foo", ResourceId(0x01040006))
- .AddSimple("android:attr/bar", ResourceId(0x01040006))
- .SetPackageId("android", 0x01)
- .SetPackageId("app", 0x7f)
- .Build();
+TEST_F(IdAssignerTests, FailWhenNonUniqueIdsAssigned) {
+ auto table = test::ResourceTableBuilder()
+ .AddSimple("android:attr/foo", ResourceId(0x01040006))
+ .AddSimple("android:attr/bar", ResourceId(0x01040006))
+ .Build();
+ IdAssigner assigner;
+ ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
+}
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+TEST_F(IdAssignerTests, FailWhenNonUniqueTypeIdsAssigned) {
+ auto table = test::ResourceTableBuilder()
+ .AddSimple("android:string/foo", ResourceId(0x01040000))
+ .AddSimple("android:attr/bar", ResourceId(0x01040006))
+ .Build();
IdAssigner assigner;
+ ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
+}
+TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIds) {
+ auto table = test::ResourceTableBuilder()
+ .AddSimple("android:attr/foo", ResourceId(0x01050000))
+ .AddSimple("android:attr/bar", ResourceId(0x01040006))
+ .Build();
+ IdAssigner assigner;
ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
}
-TEST(IdAssignerTest, AssignIdsWithIdMap) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .AddSimple("android:attr/foo")
- .AddSimple("android:attr/bar")
- .SetPackageId("android", 0x01)
- .Build();
+TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIdsRegardlessOfStagedId) {
+ auto table = test::ResourceTableBuilder()
+ .AddSimple("android:attr/foo", ResourceId(0x01050000))
+ .AddSimple("android:attr/bar", ResourceId(0x01ff0006))
+ .Add(NewResourceBuilder("android:attr/staged_baz")
+ .SetId(0x01ff0000)
+ .SetVisibility({.staged_api = true})
+ .Build())
+ .Build();
+ IdAssigner assigner;
+ ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
+}
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+TEST_F(IdAssignerTests, AssignIdsWithIdMap) {
+ auto table = test::ResourceTableBuilder()
+ .AddSimple("android:attr/foo")
+ .AddSimple("android:attr/bar")
+ .Build();
std::unordered_map<ResourceName, ResourceId> id_map = {
{test::ParseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}};
IdAssigner assigner(&id_map);
ASSERT_TRUE(assigner.Consume(context.get(), table.get()));
ASSERT_TRUE(VerifyIds(table.get()));
- Maybe<ResourceTable::SearchResult> result =
- table->FindResource(test::ParseNameOrDie("android:attr/foo"));
+ auto result = table->FindResource(test::ParseNameOrDie("android:attr/foo"));
ASSERT_TRUE(result);
const ResourceTable::SearchResult& search_result = result.value();
- EXPECT_EQ(make_value<uint8_t>(0x01), search_result.package->id);
- EXPECT_EQ(make_value<uint8_t>(0x01), search_result.type->id);
- EXPECT_EQ(make_value<uint16_t>(0x0002), search_result.entry->id);
+ EXPECT_EQ(make_value<ResourceId>(0x01010002), search_result.entry->id);
}
-::testing::AssertionResult VerifyIds(ResourceTable* table) {
- std::set<uint8_t> package_ids;
- for (auto& package : table->packages) {
- if (!package->id) {
- return ::testing::AssertionFailure() << "package " << package->name
- << " has no ID";
- }
-
- if (!package_ids.insert(package->id.value()).second) {
- return ::testing::AssertionFailure()
- << "package " << package->name << " has non-unique ID " << std::hex
- << (int)package->id.value() << std::dec;
- }
+TEST_F(IdAssignerTests, UseAllEntryIds) {
+ ResourceTable table;
+ const size_t max_entry_id = std::numeric_limits<uint16_t>::max();
+ for (size_t i = 0; i <= max_entry_id; i++) {
+ ASSERT_TRUE(
+ table.AddResource(NewResourceBuilder("android:attr/res" + std::to_string(i)).Build(),
+ context->GetDiagnostics()));
}
+ IdAssigner assigner;
+ ASSERT_TRUE(assigner.Consume(context.get(), &table));
+}
- for (auto& package : table->packages) {
- std::set<uint8_t> type_ids;
- for (auto& type : package->types) {
- if (!type->id) {
- return ::testing::AssertionFailure() << "type " << type->type
- << " of package " << package->name
- << " has no ID";
- }
+TEST_F(IdAssignerTests, ExaustEntryIds) {
+ ResourceTable table;
+ const size_t max_entry_id = std::numeric_limits<uint16_t>::max() + 1u;
+ for (size_t i = 0; i <= max_entry_id; i++) {
+ ASSERT_TRUE(
+ table.AddResource(NewResourceBuilder("android:attr/res" + std::to_string(i)).Build(),
+ context->GetDiagnostics()));
+ }
+ IdAssigner assigner;
+ ASSERT_FALSE(assigner.Consume(context.get(), &table));
+}
- if (!type_ids.insert(type->id.value()).second) {
- return ::testing::AssertionFailure()
- << "type " << type->type << " of package " << package->name
- << " has non-unique ID " << std::hex << (int)type->id.value()
- << std::dec;
- }
- }
+TEST_F(IdAssignerTests, ExaustEntryIdsLastIdIsPublic) {
+ ResourceTable table;
+ ASSERT_TRUE(table.AddResource(NewResourceBuilder("android:attr/res").SetId(0x0101ffff).Build(),
+ context->GetDiagnostics()));
+ const size_t max_entry_id = std::numeric_limits<uint16_t>::max();
+ for (size_t i = 0; i <= max_entry_id; i++) {
+ ASSERT_TRUE(
+ table.AddResource(NewResourceBuilder("android:attr/res" + std::to_string(i)).Build(),
+ context->GetDiagnostics()));
+ }
+ IdAssigner assigner;
+ ASSERT_FALSE(assigner.Consume(context.get(), &table));
+}
+::testing::AssertionResult VerifyIds(ResourceTable* table) {
+ std::set<ResourceId> seen_ids;
+ for (auto& package : table->packages) {
for (auto& type : package->types) {
- std::set<uint16_t> entry_ids;
for (auto& entry : type->entries) {
if (!entry->id) {
return ::testing::AssertionFailure()
- << "entry " << entry->name << " of type " << type->type
- << " of package " << package->name << " has no ID";
+ << "resource " << ResourceNameRef(package->name, type->type, entry->name)
+ << " has no ID";
}
-
- if (!entry_ids.insert(entry->id.value()).second) {
+ if (!seen_ids.insert(entry->id.value()).second) {
return ::testing::AssertionFailure()
- << "entry " << entry->name << " of type " << type->type
- << " of package " << package->name << " has non-unique ID "
- << std::hex << (int)entry->id.value() << std::dec;
+ << "resource " << ResourceNameRef(package->name, type->type, entry->name)
+ << " has a non-unique ID" << std::hex << entry->id.value() << std::dec;
}
}
}
}
+
return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
}
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index bc2e6990433c..4db2392b4eab 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -35,7 +35,7 @@ constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
((uint32_t)d);
}
-// Whitelist of PNG chunk types that we want to keep in the resulting PNG.
+// Allow list of PNG chunk types that we want to keep in the resulting PNG.
enum PngChunkTypes {
kPngChunkIHDR = u32(73, 72, 68, 82),
kPngChunkIDAT = u32(73, 68, 65, 84),
@@ -56,7 +56,7 @@ static uint32_t Peek32LE(const char* data) {
return word;
}
-static bool IsPngChunkWhitelisted(uint32_t type) {
+static bool IsPngChunkAllowed(uint32_t type) {
switch (type) {
case kPngChunkIHDR:
case kPngChunkIDAT:
@@ -128,7 +128,7 @@ bool PngChunkFilter::Next(const void** buffer, size_t* len) {
// Do we strip this chunk?
const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
- if (IsPngChunkWhitelisted(chunk_type)) {
+ if (IsPngChunkAllowed(chunk_type)) {
// Advance the window to include this chunk.
window_end_ += kMinChunkHeaderSize + chunk_len;
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 5e0300b3071b..3f574ee8e897 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -226,6 +226,7 @@ class Visitor : public ValueVisitor {
: pool_(pool), method_(method), localizer_(method) {}
void Visit(Plural* plural) override {
+ CloningValueTransformer cloner(pool_);
std::unique_ptr<Plural> localized = util::make_unique<Plural>();
for (size_t i = 0; i < plural->values.size(); i++) {
Visitor sub_visitor(pool_, method_);
@@ -234,7 +235,7 @@ class Visitor : public ValueVisitor {
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_));
+ localized->values[i] = plural->values[i]->Transform(cloner);
}
}
}
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
index e816c86e20a8..432d7bfdad49 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -236,13 +236,14 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) {
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<ResourceTable> table = test::ResourceTableBuilder().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()));
+ ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.pkg:plurals/foo"))
+ .SetValue(std::move(plural))
+ .Build(),
+ 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]"))};
@@ -252,6 +253,7 @@ TEST(PseudolocaleGeneratorTest, PluralsArePseudolocalized) {
const auto* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo",
test::ParseConfigOrDie("en-rXA"));
+ ASSERT_NE(nullptr, actual);
EXPECT_TRUE(actual->Equals(expected.get()));
}
@@ -273,11 +275,14 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) {
auto string = util::make_unique<String>(table->string_pool.MakeRef(original_style.str));
string->untranslatable_sections.push_back(UntranslatableSection{6u, 11u});
- ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("android:string/foo"), ConfigDescription{},
- {} /* product */, std::move(styled_string),
+ ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/foo"))
+ .SetValue(std::move(styled_string))
+ .Build(),
+ context->GetDiagnostics()));
+ ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/bar"))
+ .SetValue(std::move(string))
+ .Build(),
context->GetDiagnostics()));
- ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("android:string/bar"), ConfigDescription{},
- {} /* product */, std::move(string), context->GetDiagnostics()));
}
PseudolocaleGenerator generator;
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 2ef8b999a192..e5b3107877cb 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -187,7 +187,7 @@ TEST_F(ConfigurationParserTest, ForPath_NoFile) {
TEST_F(ConfigurationParserTest, ExtractConfiguration) {
Maybe<PostProcessingConfiguration> maybe_config =
- ExtractConfiguration(kValidConfig, "dummy.xml", &diag_);
+ ExtractConfiguration(kValidConfig, "fake.xml", &diag_);
PostProcessingConfiguration config = maybe_config.value();
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index 4a6bfd031284..f2c6b15bc0cd 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -79,8 +79,8 @@ enum {
ISGAME_ATTR = 0x10103f4,
VERSION_ATTR = 0x01010519,
CERT_DIGEST_ATTR = 0x01010548,
- REQUIRED_FEATURE_ATTR = 0x01010557,
- REQUIRED_NOT_FEATURE_ATTR = 0x01010558,
+ REQUIRED_FEATURE_ATTR = 0x01010554,
+ REQUIRED_NOT_FEATURE_ATTR = 0x01010555,
IS_STATIC_ATTR = 0x0101055a,
REQUIRED_SYSTEM_PROPERTY_NAME_ATTR = 0x01010565,
REQUIRED_SYSTEM_PROPERTY_VALUE_ATTR = 0x01010566,
@@ -132,6 +132,13 @@ class ManifestExtractor {
/** Adds an element to the list of children of the element. */
void AddChild(std::unique_ptr<Element>& child) { children_.push_back(std::move(child)); }
+ template <typename Predicate>
+ void Filter(Predicate&& func) {
+ children_.erase(std::remove_if(children_.begin(), children_.end(),
+ [&](const auto& e) { return func(e.get()); }),
+ children_.end());
+ }
+
/** Retrieves the list of children of the element. */
const std::vector<std::unique_ptr<Element>>& children() const {
return children_;
@@ -188,21 +195,17 @@ class ManifestExtractor {
/** Retrieves the resource assigned to the specified resource id if one exists. */
Value* FindValueById(const ResourceTable* table, const ResourceId& res_id,
- const ConfigDescription& config = DummyConfig()) {
+ const ConfigDescription& config = DefaultConfig()) {
if (table) {
for (auto& package : table->packages) {
- if (package->id && package->id.value() == res_id.package_id()) {
for (auto& type : package->types) {
- if (type->id && type->id.value() == res_id.type_id()) {
- for (auto& entry : type->entries) {
- if (entry->id && entry->id.value() == res_id.entry_id()) {
- if (auto value = BestConfigValue(entry.get(), config)) {
- return value;
- }
+ for (auto& entry : type->entries) {
+ if (entry->id && entry->id.value() == res_id.id) {
+ if (auto value = BestConfigValue(entry.get(), config)) {
+ return value;
}
}
}
- }
}
}
}
@@ -210,7 +213,7 @@ class ManifestExtractor {
}
/** Attempts to resolve the reference to a non-reference value. */
- Value* ResolveReference(Reference* ref, const ConfigDescription& config = DummyConfig()) {
+ Value* ResolveReference(Reference* ref, const ConfigDescription& config = DefaultConfig()) {
const int kMaxIterations = 40;
int i = 0;
while (ref && ref->id && i++ < kMaxIterations) {
@@ -231,10 +234,10 @@ class ManifestExtractor {
* this will attempt to resolve the reference to an integer value.
**/
int32_t* GetAttributeInteger(xml::Attribute* attr,
- const ConfigDescription& config = DummyConfig()) {
+ const ConfigDescription& config = DefaultConfig()) {
if (attr != nullptr) {
if (attr->compiled_value) {
- // Resolve references using the dummy configuration
+ // Resolve references using the configuration
Value* value = attr->compiled_value.get();
if (ValueCast<Reference>(value)) {
value = ResolveReference(ValueCast<Reference>(value), config);
@@ -257,7 +260,7 @@ class ManifestExtractor {
* exist or cannot be resolved to an integer value.
**/
int32_t GetAttributeIntegerDefault(xml::Attribute* attr, int32_t def,
- const ConfigDescription& config = DummyConfig()) {
+ const ConfigDescription& config = DefaultConfig()) {
auto value = GetAttributeInteger(attr, config);
if (value) {
return *value;
@@ -270,10 +273,10 @@ class ManifestExtractor {
* this will attempt to resolve the reference to a string value.
**/
const std::string* GetAttributeString(xml::Attribute* attr,
- const ConfigDescription& config = DummyConfig()) {
+ const ConfigDescription& config = DefaultConfig()) {
if (attr != nullptr) {
if (attr->compiled_value) {
- // Resolve references using the dummy configuration
+ // Resolve references using the configuration
Value* value = attr->compiled_value.get();
if (ValueCast<Reference>(value)) {
value = ResolveReference(ValueCast<Reference>(value), config);
@@ -305,7 +308,7 @@ class ManifestExtractor {
* exist or cannot be resolved to an string value.
**/
std::string GetAttributeStringDefault(xml::Attribute* attr, std::string def,
- const ConfigDescription& config = DummyConfig()) {
+ const ConfigDescription& config = DefaultConfig()) {
auto value = GetAttributeString(attr, config);
if (value) {
return *value;
@@ -322,7 +325,7 @@ class ManifestExtractor {
friend Element;
/** Creates a default configuration used to retrieve resources. */
- static ConfigDescription DummyConfig() {
+ static ConfigDescription DefaultConfig() {
ConfigDescription config;
config.orientation = android::ResTable_config::ORIENTATION_PORT;
config.density = android::ResTable_config::DENSITY_MEDIUM;
@@ -1063,17 +1066,23 @@ class UsesPermission : public ManifestExtractor::Element {
public:
UsesPermission() = default;
std::string name;
- std::string requiredFeature;
- std::string requiredNotFeature;
+ std::vector<std::string> requiredFeatures;
+ std::vector<std::string> requiredNotFeatures;
int32_t required = true;
int32_t maxSdkVersion = -1;
void Extract(xml::Element* element) override {
name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
- requiredFeature = GetAttributeStringDefault(
- FindAttribute(element, REQUIRED_FEATURE_ATTR), "");
- requiredNotFeature = GetAttributeStringDefault(
- FindAttribute(element, REQUIRED_NOT_FEATURE_ATTR), "");
+ std::string feature =
+ GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), "");
+ if (!feature.empty()) {
+ requiredFeatures.push_back(feature);
+ }
+ feature = GetAttributeStringDefault(FindAttribute(element, REQUIRED_NOT_FEATURE_ATTR), "");
+ if (!feature.empty()) {
+ requiredNotFeatures.push_back(feature);
+ }
+
required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1);
maxSdkVersion = GetAttributeIntegerDefault(
FindAttribute(element, MAX_SDK_VERSION_ATTR), -1);
@@ -1090,13 +1099,13 @@ class UsesPermission : public ManifestExtractor::Element {
if (maxSdkVersion >= 0) {
printer->Print(StringPrintf(" maxSdkVersion='%d'", maxSdkVersion));
}
- if (!requiredFeature.empty()) {
- printer->Print(StringPrintf(" requiredFeature='%s'", requiredFeature.data()));
+ printer->Print("\n");
+ for (const std::string& requiredFeature : requiredFeatures) {
+ printer->Print(StringPrintf(" required-feature='%s'\n", requiredFeature.data()));
}
- if (!requiredNotFeature.empty()) {
- printer->Print(StringPrintf(" requiredNotFeature='%s'", requiredNotFeature.data()));
+ for (const std::string& requiredNotFeature : requiredNotFeatures) {
+ printer->Print(StringPrintf(" required-not-feature='%s'\n", requiredNotFeature.data()));
}
- printer->Print("\n");
if (required == 0) {
printer->Print(StringPrintf("optional-permission: name='%s'", name.data()));
if (maxSdkVersion >= 0) {
@@ -1116,6 +1125,38 @@ class UsesPermission : public ManifestExtractor::Element {
}
};
+/** Represents <required-feature> elements. **/
+class RequiredFeature : public ManifestExtractor::Element {
+ public:
+ RequiredFeature() = default;
+ std::string name;
+
+ void Extract(xml::Element* element) override {
+ name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+ auto parent_stack = extractor()->parent_stack();
+ if (!name.empty() && ElementCast<UsesPermission>(parent_stack[0])) {
+ UsesPermission* uses_permission = ElementCast<UsesPermission>(parent_stack[0]);
+ uses_permission->requiredFeatures.push_back(name);
+ }
+ }
+};
+
+/** Represents <required-not-feature> elements. **/
+class RequiredNotFeature : public ManifestExtractor::Element {
+ public:
+ RequiredNotFeature() = default;
+ std::string name;
+
+ void Extract(xml::Element* element) override {
+ name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+ auto parent_stack = extractor()->parent_stack();
+ if (!name.empty() && ElementCast<UsesPermission>(parent_stack[0])) {
+ UsesPermission* uses_permission = ElementCast<UsesPermission>(parent_stack[0]);
+ uses_permission->requiredNotFeatures.push_back(name);
+ }
+ }
+};
+
/** Represents <uses-permission-sdk-23> elements. **/
class UsesPermissionSdk23 : public ManifestExtractor::Element {
public:
@@ -1405,6 +1446,29 @@ class UsesStaticLibrary : public ManifestExtractor::Element {
}
};
+/** Represents <uses-native-library> elements. **/
+class UsesNativeLibrary : public ManifestExtractor::Element {
+ public:
+ UsesNativeLibrary() = default;
+ std::string name;
+ int required;
+
+ void Extract(xml::Element* element) override {
+ auto parent_stack = extractor()->parent_stack();
+ if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) {
+ name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+ required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1);
+ }
+ }
+
+ void Print(text::Printer* printer) override {
+ if (!name.empty()) {
+ printer->Print(StringPrintf("uses-native-library%s:'%s'\n",
+ (required == 0) ? "-not-required" : "", name.data()));
+ }
+ }
+};
+
/**
* Represents <meta-data> elements. These tags are only printed when a flag is passed in to
* explicitly enable meta data printing.
@@ -1790,6 +1854,41 @@ class SupportsGlTexture : public ManifestExtractor::Element {
}
};
+/** Represents <property> elements. **/
+class Property : public ManifestExtractor::Element {
+ public:
+ Property() = default;
+ std::string name;
+ std::string value;
+ const int* value_int;
+ std::string resource;
+ const int* resource_int;
+
+ void Extract(xml::Element* element) override {
+ name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+ value = GetAttributeStringDefault(FindAttribute(element, VALUE_ATTR), "");
+ value_int = GetAttributeInteger(FindAttribute(element, VALUE_ATTR));
+ resource = GetAttributeStringDefault(FindAttribute(element, RESOURCE_ATTR), "");
+ resource_int = GetAttributeInteger(FindAttribute(element, RESOURCE_ATTR));
+ }
+
+ void Print(text::Printer* printer) override {
+ printer->Print(StringPrintf("property: name='%s' ", name.data()));
+ if (!value.empty()) {
+ printer->Print(StringPrintf("value='%s' ", value.data()));
+ } else if (value_int) {
+ printer->Print(StringPrintf("value='%d' ", *value_int));
+ } else {
+ if (!resource.empty()) {
+ printer->Print(StringPrintf("resource='%s' ", resource.data()));
+ } else if (resource_int) {
+ printer->Print(StringPrintf("resource='%d' ", *resource_int));
+ }
+ }
+ printer->Print("\n");
+ }
+};
+
/** Recursively prints the extracted badging element. */
static void Print(ManifestExtractor::Element* el, text::Printer* printer) {
el->Print(printer);
@@ -1822,7 +1921,8 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) {
for (xml::Element* child : element->GetChildElements()) {
if (child->name == "uses-permission" || child->name == "uses-permission-sdk-23"
|| child->name == "permission") {
- auto permission_element = ManifestExtractor::Element::Inflate(this, child);
+ // Inflate the element and its descendants
+ auto permission_element = Visit(child);
manifest->AddChild(permission_element);
}
}
@@ -1848,7 +1948,7 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) {
// Collect all the unique locales of the apk
if (locales_.find(locale_str) == locales_.end()) {
- ConfigDescription config = ManifestExtractor::DummyConfig();
+ ConfigDescription config = ManifestExtractor::DefaultConfig();
config.setBcp47Locale(locale_str.data());
locales_.insert(std::make_pair(locale_str, config));
}
@@ -1857,7 +1957,7 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) {
uint16_t density = (value->config.density == 0) ? (uint16_t) 160
: value->config.density;
if (densities_.find(density) == densities_.end()) {
- ConfigDescription config = ManifestExtractor::DummyConfig();
+ ConfigDescription config = ManifestExtractor::DefaultConfig();
config.density = density;
densities_.insert(std::make_pair(density, config));
}
@@ -1870,6 +1970,21 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) {
// Extract badging information
auto root = Visit(element);
+ // Filter out all "uses-sdk" tags besides the very last tag. The android runtime only uses the
+ // attribute values from the last defined tag.
+ std::vector<UsesSdkBadging*> filtered_uses_sdk_tags;
+ for (const auto& child : root->children()) {
+ if (auto uses_sdk = ElementCast<UsesSdkBadging>(child.get())) {
+ filtered_uses_sdk_tags.emplace_back(uses_sdk);
+ }
+ }
+ filtered_uses_sdk_tags.pop_back();
+
+ root->Filter([&](const ManifestExtractor::Element* e) {
+ return std::find(filtered_uses_sdk_tags.begin(), filtered_uses_sdk_tags.end(), e) !=
+ filtered_uses_sdk_tags.end();
+ });
+
// Print the elements in order seen
Print(root.get(), printer);
@@ -2214,37 +2329,41 @@ T* ElementCast(ManifestExtractor::Element* element) {
}
const std::unordered_map<std::string, bool> kTagCheck = {
- {"action", std::is_base_of<Action, T>::value},
- {"activity", std::is_base_of<Activity, T>::value},
- {"application", std::is_base_of<Application, T>::value},
- {"category", std::is_base_of<Category, T>::value},
- {"compatible-screens", std::is_base_of<CompatibleScreens, T>::value},
- {"feature-group", std::is_base_of<FeatureGroup, T>::value},
- {"input-type", std::is_base_of<InputType, T>::value},
- {"intent-filter", std::is_base_of<IntentFilter, T>::value},
- {"meta-data", std::is_base_of<MetaData, T>::value},
- {"manifest", std::is_base_of<Manifest, T>::value},
- {"original-package", std::is_base_of<OriginalPackage, T>::value},
- {"overlay", std::is_base_of<Overlay, T>::value},
- {"package-verifier", std::is_base_of<PackageVerifier, T>::value},
- {"permission", std::is_base_of<Permission, T>::value},
- {"provider", std::is_base_of<Provider, T>::value},
- {"receiver", std::is_base_of<Receiver, T>::value},
- {"screen", std::is_base_of<Screen, T>::value},
- {"service", std::is_base_of<Service, T>::value},
- {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value},
- {"supports-input", std::is_base_of<SupportsInput, T>::value},
- {"supports-screens", std::is_base_of<SupportsScreen, T>::value},
- {"uses-configuration", std::is_base_of<UsesConfiguarion, T>::value},
- {"uses-feature", std::is_base_of<UsesFeature, T>::value},
- {"uses-permission", std::is_base_of<UsesPermission, T>::value},
- {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value},
- {"uses-library", std::is_base_of<UsesLibrary, T>::value},
- {"uses-package", std::is_base_of<UsesPackage, T>::value},
- {"static-library", std::is_base_of<StaticLibrary, T>::value},
- {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value},
- {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value},
- {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value},
+ {"action", std::is_base_of<Action, T>::value},
+ {"activity", std::is_base_of<Activity, T>::value},
+ {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value},
+ {"application", std::is_base_of<Application, T>::value},
+ {"category", std::is_base_of<Category, T>::value},
+ {"compatible-screens", std::is_base_of<CompatibleScreens, T>::value},
+ {"feature-group", std::is_base_of<FeatureGroup, T>::value},
+ {"input-type", std::is_base_of<InputType, T>::value},
+ {"intent-filter", std::is_base_of<IntentFilter, T>::value},
+ {"meta-data", std::is_base_of<MetaData, T>::value},
+ {"manifest", std::is_base_of<Manifest, T>::value},
+ {"original-package", std::is_base_of<OriginalPackage, T>::value},
+ {"overlay", std::is_base_of<Overlay, T>::value},
+ {"package-verifier", std::is_base_of<PackageVerifier, T>::value},
+ {"permission", std::is_base_of<Permission, T>::value},
+ {"property", std::is_base_of<Property, T>::value},
+ {"provider", std::is_base_of<Provider, T>::value},
+ {"receiver", std::is_base_of<Receiver, T>::value},
+ {"required-feature", std::is_base_of<RequiredFeature, T>::value},
+ {"required-not-feature", std::is_base_of<RequiredNotFeature, T>::value},
+ {"screen", std::is_base_of<Screen, T>::value},
+ {"service", std::is_base_of<Service, T>::value},
+ {"static-library", std::is_base_of<StaticLibrary, T>::value},
+ {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value},
+ {"supports-input", std::is_base_of<SupportsInput, T>::value},
+ {"supports-screens", std::is_base_of<SupportsScreen, T>::value},
+ {"uses-configuration", std::is_base_of<UsesConfiguarion, T>::value},
+ {"uses-feature", std::is_base_of<UsesFeature, T>::value},
+ {"uses-library", std::is_base_of<UsesLibrary, T>::value},
+ {"uses-native-library", std::is_base_of<UsesNativeLibrary, T>::value},
+ {"uses-package", std::is_base_of<UsesPackage, T>::value},
+ {"uses-permission", std::is_base_of<UsesPermission, T>::value},
+ {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value},
+ {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value},
+ {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value},
};
auto check = kTagCheck.find(element->tag());
@@ -2264,38 +2383,42 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate(
const std::unordered_map<std::string,
std::function<std::unique_ptr<ManifestExtractor::Element>()>>
kTagCheck = {
- {"action", &CreateType<Action>},
- {"activity", &CreateType<Activity>},
- {"application", &CreateType<Application>},
- {"category", &CreateType<Category>},
- {"compatible-screens", &CreateType<CompatibleScreens>},
- {"feature-group", &CreateType<FeatureGroup>},
- {"input-type", &CreateType<InputType>},
- {"intent-filter",&CreateType<IntentFilter>},
- {"manifest", &CreateType<Manifest>},
- {"meta-data", &CreateType<MetaData>},
- {"original-package", &CreateType<OriginalPackage>},
- {"overlay", &CreateType<Overlay>},
- {"package-verifier", &CreateType<PackageVerifier>},
- {"permission", &CreateType<Permission>},
- {"provider", &CreateType<Provider>},
- {"receiver", &CreateType<Receiver>},
- {"screen", &CreateType<Screen>},
- {"service", &CreateType<Service>},
- {"supports-gl-texture", &CreateType<SupportsGlTexture>},
- {"supports-input", &CreateType<SupportsInput>},
- {"supports-screens", &CreateType<SupportsScreen>},
- {"uses-configuration", &CreateType<UsesConfiguarion>},
- {"uses-feature", &CreateType<UsesFeature>},
- {"uses-permission", &CreateType<UsesPermission>},
- {"uses-permission-sdk-23", &CreateType<UsesPermissionSdk23>},
- {"uses-library", &CreateType<UsesLibrary>},
- {"static-library", &CreateType<StaticLibrary>},
- {"uses-static-library", &CreateType<UsesStaticLibrary>},
- {"uses-package", &CreateType<UsesPackage>},
- {"additional-certificate", &CreateType<AdditionalCertificate>},
- {"uses-sdk", &CreateType<UsesSdkBadging>},
- };
+ {"action", &CreateType<Action>},
+ {"activity", &CreateType<Activity>},
+ {"additional-certificate", &CreateType<AdditionalCertificate>},
+ {"application", &CreateType<Application>},
+ {"category", &CreateType<Category>},
+ {"compatible-screens", &CreateType<CompatibleScreens>},
+ {"feature-group", &CreateType<FeatureGroup>},
+ {"input-type", &CreateType<InputType>},
+ {"intent-filter", &CreateType<IntentFilter>},
+ {"manifest", &CreateType<Manifest>},
+ {"meta-data", &CreateType<MetaData>},
+ {"original-package", &CreateType<OriginalPackage>},
+ {"overlay", &CreateType<Overlay>},
+ {"package-verifier", &CreateType<PackageVerifier>},
+ {"permission", &CreateType<Permission>},
+ {"property", &CreateType<Property>},
+ {"provider", &CreateType<Provider>},
+ {"receiver", &CreateType<Receiver>},
+ {"required-feature", &CreateType<RequiredFeature>},
+ {"required-not-feature", &CreateType<RequiredNotFeature>},
+ {"screen", &CreateType<Screen>},
+ {"service", &CreateType<Service>},
+ {"static-library", &CreateType<StaticLibrary>},
+ {"supports-gl-texture", &CreateType<SupportsGlTexture>},
+ {"supports-input", &CreateType<SupportsInput>},
+ {"supports-screens", &CreateType<SupportsScreen>},
+ {"uses-configuration", &CreateType<UsesConfiguarion>},
+ {"uses-feature", &CreateType<UsesFeature>},
+ {"uses-library", &CreateType<UsesLibrary>},
+ {"uses-native-library", &CreateType<UsesNativeLibrary>},
+ {"uses-package", &CreateType<UsesPackage>},
+ {"uses-permission", &CreateType<UsesPermission>},
+ {"uses-permission-sdk-23", &CreateType<UsesPermissionSdk23>},
+ {"uses-sdk", &CreateType<UsesSdkBadging>},
+ {"uses-static-library", &CreateType<UsesStaticLibrary>},
+ };
// Attempt to map the xml tag to a element inflater
std::unique_ptr<ManifestExtractor::Element> element;
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index cccd9faa9b39..72eaa3561a02 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -192,8 +192,7 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
std::u16string package_name = strcpy16_dtoh((const char16_t*)package_header->name,
arraysize(package_header->name));
- ResourceTablePackage* package =
- table_->CreatePackage(util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id));
+ ResourceTablePackage* package = table_->FindOrCreatePackage(util::Utf16ToUtf8(package_name));
if (!package) {
diag_->Error(DiagMessage(source_)
<< "incompatible package '" << package_name << "' with ID " << package_id);
@@ -232,13 +231,13 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
break;
case android::RES_TABLE_TYPE_SPEC_TYPE:
- if (!ParseTypeSpec(package, parser.chunk())) {
+ if (!ParseTypeSpec(package, parser.chunk(), package_id)) {
return false;
}
break;
case android::RES_TABLE_TYPE_TYPE:
- if (!ParseType(package, parser.chunk())) {
+ if (!ParseType(package, parser.chunk(), package_id)) {
return false;
}
break;
@@ -255,6 +254,12 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
}
break;
+ case android::RES_TABLE_STAGED_ALIAS_TYPE:
+ if (!ParseStagedAliases(parser.chunk())) {
+ return false;
+ }
+ break;
+
default:
diag_->Warn(DiagMessage(source_)
<< "unexpected chunk type "
@@ -276,7 +281,7 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
}
bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package,
- const ResChunk_header* chunk) {
+ const ResChunk_header* chunk, uint8_t package_id) {
if (type_pool_.getError() != NO_ERROR) {
diag_->Error(DiagMessage(source_) << "missing type string pool");
return false;
@@ -317,14 +322,14 @@ bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package,
const uint32_t* type_spec_flags = reinterpret_cast<const uint32_t*>(
reinterpret_cast<uintptr_t>(type_spec) + util::DeviceToHost16(type_spec->header.headerSize));
for (size_t i = 0; i < entry_count; i++) {
- ResourceId id(package->id.value_or_default(0x0), type_spec->id, static_cast<size_t>(i));
+ ResourceId id(package_id, type_spec->id, static_cast<size_t>(i));
entry_type_spec_flags_[id] = util::DeviceToHost32(type_spec_flags[i]);
}
return true;
}
bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
- const ResChunk_header* chunk) {
+ const ResChunk_header* chunk, uint8_t package_id) {
if (type_pool_.getError() != NO_ERROR) {
diag_->Error(DiagMessage(source_) << "missing type string pool");
return false;
@@ -354,13 +359,9 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
const std::string type_str = util::GetString(type_pool_, type->id - 1);
const ResourceType* parsed_type = ParseResourceType(type_str);
if (!parsed_type) {
- // Be lenient on the name of the type if the table is lenient on resource validation.
- bool log_error = table_->GetValidateResources();
- if (log_error) {
- diag_->Error(DiagMessage(source_) << "invalid type name '" << type_str
- << "' for type with ID " << type->id);
- }
- return !log_error;
+ diag_->Warn(DiagMessage(source_)
+ << "invalid type name '" << type_str << "' for type with ID " << type->id);
+ return true;
}
TypeVariant tv(type);
@@ -372,7 +373,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
const ResourceName name(package->name, *parsed_type,
util::GetString(key_pool_, util::DeviceToHost32(entry->key.index)));
- const ResourceId res_id(package->id.value(), type->id, static_cast<uint16_t>(it.index()));
+ const ResourceId res_id(package_id, type->id, static_cast<uint16_t>(it.index()));
std::unique_ptr<Value> resource_value;
if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
@@ -392,18 +393,21 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
return false;
}
- if (!table_->AddResourceWithIdMangled(name, res_id, config, {}, std::move(resource_value),
- diag_)) {
- return false;
- }
+ NewResourceBuilder res_builder(name);
+ res_builder.SetValue(std::move(resource_value), config)
+ .SetId(res_id, OnIdConflict::CREATE_ENTRY)
+ .SetAllowMangled(true);
if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
- Visibility visibility;
- visibility.level = Visibility::Level::kPublic;
- if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) {
- return false;
+ Visibility visibility{Visibility::Level::kPublic};
+
+ auto spec_flags = entry_type_spec_flags_.find(res_id);
+ if (spec_flags != entry_type_spec_flags_.end() &&
+ spec_flags->second & ResTable_typeSpec::SPEC_STAGED_API) {
+ visibility.staged_api = true;
}
+ res_builder.SetVisibility(visibility);
// Erase the ID from the map once processed, so that we don't mark the same symbol more than
// once.
entry_type_spec_flags_.erase(res_id);
@@ -415,6 +419,10 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
if (cache_iter == id_index_.end()) {
id_index_.insert({res_id, name});
}
+
+ if (!table_->AddResource(res_builder.Build(), diag_)) {
+ return false;
+ }
}
return true;
}
@@ -472,7 +480,12 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) {
OverlayableItem overlayable_item(overlayable);
overlayable_item.policies = policy_header->policy_flags;
- if (!table_->SetOverlayable(iter->second, overlayable_item, diag_)) {
+ if (!table_->AddResource(NewResourceBuilder(iter->second)
+ .SetId(res_id, OnIdConflict::CREATE_ENTRY)
+ .SetOverlayable(std::move(overlayable_item))
+ .SetAllowMangled(true)
+ .Build(),
+ diag_)) {
return false;
}
}
@@ -482,6 +495,52 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) {
return true;
}
+bool BinaryResourceParser::ParseStagedAliases(const ResChunk_header* chunk) {
+ auto header = ConvertTo<ResTable_staged_alias_header>(chunk);
+ if (!header) {
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_staged_alias_header chunk");
+ return false;
+ }
+
+ const auto ref_begin = reinterpret_cast<const ResTable_staged_alias_entry*>(
+ ((uint8_t*)header) + util::DeviceToHost32(header->header.headerSize));
+ const auto ref_end = ref_begin + util::DeviceToHost32(header->count);
+ for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) {
+ const auto staged_id = ResourceId(util::DeviceToHost32(ref_iter->stagedResId));
+ const auto finalized_id = ResourceId(util::DeviceToHost32(ref_iter->finalizedResId));
+
+ // If the staged alias chunk comes before the type chunks, the resource ids and resource name
+ // pairing will not exist at this point.
+ const auto iter = id_index_.find(finalized_id);
+ if (iter == id_index_.cend()) {
+ diag_->Error(DiagMessage(source_) << "failed to find resource name for finalized"
+ << " resource ID " << finalized_id);
+ return false;
+ }
+
+ // Set the staged id of the finalized resource.
+ const auto& resource_name = iter->second;
+ const StagedId staged_id_def{.id = staged_id};
+ if (!table_->AddResource(NewResourceBuilder(resource_name)
+ .SetId(finalized_id, OnIdConflict::CREATE_ENTRY)
+ .SetStagedId(staged_id_def)
+ .SetAllowMangled(true)
+ .Build(),
+ diag_)) {
+ return false;
+ }
+
+ // Since a the finalized resource entry is cloned and added to the resource table under the
+ // staged resource id, remove the cloned resource entry from the table.
+ if (!table_->RemoveResource(resource_name, staged_id)) {
+ diag_->Error(DiagMessage(source_) << "failed to find resource entry for staged "
+ << " resource ID " << staged_id);
+ return false;
+ }
+ }
+ return true;
+}
+
std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name,
const ConfigDescription& config,
const android::Res_value& value) {
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index a2eee5006964..cd71d160703a 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -51,10 +51,13 @@ class BinaryResourceParser {
bool ParseTable(const android::ResChunk_header* chunk);
bool ParsePackage(const android::ResChunk_header* chunk);
- bool ParseTypeSpec(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
- bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
+ bool ParseTypeSpec(const ResourceTablePackage* package, const android::ResChunk_header* chunk,
+ uint8_t package_id);
+ bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk,
+ uint8_t package_id);
bool ParseLibrary(const android::ResChunk_header* chunk);
bool ParseOverlayable(const android::ResChunk_header* chunk);
+ bool ParseStagedAliases(const android::ResChunk_header* chunk);
std::unique_ptr<Item> ParseValue(const ResourceNameRef& name,
const android::ConfigDescription& config,
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 4784ecf3d12c..a9192e889c17 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -59,47 +59,35 @@ static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
dst[i] = 0;
}
-static bool cmp_style_ids(ResourceId a, ResourceId b) {
- // If one of a and b is from the framework package (package ID 0x01), and the
- // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the
- // framework ID. This ensures that when AssetManager resolves the dynamic IDs,
- // they will be in sorted order as expected by AssetManager.
- if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) ||
- (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) {
- return b < a;
- }
- return a < b;
-}
-
-static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) {
- if (a.key.id) {
- if (b.key.id) {
- return cmp_style_ids(a.key.id.value(), b.key.id.value());
+static bool cmp_style_entries(const Style::Entry* a, const Style::Entry* b) {
+ if (a->key.id) {
+ if (b->key.id) {
+ return cmp_ids_dynamic_after_framework(a->key.id.value(), b->key.id.value());
}
return true;
- } else if (!b.key.id) {
- return a.key.name.value() < b.key.name.value();
+ } else if (!b->key.id) {
+ return a->key.name.value() < b->key.name.value();
}
return false;
}
struct FlatEntry {
- ResourceEntry* entry;
- Value* value;
+ const ResourceTableEntryView* entry;
+ const Value* value;
// The entry string pool index to the entry's name.
uint32_t entry_key;
};
-class MapFlattenVisitor : public ValueVisitor {
+class MapFlattenVisitor : public ConstValueVisitor {
public:
- using ValueVisitor::Visit;
+ using ConstValueVisitor::Visit;
MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer)
: out_entry_(out_entry), buffer_(buffer) {
}
- void Visit(Attribute* attr) override {
+ void Visit(const Attribute* attr) override {
{
Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask);
@@ -118,13 +106,13 @@ class MapFlattenVisitor : public ValueVisitor {
FlattenEntry(&key, &val);
}
- for (Attribute::Symbol& s : attr->symbols) {
+ for (const Attribute::Symbol& s : attr->symbols) {
BinaryPrimitive val(s.type, s.value);
FlattenEntry(&s.symbol, &val);
}
}
- void Visit(Style* style) override {
+ void Visit(const Style* style) override {
if (style->parent) {
const Reference& parent_ref = style->parent.value();
CHECK(bool(parent_ref.id)) << "parent has no ID";
@@ -132,21 +120,26 @@ class MapFlattenVisitor : public ValueVisitor {
}
// Sort the style.
- std::sort(style->entries.begin(), style->entries.end(), cmp_style_entries);
+ std::vector<const Style::Entry*> sorted_entries;
+ for (const auto& entry : style->entries) {
+ sorted_entries.emplace_back(&entry);
+ }
- for (Style::Entry& entry : style->entries) {
- FlattenEntry(&entry.key, entry.value.get());
+ std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_style_entries);
+
+ for (const Style::Entry* entry : sorted_entries) {
+ FlattenEntry(&entry->key, entry->value.get());
}
}
- void Visit(Styleable* styleable) override {
+ void Visit(const Styleable* styleable) override {
for (auto& attr_ref : styleable->entries) {
BinaryPrimitive val(Res_value{});
FlattenEntry(&attr_ref, &val);
}
}
- void Visit(Array* array) override {
+ void Visit(const Array* array) override {
const size_t count = array->elements.size();
for (size_t i = 0; i < count; i++) {
Reference key(android::ResTable_map::ATTR_MIN + i);
@@ -154,7 +147,7 @@ class MapFlattenVisitor : public ValueVisitor {
}
}
- void Visit(Plural* plural) override {
+ void Visit(const Plural* plural) override {
const size_t count = plural->values.size();
for (size_t i = 0; i < count; i++) {
if (!plural->values[i]) {
@@ -208,16 +201,16 @@ class MapFlattenVisitor : public ValueVisitor {
private:
DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor);
- void FlattenKey(Reference* key, ResTable_map* out_entry) {
+ void FlattenKey(const Reference* key, ResTable_map* out_entry) {
CHECK(bool(key->id)) << "key has no ID";
out_entry->name.ident = util::HostToDevice32(key->id.value().id);
}
- void FlattenValue(Item* value, ResTable_map* out_entry) {
+ void FlattenValue(const Item* value, ResTable_map* out_entry) {
CHECK(value->Flatten(&out_entry->value)) << "flatten failed";
}
- void FlattenEntry(Reference* key, Item* value) {
+ void FlattenEntry(const Reference* key, Item* value) {
ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
FlattenKey(key, out_entry);
FlattenValue(value, out_entry);
@@ -238,7 +231,7 @@ struct OverlayableChunk {
class PackageFlattener {
public:
- PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
+ PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package,
const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries,
bool collapse_key_stringpool,
const std::set<ResourceName>& name_collapse_exemptions)
@@ -255,20 +248,20 @@ class PackageFlattener {
TRACE_CALL();
ChunkWriter pkg_writer(buffer);
ResTable_package* pkg_header = pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
- pkg_header->id = util::HostToDevice32(package_->id.value());
+ pkg_header->id = util::HostToDevice32(package_.id.value());
// AAPT truncated the package name, so do the same.
// Shared libraries require full package names, so don't truncate theirs.
if (context_->GetPackageType() != PackageType::kApp &&
- package_->name.size() >= arraysize(pkg_header->name)) {
- diag_->Error(DiagMessage() << "package name '" << package_->name
+ package_.name.size() >= arraysize(pkg_header->name)) {
+ diag_->Error(DiagMessage() << "package name '" << package_.name
<< "' is too long. "
"Shared libraries cannot have truncated package names");
return false;
}
// Copy the package name in device endianness.
- strcpy16_htod(pkg_header->name, arraysize(pkg_header->name), util::Utf8ToUtf16(package_->name));
+ strcpy16_htod(pkg_header->name, arraysize(pkg_header->name), util::Utf8ToUtf16(package_.name));
// Serialize the types. We do this now so that our type and key strings
// are populated. We write those first.
@@ -285,7 +278,7 @@ class PackageFlattener {
buffer->AppendBuffer(std::move(type_buffer));
// If there are libraries (or if the package ID is 0x00), encode a library chunk.
- if (package_->id.value() == 0x00 || !shared_libs_->empty()) {
+ if (package_.id.value() == 0x00 || !shared_libs_->empty()) {
FlattenLibrarySpec(buffer);
}
@@ -293,6 +286,10 @@ class PackageFlattener {
return false;
}
+ if (!FlattenAliases(buffer)) {
+ return false;
+ }
+
pkg_writer.Finish();
return true;
}
@@ -327,7 +324,7 @@ class PackageFlattener {
}
bool FlattenValue(FlatEntry* entry, BigBuffer* buffer) {
- if (Item* item = ValueCast<Item>(entry->value)) {
+ if (const Item* item = ValueCast<Item>(entry->value)) {
WriteEntry<ResTable_entry, true>(entry, buffer);
Res_value* outValue = buffer->NextBlock<Res_value>();
CHECK(item->Flatten(outValue)) << "flatten failed";
@@ -341,7 +338,7 @@ class PackageFlattener {
return true;
}
- bool FlattenConfig(const ResourceTableType* type, const ConfigDescription& config,
+ bool FlattenConfig(const ResourceTableTypeView& type, const ConfigDescription& config,
const size_t num_total_entries, std::vector<FlatEntry>* entries,
BigBuffer* buffer) {
CHECK(num_total_entries != 0);
@@ -349,7 +346,7 @@ class PackageFlattener {
ChunkWriter type_writer(buffer);
ResTable_type* type_header = type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
- type_header->id = type->id.value();
+ type_header->id = type.id.value();
type_header->config = config;
type_header->config.swapHtoD();
@@ -363,7 +360,7 @@ class PackageFlattener {
if (!FlattenValue(&flat_entry, &values_buffer)) {
diag_->Error(DiagMessage()
<< "failed to flatten resource '"
- << ResourceNameRef(package_->name, type->type, flat_entry.entry->name)
+ << ResourceNameRef(package_.name, type.type, flat_entry.entry->name)
<< "' for configuration '" << config << "'");
return false;
}
@@ -411,55 +408,46 @@ class PackageFlattener {
return true;
}
- std::vector<ResourceTableType*> CollectAndSortTypes() {
- std::vector<ResourceTableType*> sorted_types;
- for (auto& type : package_->types) {
- if (type->type == ResourceType::kStyleable) {
- // Styleables aren't real Resource Types, they are represented in the
- // R.java file.
- continue;
- }
-
- CHECK(bool(type->id)) << "type must have an ID set";
-
- sorted_types.push_back(type.get());
+ bool FlattenAliases(BigBuffer* buffer) {
+ if (aliases_.empty()) {
+ return true;
}
- std::sort(sorted_types.begin(), sorted_types.end(), cmp_ids<ResourceTableType>);
- return sorted_types;
- }
- std::vector<ResourceEntry*> CollectAndSortEntries(ResourceTableType* type) {
- // Sort the entries by entry ID.
- std::vector<ResourceEntry*> sorted_entries;
- for (auto& entry : type->entries) {
- CHECK(bool(entry->id)) << "entry must have an ID set";
- sorted_entries.push_back(entry.get());
+ ChunkWriter alias_writer(buffer);
+ auto header =
+ alias_writer.StartChunk<ResTable_staged_alias_header>(RES_TABLE_STAGED_ALIAS_TYPE);
+ header->count = util::HostToDevice32(aliases_.size());
+
+ auto mapping = alias_writer.NextBlock<ResTable_staged_alias_entry>(aliases_.size());
+ for (auto& p : aliases_) {
+ mapping->stagedResId = util::HostToDevice32(p.first);
+ mapping->finalizedResId = util::HostToDevice32(p.second);
+ ++mapping;
}
- std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_ids<ResourceEntry>);
- return sorted_entries;
+ alias_writer.Finish();
+ return true;
}
bool FlattenOverlayable(BigBuffer* buffer) {
std::set<ResourceId> seen_ids;
std::map<std::string, OverlayableChunk> overlayable_chunks;
- CHECK(bool(package_->id)) << "package must have an ID set when flattening <overlayable>";
- for (auto& type : package_->types) {
- CHECK(bool(type->id)) << "type must have an ID set when flattening <overlayable>";
- for (auto& entry : type->entries) {
- CHECK(bool(type->id)) << "entry must have an ID set when flattening <overlayable>";
- if (!entry->overlayable_item) {
+ CHECK(bool(package_.id)) << "package must have an ID set when flattening <overlayable>";
+ for (auto& type : package_.types) {
+ CHECK(bool(type.id)) << "type must have an ID set when flattening <overlayable>";
+ for (auto& entry : type.entries) {
+ CHECK(bool(type.id)) << "entry must have an ID set when flattening <overlayable>";
+ if (!entry.overlayable_item) {
continue;
}
- OverlayableItem& item = entry->overlayable_item.value();
+ const OverlayableItem& item = entry.overlayable_item.value();
// Resource ids should only appear once in the resource table
- ResourceId id = android::make_resid(package_->id.value(), type->id.value(),
- entry->id.value());
+ ResourceId id = android::make_resid(package_.id.value(), type.id.value(), entry.id.value());
CHECK(seen_ids.find(id) == seen_ids.end())
<< "multiple overlayable definitions found for resource "
- << ResourceName(package_->name, type->type, entry->name).to_string();
+ << ResourceName(package_.name, type.type, entry.name).to_string();
seen_ids.insert(id);
// Find the overlayable chunk with the specified name
@@ -487,9 +475,8 @@ class PackageFlattener {
if (item.policies == 0) {
context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source)
- << "overlayable "
- << entry->name
- << " does not specify policy");
+ << "overlayable " << entry.name
+ << " does not specify policy");
return false;
}
@@ -554,14 +541,15 @@ class PackageFlattener {
return true;
}
- bool FlattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sorted_entries,
+ bool FlattenTypeSpec(const ResourceTableTypeView& type,
+ const std::vector<ResourceTableEntryView>& sorted_entries,
BigBuffer* buffer) {
ChunkWriter type_spec_writer(buffer);
ResTable_typeSpec* spec_header =
type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
- spec_header->id = type->id.value();
+ spec_header->id = type.id.value();
- if (sorted_entries->empty()) {
+ if (sorted_entries.empty()) {
type_spec_writer.Finish();
return true;
}
@@ -569,7 +557,7 @@ class PackageFlattener {
// We can't just take the size of the vector. There may be holes in the
// entry ID space.
// Since the entries are sorted by ID, the last one will be the biggest.
- const size_t num_entries = sorted_entries->back()->id.value() + 1;
+ const size_t num_entries = sorted_entries.back().id.value() + 1;
spec_header->entryCount = util::HostToDevice32(num_entries);
@@ -577,22 +565,23 @@ class PackageFlattener {
// show for which configuration axis the resource changes.
uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
- const size_t actual_num_entries = sorted_entries->size();
- for (size_t entryIndex = 0; entryIndex < actual_num_entries; entryIndex++) {
- ResourceEntry* entry = sorted_entries->at(entryIndex);
+ for (const ResourceTableEntryView& entry : sorted_entries) {
+ const uint16_t entry_id = entry.id.value();
// Populate the config masks for this entry.
-
- if (entry->visibility.level == Visibility::Level::kPublic) {
- config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
+ uint32_t& entry_config_masks = config_masks[entry_id];
+ if (entry.visibility.level == Visibility::Level::kPublic) {
+ entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
+ }
+ if (entry.visibility.staged_api) {
+ entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_STAGED_API);
}
- const size_t config_count = entry->values.size();
+ const size_t config_count = entry.values.size();
for (size_t i = 0; i < config_count; i++) {
- const ConfigDescription& config = entry->values[i]->config;
+ const ConfigDescription& config = entry.values[i]->config;
for (size_t j = i + 1; j < config_count; j++) {
- config_masks[entry->id.value()] |=
- util::HostToDevice32(config.diff(entry->values[j]->config));
+ config_masks[entry_id] |= util::HostToDevice32(config.diff(entry.values[j]->config));
}
}
}
@@ -601,34 +590,30 @@ class PackageFlattener {
}
bool FlattenTypes(BigBuffer* buffer) {
- // Sort the types by their IDs. They will be inserted into the StringPool in
- // this order.
- std::vector<ResourceTableType*> sorted_types = CollectAndSortTypes();
-
size_t expected_type_id = 1;
- for (ResourceTableType* type : sorted_types) {
+ for (const ResourceTableTypeView& type : package_.types) {
+ if (type.type == ResourceType::kStyleable || type.type == ResourceType::kMacro) {
+ // Styleables and macros are not real resource types.
+ continue;
+ }
+
// If there is a gap in the type IDs, fill in the StringPool
// with empty values until we reach the ID we expect.
- while (type->id.value() > expected_type_id) {
+ while (type.id.value() > expected_type_id) {
std::stringstream type_name;
type_name << "?" << expected_type_id;
type_pool_.MakeRef(type_name.str());
expected_type_id++;
}
expected_type_id++;
- type_pool_.MakeRef(to_string(type->type));
-
- std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
- if (sorted_entries.empty()) {
- continue;
- }
+ type_pool_.MakeRef(to_string(type.type));
- if (!FlattenTypeSpec(type, &sorted_entries, buffer)) {
+ if (!FlattenTypeSpec(type, type.entries, buffer)) {
return false;
}
// Since the entries are sorted by ID, the last ID will be the largest.
- const size_t num_entries = sorted_entries.back()->id.value() + 1;
+ const size_t num_entries = type.entries.back().id.value() + 1;
// The binary resource table lists resource entries for each
// configuration.
@@ -641,20 +626,26 @@ class PackageFlattener {
// hardcoded string uses characters which make it an invalid resource name
const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
- for (ResourceEntry* entry : sorted_entries) {
+ for (const ResourceTableEntryView& entry : type.entries) {
+ if (entry.staged_id) {
+ aliases_.insert(std::make_pair(
+ entry.staged_id.value().id.id,
+ ResourceId(package_.id.value(), type.id.value(), entry.id.value()).id));
+ }
+
uint32_t local_key_index;
- ResourceName resource_name({}, type->type, entry->name);
+ ResourceName resource_name({}, type.type, entry.name);
if (!collapse_key_stringpool_ ||
name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
- local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
+ local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
} else {
// resource isn't exempt from collapse, add it as obfuscated value
local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
}
// Group values by configuration.
- for (auto& config_value : entry->values) {
+ for (auto& config_value : entry.values) {
config_to_entry_list_map[config_value->config].push_back(
- FlatEntry{entry, config_value->value.get(), local_key_index});
+ FlatEntry{&entry, config_value->value.get(), local_key_index});
}
}
@@ -673,17 +664,17 @@ class PackageFlattener {
ResTable_lib_header* lib_header =
lib_writer.StartChunk<ResTable_lib_header>(RES_TABLE_LIBRARY_TYPE);
- const size_t num_entries = (package_->id.value() == 0x00 ? 1 : 0) + shared_libs_->size();
+ const size_t num_entries = (package_.id.value() == 0x00 ? 1 : 0) + shared_libs_->size();
CHECK(num_entries > 0);
lib_header->count = util::HostToDevice32(num_entries);
ResTable_lib_entry* lib_entry = buffer->NextBlock<ResTable_lib_entry>(num_entries);
- if (package_->id.value() == 0x00) {
+ if (package_.id.value() == 0x00) {
// Add this package
lib_entry->packageId = util::HostToDevice32(0x00);
strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName),
- util::Utf8ToUtf16(package_->name));
+ util::Utf8ToUtf16(package_.name));
++lib_entry;
}
@@ -698,13 +689,14 @@ class PackageFlattener {
IAaptContext* context_;
IDiagnostics* diag_;
- ResourceTablePackage* package_;
+ const ResourceTablePackageView package_;
const std::map<size_t, std::string>* shared_libs_;
bool use_sparse_entries_;
StringPool type_pool_;
StringPool key_pool_;
bool collapse_key_stringpool_;
const std::set<ResourceName>& name_collapse_exemptions_;
+ std::map<uint32_t, uint32_t> aliases_;
};
} // namespace
@@ -722,9 +714,11 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
});
// Write the ResTable header.
+ const auto& table_view =
+ table->GetPartitionedView(ResourceTableViewOptions{.create_alias_entries = true});
ChunkWriter table_writer(buffer_);
ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
- table_header->packageCount = util::HostToDevice32(table->packages.size());
+ table_header->packageCount = util::HostToDevice32(table_view.packages.size());
// Flatten the values string pool.
StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool,
@@ -733,24 +727,25 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
BigBuffer package_buffer(1024);
// Flatten each package.
- for (auto& package : table->packages) {
+ for (auto& package : table_view.packages) {
if (context->GetPackageType() == PackageType::kApp) {
// Write a self mapping entry for this package if the ID is non-standard (0x7f).
- const uint8_t package_id = package->id.value();
+ CHECK((bool)package.id) << "Resource ids have not been assigned before flattening the table";
+ const uint8_t package_id = package.id.value();
if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
- auto result = table->included_packages_.insert({package_id, package->name});
- if (!result.second && result.first->second != package->name) {
+ auto result = table->included_packages_.insert({package_id, package.name});
+ if (!result.second && result.first->second != package.name) {
// A mapping for this package ID already exists, and is a different package. Error!
context->GetDiagnostics()->Error(
DiagMessage() << android::base::StringPrintf(
"can't map package ID %02x to '%s'. Already mapped to '%s'", package_id,
- package->name.c_str(), result.first->second.c_str()));
+ package.name.c_str(), result.first->second.c_str()));
return false;
}
}
}
- PackageFlattener flattener(context, package.get(), &table->included_packages_,
+ PackageFlattener flattener(context, package, &table->included_packages_,
options_.use_sparse_entries, options_.collapse_key_stringpool,
options_.name_collapse_exemptions);
if (!flattener.FlattenPackage(&package_buffer)) {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index f8b8a1c4e35b..8139d7385092 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -155,7 +155,6 @@ class TableFlattenerTest : public ::testing::Test {
TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
.AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
.AddValue("com.app.test:id/three", ResourceId(0x7f020002),
@@ -204,7 +203,6 @@ TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddSimple("com.app.test:id/one", ResourceId(0x7f020001))
.AddSimple("com.app.test:id/three", ResourceId(0x7f020003))
.Build();
@@ -225,7 +223,6 @@ TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
attr.max_int = 23;
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>(attr))
.Build();
@@ -248,7 +245,6 @@ TEST_F(TableFlattenerTest, FlattenArray) {
2u));
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddValue("android:array/foo", ResourceId(0x01010000), std::move(array))
.Build();
@@ -300,10 +296,10 @@ static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
IAaptContext* context, const ConfigDescription& sparse_config, float load) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId(context->GetCompilationPackage(), context->GetPackageId())
.Build();
// Add regular entries.
+ CloningValueTransformer cloner(&table->string_pool);
int stride = static_cast<int>(1.0f / load);
for (int i = 0; i < 100; i++) {
const ResourceName name = test::ParseNameOrDie(
@@ -311,15 +307,20 @@ static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
const auto value =
util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
- CHECK(table->AddResourceWithId(name, resid, ConfigDescription::DefaultConfig(), "",
- std::unique_ptr<Value>(value->Clone(nullptr)),
- context->GetDiagnostics()));
+ CHECK(table->AddResource(NewResourceBuilder(name)
+ .SetId(resid)
+ .SetValue(std::unique_ptr<Value>(value->Transform(cloner)))
+ .Build(),
+ context->GetDiagnostics()));
// Every few entries, write out a sparse_config value. This will give us the desired load.
if (i % stride == 0) {
- CHECK(table->AddResourceWithId(name, resid, sparse_config, "",
- std::unique_ptr<Value>(value->Clone(nullptr)),
- context->GetDiagnostics()));
+ CHECK(table->AddResource(
+ NewResourceBuilder(name)
+ .SetId(resid)
+ .SetValue(std::unique_ptr<Value>(value->Transform(cloner)), sparse_config)
+ .Build(),
+ context->GetDiagnostics()));
}
}
return table;
@@ -417,7 +418,6 @@ TEST_F(TableFlattenerTest, FlattenSharedLibrary) {
test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("lib", 0x00)
.AddValue("lib:id/foo", ResourceId(0x00010000), util::make_unique<Id>())
.Build();
ResourceTable result;
@@ -426,7 +426,7 @@ TEST_F(TableFlattenerTest, FlattenSharedLibrary) {
Maybe<ResourceTable::SearchResult> search_result =
result.FindResource(test::ParseNameOrDie("lib:id/foo"));
ASSERT_TRUE(search_result);
- EXPECT_EQ(0x00u, search_result.value().package->id.value());
+ EXPECT_EQ(0x00u, search_result.value().entry->id.value().package_id());
auto iter = result.included_packages_.find(0x00);
ASSERT_NE(result.included_packages_.end(), iter);
@@ -438,7 +438,6 @@ TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) {
test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("lib", 0x00)
.AddValue("lib:style/Theme",
ResourceId(0x00030001),
test::StyleBuilder()
@@ -458,9 +457,7 @@ TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) {
Maybe<ResourceTable::SearchResult> search_result =
result.FindResource(test::ParseNameOrDie("lib:style/Theme"));
ASSERT_TRUE(search_result);
- EXPECT_EQ(0x00u, search_result.value().package->id.value());
- EXPECT_EQ(0x03u, search_result.value().type->id.value());
- EXPECT_EQ(0x01u, search_result.value().entry->id.value());
+ EXPECT_EQ(0x00030001u, search_result.value().entry->id.value());
ASSERT_EQ(1u, search_result.value().entry->values.size());
Value* value = search_result.value().entry->values[0]->value.get();
Style* style = ValueCast<Style>(value);
@@ -479,7 +476,6 @@ TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("app", 0x7f)
.AddValue("app:id/foo", ResourceId(0x7f010000),
test::BuildReference("lib_one:id/foo", ResourceId(0x02010000)))
.AddValue("app:id/bar", ResourceId(0x7f010001),
@@ -509,7 +505,6 @@ TEST_F(TableFlattenerTest, PackageWithNonStandardIdHasDynamicRefTable) {
std::unique_ptr<IAaptContext> context =
test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x80).Build();
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .SetPackageId("app", 0x80)
.AddSimple("app:id/foo", ResourceId(0x80010000))
.Build();
@@ -532,7 +527,6 @@ TEST_F(TableFlattenerTest, LongPackageNameIsTruncated) {
test::ContextBuilder().SetCompilationPackage(kPackageName).SetPackageId(0x7f).Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId(kPackageName, 0x7f)
.AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
.Build();
@@ -553,7 +547,6 @@ TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) {
.Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId(kPackageName, 0x7f)
.AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
.Build();
@@ -564,7 +557,6 @@ TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) {
TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
.AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
.AddValue("com.app.test:id/three", ResourceId(0x7f020002),
@@ -618,7 +610,6 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucce
TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
.AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
.AddValue("com.app.test:id/three", ResourceId(0x7f020002),
@@ -680,7 +671,6 @@ TEST_F(TableFlattenerTest, FlattenOverlayable) {
std::string name = "com.app.test:integer/overlayable";
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddSimple(name, ResourceId(0x7f020000))
.SetOverlayable(name, overlayable_item)
.Build();
@@ -717,7 +707,6 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddSimple(name_zero, ResourceId(0x7f020000))
.SetOverlayable(name_zero, overlayable_item_zero)
.AddSimple(name_one, ResourceId(0x7f020001))
@@ -780,7 +769,6 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddSimple(name_zero, ResourceId(0x7f020000))
.SetOverlayable(name_zero, overlayable_item_zero)
.AddSimple(name_one, ResourceId(0x7f020001))
@@ -842,7 +830,6 @@ TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddSimple(name_zero, ResourceId(0x7f020000))
.SetOverlayable(name_zero, overlayable_item_zero)
.Build();
diff --git a/tools/aapt2/format/binary/XmlFlattener_test.cpp b/tools/aapt2/format/binary/XmlFlattener_test.cpp
index c24488b16153..d97e8882e5a2 100644
--- a/tools/aapt2/format/binary/XmlFlattener_test.cpp
+++ b/tools/aapt2/format/binary/XmlFlattener_test.cpp
@@ -222,7 +222,7 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
android:id="@id/foo"
app:foo="@id/foo" />)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
// The tree needs a custom DynamicRefTable since it is not using a standard app ID (0x7f).
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 7eb8ebd9a043..236c38167545 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -425,15 +425,9 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
io::IFileCollection* files,
const std::vector<std::shared_ptr<Overlayable>>& overlayables,
ResourceTable* out_table, std::string* out_error) {
- Maybe<uint8_t> id;
- if (pb_package.has_package_id()) {
- id = static_cast<uint8_t>(pb_package.package_id().id());
- }
-
std::map<ResourceId, ResourceNameRef> id_index;
- ResourceTablePackage* pkg =
- out_table->CreatePackageAllowingDuplicateNames(pb_package.package_name(), id);
+ ResourceTablePackage* pkg = out_table->FindOrCreatePackage(pb_package.package_name());
for (const pb::Type& pb_type : pb_package.type()) {
const ResourceType* res_type = ParseResourceType(pb_type.name());
if (res_type == nullptr) {
@@ -444,14 +438,15 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
}
ResourceTableType* type = pkg->FindOrCreateType(*res_type);
- if (pb_type.has_type_id()) {
- type->id = static_cast<uint8_t>(pb_type.type_id().id());
- }
for (const pb::Entry& pb_entry : pb_type.entry()) {
- ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name());
- if (pb_entry.has_entry_id()) {
- entry->id = static_cast<uint16_t>(pb_entry.entry_id().id());
+ ResourceEntry* entry = type->CreateEntry(pb_entry.name());
+ const ResourceId resource_id(
+ pb_package.has_package_id() ? static_cast<uint8_t>(pb_package.package_id().id()) : 0u,
+ pb_type.has_type_id() ? static_cast<uint8_t>(pb_type.type_id().id()) : 0u,
+ pb_entry.has_entry_id() ? static_cast<uint16_t>(pb_entry.entry_id().id()) : 0u);
+ if (resource_id.id != 0u) {
+ entry->id = resource_id;
}
// Deserialize the symbol status (public/private with source and comments).
@@ -461,6 +456,7 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
DeserializeSourceFromPb(pb_visibility.source(), src_pool, &entry->visibility.source);
}
entry->visibility.comment = pb_visibility.comment();
+ entry->visibility.staged_api = pb_visibility.staged_api();
const Visibility::Level level = DeserializeVisibilityFromPb(pb_visibility.level());
entry->visibility.level = level;
@@ -490,8 +486,10 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
// Find the overlayable to which this item belongs
pb::OverlayableItem pb_overlayable_item = pb_entry.overlayable_item();
if (pb_overlayable_item.overlayable_idx() >= overlayables.size()) {
- *out_error = android::base::StringPrintf("invalid overlayable_idx value %d",
- pb_overlayable_item.overlayable_idx());
+ *out_error =
+ android::base::StringPrintf("invalid overlayable_idx value %d for entry %s/%s",
+ pb_overlayable_item.overlayable_idx(),
+ pb_type.name().c_str(), pb_entry.name().c_str());
return false;
}
@@ -500,10 +498,20 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
out_error)) {
return false;
}
-
entry->overlayable_item = std::move(overlayable_item);
}
+ if (pb_entry.has_staged_id()) {
+ const pb::StagedId& pb_staged_id = pb_entry.staged_id();
+
+ StagedId staged_id;
+ if (pb_staged_id.has_source()) {
+ DeserializeSourceFromPb(pb_staged_id.source(), src_pool, &staged_id.source);
+ }
+ staged_id.id = pb_staged_id.staged_id();
+ entry->staged_id = std::move(staged_id);
+ }
+
ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
pb_entry.entry_id().id());
if (resid.is_valid()) {
@@ -658,6 +666,38 @@ static bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* o
}
out_ref->name = name_ref.ToResourceName();
}
+ if (pb_ref.type_flags() != 0) {
+ out_ref->type_flags = pb_ref.type_flags();
+ }
+ out_ref->allow_raw = pb_ref.allow_raw();
+ return true;
+}
+
+static bool DeserializeMacroFromPb(const pb::MacroBody& pb_ref, Macro* out_ref,
+ std::string* out_error) {
+ out_ref->raw_value = pb_ref.raw_string();
+
+ if (pb_ref.has_style_string()) {
+ out_ref->style_string.str = pb_ref.style_string().str();
+ for (const auto& span : pb_ref.style_string().spans()) {
+ out_ref->style_string.spans.emplace_back(Span{
+ .name = span.name(), .first_char = span.start_index(), .last_char = span.end_index()});
+ }
+ }
+
+ for (const auto& untranslatable_section : pb_ref.untranslatable_sections()) {
+ out_ref->untranslatable_sections.emplace_back(
+ UntranslatableSection{.start = static_cast<size_t>(untranslatable_section.start_index()),
+ .end = static_cast<size_t>(untranslatable_section.end_index())});
+ }
+
+ for (const auto& namespace_decls : pb_ref.namespace_stack()) {
+ out_ref->alias_namespaces.emplace_back(
+ Macro::Namespace{.alias = namespace_decls.prefix(),
+ .package_name = namespace_decls.package_name(),
+ .is_private = namespace_decls.is_private()});
+ }
+
return true;
}
@@ -803,6 +843,15 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
value = std::move(plural);
} break;
+ case pb::CompoundValue::kMacro: {
+ const pb::MacroBody& pb_macro = pb_compound_value.macro();
+ auto macro = std::make_unique<Macro>();
+ if (!DeserializeMacroFromPb(pb_macro, macro.get(), out_error)) {
+ return {};
+ }
+ value = std::move(macro);
+ } break;
+
default:
LOG(FATAL) << "unknown compound value: " << (int)pb_compound_value.value_case();
break;
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 831229ffa383..6042ba89bf8a 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -345,51 +345,71 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table
pb_fingerprint->set_version(util::GetToolFingerprint());
std::vector<Overlayable*> overlayables;
- for (const std::unique_ptr<ResourceTablePackage>& package : table.packages) {
+ auto table_view = table.GetPartitionedView();
+ for (const auto& package : table_view.packages) {
pb::Package* pb_package = out_table->add_package();
- if (package->id) {
- pb_package->mutable_package_id()->set_id(package->id.value());
+ if (package.id) {
+ pb_package->mutable_package_id()->set_id(package.id.value());
}
- pb_package->set_package_name(package->name);
+ pb_package->set_package_name(package.name);
- for (const std::unique_ptr<ResourceTableType>& type : package->types) {
+ for (const auto& type : package.types) {
pb::Type* pb_type = pb_package->add_type();
- if (type->id) {
- pb_type->mutable_type_id()->set_id(type->id.value());
+ if (type.id) {
+ pb_type->mutable_type_id()->set_id(type.id.value());
}
- pb_type->set_name(to_string(type->type).to_string());
+ pb_type->set_name(to_string(type.type).to_string());
- for (const std::unique_ptr<ResourceEntry>& entry : type->entries) {
+ // hardcoded string uses characters which make it an invalid resource name
+ static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
+ for (const auto& entry : type.entries) {
pb::Entry* pb_entry = pb_type->add_entry();
- if (entry->id) {
- pb_entry->mutable_entry_id()->set_id(entry->id.value());
+ if (entry.id) {
+ pb_entry->mutable_entry_id()->set_id(entry.id.value());
+ }
+ ResourceName resource_name({}, type.type, entry.name);
+ if (options.collapse_key_stringpool &&
+ options.name_collapse_exemptions.find(resource_name) ==
+ options.name_collapse_exemptions.end()) {
+ pb_entry->set_name(obfuscated_resource_name);
+ } else {
+ pb_entry->set_name(entry.name);
}
- pb_entry->set_name(entry->name);
// Write the Visibility struct.
pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
- pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level));
+ pb_visibility->set_staged_api(entry.visibility.staged_api);
+ pb_visibility->set_level(SerializeVisibilityToPb(entry.visibility.level));
if (source_pool != nullptr) {
- SerializeSourceToPb(entry->visibility.source, source_pool.get(),
+ SerializeSourceToPb(entry.visibility.source, source_pool.get(),
pb_visibility->mutable_source());
}
- pb_visibility->set_comment(entry->visibility.comment);
+ pb_visibility->set_comment(entry.visibility.comment);
- if (entry->allow_new) {
+ if (entry.allow_new) {
pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new();
if (source_pool != nullptr) {
- SerializeSourceToPb(entry->allow_new.value().source, source_pool.get(),
+ SerializeSourceToPb(entry.allow_new.value().source, source_pool.get(),
pb_allow_new->mutable_source());
}
- pb_allow_new->set_comment(entry->allow_new.value().comment);
+ pb_allow_new->set_comment(entry.allow_new.value().comment);
}
- if (entry->overlayable_item) {
- SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables,
+ if (entry.overlayable_item) {
+ SerializeOverlayableItemToPb(entry.overlayable_item.value(), overlayables,
source_pool.get(), pb_entry, out_table);
}
- for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
+ if (entry.staged_id) {
+ pb::StagedId* pb_staged_id = pb_entry->mutable_staged_id();
+ if (source_pool != nullptr) {
+ SerializeSourceToPb(entry.staged_id.value().source, source_pool.get(),
+ pb_staged_id->mutable_source());
+ }
+ pb_staged_id->set_staged_id(entry.staged_id.value().id.id);
+ }
+
+ for (const ResourceConfigValue* config_value : entry.values) {
pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
SerializeConfig(config_value->config, pb_config_value->mutable_config());
pb_config_value->mutable_config()->set_product(config_value->product);
@@ -429,6 +449,36 @@ static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref)
if (ref.is_dynamic) {
pb_ref->mutable_is_dynamic()->set_value(ref.is_dynamic);
}
+ if (ref.type_flags) {
+ pb_ref->set_type_flags(*ref.type_flags);
+ }
+ pb_ref->set_allow_raw(ref.allow_raw);
+}
+
+static void SerializeMacroToPb(const Macro& ref, pb::MacroBody* pb_macro) {
+ pb_macro->set_raw_string(ref.raw_value);
+
+ auto pb_style_str = pb_macro->mutable_style_string();
+ pb_style_str->set_str(ref.style_string.str);
+ for (const auto& span : ref.style_string.spans) {
+ auto pb_span = pb_style_str->add_spans();
+ pb_span->set_name(span.name);
+ pb_span->set_start_index(span.first_char);
+ pb_span->set_end_index(span.last_char);
+ }
+
+ for (const auto& untranslatable_section : ref.untranslatable_sections) {
+ auto pb_section = pb_macro->add_untranslatable_sections();
+ pb_section->set_start_index(untranslatable_section.start);
+ pb_section->set_end_index(untranslatable_section.end);
+ }
+
+ for (const auto& namespace_decls : ref.alias_namespaces) {
+ auto pb_namespace = pb_macro->add_namespace_stack();
+ pb_namespace->set_prefix(namespace_decls.alias);
+ pb_namespace->set_package_name(namespace_decls.package_name);
+ pb_namespace->set_is_private(namespace_decls.is_private);
+ }
}
template <typename T>
@@ -632,6 +682,11 @@ class ValueSerializer : public ConstValueVisitor {
}
}
+ void Visit(const Macro* macro) override {
+ pb::MacroBody* pb_macro = out_value_->mutable_compound_value()->mutable_macro();
+ SerializeMacroToPb(*macro, pb_macro);
+ }
+
void VisitAny(const Value* unknown) override {
LOG(FATAL) << "unimplemented value: " << *unknown;
}
diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h
index 7a3ea9903732..b0d56307fbe4 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.h
+++ b/tools/aapt2/format/proto/ProtoSerialize.h
@@ -38,6 +38,15 @@ struct SerializeXmlOptions {
struct SerializeTableOptions {
/** Prevent serializing the source pool and source protos. */
bool exclude_sources = false;
+
+ // When true, all the entry names in pb:ResourceTable are collapsed to a
+ // single entry name. When the proto table is converted to binary
+ // resources.arsc, the key string pool is collapsed to a single entry. All
+ // resource entries have name indices that point to this single value.
+ bool collapse_key_stringpool = false;
+
+ // Set of resources to avoid collapsing to a single entry in key stringpool.
+ std::set<ResourceName> name_collapse_exemptions;
};
// Serializes a Value to its protobuf representation. An optional StringPool will hold the
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 1a7de6dc1c48..38c811fe3619 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -24,6 +24,7 @@ using ::android::ConfigDescription;
using ::android::StringPiece;
using ::testing::Eq;
using ::testing::IsEmpty;
+using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::SizeIs;
using ::testing::StrEq;
@@ -39,11 +40,67 @@ class MockFileCollection : public io::IFileCollection {
MOCK_METHOD0(GetDirSeparator, char());
};
+ResourceEntry* GetEntry(ResourceTable* table, const ResourceNameRef& res_name) {
+ auto result = table->FindResource(res_name);
+ return (result) ? result.value().entry : nullptr;
+}
+
+ResourceEntry* GetEntry(ResourceTable* table, const ResourceNameRef& res_name, ResourceId id) {
+ auto result = table->FindResource(res_name, id);
+ return (result) ? result.value().entry : nullptr;
+}
+
+TEST(ProtoSerializeTest, SerializeVisibility) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .Add(NewResourceBuilder("com.app.a:bool/foo")
+ .SetVisibility({Visibility::Level::kUndefined})
+ .Build())
+ .Add(NewResourceBuilder("com.app.a:bool/bar")
+ .SetVisibility({Visibility::Level::kPrivate})
+ .Build())
+ .Add(NewResourceBuilder("com.app.a:bool/baz")
+ .SetVisibility({Visibility::Level::kPublic})
+ .Build())
+ .Add(NewResourceBuilder("com.app.a:bool/fiz")
+ .SetVisibility({.level = Visibility::Level::kPublic, .staged_api = true})
+ .Build())
+ .Build();
+
+ ResourceTable new_table;
+ pb::ResourceTable pb_table;
+ MockFileCollection files;
+ std::string error;
+ SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
+ EXPECT_THAT(error, IsEmpty());
+
+ auto search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
+ ASSERT_TRUE(search_result);
+ EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
+ EXPECT_FALSE(search_result.value().entry->visibility.staged_api);
+
+ search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar"));
+ ASSERT_TRUE(search_result);
+ EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kPrivate));
+ EXPECT_FALSE(search_result.value().entry->visibility.staged_api);
+
+ search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz"));
+ ASSERT_TRUE(search_result);
+ EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
+ EXPECT_FALSE(search_result.value().entry->visibility.staged_api);
+
+ search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz"));
+ ASSERT_TRUE(search_result);
+ EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
+ EXPECT_TRUE(search_result.value().entry->visibility.staged_api);
+}
+
TEST(ProtoSerializeTest, SerializeSinglePackage) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.AddFileReference("com.app.a:layout/main", ResourceId(0x7f020000), "res/layout/main.xml")
.AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main")
.AddString("com.app.a:string/text", {}, "hi")
@@ -52,11 +109,11 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
true /*allow_new*/)
.Build();
- Visibility public_symbol;
- public_symbol.level = Visibility::Level::kPublic;
- ASSERT_TRUE(table->SetVisibilityWithId(test::ParseNameOrDie("com.app.a:layout/main"),
- public_symbol, ResourceId(0x7f020000),
- context->GetDiagnostics()));
+ ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:layout/main"))
+ .SetId(0x7f020000)
+ .SetVisibility({Visibility::Level::kPublic})
+ .Build(),
+ context->GetDiagnostics()));
Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo");
ASSERT_THAT(id, NotNull());
@@ -64,25 +121,35 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
// Make a plural.
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
plural->values[Plural::One] = util::make_unique<String>(table->string_pool.MakeRef("one"));
- ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"), ConfigDescription{},
- {}, std::move(plural), context->GetDiagnostics()));
+ ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:plurals/hey"))
+ .SetValue(std::move(plural))
+ .Build(),
+ context->GetDiagnostics()));
// Make a styled string.
StyleString style_string;
style_string.str = "hello";
style_string.spans.push_back(Span{"b", 0u, 4u});
+ ASSERT_TRUE(table->AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("com.app.a:string/styled"))
+ .SetValue(util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)))
+ .Build(),
+ context->GetDiagnostics()));
+
+ // Make a resource with different products.
ASSERT_TRUE(
- table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {},
- util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)),
+ table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:integer/one"))
+ .SetValue(test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u),
+ test::ParseConfigOrDie("land"))
+ .Build(),
context->GetDiagnostics()));
- // Make a resource with different products.
- ASSERT_TRUE(table->AddResource(
- test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), {},
- 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_HEX, 321u), context->GetDiagnostics()));
+ ASSERT_TRUE(
+ table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:integer/one"))
+ .SetValue(test::BuildPrimitive(android::Res_value::TYPE_INT_HEX, 321u),
+ test::ParseConfigOrDie("land"), "tablet")
+ .Build(),
+ 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
@@ -90,16 +157,20 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
Reference expected_ref;
expected_ref.name = test::ParseNameOrDie("android:layout/main");
expected_ref.id = ResourceId(0x01020000);
- ASSERT_TRUE(table->AddResource(
- test::ParseNameOrDie("com.app.a:layout/abc"), ConfigDescription::DefaultConfig(), {},
- util::make_unique<Reference>(expected_ref), context->GetDiagnostics()));
+ ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:layout/abc"))
+ .SetValue(util::make_unique<Reference>(expected_ref))
+ .Build(),
+ context->GetDiagnostics()));
// Make an overlayable resource.
OverlayableItem overlayable_item(std::make_shared<Overlayable>(
"OverlayableName", "overlay://theme", Source("res/values/overlayable.xml", 40)));
overlayable_item.source = Source("res/values/overlayable.xml", 42);
- ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
- overlayable_item, test::GetDiagnostics()));
+ ASSERT_TRUE(
+ table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:integer/overlayable"))
+ .SetOverlayable(overlayable_item)
+ .Build(),
+ context->GetDiagnostics()));
pb::ResourceTable pb_table;
SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
@@ -662,4 +733,222 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) {
EXPECT_FALSE(actual_ref->is_dynamic);
}
+TEST(ProtoSerializeTest, CollapsingResourceNamesNoNameCollapseExemptionsSucceeds) {
+ const uint32_t id_one_id = 0x7f020000;
+ const uint32_t id_two_id = 0x7f020001;
+ const uint32_t id_three_id = 0x7f020002;
+ const uint32_t integer_three_id = 0x7f030000;
+ const uint32_t string_test_id = 0x7f040000;
+ const uint32_t layout_bar_id = 0x7f050000;
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddSimple("com.app.test:id/one", ResourceId(id_one_id))
+ .AddSimple("com.app.test:id/two", ResourceId(id_two_id))
+ .AddValue("com.app.test:id/three", ResourceId(id_three_id),
+ test::BuildReference("com.app.test:id/one", ResourceId(id_one_id)))
+ .AddValue("com.app.test:integer/one", ResourceId(integer_three_id),
+ util::make_unique<BinaryPrimitive>(
+ uint8_t(android::Res_value::TYPE_INT_DEC), 1u))
+ .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
+ ResourceId(integer_three_id),
+ util::make_unique<BinaryPrimitive>(
+ uint8_t(android::Res_value::TYPE_INT_DEC), 2u))
+ .AddString("com.app.test:string/test", ResourceId(string_test_id), "foo")
+ .AddFileReference("com.app.test:layout/bar", ResourceId(layout_bar_id),
+ "res/layout/bar.xml")
+ .Build();
+
+ SerializeTableOptions options;
+ options.collapse_key_stringpool = true;
+
+ pb::ResourceTable pb_table;
+
+ SerializeTableToPb(*table, &pb_table, context->GetDiagnostics(), options);
+ test::TestFile file_a("res/layout/bar.xml");
+ MockFileCollection files;
+ EXPECT_CALL(files, FindFile(Eq("res/layout/bar.xml")))
+ .WillRepeatedly(::testing::Return(&file_a));
+ ResourceTable new_table;
+ std::string error;
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)) << error;
+ EXPECT_THAT(error, IsEmpty());
+
+ ResourceName real_id_resource(
+ "com.app.test", ResourceType::kId, "one");
+ EXPECT_THAT(GetEntry(&new_table, real_id_resource), IsNull());
+
+ ResourceName obfuscated_id_resource(
+ "com.app.test", ResourceType::kId, "0_resource_name_obfuscated");
+
+ EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource,
+ id_one_id), NotNull());
+ EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource,
+ id_two_id), NotNull());
+ ResourceEntry* entry = GetEntry(&new_table, obfuscated_id_resource, id_three_id);
+ EXPECT_THAT(entry, NotNull());
+ ResourceConfigValue* config_value = entry->FindValue({});
+ Reference* ref = ValueCast<Reference>(config_value->value.get());
+ EXPECT_THAT(ref->id.value(), Eq(id_one_id));
+
+ ResourceName obfuscated_integer_resource(
+ "com.app.test", ResourceType::kInteger, "0_resource_name_obfuscated");
+ entry = GetEntry(&new_table, obfuscated_integer_resource, integer_three_id);
+ EXPECT_THAT(entry, NotNull());
+ config_value = entry->FindValue({});
+ BinaryPrimitive* bp = ValueCast<BinaryPrimitive>(config_value->value.get());
+ EXPECT_THAT(bp->value.data, Eq(1u));
+
+ config_value = entry->FindValue(test::ParseConfigOrDie("v1"));
+ bp = ValueCast<BinaryPrimitive>(config_value->value.get());
+ EXPECT_THAT(bp->value.data, Eq(2u));
+
+ ResourceName obfuscated_string_resource(
+ "com.app.test", ResourceType::kString, "0_resource_name_obfuscated");
+ entry = GetEntry(&new_table, obfuscated_string_resource, string_test_id);
+ EXPECT_THAT(entry, NotNull());
+ config_value = entry->FindValue({});
+ String* s = ValueCast<String>(config_value->value.get());
+ EXPECT_THAT(*(s->value), Eq("foo"));
+
+ ResourceName obfuscated_layout_resource(
+ "com.app.test", ResourceType::kLayout, "0_resource_name_obfuscated");
+ entry = GetEntry(&new_table, obfuscated_layout_resource, layout_bar_id);
+ EXPECT_THAT(entry, NotNull());
+ config_value = entry->FindValue({});
+ FileReference* f = ValueCast<FileReference>(config_value->value.get());
+ EXPECT_THAT(*(f->path), Eq("res/layout/bar.xml"));
+}
+
+TEST(ProtoSerializeTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
+ const uint32_t id_one_id = 0x7f020000;
+ const uint32_t id_two_id = 0x7f020001;
+ const uint32_t id_three_id = 0x7f020002;
+ const uint32_t integer_three_id = 0x7f030000;
+ const uint32_t string_test_id = 0x7f040000;
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddSimple("com.app.test:id/one", ResourceId(id_one_id))
+ .AddSimple("com.app.test:id/two", ResourceId(id_two_id))
+ .AddValue("com.app.test:id/three", ResourceId(id_three_id),
+ test::BuildReference("com.app.test:id/one", ResourceId(id_one_id)))
+ .AddValue("com.app.test:integer/one", ResourceId(integer_three_id),
+ util::make_unique<BinaryPrimitive>(
+ uint8_t(android::Res_value::TYPE_INT_DEC), 1u))
+ .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
+ ResourceId(integer_three_id),
+ util::make_unique<BinaryPrimitive>(
+ uint8_t(android::Res_value::TYPE_INT_DEC), 2u))
+ .AddString("com.app.test:string/test", ResourceId(string_test_id), "foo")
+ .Build();
+
+ SerializeTableOptions options;
+ options.collapse_key_stringpool = true;
+ options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one"));
+ options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test"));
+ pb::ResourceTable pb_table;
+
+ SerializeTableToPb(*table, &pb_table, context->GetDiagnostics(), options);
+ MockFileCollection files;
+ ResourceTable new_table;
+ std::string error;
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)) << error;
+ EXPECT_THAT(error, IsEmpty());
+
+ EXPECT_THAT(GetEntry(&new_table, ResourceName("com.app.test", ResourceType::kId, "one"),
+ id_one_id), NotNull());
+ ResourceName obfuscated_id_resource(
+ "com.app.test", ResourceType::kId, "0_resource_name_obfuscated");
+ EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, id_one_id), IsNull());
+
+ ResourceName real_id_resource(
+ "com.app.test", ResourceType::kId, "two");
+ EXPECT_THAT(GetEntry(&new_table, real_id_resource, id_two_id), IsNull());
+ EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, id_two_id), NotNull());
+
+ ResourceEntry* entry = GetEntry(&new_table, obfuscated_id_resource, id_three_id);
+ EXPECT_THAT(entry, NotNull());
+ ResourceConfigValue* config_value = entry->FindValue({});
+ Reference* ref = ValueCast<Reference>(config_value->value.get());
+ EXPECT_THAT(ref->id.value(), Eq(id_one_id));
+
+ // Note that this resource is also named "one", but it's a different type, so gets obfuscated.
+ ResourceName obfuscated_integer_resource(
+ "com.app.test", ResourceType::kInteger, "0_resource_name_obfuscated");
+ entry = GetEntry(&new_table, obfuscated_integer_resource, integer_three_id);
+ EXPECT_THAT(entry, NotNull());
+ config_value = entry->FindValue({});
+ BinaryPrimitive* bp = ValueCast<BinaryPrimitive>(config_value->value.get());
+ EXPECT_THAT(bp->value.data, Eq(1u));
+
+ config_value = entry->FindValue(test::ParseConfigOrDie("v1"));
+ bp = ValueCast<BinaryPrimitive>(config_value->value.get());
+ EXPECT_THAT(bp->value.data, Eq(2u));
+
+ entry = GetEntry(&new_table, ResourceName("com.app.test", ResourceType::kString, "test"),
+ string_test_id);
+ EXPECT_THAT(entry, NotNull());
+ config_value = entry->FindValue({});
+ String* s = ValueCast<String>(config_value->value.get());
+ EXPECT_THAT(*(s->value), Eq("foo"));
+}
+
+TEST(ProtoSerializeTest, SerializeMacro) {
+ auto original = std::make_unique<Macro>();
+ original->raw_value = "\nThis being human is a guest house.";
+ original->style_string.str = " This being human is a guest house.";
+ original->style_string.spans.emplace_back(Span{.name = "b", .first_char = 12, .last_char = 16});
+ original->untranslatable_sections.emplace_back(UntranslatableSection{.start = 12, .end = 17});
+ original->alias_namespaces.emplace_back(
+ Macro::Namespace{.alias = "prefix", .package_name = "package.name", .is_private = true});
+
+ CloningValueTransformer cloner(nullptr);
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .Add(NewResourceBuilder("com.app.a:macro/foo")
+ .SetValue(original->Transform(cloner))
+ .Build())
+ .Build();
+
+ ResourceTable new_table;
+ pb::ResourceTable pb_table;
+ MockFileCollection files;
+ std::string error;
+ SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
+ EXPECT_THAT(error, IsEmpty());
+
+ Macro* deserialized = test::GetValue<Macro>(&new_table, "com.app.a:macro/foo");
+ ASSERT_THAT(deserialized, NotNull());
+ EXPECT_THAT(deserialized->raw_value, Eq(original->raw_value));
+ EXPECT_THAT(deserialized->style_string.str, Eq(original->style_string.str));
+ EXPECT_THAT(deserialized->style_string.spans, Eq(original->style_string.spans));
+ EXPECT_THAT(deserialized->untranslatable_sections, Eq(original->untranslatable_sections));
+ EXPECT_THAT(deserialized->alias_namespaces, Eq(original->alias_namespaces));
+}
+
+TEST(ProtoSerializeTest, StagedId) {
+ CloningValueTransformer cloner(nullptr);
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .Add(NewResourceBuilder("com.app.a:string/foo")
+ .SetStagedId(StagedId{.id = 0x01ff0001})
+ .Build())
+ .Build();
+
+ ResourceTable new_table;
+ pb::ResourceTable pb_table;
+ MockFileCollection files;
+ std::string error;
+ SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
+ EXPECT_THAT(error, IsEmpty());
+
+ auto result = new_table.FindResource(test::ParseNameOrDie("com.app.a:string/foo"));
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result.value().entry->staged_id);
+ EXPECT_THAT(result.value().entry->staged_id.value().id, Eq(ResourceId(0x01ff0001)));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 1e4b6816075a..2acdadb3c034 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -59,8 +59,9 @@ class ClassMember {
template <typename T>
class PrimitiveMember : public ClassMember {
public:
- PrimitiveMember(const android::StringPiece& name, const T& val)
- : name_(name.to_string()), val_(val) {}
+ PrimitiveMember(const android::StringPiece& name, const T& val, bool staged_api = false)
+ : name_(name.to_string()), val_(val), staged_api_(staged_api) {
+ }
bool empty() const override {
return false;
@@ -70,8 +71,8 @@ class PrimitiveMember : public ClassMember {
return name_;
}
- void Print(bool final, text::Printer* printer, bool strip_api_annotations = false)
- const override {
+ void Print(bool final, text::Printer* printer,
+ bool strip_api_annotations = false) const override {
using std::to_string;
ClassMember::Print(final, printer, strip_api_annotations);
@@ -80,7 +81,15 @@ class PrimitiveMember : public ClassMember {
if (final) {
printer->Print("final ");
}
- printer->Print("int ").Print(name_).Print("=").Print(to_string(val_)).Print(";");
+ printer->Print("int ").Print(name_);
+ if (staged_api_) {
+ // Prevent references to staged apis from being inline by setting their value out-of-line.
+ printer->Print("; static { ").Print(name_);
+ }
+ printer->Print("=").Print(to_string(val_)).Print(";");
+ if (staged_api_) {
+ printer->Print(" }");
+ }
}
private:
@@ -88,14 +97,16 @@ class PrimitiveMember : public ClassMember {
std::string name_;
T val_;
+ bool staged_api_;
};
// Specialization for strings so they get the right type and are quoted with "".
template <>
class PrimitiveMember<std::string> : public ClassMember {
public:
- PrimitiveMember(const android::StringPiece& name, const std::string& val)
- : name_(name.to_string()), val_(val) {}
+ PrimitiveMember(const android::StringPiece& name, const std::string& val, bool staged_api = false)
+ : name_(name.to_string()), val_(val) {
+ }
bool empty() const override {
return false;
@@ -127,13 +138,13 @@ using IntMember = PrimitiveMember<uint32_t>;
using ResourceMember = PrimitiveMember<ResourceId>;
using StringMember = PrimitiveMember<std::string>;
-template <typename T>
+template <typename T, typename StringConverter>
class PrimitiveArrayMember : public ClassMember {
public:
explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {}
void AddElement(const T& val) {
- elements_.push_back(val);
+ elements_.emplace_back(val);
}
bool empty() const override {
@@ -158,7 +169,7 @@ class PrimitiveArrayMember : public ClassMember {
printer->Println();
}
- printer->Print(to_string(*current));
+ printer->Print(StringConverter::ToString(*current));
if (std::distance(current, end) > 1) {
printer->Print(", ");
}
@@ -175,7 +186,24 @@ class PrimitiveArrayMember : public ClassMember {
std::vector<T> elements_;
};
-using ResourceArrayMember = PrimitiveArrayMember<ResourceId>;
+struct FieldReference {
+ explicit FieldReference(std::string reference) : ref(std::move(reference)) {
+ }
+ std::string ref;
+};
+
+struct ResourceArrayMemberStringConverter {
+ static std::string ToString(const std::variant<ResourceId, FieldReference>& ref) {
+ if (auto id = std::get_if<ResourceId>(&ref)) {
+ return to_string(*id);
+ } else {
+ return std::get<FieldReference>(ref).ref;
+ }
+ }
+};
+
+using ResourceArrayMember = PrimitiveArrayMember<std::variant<ResourceId, FieldReference>,
+ ResourceArrayMemberStringConverter>;
// Represents a method in a class.
class MethodDefinition : public ClassMember {
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index dffad3b99c06..de6524dc7027 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -218,16 +218,22 @@ struct StyleableAttr {
static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) {
const ResourceId lhs_id = lhs.attr_ref->id.value_or_default(ResourceId(0));
const ResourceId rhs_id = rhs.attr_ref->id.value_or_default(ResourceId(0));
- if (lhs_id < rhs_id) {
- return true;
- } else if (lhs_id > rhs_id) {
- return false;
- } else {
+ if (lhs_id == rhs_id) {
return lhs.attr_ref->name.value() < rhs.attr_ref->name.value();
}
+ return cmp_ids_dynamic_after_framework(lhs_id, rhs_id);
}
-void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
+static FieldReference GetRFieldReference(const ResourceName& name,
+ StringPiece fallback_package_name) {
+ const std::string package_name =
+ name.package.empty() ? fallback_package_name.to_string() : name.package;
+ const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry);
+ return FieldReference(
+ StringPrintf("%s.R.%s.%s", package_name.c_str(), to_string(name.type).data(), entry.c_str()));
+}
+
+bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
const Styleable& styleable,
const StringPiece& package_name_to_generate,
ClassDefinition* out_class_def,
@@ -343,14 +349,29 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res
// Add the ResourceIds to the array member.
for (size_t i = 0; i < attr_count; i++) {
- const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0));
- array_def->AddElement(id);
+ const StyleableAttr& attr = sorted_attributes[i];
+ std::string r_txt_contents;
+ if (attr.symbol && attr.symbol.value().is_dynamic) {
+ if (!attr.attr_ref->name) {
+ error_ = "unable to determine R.java field name of dynamic resource";
+ return false;
+ }
+
+ const FieldReference field_name =
+ GetRFieldReference(attr.attr_ref->name.value(), package_name_to_generate);
+ array_def->AddElement(field_name);
+ r_txt_contents = field_name.ref;
+ } else {
+ const ResourceId attr_id = attr.attr_ref->id.value_or_default(ResourceId(0));
+ array_def->AddElement(attr_id);
+ r_txt_contents = to_string(attr_id);
+ }
if (r_txt_printer != nullptr) {
if (i != 0) {
r_txt_printer->Print(",");
}
- r_txt_printer->Print(" ").Print(id.to_string());
+ r_txt_printer->Print(" ").Print(r_txt_contents);
}
}
@@ -422,19 +443,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res
}
}
- // If there is a rewrite method to generate, add the statements that rewrite package IDs
- // for this styleable.
- if (out_rewrite_method != nullptr) {
- out_rewrite_method->AppendStatement(
- StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data()));
- out_rewrite_method->AppendStatement(
- StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
- out_rewrite_method->AppendStatement(
- StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | packageIdBits;",
- array_field_name.data(), array_field_name.data()));
- out_rewrite_method->AppendStatement(" }");
- out_rewrite_method->AppendStatement("}");
- }
+ return true;
}
void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id,
@@ -451,8 +460,8 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso
const std::string field_name = TransformToFieldName(name.entry);
if (out_class_def != nullptr) {
- std::unique_ptr<ResourceMember> resource_member =
- util::make_unique<ResourceMember>(field_name, real_id);
+ auto resource_member =
+ util::make_unique<ResourceMember>(field_name, real_id, entry.visibility.staged_api);
// Build the comments and annotations for this entry.
AnnotationProcessor* processor = resource_member->GetCommentBuilder();
@@ -534,8 +543,8 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate
// Create an ID if there is one (static libraries don't need one).
ResourceId id;
- if (package.id && type.id && entry->id) {
- id = ResourceId(package.id.value(), type.id.value(), entry->id.value());
+ if (entry->id) {
+ id = entry->id.value();
}
// We need to make sure we hide the fact that we are generating kAttrPrivate attributes.
@@ -554,12 +563,11 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate
if (resource_name.type == ResourceType::kStyleable) {
CHECK(!entry->values.empty());
-
- const Styleable* styleable =
- static_cast<const Styleable*>(entry->values.front()->value.get());
-
- ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def,
- out_rewrite_method_def, r_txt_printer);
+ const auto styleable = reinterpret_cast<const Styleable*>(entry->values.front()->value.get());
+ if (!ProcessStyleable(resource_name, id, *styleable, package_name_to_generate,
+ out_type_class_def, out_rewrite_method_def, r_txt_printer)) {
+ return false;
+ }
} else {
ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def,
r_txt_printer);
@@ -608,8 +616,9 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
for (const auto& package : table_->packages) {
for (const auto& type : package->types) {
- if (type->type == ResourceType::kAttrPrivate) {
- // We generate these as part of the kAttr type, so skip them here.
+ if (type->type == ResourceType::kAttrPrivate || type->type == ResourceType::kMacro) {
+ // We generate kAttrPrivate as part of the kAttr type, so skip them here.
+ // Macros are not actual resources, so skip them as well.
continue;
}
@@ -629,8 +638,7 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
if (type->type == ResourceType::kAttr) {
// Also include private attributes in this same class.
- const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate);
- if (priv_type) {
+ if (const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate)) {
if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(),
rewrite_method.get(), r_txt_printer.get())) {
return false;
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 853120b3cb98..d9d1b39805f9 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -105,7 +105,7 @@ class JavaClassGenerator {
// Writes a styleable resource to the R.java file, optionally writing out a rewrite rule for
// its package ID if `out_rewrite_method` is not nullptr.
// `package_name_to_generate` is the package
- void ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
+ bool ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
const Styleable& styleable,
const android::StringPiece& package_name_to_generate,
ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method,
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 1e1fe4740c6b..40395ed64fe3 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -34,7 +34,6 @@ namespace aapt {
TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddSimple("android:id/class", ResourceId(0x01020000))
.Build();
@@ -54,7 +53,6 @@ TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddSimple("android:id/hey-man", ResourceId(0x01020000))
.AddValue("android:attr/cool.attr", ResourceId(0x01010000),
test::AttributeBuilder().Build())
@@ -84,7 +82,6 @@ TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddSimple("android:id/one", ResourceId(0x01020000))
.AddSimple("android:id/com.foo$two", ResourceId(0x01020001))
.Build();
@@ -110,8 +107,6 @@ TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
TEST(JavaClassGeneratorTest, StyleableAttributesWithDifferentPackageName) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
- .SetPackageId("app", 0x7f)
.AddValue("app:attr/foo", ResourceId(0x7f010000),
test::AttributeBuilder().Build())
.AddValue("app:attr/bar", ResourceId(0x7f010001),
@@ -159,7 +154,6 @@ TEST(JavaClassGeneratorTest, StyleableAttributesWithDifferentPackageName) {
TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddSimple("android:attr/two", ResourceId(0x01010001))
.AddSimple("android:^attr-private/one", ResourceId(0x01010000))
.Build();
@@ -184,7 +178,6 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
StdErrDiagnostics diag;
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddSimple("android:id/one", ResourceId(0x01020000))
.AddSimple("android:id/two", ResourceId(0x01020001))
.AddSimple("android:id/three", ResourceId(0x01020002))
@@ -276,8 +269,6 @@ ResourceId>()),
TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
- .SetPackageId("com.lib", 0x02)
.AddValue("android:attr/bar", ResourceId(0x01010000), test::AttributeBuilder().Build())
.AddValue("com.lib:attr/bar", ResourceId(0x02010000), test::AttributeBuilder().Build())
.AddValue("android:styleable/foo", ResourceId(0x01030000),
@@ -306,7 +297,6 @@ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddSimple("android:id/foo", ResourceId(0x01010000))
.Build();
test::GetValue<Id>(table.get(), "android:id/foo")
@@ -344,12 +334,12 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent)
styleable.entries.push_back(Reference(test::ParseNameOrDie("android:attr/one")));
styleable.SetComment(StringPiece("This is a styleable"));
+ CloningValueTransformer cloner(nullptr);
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddValue("android:attr/one", util::make_unique<Attribute>(attr))
.AddValue("android:styleable/Container",
- std::unique_ptr<Styleable>(styleable.Clone(nullptr)))
+ std::unique_ptr<Styleable>(styleable.Transform(cloner)))
.Build();
std::unique_ptr<IAaptContext> context =
@@ -382,12 +372,12 @@ TEST(JavaClassGeneratorTest, CommentsForStyleableHiddenAttributesAreNotPresent)
styleable.entries.push_back(Reference(test::ParseNameOrDie("android:attr/one")));
styleable.SetComment(StringPiece("This is a styleable"));
+ CloningValueTransformer cloner(nullptr);
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddValue("android:attr/one", util::make_unique<Attribute>(attr))
.AddValue("android:styleable/Container",
- std::unique_ptr<Styleable>(styleable.Clone(nullptr)))
+ std::unique_ptr<Styleable>(styleable.Transform(cloner)))
.Build();
std::unique_ptr<IAaptContext> context =
@@ -415,7 +405,6 @@ TEST(JavaClassGeneratorTest, CommentsForStyleableHiddenAttributesAreNotPresent)
TEST(JavaClassGeneratorTest, StyleableAndIndicesAreColocated) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddValue("android:attr/layout_gravity", util::make_unique<Attribute>())
.AddValue("android:attr/background", util::make_unique<Attribute>())
.AddValue("android:styleable/ActionBar",
@@ -467,7 +456,6 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddValue("android:attr/one", util::make_unique<Attribute>(attr))
.Build();
@@ -499,7 +487,6 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) {
TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x00)
.AddValue("android:attr/foo", ResourceId(0x00010000), util::make_unique<Attribute>())
.AddValue("android:id/foo", ResourceId(0x00020000), util::make_unique<Id>())
.AddValue(
@@ -536,7 +523,6 @@ TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary)
TEST(JavaClassGeneratorTest, OnlyGenerateRText) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>())
.AddValue("android:styleable/hey.dude", ResourceId(0x01020000),
test::StyleableBuilder()
@@ -551,4 +537,58 @@ TEST(JavaClassGeneratorTest, OnlyGenerateRText) {
ASSERT_TRUE(generator.Generate("android", nullptr));
}
+TEST(JavaClassGeneratorTest, SortsDynamicAttributesAfterFrameworkAttributes) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddValue("android:attr/framework_attr", ResourceId(0x01010000),
+ test::AttributeBuilder().Build())
+ .AddValue("lib:attr/dynamic_attr", ResourceId(0x00010000),
+ test::AttributeBuilder().Build())
+ .AddValue("lib:styleable/MyStyleable", ResourceId(0x00030000),
+ test::StyleableBuilder()
+ .AddItem("android:attr/framework_attr", ResourceId(0x01010000))
+ .AddItem("lib:attr/dynamic_attr", ResourceId(0x00010000))
+ .Build())
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"custom"})
+ .SetCompilationPackage("custom")
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+
+ std::string output;
+ StringOutputStream out(&output);
+ EXPECT_TRUE(generator.Generate("lib", &out));
+ out.Flush();
+
+ EXPECT_THAT(output, HasSubstr("public static final int[] MyStyleable={"));
+ EXPECT_THAT(output, HasSubstr("0x01010000, lib.R.attr.dynamic_attr"));
+ EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_android_framework_attr=0;"));
+ EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_dynamic_attr=1;"));
+}
+
+TEST(JavaClassGeneratorTest, SkipMacros) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddValue("android:macro/bar", ResourceId(0x01010000), test::AttributeBuilder().Build())
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"android"})
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+
+ std::string output;
+ StringOutputStream out(&output);
+ EXPECT_TRUE(generator.Generate("android", &out));
+ out.Flush();
+
+ EXPECT_THAT(output, Not(HasSubstr("bar")));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index c7ae0b6a75cc..e1040666e410 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -247,8 +247,10 @@ TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) {
ResourceTable table;
StdErrDiagnostics errDiagnostics;
- table.AddResource(bar_layout->file.name, ConfigDescription::DefaultConfig(), "",
- util::make_unique<FileReference>(), &errDiagnostics);
+ table.AddResource(NewResourceBuilder(bar_layout->file.name)
+ .SetValue(util::make_unique<FileReference>())
+ .Build(),
+ &errDiagnostics);
std::unique_ptr<IAaptContext> context =
test::ContextBuilder()
@@ -262,7 +264,7 @@ TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) {
</View>)");
foo_layout->file.name = test::ParseNameOrDie("com.foo:layout/foo");
- XmlReferenceLinker xml_linker;
+ XmlReferenceLinker xml_linker(nullptr);
ASSERT_TRUE(xml_linker.Consume(context.get(), bar_layout.get()));
ASSERT_TRUE(xml_linker.Consume(context.get(), foo_layout.get()));
diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp
index ba9646f9aeb4..ec3c5431c7a3 100644
--- a/tools/aapt2/jni/aapt2_jni.cpp
+++ b/tools/aapt2/jni/aapt2_jni.cpp
@@ -139,5 +139,5 @@ JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink(JNIEnv*
JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping(
JNIEnv *env, jclass aapt_obj) {
- // This is just a dummy method to see if the library has been loaded.
+ // This is just a no-op method to see if the library has been loaded.
}
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
index 73b92542a755..876494e617a6 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -72,6 +72,7 @@ ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry,
bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) {
TRACE_NAME("AutoVersioner::Consume");
+ CloningValueTransformer cloner(&table->string_pool);
for (auto& package : table->packages) {
for (auto& type : package->types) {
if (type->type != ResourceType::kStyle) {
@@ -128,7 +129,7 @@ bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) {
ConfigDescription new_config(config_value->config);
new_config.sdkVersion = static_cast<uint16_t>(min_sdk_stripped.value());
- std::unique_ptr<Style> new_style(style->Clone(&table->string_pool));
+ std::unique_ptr<Style> new_style(style->Transform(cloner));
new_style->SetComment(style->GetComment());
new_style->SetSource(style->GetSource());
diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp
index 1117472f104b..02fd00bba439 100644
--- a/tools/aapt2/link/AutoVersioner_test.cpp
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -54,7 +54,6 @@ TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) {
TEST(AutoVersionerTest, VersionStylesForTable) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("app", 0x7f)
.AddValue(
"app:style/Foo", test::ParseConfigOrDie("v4"),
ResourceId(0x7f020000),
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index c9b8d3993959..be6c930b9284 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -133,12 +133,14 @@ class XmlNamespaceRemover : public IXmlResourceConsumer {
// Once an XmlResource is processed by this linker, it is ready to be flattened.
class XmlReferenceLinker : public IXmlResourceConsumer {
public:
- XmlReferenceLinker() = default;
+ explicit XmlReferenceLinker(ResourceTable* table) : table_(table) {
+ }
bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
private:
DISALLOW_COPY_AND_ASSIGN(XmlReferenceLinker);
+ ResourceTable* table_;
};
} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index c813a446b8db..8abd9dec56be 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -112,6 +112,27 @@ static xml::XmlNodeAction::ActionFuncWithDiag RequiredAndroidAttribute(const std
};
}
+static xml::XmlNodeAction::ActionFuncWithDiag RequiredOneAndroidAttribute(
+ const std::string& attrName1, const std::string& attrName2) {
+ return [=](xml::Element* el, SourcePathDiagnostics* diag) -> bool {
+ xml::Attribute* attr1 = el->FindAttribute(xml::kSchemaAndroid, attrName1);
+ xml::Attribute* attr2 = el->FindAttribute(xml::kSchemaAndroid, attrName2);
+ if (attr1 == nullptr && attr2 == nullptr) {
+ diag->Error(DiagMessage(el->line_number)
+ << "<" << el->name << "> is missing required attribute 'android:" << attrName1
+ << "' or 'android:" << attrName2 << "'");
+ return false;
+ }
+ if (attr1 != nullptr && attr2 != nullptr) {
+ diag->Error(DiagMessage(el->line_number)
+ << "<" << el->name << "> can only specify one of attribute 'android:" << attrName1
+ << "' or 'android:" << attrName2 << "'");
+ return false;
+ }
+ return true;
+ };
+}
+
static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* diag) {
constexpr const char* kFeatureSplit = "featureSplit";
constexpr const char* kIsFeatureSplit = "isFeatureSplit";
@@ -142,7 +163,8 @@ static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics*
return true;
}
-static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
+static bool VerifyManifest(xml::Element* el, xml::XmlActionExecutorPolicy policy,
+ SourcePathDiagnostics* diag) {
xml::Attribute* attr = el->FindAttribute({}, "package");
if (!attr) {
diag->Error(DiagMessage(el->line_number)
@@ -153,10 +175,16 @@ static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
<< "attribute 'package' in <manifest> tag must not be a reference");
return false;
} else if (!util::IsAndroidPackageName(attr->value)) {
- diag->Error(DiagMessage(el->line_number)
- << "attribute 'package' in <manifest> tag is not a valid Android package name: '"
- << attr->value << "'");
- return false;
+ DiagMessage error_msg(el->line_number);
+ error_msg << "attribute 'package' in <manifest> tag is not a valid Android package name: '"
+ << attr->value << "'";
+ if (policy == xml::XmlActionExecutorPolicy::kAllowListWarning) {
+ // Treat the error only as a warning.
+ diag->Warn(error_msg);
+ } else {
+ diag->Error(error_msg);
+ return false;
+ }
}
attr = el->FindAttribute({}, "split");
@@ -282,6 +310,10 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
// Common <meta-data> actions.
xml::XmlNodeAction meta_data_action;
+ // Common <property> actions.
+ xml::XmlNodeAction property_action;
+ property_action.Action(RequiredOneAndroidAttribute("resource", "value"));
+
// Common <uses-feature> actions.
xml::XmlNodeAction uses_feature_action;
uses_feature_action.Action(VerifyUsesFeature);
@@ -292,6 +324,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
component_action["intent-filter"] = intent_filter_action;
component_action["preferred"] = intent_filter_action;
component_action["meta-data"] = meta_data_action;
+ component_action["property"] = property_action;
// Manifest actions.
xml::XmlNodeAction& manifest_action = (*executor)["manifest"];
@@ -334,6 +367,16 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
}
}
+ if (options_.revision_code_default) {
+ if (options_.replace_version) {
+ el->RemoveAttribute(xml::kSchemaAndroid, "revisionCode");
+ }
+ if (el->FindAttribute(xml::kSchemaAndroid, "revisionCode") == nullptr) {
+ el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "revisionCode",
+ options_.revision_code_default.value()});
+ }
+ }
+
return true;
});
@@ -376,10 +419,6 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
});
manifest_action["instrumentation"]["meta-data"] = meta_data_action;
- // TODO moltmann: Remove
- manifest_action["feature"];
- manifest_action["feature"]["inherit-from"];
-
manifest_action["attribution"];
manifest_action["attribution"]["inherit-from"];
manifest_action["original-package"];
@@ -397,6 +436,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
manifest_action["protected-broadcast"];
manifest_action["adopt-permissions"];
manifest_action["uses-permission"];
+ manifest_action["uses-permission"]["required-feature"].Action(RequiredNameIsNotEmpty);
+ manifest_action["uses-permission"]["required-not-feature"].Action(RequiredNameIsNotEmpty);
manifest_action["uses-permission-sdk-23"];
manifest_action["permission"];
manifest_action["permission"]["meta-data"] = meta_data_action;
@@ -426,8 +467,10 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
application_action.Action(OptionalNameIsJavaClassName);
application_action["uses-library"].Action(RequiredNameIsNotEmpty);
+ application_action["uses-native-library"].Action(RequiredNameIsNotEmpty);
application_action["library"].Action(RequiredNameIsNotEmpty);
application_action["profileable"];
+ application_action["property"] = property_action;
xml::XmlNodeAction& static_library_action = application_action["static-library"];
static_library_action.Action(RequiredNameIsJavaPackage);
@@ -573,8 +616,8 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) {
}
xml::XmlActionExecutorPolicy policy = options_.warn_validation
- ? xml::XmlActionExecutorPolicy::kWhitelistWarning
- : xml::XmlActionExecutorPolicy::kWhitelist;
+ ? xml::XmlActionExecutorPolicy::kAllowListWarning
+ : xml::XmlActionExecutorPolicy::kAllowList;
if (!executor.Execute(policy, context->GetDiagnostics(), doc)) {
return false;
}
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index ec4367b450fb..34ad8d586df1 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -60,6 +60,10 @@ struct ManifestFixerOptions {
// replace_version is set.
Maybe<std::string> version_code_major_default;
+ // The revision code to set if 'android:revisionCode' is not defined in <manifest> or if
+ // replace_version is set.
+ Maybe<std::string> revision_code_default;
+
// The version of the framework being compiled against to set for 'android:compileSdkVersion' in
// the <manifest> tag.
Maybe<std::string> compile_sdk_version;
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 0791805e2506..432f10bdab97 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -445,6 +445,66 @@ TEST_F(ManifestFixerTest, ReplaceVersionNameAndCode) {
EXPECT_THAT(attr->value, StrEq("0x20000000"));
}
+TEST_F(ManifestFixerTest, UseDefaultRevisionCode) {
+ ManifestFixerOptions options;
+ options.revision_code_default = std::string("0x10000000");
+
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android"
+ android:versionCode="0x00000001" />)EOF",
+ options);
+ ASSERT_THAT(doc, NotNull());
+
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
+
+ xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x10000000"));
+}
+
+TEST_F(ManifestFixerTest, DontUseDefaultRevisionCode) {
+ ManifestFixerOptions options;
+ options.revision_code_default = std::string("0x10000000");
+
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android"
+ android:versionCode="0x00000001"
+ android:revisionCode="0x00000002" />)EOF",
+ options);
+ ASSERT_THAT(doc, NotNull());
+
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
+
+ xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x00000002"));
+}
+
+TEST_F(ManifestFixerTest, ReplaceRevisionCode) {
+ ManifestFixerOptions options;
+ options.replace_version = true;
+ options.revision_code_default = std::string("0x10000000");
+
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android"
+ android:versionCode="0x00000001"
+ android:revisionCode="0x00000002" />)EOF",
+ options);
+ ASSERT_THAT(doc, NotNull());
+
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
+
+ xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x10000000"));
+}
+
TEST_F(ManifestFixerTest, ReplaceVersionName) {
ManifestFixerOptions options;
options.replace_version = true;
@@ -886,4 +946,78 @@ TEST_F(ManifestFixerTest, UsesLibraryMustHaveNonEmptyName) {
EXPECT_THAT(Verify(input), NotNull());
}
+TEST_F(ManifestFixerTest, ApplicationPropertyAttributeRequired) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <property android:name="" />
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+}
+
+TEST_F(ManifestFixerTest, ApplicationPropertyOnlyOneAttributeDefined) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <property android:name="" android:value="" android:resource="" />
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <property android:name="" android:resource="" />
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <property android:name="" android:value="" />
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+}
+
+TEST_F(ManifestFixerTest, ComponentPropertyOnlyOneAttributeDefined) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MyActivity">
+ <property android:name="" android:value="" android:resource="" />
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MyActivity">
+ <property android:name="" android:value="" />
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MyActivity">
+ <property android:name="" android:resource="" />
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+}
} // namespace aapt
diff --git a/tools/aapt2/link/NoDefaultResourceRemover_test.cpp b/tools/aapt2/link/NoDefaultResourceRemover_test.cpp
index d129c9ac8db7..3179fefc3fd0 100644
--- a/tools/aapt2/link/NoDefaultResourceRemover_test.cpp
+++ b/tools/aapt2/link/NoDefaultResourceRemover_test.cpp
@@ -21,10 +21,9 @@
namespace aapt {
TEST(NoDefaultResourceRemoverTest, RemoveEntryWithNoDefaultAndOnlyLocales) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetPackageId(0x01).Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddSimple("android:string/foo")
.AddSimple("android:string/foo", test::ParseConfigOrDie("en-rGB"))
.AddSimple("android:string/foo", test::ParseConfigOrDie("fr-rFR"))
@@ -47,10 +46,10 @@ TEST(NoDefaultResourceRemoverTest, RemoveEntryWithNoDefaultAndOnlyLocales) {
}
TEST(NoDefaultResourceRemoverTest, KeepEntryWithLocalesAndDensities) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetMinSdkVersion(26).Build();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetPackageId(0x01).SetMinSdkVersion(26).Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddSimple("android:drawable/keep1", test::ParseConfigOrDie("mdpi")) // v4
.AddSimple("android:drawable/keep1", test::ParseConfigOrDie("en-rGB"))
.AddSimple("android:drawable/keep1", test::ParseConfigOrDie("fr-rFR"))
@@ -71,10 +70,10 @@ TEST(NoDefaultResourceRemoverTest, KeepEntryWithLocalesAndDensities) {
}
TEST(NoDefaultResourceRemoverTest, RemoveEntryWithLocalesAndDensitiesLowVersion) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetMinSdkVersion(3).Build();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetPackageId(0x01).SetMinSdkVersion(3).Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddSimple("android:drawable/remove1", test::ParseConfigOrDie("mdpi")) // v4
.AddSimple("android:drawable/remove1", test::ParseConfigOrDie("en-rGB"))
.AddSimple("android:drawable/remove1", test::ParseConfigOrDie("fr-rFR"))
@@ -87,10 +86,10 @@ TEST(NoDefaultResourceRemoverTest, RemoveEntryWithLocalesAndDensitiesLowVersion)
}
TEST(NoDefaultResourceRemoverTest, KeepEntryWithVersion) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetMinSdkVersion(8).Build();
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetPackageId(0x01).SetMinSdkVersion(8).Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("android", 0x01)
.AddSimple("android:drawable/keep1", test::ParseConfigOrDie("v8"))
.AddSimple("android:drawable/keep1", test::ParseConfigOrDie("en-rGB"))
.AddSimple("android:drawable/keep1", test::ParseConfigOrDie("fr-rFR"))
diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp
index dd4767463711..4f78bbcece33 100644
--- a/tools/aapt2/link/ProductFilter_test.cpp
+++ b/tools/aapt2/link/ProductFilter_test.cpp
@@ -30,21 +30,29 @@ TEST(ProductFilterTest, SelectTwoProducts) {
ResourceTable table;
ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"), land, "",
- test::ValueBuilder<Id>().SetSource(Source("land/default.xml")).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source("land/default.xml")).Build(), land)
+ .Build(),
context->GetDiagnostics()));
+
ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"), land, "tablet",
- test::ValueBuilder<Id>().SetSource(Source("land/tablet.xml")).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source("land/tablet.xml")).Build(), land,
+ "tablet")
+ .Build(),
context->GetDiagnostics()));
ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"), port, "",
- test::ValueBuilder<Id>().SetSource(Source("port/default.xml")).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source("port/default.xml")).Build(), port)
+ .Build(),
context->GetDiagnostics()));
+
ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"), port, "tablet",
- test::ValueBuilder<Id>().SetSource(Source("port/tablet.xml")).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source("port/tablet.xml")).Build(), port,
+ "tablet")
+ .Build(),
context->GetDiagnostics()));
ProductFilter filter({"tablet"});
@@ -65,15 +73,17 @@ TEST(ProductFilterTest, SelectDefaultProduct) {
ResourceTable table;
ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"),
- ConfigDescription::DefaultConfig(), "",
- test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build())
+ .Build(),
context->GetDiagnostics()));
+
ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"),
- ConfigDescription::DefaultConfig(), "tablet",
- test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(), {}, "tablet")
+ .Build(),
context->GetDiagnostics()));
+ ;
ProductFilter filter(std::unordered_set<std::string>{});
ASSERT_TRUE(filter.Consume(context.get(), &table));
@@ -91,19 +101,22 @@ TEST(ProductFilterTest, FailOnAmbiguousProduct) {
ResourceTable table;
ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"),
- ConfigDescription::DefaultConfig(), "",
- test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build())
+ .Build(),
context->GetDiagnostics()));
+
ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"),
- ConfigDescription::DefaultConfig(), "tablet",
- test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(), {}, "tablet")
+ .Build(),
context->GetDiagnostics()));
+
ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"),
- ConfigDescription::DefaultConfig(), "no-sdcard",
- test::ValueBuilder<Id>().SetSource(Source("no-sdcard.xml")).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source("no-sdcard.xml")).Build(), {},
+ "no-sdcard")
+ .Build(),
context->GetDiagnostics()));
ProductFilter filter({"tablet", "no-sdcard"});
@@ -114,15 +127,17 @@ TEST(ProductFilterTest, FailOnMultipleDefaults) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
ResourceTable table;
+ ASSERT_TRUE(
+ table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source(".xml")).Build())
+ .Build(),
+ context->GetDiagnostics()));
+
ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"),
- ConfigDescription::DefaultConfig(), "",
- test::ValueBuilder<Id>().SetSource(Source(".xml")).Build(),
- context->GetDiagnostics()));
- ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:string/one"),
- ConfigDescription::DefaultConfig(), "default",
- test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(),
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(), {},
+ "default")
+ .Build(),
context->GetDiagnostics()));
ProductFilter filter(std::unordered_set<std::string>{});
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 8e49fabe6a5c..4ac25bd6a0e0 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -21,6 +21,7 @@
#include "androidfw/ResourceTypes.h"
#include "Diagnostics.h"
+#include "ResourceParser.h"
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
@@ -37,128 +38,153 @@ using ::android::StringPiece;
using ::android::base::StringPrintf;
namespace aapt {
-
namespace {
+struct LoggingResourceName {
+ LoggingResourceName(const Reference& ref, const CallSite& callsite,
+ const xml::IPackageDeclStack* decls)
+ : ref_(ref), callsite_(callsite), decls_(decls) {
+ }
-// The ReferenceLinkerVisitor will follow all references and make sure they point
-// to resources that actually exist, either in the local resource table, or as external
-// symbols. Once the target resource has been found, the ID of the resource will be assigned
-// to the reference object.
-//
-// NOTE: All of the entries in the ResourceTable must be assigned IDs.
-class ReferenceLinkerVisitor : public DescendingValueVisitor {
- public:
- using DescendingValueVisitor::Visit;
-
- ReferenceLinkerVisitor(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols,
- StringPool* string_pool, xml::IPackageDeclStack* decl)
- : callsite_(callsite),
- context_(context),
- symbols_(symbols),
- package_decls_(decl),
- string_pool_(string_pool) {}
-
- void Visit(Reference* ref) override {
- if (!ReferenceLinker::LinkReference(callsite_, ref, context_, symbols_, package_decls_)) {
- error_ = true;
- }
+ const Reference& ref_;
+ const CallSite& callsite_;
+ const xml::IPackageDeclStack* decls_;
+};
+
+inline ::std::ostream& operator<<(::std::ostream& out, const LoggingResourceName& name) {
+ if (!name.ref_.name) {
+ out << name.ref_.id.value();
+ return out;
}
- // We visit the Style specially because during this phase, values of attributes are
- // all RawString values. Now that we are expected to resolve all symbols, we can
- // lookup the attributes to find out which types are allowed for the attributes' values.
- void Visit(Style* style) override {
- if (style->parent) {
- Visit(&style->parent.value());
- }
+ out << name.ref_.name.value();
+
+ Reference fully_qualified = name.ref_;
+ xml::ResolvePackage(name.decls_, &fully_qualified);
+
+ ResourceName& full_name = fully_qualified.name.value();
+ if (full_name.package.empty()) {
+ full_name.package = name.callsite_.package;
+ }
- for (Style::Entry& entry : style->entries) {
- std::string err_str;
+ if (full_name != name.ref_.name.value()) {
+ out << " (aka " << full_name << ")";
+ }
+ return out;
+}
- // Transform the attribute reference so that it is using the fully qualified package
- // name. This will also mark the reference as being able to see private resources if
- // there was a '*' in the reference or if the package came from the private namespace.
- Reference transformed_reference = entry.key;
- ResolvePackage(package_decls_, &transformed_reference);
+} // namespace
- // 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_, context_, symbols_, &err_str);
- if (symbol) {
- // Assign our style key the correct ID. The ID may not exist.
- entry.key.id = symbol->id;
-
- // Try to convert the value to a more specific, typed value based on the attribute it is
- // set to.
- entry.value = ParseValueWithAttribute(std::move(entry.value), symbol->attribute.get());
-
- // Link/resolve the final value (mostly if it's a reference).
- entry.value->Accept(this);
-
- // Now verify that the type of this item is compatible with the
- // attribute it is defined for. We pass `nullptr` as the DiagMessage so that this
- // check is fast and we avoid creating a DiagMessage when the match is successful.
- if (!symbol->attribute->Matches(*entry.value, nullptr)) {
- // The actual type of this item is incompatible with the attribute.
- DiagMessage msg(entry.key.GetSource());
-
- // Call the matches method again, this time with a DiagMessage so we fill in the actual
- // error message.
- symbol->attribute->Matches(*entry.value, &msg);
- context_->GetDiagnostics()->Error(msg);
- error_ = true;
- }
+std::unique_ptr<Reference> ReferenceLinkerTransformer::TransformDerived(const Reference* value) {
+ auto linked_item =
+ ReferenceLinker::LinkReference(callsite_, *value, context_, symbols_, table_, package_decls_);
+ if (linked_item) {
+ auto linked_item_ptr = linked_item.release();
+ if (auto ref = ValueCast<Reference>(linked_item_ptr)) {
+ return std::unique_ptr<Reference>(ref);
+ }
+ context_->GetDiagnostics()->Error(DiagMessage(value->GetSource())
+ << "value of '"
+ << LoggingResourceName(*value, callsite_, package_decls_)
+ << "' must be a resource reference");
+ delete linked_item_ptr;
+ }
+
+ error_ = true;
+ return CloningValueTransformer::TransformDerived(value);
+}
+
+std::unique_ptr<Style> ReferenceLinkerTransformer::TransformDerived(const Style* style) {
+ // We visit the Style specially because during this phase, values of attributes are either
+ // RawString or Reference values. Now that we are expected to resolve all symbols, we can lookup
+ // the attributes to find out which types are allowed for the attributes' values.
+ auto new_style = CloningValueTransformer::TransformDerived(style);
+ if (new_style->parent) {
+ new_style->parent = *TransformDerived(&style->parent.value());
+ }
- } else {
+ for (Style::Entry& entry : new_style->entries) {
+ std::string err_str;
+
+ // Transform the attribute reference so that it is using the fully qualified package
+ // name. This will also mark the reference as being able to see private resources if
+ // there was a '*' in the reference or if the package came from the private namespace.
+ Reference transformed_reference = entry.key;
+ ResolvePackage(package_decls_, &transformed_reference);
+
+ // 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_, context_, symbols_, &err_str);
+ if (symbol) {
+ // Assign our style key the correct ID. The ID may not exist.
+ entry.key.id = symbol->id;
+
+ // Link/resolve the final value if it's a reference.
+ entry.value = entry.value->Transform(*this);
+
+ // Try to convert the value to a more specific, typed value based on the attribute it is
+ // set to.
+ entry.value = ParseValueWithAttribute(std::move(entry.value), symbol->attribute.get());
+
+ // Now verify that the type of this item is compatible with the
+ // attribute it is defined for. We pass `nullptr` as the DiagMessage so that this
+ // check is fast and we avoid creating a DiagMessage when the match is successful.
+ if (!symbol->attribute->Matches(*entry.value, nullptr)) {
+ // The actual type of this item is incompatible with the attribute.
DiagMessage msg(entry.key.GetSource());
- msg << "style attribute '";
- ReferenceLinker::WriteResourceName(entry.key, callsite_, package_decls_, &msg);
- msg << "' " << err_str;
+
+ // Call the matches method again, this time with a DiagMessage so we fill in the actual
+ // error message.
+ symbol->attribute->Matches(*entry.value, &msg);
context_->GetDiagnostics()->Error(msg);
error_ = true;
}
+ } else {
+ context_->GetDiagnostics()->Error(DiagMessage(entry.key.GetSource())
+ << "style attribute '"
+ << LoggingResourceName(entry.key, callsite_, package_decls_)
+ << "' " << err_str);
+
+ error_ = true;
}
}
+ return new_style;
+}
- bool HasError() {
- return error_;
+std::unique_ptr<Item> ReferenceLinkerTransformer::TransformItem(const Reference* value) {
+ auto linked_value =
+ ReferenceLinker::LinkReference(callsite_, *value, context_, symbols_, table_, package_decls_);
+ if (linked_value) {
+ return linked_value;
}
+ error_ = true;
+ return CloningValueTransformer::TransformDerived(value);
+}
- private:
- DISALLOW_COPY_AND_ASSIGN(ReferenceLinkerVisitor);
-
- // Transform a RawString value into a more specific, appropriate value, based on the
- // Attribute. If a non RawString value is passed in, this is an identity transform.
- std::unique_ptr<Item> ParseValueWithAttribute(std::unique_ptr<Item> value,
- const Attribute* attr) {
- if (RawString* raw_string = ValueCast<RawString>(value.get())) {
- std::unique_ptr<Item> transformed =
- ResourceUtils::TryParseItemForAttribute(*raw_string->value, attr);
-
- // If we could not parse as any specific type, try a basic STRING.
- if (!transformed && (attr->type_mask & android::ResTable_map::TYPE_STRING)) {
- StringBuilder string_builder;
- string_builder.AppendText(*raw_string->value);
- if (string_builder) {
- transformed =
- util::make_unique<String>(string_pool_->MakeRef(string_builder.to_string()));
- }
+// Transform a RawString value into a more specific, appropriate value, based on the
+// Attribute. If a non RawString value is passed in, this is an identity transform.
+std::unique_ptr<Item> ReferenceLinkerTransformer::ParseValueWithAttribute(
+ std::unique_ptr<Item> value, const Attribute* attr) {
+ if (RawString* raw_string = ValueCast<RawString>(value.get())) {
+ std::unique_ptr<Item> transformed =
+ ResourceUtils::TryParseItemForAttribute(*raw_string->value, attr);
+
+ // If we could not parse as any specific type, try a basic STRING.
+ if (!transformed && (attr->type_mask & android::ResTable_map::TYPE_STRING)) {
+ StringBuilder string_builder;
+ string_builder.AppendText(*raw_string->value);
+ if (string_builder) {
+ transformed = util::make_unique<String>(pool_->MakeRef(string_builder.to_string()));
}
+ }
- if (transformed) {
- return transformed;
- }
+ if (transformed) {
+ return transformed;
}
- return value;
}
+ return value;
+}
- const CallSite& callsite_;
- IAaptContext* context_;
- SymbolTable* symbols_;
- xml::IPackageDeclStack* package_decls_;
- StringPool* string_pool_;
- bool error_ = false;
-};
+namespace {
class EmptyDeclStack : public xml::IPackageDeclStack {
public:
@@ -175,6 +201,27 @@ class EmptyDeclStack : public xml::IPackageDeclStack {
DISALLOW_COPY_AND_ASSIGN(EmptyDeclStack);
};
+struct MacroDeclStack : public xml::IPackageDeclStack {
+ explicit MacroDeclStack(std::vector<Macro::Namespace> namespaces)
+ : alias_namespaces_(std::move(namespaces)) {
+ }
+
+ Maybe<xml::ExtractedPackage> TransformPackageAlias(const StringPiece& alias) const override {
+ if (alias.empty()) {
+ return xml::ExtractedPackage{{}, true /*private*/};
+ }
+ for (auto it = alias_namespaces_.rbegin(); it != alias_namespaces_.rend(); ++it) {
+ if (alias == StringPiece(it->alias)) {
+ return xml::ExtractedPackage{it->package_name, it->is_private};
+ }
+ }
+ return {};
+ }
+
+ private:
+ std::vector<Macro::Namespace> alias_namespaces_;
+};
+
// The symbol is visible if it is public, or if the reference to it is requesting private access
// or if the callsite comes from the same package.
bool IsSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref,
@@ -220,8 +267,6 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& refer
// 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) {
@@ -295,29 +340,6 @@ Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference&
return xml::AaptAttribute(*symbol->attribute, symbol->id);
}
-void ReferenceLinker::WriteResourceName(const Reference& ref, const CallSite& callsite,
- const xml::IPackageDeclStack* decls, DiagMessage* out_msg) {
- CHECK(out_msg != nullptr);
- if (!ref.name) {
- *out_msg << ref.id.value();
- return;
- }
-
- *out_msg << ref.name.value();
-
- Reference fully_qualified = ref;
- xml::ResolvePackage(decls, &fully_qualified);
-
- ResourceName& full_name = fully_qualified.name.value();
- if (full_name.package.empty()) {
- full_name.package = callsite.package;
- }
-
- if (full_name != ref.name.value()) {
- *out_msg << " (aka " << full_name << ")";
- }
-}
-
void ReferenceLinker::WriteAttributeName(const Reference& ref, const CallSite& callsite,
const xml::IPackageDeclStack* decls,
DiagMessage* out_msg) {
@@ -348,18 +370,71 @@ void ReferenceLinker::WriteAttributeName(const Reference& ref, const CallSite& c
}
}
-bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* reference,
- IAaptContext* context, SymbolTable* symbols,
- const xml::IPackageDeclStack* decls) {
- CHECK(reference != nullptr);
- if (!reference->name && !reference->id) {
+std::unique_ptr<Item> ReferenceLinker::LinkReference(const CallSite& callsite,
+ const Reference& reference,
+ IAaptContext* context, SymbolTable* symbols,
+ ResourceTable* table,
+ const xml::IPackageDeclStack* decls) {
+ if (!reference.name && !reference.id) {
// This is @null.
- return true;
+ return std::make_unique<Reference>(reference);
}
- Reference transformed_reference = *reference;
+ Reference transformed_reference = reference;
xml::ResolvePackage(decls, &transformed_reference);
+ if (transformed_reference.name.value().type == ResourceType::kMacro) {
+ if (transformed_reference.name.value().package.empty()) {
+ transformed_reference.name.value().package = callsite.package;
+ }
+
+ auto result = table->FindResource(transformed_reference.name.value());
+ if (!result || result.value().entry->values.empty()) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(reference.GetSource())
+ << "failed to find definition for "
+ << LoggingResourceName(transformed_reference, callsite, decls));
+ return {};
+ }
+
+ auto& macro_values = result.value().entry->values;
+ CHECK(macro_values.size() == 1) << "Macros can only be defined in the default configuration.";
+
+ auto macro = ValueCast<Macro>(macro_values[0]->value.get());
+ CHECK(macro != nullptr) << "Value of macro resource is not a Macro (actual "
+ << *macro_values[0]->value << ")";
+
+ // Re-create the state used to parse the macro tag to compile the macro contents as if it was
+ // defined inline
+ uint32_t type_flags = 0;
+ if (reference.type_flags.has_value()) {
+ type_flags = reference.type_flags.value();
+ }
+
+ MacroDeclStack namespace_stack(macro->alias_namespaces);
+ FlattenedXmlSubTree sub_tree{.raw_value = macro->raw_value,
+ .style_string = macro->style_string,
+ .untranslatable_sections = macro->untranslatable_sections,
+ .namespace_resolver = &namespace_stack,
+ .source = macro->GetSource()};
+
+ auto new_value = ResourceParser::ParseXml(sub_tree, type_flags, reference.allow_raw, *table,
+ macro_values[0]->config, *context->GetDiagnostics());
+ if (new_value == nullptr) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(reference.GetSource())
+ << "failed to substitute macro "
+ << LoggingResourceName(transformed_reference, callsite, decls)
+ << ": failed to parse contents as one of type(s) " << Attribute::MaskString(type_flags));
+ return {};
+ }
+
+ if (auto ref = ValueCast<Reference>(new_value.get())) {
+ return LinkReference(callsite, *ref, context, symbols, table, decls);
+ }
+ return new_value;
+ }
+
std::string err_str;
const SymbolTable::Symbol* s =
ResolveSymbolCheckVisibility(transformed_reference, callsite, context, symbols, &err_str);
@@ -367,17 +442,17 @@ bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* referen
// The ID may not exist. This is fine because of the possibility of building
// against libraries without assigned IDs.
// Ex: Linking against own resources when building a static library.
- reference->id = s->id;
- reference->is_dynamic = s->is_dynamic;
- return true;
+ auto new_ref = std::make_unique<Reference>(reference);
+ new_ref->id = s->id;
+ new_ref->is_dynamic = s->is_dynamic;
+ return std::move(new_ref);
}
- DiagMessage error_msg(reference->GetSource());
- error_msg << "resource ";
- WriteResourceName(*reference, callsite, decls, &error_msg);
- error_msg << " " << err_str;
- context->GetDiagnostics()->Error(error_msg);
- return false;
+ context->GetDiagnostics()->Error(DiagMessage(reference.GetSource())
+ << "resource "
+ << LoggingResourceName(transformed_reference, callsite, decls)
+ << " " << err_str);
+ return {};
}
bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) {
@@ -412,14 +487,15 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) {
// The context of this resource is the package in which it is defined.
const CallSite callsite{name.package};
- ReferenceLinkerVisitor visitor(callsite, context, context->GetExternalSymbols(),
- &table->string_pool, &decl_stack);
+ ReferenceLinkerTransformer reference_transformer(callsite, context,
+ context->GetExternalSymbols(),
+ &table->string_pool, table, &decl_stack);
for (auto& config_value : entry->values) {
- config_value->value->Accept(&visitor);
+ config_value->value = config_value->value->Transform(reference_transformer);
}
- if (visitor.HasError()) {
+ if (reference_transformer.HasError()) {
error = true;
}
}
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
index 1256709edbf4..770f1e500ac0 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -28,6 +28,41 @@
namespace aapt {
+// A ValueTransformer that returns fully linked versions of resource and macro references.
+class ReferenceLinkerTransformer : public CloningValueTransformer {
+ public:
+ ReferenceLinkerTransformer(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols,
+ StringPool* string_pool, ResourceTable* table,
+ xml::IPackageDeclStack* decl)
+ : CloningValueTransformer(string_pool),
+ callsite_(callsite),
+ context_(context),
+ symbols_(symbols),
+ table_(table),
+ package_decls_(decl) {
+ }
+
+ std::unique_ptr<Reference> TransformDerived(const Reference* value) override;
+ std::unique_ptr<Item> TransformItem(const Reference* value) override;
+ std::unique_ptr<Style> TransformDerived(const Style* value) override;
+
+ bool HasError() {
+ return error_;
+ }
+
+ private:
+ // Transform a RawString value into a more specific, appropriate value, based on the
+ // Attribute. If a non RawString value is passed in, this is an identity transform.
+ std::unique_ptr<Item> ParseValueWithAttribute(std::unique_ptr<Item> value, const Attribute* attr);
+
+ const CallSite& callsite_;
+ IAaptContext* context_;
+ SymbolTable* symbols_;
+ ResourceTable* table_;
+ xml::IPackageDeclStack* package_decls_;
+ bool error_ = false;
+};
+
// Resolves all references to resources in the ResourceTable and assigns them IDs.
// The ResourceTable must already have IDs assigned to each resource.
// Once the ResourceTable is processed by this linker, it is ready to be flattened.
@@ -70,19 +105,28 @@ class ReferenceLinker : public IResourceTableConsumer {
// Writes the resource name to the DiagMessage, using the
// "orig_name (aka <transformed_name>)" syntax.
- static void WriteResourceName(const Reference& orig, const CallSite& callsite,
- const xml::IPackageDeclStack* decls, DiagMessage* out_msg);
+ /*static void WriteResourceName(const Reference& orig, const CallSite& callsite,
+ const xml::IPackageDeclStack* decls, DiagMessage* out_msg);*/
// Same as WriteResourceName but omits the 'attr' part.
static void WriteAttributeName(const Reference& ref, const CallSite& callsite,
const xml::IPackageDeclStack* decls, DiagMessage* out_msg);
- // Transforms the package name of the reference to the fully qualified package name using
- // the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
- // to the reference at the callsite, the reference is updated with an ID.
- // Returns false on failure, and an error message is logged to the IDiagnostics in the context.
- static bool LinkReference(const CallSite& callsite, Reference* reference, IAaptContext* context,
- SymbolTable* symbols, const xml::IPackageDeclStack* decls);
+ // Returns a fully linked version a resource reference.
+ //
+ // If the reference points to a non-macro resource, the xml::IPackageDeclStack is used to
+ // determine the fully qualified name of the referenced resource. If the symbol is visible
+ // to the reference at the callsite, a copy of the reference with an updated updated ID is
+ // returned.
+ //
+ // If the reference points to a macro, the ResourceTable is used to find the macro definition and
+ // substitute its contents in place of the reference.
+ //
+ // Returns nullptr on failure, and an error message is logged to the IDiagnostics in the context.
+ static std::unique_ptr<Item> LinkReference(const CallSite& callsite, const Reference& reference,
+ IAaptContext* context, SymbolTable* symbols,
+ ResourceTable* table,
+ const xml::IPackageDeclStack* decls);
// Links all references in the ResourceTable.
bool Consume(IAaptContext* context, ResourceTable* table) override;
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
index a31ce9496d0c..2d8f0d39053f 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -28,7 +28,6 @@ namespace aapt {
TEST(ReferenceLinkerTest, LinkSimpleReferences) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddReference("com.app.test:string/foo", ResourceId(0x7f020000),
"com.app.test:string/bar")
@@ -75,7 +74,6 @@ TEST(ReferenceLinkerTest, LinkSimpleReferences) {
TEST(ReferenceLinkerTest, LinkStyleAttributes) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddValue("com.app.test:style/Theme",
test::StyleBuilder()
.SetParent("android:style/Theme.Material")
@@ -155,7 +153,6 @@ TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddValue("com.app.test:style/Theme", ResourceId(0x7f020000),
test::StyleBuilder()
.AddItem("com.android.support:attr/foo",
@@ -176,7 +173,6 @@ TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) {
TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddReference("com.app.test:string/foo", ResourceId(0x7f020000),
"android:string/hidden")
.Build();
@@ -201,7 +197,6 @@ TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) {
TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddReference("com.app.test:string/foo", ResourceId(0x7f020000),
"com.app.lib:string/hidden")
.Build();
@@ -229,7 +224,6 @@ TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) {
TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .SetPackageId("com.app.test", 0x7f)
.AddValue("com.app.test:style/Theme",
test::StyleBuilder()
.AddItem("android:attr/hidden",
@@ -371,4 +365,22 @@ TEST(ReferenceLinkerTest, ReferenceSymbolFromOtherSplit) {
EXPECT_THAT(s, IsNull());
}
+TEST(ReferenceLinkerTest, MacroFailToFindDefinition) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddReference("com.app.test:string/foo", ResourceId(0x7f020000), "com.app.test:macro/bar")
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+ .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .Build();
+
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.Consume(context.get(), table.get()));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index c25e4503a208..22f4d18dc3ca 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -31,11 +31,10 @@ namespace aapt {
TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table,
const TableMergerOptions& options)
- : context_(context), master_table_(out_table), options_(options) {
+ : context_(context), main_table_(out_table), options_(options) {
// Create the desired package that all tables will be merged into.
- master_package_ =
- master_table_->CreatePackage(context_->GetCompilationPackage(), context_->GetPackageId());
- CHECK(master_package_ != nullptr) << "package name or ID already taken";
+ main_package_ = main_table_->FindOrCreatePackage(context_->GetCompilationPackage());
+ CHECK(main_package_ != nullptr) << "package name or ID already taken";
}
bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) {
@@ -85,20 +84,9 @@ bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_n
static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type,
ResourceTableType* src_type) {
- if (src_type->visibility_level > dst_type->visibility_level) {
+ if (src_type->visibility_level >= dst_type->visibility_level) {
// The incoming type's visibility is stronger, so we should override the visibility.
- if (src_type->visibility_level == Visibility::Level::kPublic) {
- // Only copy the ID if the source is public, or else the ID is meaningless.
- dst_type->id = src_type->id;
- }
dst_type->visibility_level = src_type->visibility_level;
- } else if (dst_type->visibility_level == Visibility::Level::kPublic &&
- src_type->visibility_level == Visibility::Level::kPublic && dst_type->id &&
- src_type->id && dst_type->id.value() != src_type->id.value()) {
- // Both types are public and have different IDs.
- context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge type '" << src_type->type
- << "': conflicting public IDs");
- return false;
}
return true;
}
@@ -163,6 +151,18 @@ static bool MergeEntry(IAaptContext* context, const Source& src,
dst_entry->overlayable_item = std::move(src_entry->overlayable_item);
}
+ if (src_entry->staged_id) {
+ if (dst_entry->staged_id &&
+ dst_entry->staged_id.value().id != src_entry->staged_id.value().id) {
+ context->GetDiagnostics()->Error(DiagMessage(src_entry->staged_id.value().source)
+ << "conflicting staged id declaration for resource '"
+ << src_entry->name << "'");
+ context->GetDiagnostics()->Error(DiagMessage(dst_entry->staged_id.value().source)
+ << "previous declaration here");
+ }
+ dst_entry->staged_id = std::move(src_entry->staged_id);
+ }
+
return true;
}
@@ -235,7 +235,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package,
bool error = false;
for (auto& src_type : src_package->types) {
- ResourceTableType* dst_type = master_package_->FindOrCreateType(src_type->type);
+ ResourceTableType* dst_type = main_package_->FindOrCreateType(src_type->type);
if (!MergeType(context_, src, dst_type, src_type.get())) {
error = true;
continue;
@@ -279,7 +279,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package,
if (dst_config_value) {
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);
+ dst_config_value, src_config_value.get(), &main_table_->string_pool);
if (collision_result == CollisionResult::kConflict) {
error = true;
continue;
@@ -292,13 +292,13 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package,
}
// Continue if we're taking the new resource.
-
+ CloningValueTransformer cloner(&main_table_->string_pool);
if (FileReference* f = ValueCast<FileReference>(src_config_value->value.get())) {
std::unique_ptr<FileReference> new_file_ref;
if (mangle_package) {
new_file_ref = CloneAndMangleFile(src_package->name, *f);
} else {
- new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool));
+ new_file_ref = std::unique_ptr<FileReference>(f->Transform(cloner));
}
dst_config_value->value = std::move(new_file_ref);
@@ -306,8 +306,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package,
Maybe<std::string> original_comment = (dst_config_value->value)
? dst_config_value->value->GetComment() : Maybe<std::string>();
- dst_config_value->value = std::unique_ptr<Value>(
- src_config_value->value->Clone(&master_table_->string_pool));
+ dst_config_value->value = src_config_value->value->Transform(cloner);
// Keep the comment from the original resource and ignore all comments from overlaying
// resources
@@ -328,14 +327,16 @@ std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile(
std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string());
std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string();
std::unique_ptr<FileReference> new_file_ref =
- util::make_unique<FileReference>(master_table_->string_pool.MakeRef(newPath));
+ util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath));
new_file_ref->SetComment(file_ref.GetComment());
new_file_ref->SetSource(file_ref.GetSource());
new_file_ref->type = file_ref.type;
new_file_ref->file = file_ref.file;
return new_file_ref;
}
- return std::unique_ptr<FileReference>(file_ref.Clone(&master_table_->string_pool));
+
+ CloningValueTransformer cloner(&main_table_->string_pool);
+ return std::unique_ptr<FileReference>(file_ref.Transform(cloner));
}
bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) {
@@ -347,7 +348,7 @@ bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFi
file_ref->type = file_desc.type;
file_ref->file = file;
- ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0);
+ ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package);
pkg->FindOrCreateType(file_desc.name.type)
->FindOrCreateEntry(file_desc.name.entry)
->FindOrCreateValue(file_desc.config, {})
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index a35a134a887d..e01a0c186392 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -80,9 +80,9 @@ class TableMerger {
DISALLOW_COPY_AND_ASSIGN(TableMerger);
IAaptContext* context_;
- ResourceTable* master_table_;
+ ResourceTable* main_table_;
TableMergerOptions options_;
- ResourceTablePackage* master_package_;
+ ResourceTablePackage* main_package_;
std::set<std::string> merged_packages_;
bool MergeImpl(const Source& src, ResourceTable* src_table, bool overlay, bool allow_new);
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 69cf5ee7002b..4358fb565a7d 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -55,16 +55,13 @@ struct TableMergerTest : public ::testing::Test {
TEST_F(TableMergerTest, SimpleMerge) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.AddReference("com.app.a:id/foo", "com.app.a:id/bar")
.AddReference("com.app.a:id/bar", "com.app.b:id/foo")
- .AddValue(
- "com.app.a:styleable/view",
- test::StyleableBuilder().AddItem("com.app.b:id/foo").Build())
+ .AddValue("com.app.a:styleable/view",
+ test::StyleableBuilder().AddItem("com.app.b:id/foo").Build())
.Build();
std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder()
- .SetPackageId("com.app.b", 0x7f)
.AddSimple("com.app.b:id/foo")
.Build();
@@ -129,12 +126,10 @@ TEST_F(TableMergerTest, MergeFileReferences) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.AddFileReference("com.app.a:xml/file", "res/xml/file.xml", &file_a)
.Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("com.app.b", 0x7f)
.AddFileReference("com.app.b:xml/file", "res/xml/file.xml", &file_b)
.Build();
@@ -158,12 +153,10 @@ TEST_F(TableMergerTest, MergeFileReferences) {
TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
- .SetPackageId("", 0x00)
.AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
- .SetPackageId("", 0x00)
.AddValue("bool/foo", ResourceUtils::TryParseBool("false"))
.Build();
@@ -194,14 +187,12 @@ TEST_F(TableMergerTest, DoNotOverrideResourceComment) {
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
- .SetPackageId("", 0x00)
.AddValue("bool/foo", std::move(foo_original))
.AddValue("bool/bar", std::move(bar_original))
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
- .SetPackageId("", 0x00)
.AddValue("bool/foo", std::move(foo_overlay))
.AddValue("bool/bar", std::move(bar_overlay))
.AddValue("bool/baz", std::move(baz_overlay))
@@ -226,12 +217,10 @@ TEST_F(TableMergerTest, DoNotOverrideResourceComment) {
TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) {
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
@@ -247,12 +236,10 @@ TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) {
TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) {
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), Visibility::Level::kPublic)
.Build();
@@ -268,12 +255,10 @@ TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) {
TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) {
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), Visibility::Level::kPublic)
.Build();
@@ -289,12 +274,10 @@ TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) {
TEST_F(TableMergerTest, FailConflictingVisibility) {
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
.Build();
std::unique_ptr<ResourceTable> overlay =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPrivate)
.Build();
@@ -318,11 +301,9 @@ TEST_F(TableMergerTest, FailConflictingVisibility) {
}
TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
- std::unique_ptr<ResourceTable> table_a =
- test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
+ std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder().Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.SetSymbolState("bool/foo", {}, Visibility::Level::kUndefined, true /*allow new overlay*/)
.AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
.Build();
@@ -337,11 +318,9 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
}
TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
- std::unique_ptr<ResourceTable> table_a =
- test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
+ std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder().Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
.Build();
@@ -355,11 +334,9 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
}
TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
- std::unique_ptr<ResourceTable> table_a =
- test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
+ std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder().Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
.AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
.Build();
@@ -375,7 +352,6 @@ TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.AddValue("com.app.a:styleable/Foo",
test::StyleableBuilder()
.AddItem("com.app.a:attr/bar")
@@ -391,7 +367,6 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.AddValue("com.app.a:styleable/Foo", test::StyleableBuilder()
.AddItem("com.app.a:attr/bat")
.AddItem("com.app.a:attr/foo")
@@ -441,7 +416,6 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
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())
@@ -452,7 +426,6 @@ TEST_F(TableMergerTest, OverrideStyleInsteadOfOverlaying) {
.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())
@@ -494,13 +467,11 @@ TEST_F(TableMergerTest, SetOverlayable) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.SetOverlayable("bool/foo", overlayable_item)
.Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.AddSimple("bool/foo")
.Build();
@@ -527,7 +498,6 @@ TEST_F(TableMergerTest, SetOverlayableLater) {
"overlay://customization");
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.AddSimple("bool/foo")
.Build();
@@ -536,7 +506,6 @@ TEST_F(TableMergerTest, SetOverlayableLater) {
overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.SetOverlayable("bool/foo", overlayable_item)
.Build();
@@ -565,7 +534,6 @@ TEST_F(TableMergerTest, SameResourceDifferentNameFail) {
overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.SetOverlayable("bool/foo", overlayable_item_first)
.Build();
@@ -575,7 +543,6 @@ TEST_F(TableMergerTest, SameResourceDifferentNameFail) {
overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.SetOverlayable("bool/foo", overlayable_item_second)
.Build();
@@ -594,7 +561,6 @@ TEST_F(TableMergerTest, SameResourceDifferentActorFail) {
overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.SetOverlayable("bool/foo", overlayable_item_first)
.Build();
@@ -604,7 +570,6 @@ TEST_F(TableMergerTest, SameResourceDifferentActorFail) {
overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.SetOverlayable("bool/foo", overlayable_item_second)
.Build();
@@ -623,7 +588,6 @@ TEST_F(TableMergerTest, SameResourceDifferentPoliciesFail) {
overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.SetOverlayable("bool/foo", overlayable_item_first)
.Build();
@@ -633,7 +597,6 @@ TEST_F(TableMergerTest, SameResourceDifferentPoliciesFail) {
overlayable_item_second.policies |= PolicyFlags::SIGNATURE;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.SetOverlayable("bool/foo", overlayable_item_second)
.Build();
@@ -653,7 +616,6 @@ TEST_F(TableMergerTest, SameResourceSameOverlayable) {
overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.SetOverlayable("bool/foo", overlayable_item_first)
.Build();
@@ -661,7 +623,6 @@ TEST_F(TableMergerTest, SameResourceSameOverlayable) {
overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
- .SetPackageId("com.app.a", 0x7f)
.SetOverlayable("bool/foo", overlayable_item_second)
.Build();
diff --git a/tools/aapt2/link/XmlCompatVersioner.cpp b/tools/aapt2/link/XmlCompatVersioner.cpp
index 20ebdc696814..957b64cd8dbb 100644
--- a/tools/aapt2/link/XmlCompatVersioner.cpp
+++ b/tools/aapt2/link/XmlCompatVersioner.cpp
@@ -23,9 +23,10 @@
namespace aapt {
static xml::Attribute CopyAttr(const xml::Attribute& src, StringPool* out_string_pool) {
+ CloningValueTransformer cloner(out_string_pool);
xml::Attribute dst{src.namespace_uri, src.name, src.value, src.compiled_attribute};
if (src.compiled_value != nullptr) {
- dst.compiled_value.reset(src.compiled_value->Clone(out_string_pool));
+ dst.compiled_value = src.compiled_value->Transform(cloner);
}
return dst;
}
@@ -34,6 +35,7 @@ static xml::Attribute CopyAttr(const xml::Attribute& src, StringPool* out_string
// (came from a rule).
static bool CopyAttribute(const xml::Attribute& src_attr, bool generated, xml::Element* dst_el,
StringPool* out_string_pool) {
+ CloningValueTransformer cloner(out_string_pool);
xml::Attribute* dst_attr = dst_el->FindAttribute(src_attr.namespace_uri, src_attr.name);
if (dst_attr != nullptr) {
if (generated) {
@@ -41,7 +43,7 @@ static bool CopyAttribute(const xml::Attribute& src_attr, bool generated, xml::E
dst_attr->value = src_attr.value;
dst_attr->compiled_attribute = src_attr.compiled_attribute;
if (src_attr.compiled_value != nullptr) {
- dst_attr->compiled_value.reset(src_attr.compiled_value->Clone(out_string_pool));
+ dst_attr->compiled_value = src_attr.compiled_value->Transform(cloner);
}
return true;
}
@@ -143,8 +145,8 @@ std::vector<std::unique_ptr<xml::XmlResource>> XmlCompatVersioner::Process(
// Iterate from smallest to largest API version.
for (ApiVersion api : apis_referenced) {
- std::set<ApiVersion> dummy;
- versioned_docs.push_back(ProcessDoc(api, api_range.end, doc, &dummy));
+ std::set<ApiVersion> tmp;
+ versioned_docs.push_back(ProcessDoc(api, api_range.end, doc, &tmp));
}
return versioned_docs;
}
@@ -158,7 +160,8 @@ static inline std::unique_ptr<Item> CloneIfNotNull(const std::unique_ptr<Item>&
if (src == nullptr) {
return {};
}
- return std::unique_ptr<Item>(src->Clone(out_string_pool));
+ CloningValueTransformer cloner(out_string_pool);
+ return src->Transform(cloner);
}
std::vector<DegradeResult> DegradeToManyRule::Degrade(const xml::Element& src_el,
diff --git a/tools/aapt2/link/XmlCompatVersioner_test.cpp b/tools/aapt2/link/XmlCompatVersioner_test.cpp
index a98ab0f76de4..d63809615a5b 100644
--- a/tools/aapt2/link/XmlCompatVersioner_test.cpp
+++ b/tools/aapt2/link/XmlCompatVersioner_test.cpp
@@ -82,7 +82,7 @@ TEST_F(XmlCompatVersionerTest, NoRulesOnlyStripsAndCopies) {
app:foo="16dp"
foo="bar"/>)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
XmlCompatVersioner::Rules rules;
@@ -121,7 +121,7 @@ TEST_F(XmlCompatVersionerTest, SingleRule) {
app:foo="16dp"
foo="bar"/>)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
XmlCompatVersioner::Rules rules;
@@ -181,7 +181,7 @@ TEST_F(XmlCompatVersionerTest, ChainedRule) {
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingHorizontal="24dp" />)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
XmlCompatVersioner::Rules rules;
@@ -256,7 +256,7 @@ TEST_F(XmlCompatVersionerTest, DegradeRuleOverridesExistingAttribute) {
android:paddingLeft="16dp"
android:paddingRight="16dp"/>)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
Item* padding_horizontal_value =
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index c3c16b92f712..aaa085e2eb15 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -33,49 +33,18 @@ namespace aapt {
namespace {
-// Visits all references (including parents of styles, references in styles, arrays, etc) and
-// links their symbolic name to their Resource ID, performing mangling and package aliasing
-// as needed.
-class ReferenceVisitor : public DescendingValueVisitor {
- public:
- using DescendingValueVisitor::Visit;
-
- ReferenceVisitor(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols,
- xml::IPackageDeclStack* decls)
- : callsite_(callsite), context_(context), symbols_(symbols), decls_(decls), error_(false) {}
-
- void Visit(Reference* ref) override {
- if (!ReferenceLinker::LinkReference(callsite_, ref, context_, symbols_, decls_)) {
- error_ = true;
- }
- }
-
- bool HasError() const {
- return error_;
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ReferenceVisitor);
-
- const CallSite& callsite_;
- IAaptContext* context_;
- SymbolTable* symbols_;
- xml::IPackageDeclStack* decls_;
- bool error_;
-};
-
// Visits each xml Element and compiles the attributes within.
class XmlVisitor : public xml::PackageAwareVisitor {
public:
using xml::PackageAwareVisitor::Visit;
- XmlVisitor(const Source& source, const CallSite& callsite, IAaptContext* context,
- SymbolTable* symbols)
+ XmlVisitor(const Source& source, StringPool* pool, const CallSite& callsite,
+ IAaptContext* context, ResourceTable* table, SymbolTable* symbols)
: source_(source),
callsite_(callsite),
context_(context),
symbols_(symbols),
- reference_visitor_(callsite, context, symbols, this) {
+ reference_transformer_(callsite, context, symbols, pool, table, this) {
}
void Visit(xml::Element* el) override {
@@ -127,7 +96,7 @@ class XmlVisitor : public xml::PackageAwareVisitor {
if (attr.compiled_value) {
// With a compiledValue, we must resolve the reference and assign it an ID.
attr.compiled_value->SetSource(source);
- attr.compiled_value->Accept(&reference_visitor_);
+ attr.compiled_value = attr.compiled_value->Transform(reference_transformer_);
} else if ((attribute->type_mask & android::ResTable_map::TYPE_STRING) == 0) {
// We won't be able to encode this as a string.
DiagMessage msg(source);
@@ -143,7 +112,7 @@ class XmlVisitor : public xml::PackageAwareVisitor {
}
bool HasError() {
- return error_ || reference_visitor_.HasError();
+ return error_ || reference_transformer_.HasError();
}
private:
@@ -154,7 +123,7 @@ class XmlVisitor : public xml::PackageAwareVisitor {
IAaptContext* context_;
SymbolTable* symbols_;
- ReferenceVisitor reference_visitor_;
+ ReferenceLinkerTransformer reference_transformer_;
bool error_ = false;
};
@@ -173,7 +142,8 @@ bool XmlReferenceLinker::Consume(IAaptContext* context, xml::XmlResource* resour
callsite.package = context->GetCompilationPackage();
}
- XmlVisitor visitor(resource->file.source, callsite, context, context->GetExternalSymbols());
+ XmlVisitor visitor(resource->file.source, &resource->string_pool, callsite, context, table_,
+ context->GetExternalSymbols());
if (resource->root) {
resource->root->Accept(&visitor);
return !visitor.HasError();
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index 0ce2e50d6e44..ddf5b9a22c2f 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -91,7 +91,7 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
nonAaptAttrRef="@id/id"
class="hello" />)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
xml::Element* view_el = doc->root.get();
@@ -144,7 +144,7 @@ TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) {
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:colorAccent="@android:color/hidden" />)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_FALSE(linker.Consume(context_.get(), doc.get()));
}
@@ -153,7 +153,7 @@ TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:colorAccent="@*android:color/hidden" />)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
}
@@ -162,7 +162,7 @@ TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
<View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
support:colorAccent="#ff0000" />)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
xml::Element* view_el = doc->root.get();
@@ -181,7 +181,7 @@ TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
<View xmlns:app="http://schemas.android.com/apk/res-auto"
app:colorAccent="@app:color/red" />)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
xml::Element* view_el = doc->root.get();
@@ -203,7 +203,7 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
<View xmlns:app="http://schemas.android.com/apk/res/com.app.test" app:attr="@app:id/id"/>
</View>)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
xml::Element* view_el = doc->root.get();
@@ -239,7 +239,7 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
<View xmlns:android="http://schemas.android.com/apk/res/com.app.test"
android:attr="@id/id"/>)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
xml::Element* view_el = doc->root.get();
@@ -261,7 +261,7 @@ TEST_F(XmlReferenceLinkerTest, AddAngleOnGradientForAndroidQ) {
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
<gradient />)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
xml::Element* gradient_el = doc->root.get();
@@ -283,7 +283,7 @@ TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForAndroidQ) {
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:angle="90"/>)");
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
xml::Element* gradient_el = doc->root.get();
@@ -305,7 +305,7 @@ TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForPostAndroidQ) {
<gradient xmlns:android="http://schemas.android.com/apk/res/android" />)");
context_->SetMinSdkVersion(30);
- XmlReferenceLinker linker;
+ XmlReferenceLinker linker(nullptr);
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
xml::Element* gradient_el = doc->root.get();
diff --git a/tools/aapt2/optimize/ResourceFilter.cpp b/tools/aapt2/optimize/ResourceFilter.cpp
index 250b65197a7d..08c045bf68f7 100644
--- a/tools/aapt2/optimize/ResourceFilter.cpp
+++ b/tools/aapt2/optimize/ResourceFilter.cpp
@@ -20,8 +20,8 @@
namespace aapt {
-ResourceFilter::ResourceFilter(const std::unordered_set<ResourceName>& blacklist)
- : blacklist_(blacklist) {
+ResourceFilter::ResourceFilter(const std::unordered_set<ResourceName>& exclude_list)
+ : exclude_list_(exclude_list) {
}
bool ResourceFilter::Consume(IAaptContext* context, ResourceTable* table) {
@@ -29,7 +29,7 @@ bool ResourceFilter::Consume(IAaptContext* context, ResourceTable* table) {
for (auto& type : package->types) {
for (auto it = type->entries.begin(); it != type->entries.end(); ) {
ResourceName resource = ResourceName({}, type->type, (*it)->name);
- if (blacklist_.find(resource) != blacklist_.end()) {
+ if (exclude_list_.find(resource) != exclude_list_.end()) {
it = type->entries.erase(it);
} else {
++it;
diff --git a/tools/aapt2/optimize/ResourceFilter.h b/tools/aapt2/optimize/ResourceFilter.h
index d4baf654b0ff..a2645333e497 100644
--- a/tools/aapt2/optimize/ResourceFilter.h
+++ b/tools/aapt2/optimize/ResourceFilter.h
@@ -25,16 +25,16 @@
namespace aapt {
-// Removes non-whitelisted entries from resource table.
+// Removes exclude-listed entries from resource table.
class ResourceFilter : public IResourceTableConsumer {
public:
- explicit ResourceFilter(const std::unordered_set<ResourceName>& blacklist);
+ explicit ResourceFilter(const std::unordered_set<ResourceName>& exclude_list);
bool Consume(IAaptContext* context, ResourceTable* table) override;
private:
DISALLOW_COPY_AND_ASSIGN(ResourceFilter);
- std::unordered_set<ResourceName> blacklist_;
+ std::unordered_set<ResourceName> exclude_list_;
};
} // namespace aapt
diff --git a/tools/aapt2/optimize/ResourceFilter_test.cpp b/tools/aapt2/optimize/ResourceFilter_test.cpp
index ef57f9c56dab..34d8fd280fa9 100644
--- a/tools/aapt2/optimize/ResourceFilter_test.cpp
+++ b/tools/aapt2/optimize/ResourceFilter_test.cpp
@@ -31,22 +31,22 @@ TEST(ResourceFilterTest, SomeValuesAreFilteredOut) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .AddString("android:string/notblacklisted", ResourceId{}, default_config, "value")
- .AddString("android:string/blacklisted", ResourceId{}, default_config, "value")
- .AddString("android:string/notblacklisted2", ResourceId{}, default_config, "value")
- .AddString("android:string/blacklisted2", ResourceId{}, default_config, "value")
+ .AddString("android:string/notexclude_listed", ResourceId{}, default_config, "value")
+ .AddString("android:string/exclude_listed", ResourceId{}, default_config, "value")
+ .AddString("android:string/notexclude_listed2", ResourceId{}, default_config, "value")
+ .AddString("android:string/exclude_listed2", ResourceId{}, default_config, "value")
.Build();
- std::unordered_set<ResourceName> blacklist = {
- ResourceName({}, ResourceType::kString, "blacklisted"),
- ResourceName({}, ResourceType::kString, "blacklisted2"),
+ std::unordered_set<ResourceName> exclude_list = {
+ ResourceName({}, ResourceType::kString, "exclude_listed"),
+ ResourceName({}, ResourceType::kString, "exclude_listed2"),
};
- ASSERT_TRUE(ResourceFilter(blacklist).Consume(context.get(), table.get()));
- EXPECT_THAT(table, HasValue("android:string/notblacklisted", default_config));
- EXPECT_THAT(table, HasValue("android:string/notblacklisted2", default_config));
- EXPECT_THAT(table, Not(HasValue("android:string/blacklisted", default_config)));
- EXPECT_THAT(table, Not(HasValue("android:string/blacklisted2", default_config)));
+ ASSERT_TRUE(ResourceFilter(exclude_list).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/notexclude_listed", default_config));
+ EXPECT_THAT(table, HasValue("android:string/notexclude_listed2", default_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/exclude_listed", default_config)));
+ EXPECT_THAT(table, Not(HasValue("android:string/exclude_listed2", default_config)));
}
TEST(ResourceFilterTest, TypeIsCheckedBeforeFiltering) {
@@ -55,21 +55,21 @@ TEST(ResourceFilterTest, TypeIsCheckedBeforeFiltering) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
- .AddString("android:string/notblacklisted", ResourceId{}, default_config, "value")
- .AddString("android:string/blacklisted", ResourceId{}, default_config, "value")
- .AddString("android:drawable/notblacklisted", ResourceId{}, default_config, "value")
- .AddString("android:drawable/blacklisted", ResourceId{}, default_config, "value")
+ .AddString("android:string/notexclude_listed", ResourceId{}, default_config, "value")
+ .AddString("android:string/exclude_listed", ResourceId{}, default_config, "value")
+ .AddString("android:drawable/notexclude_listed", ResourceId{}, default_config, "value")
+ .AddString("android:drawable/exclude_listed", ResourceId{}, default_config, "value")
.Build();
- std::unordered_set<ResourceName> blacklist = {
- ResourceName({}, ResourceType::kString, "blacklisted"),
+ std::unordered_set<ResourceName> exclude_list = {
+ ResourceName({}, ResourceType::kString, "exclude_listed"),
};
- ASSERT_TRUE(ResourceFilter(blacklist).Consume(context.get(), table.get()));
- EXPECT_THAT(table, HasValue("android:string/notblacklisted", default_config));
- EXPECT_THAT(table, HasValue("android:drawable/blacklisted", default_config));
- EXPECT_THAT(table, HasValue("android:drawable/notblacklisted", default_config));
- EXPECT_THAT(table, Not(HasValue("android:string/blacklisted", default_config)));
+ ASSERT_TRUE(ResourceFilter(exclude_list).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/notexclude_listed", default_config));
+ EXPECT_THAT(table, HasValue("android:drawable/exclude_listed", default_config));
+ EXPECT_THAT(table, HasValue("android:drawable/notexclude_listed", default_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/exclude_listed", default_config)));
}
} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index ad716c78d65d..d385267fe5ed 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -197,9 +197,10 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName(
std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>();
symbol->is_public = (sr.entry->visibility.level == Visibility::Level::kPublic);
- if (sr.package->id && sr.type->id && sr.entry->id) {
- symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
- symbol->is_dynamic = (sr.package->id.value() == 0);
+ if (sr.entry->id) {
+ symbol->id = sr.entry->id.value();
+ symbol->is_dynamic =
+ (sr.entry->id.value().package_id() == 0) || sr.entry->visibility.staged_api;
}
if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
@@ -227,8 +228,7 @@ bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) {
apk_assets.push_back(apk_asset.get());
}
- asset_manager_.SetApkAssets(apk_assets, true /* invalidate_caches */,
- false /* filter_incompatible_configs */);
+ asset_manager_.SetApkAssets(apk_assets);
return true;
}
return false;
@@ -351,9 +351,9 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName(
return true;
}
- auto value = asset_manager_.GetResource(res_id.id, true /* may_be_bag */);
- if (value.has_value()) {
- type_spec_flags = value->flags;
+ auto flags = asset_manager_.GetResourceTypeSpecFlags(res_id.id);
+ if (flags.has_value()) {
+ type_spec_flags = *flags;
found = true;
return false;
}
@@ -371,11 +371,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName(
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = res_id;
- s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package);
}
if (s) {
s->is_public = (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
+ s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package) ||
+ (type_spec_flags & android::ResTable_typeSpec::SPEC_STAGED_API) != 0;
return s;
}
return {};
@@ -406,8 +407,8 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById(
return {};
}
- auto value = asset_manager_.GetResource(id.id, true /* may_be_bag */);
- if (!value.has_value()) {
+ auto flags = asset_manager_.GetResourceTypeSpecFlags(id.id);
+ if (!flags.has_value()) {
return {};
}
@@ -418,11 +419,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById(
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = id;
- s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package);
}
if (s) {
- s->is_public = (value->flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
+ s->is_public = (*flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
+ s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package) ||
+ (*flags & android::ResTable_typeSpec::SPEC_STAGED_API) != 0;
return s;
}
return {};
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 6a672717f38e..116b2ab9aa98 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -185,7 +185,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) {
// Initialize all packages for splits.
for (size_t idx = 0; idx < split_count; idx++) {
ResourceTable* split_table = splits_[idx].get();
- split_table->CreatePackage(pkg->name, pkg->id);
+ split_table->FindOrCreatePackage(pkg->name);
}
for (auto& type : pkg->types) {
@@ -229,6 +229,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) {
for (size_t idx = 0; idx < split_count; idx++) {
const SplitConstraints& split_constraint = split_constraints_[idx];
ResourceTable* split_table = splits_[idx].get();
+ CloningValueTransformer cloner(&split_table->string_pool);
// Select the values we want from this entry for this split.
SplitValueSelector selector(split_constraint);
@@ -241,10 +242,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) {
// not have actual values for each type/entry.
ResourceTablePackage* split_pkg = split_table->FindPackage(pkg->name);
ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type);
- if (!split_type->id) {
- split_type->id = type->id;
- split_type->visibility_level = type->visibility_level;
- }
+ split_type->visibility_level = type->visibility_level;
ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name);
if (!split_entry->id) {
@@ -257,8 +255,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) {
for (ResourceConfigValue* config_value : selected_values) {
ResourceConfigValue* new_config_value =
split_entry->FindOrCreateValue(config_value->config, config_value->product);
- new_config_value->value = std::unique_ptr<Value>(
- config_value->value->Clone(&split_table->string_pool));
+ new_config_value->value = config_value->value->Transform(cloner);
}
}
}
diff --git a/tools/aapt2/split/TableSplitter_test.cpp b/tools/aapt2/split/TableSplitter_test.cpp
index cdf07386c70f..c6a2ff33979b 100644
--- a/tools/aapt2/split/TableSplitter_test.cpp
+++ b/tools/aapt2/split/TableSplitter_test.cpp
@@ -193,15 +193,23 @@ TEST(TableSplitterTest, SplitTableByConfigAndDensity) {
ResourceTable table;
const ResourceName foo = test::ParseNameOrDie("android:string/foo");
- ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-hdpi"), {},
- util::make_unique<Id>(),
- test::GetDiagnostics()));
- ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-xhdpi"), {},
- util::make_unique<Id>(),
- test::GetDiagnostics()));
- ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-xxhdpi"), {},
- util::make_unique<Id>(),
- test::GetDiagnostics()));
+ ASSERT_TRUE(
+ table.AddResource(NewResourceBuilder(foo)
+ .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land-hdpi"))
+ .Build(),
+ test::GetDiagnostics()));
+
+ ASSERT_TRUE(
+ table.AddResource(NewResourceBuilder(foo)
+ .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land-xhdpi"))
+ .Build(),
+ test::GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(foo)
+ .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land-xxhdpi"))
+ .Build(),
+ test::GetDiagnostics()));
std::vector<SplitConstraints> constraints;
constraints.push_back(
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 9a93f2a7476c..4816596da487 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -34,13 +34,6 @@ using ::android::StringPiece;
namespace aapt {
namespace test {
-ResourceTableBuilder& ResourceTableBuilder::SetPackageId(const StringPiece& package_name,
- uint8_t id) {
- ResourceTablePackage* package = table_->CreatePackage(package_name, id);
- CHECK(package != nullptr);
- return *this;
-}
-
ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
const ResourceId& id) {
return AddValue(name, id, util::make_unique<Id>());
@@ -118,8 +111,13 @@ ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
const ResourceId& id,
std::unique_ptr<Value> value) {
ResourceName res_name = ParseNameOrDie(name);
- CHECK(table_->AddResourceWithIdMangled(res_name, id, config, {}, std::move(value),
- GetDiagnostics()));
+ NewResourceBuilder builder(res_name);
+ builder.SetValue(std::move(value), config).SetAllowMangled(true);
+ if (id.id != 0U) {
+ builder.SetId(id);
+ }
+
+ CHECK(table_->AddResource(builder.Build(), GetDiagnostics()));
return *this;
}
@@ -128,10 +126,13 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na
Visibility::Level level,
bool allow_new) {
ResourceName res_name = ParseNameOrDie(name);
- Visibility visibility;
- visibility.level = level;
- CHECK(table_->SetVisibilityWithIdMangled(res_name, visibility, id, GetDiagnostics()));
- CHECK(table_->SetAllowNewMangled(res_name, AllowNew{}, GetDiagnostics()));
+ NewResourceBuilder builder(res_name);
+ builder.SetVisibility({level}).SetAllowNew({}).SetAllowMangled(true);
+ if (id.id != 0U) {
+ builder.SetId(id);
+ }
+
+ CHECK(table_->AddResource(builder.Build(), GetDiagnostics()));
return *this;
}
@@ -139,7 +140,14 @@ ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& na
const OverlayableItem& overlayable) {
ResourceName res_name = ParseNameOrDie(name);
- CHECK(table_->SetOverlayable(res_name, overlayable, GetDiagnostics()));
+ CHECK(table_->AddResource(
+ NewResourceBuilder(res_name).SetOverlayable(overlayable).SetAllowMangled(true).Build(),
+ GetDiagnostics()));
+ return *this;
+}
+
+ResourceTableBuilder& ResourceTableBuilder::Add(NewResource&& res) {
+ CHECK(table_->AddResource(std::move(res), GetDiagnostics()));
return *this;
}
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index c971a1b47fd5..3ff955d65f24 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -39,7 +39,6 @@ class ResourceTableBuilder {
public:
ResourceTableBuilder() = default;
- ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id);
ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {});
ResourceTableBuilder& AddSimple(const android::StringPiece& name,
const android::ConfigDescription& config,
@@ -75,6 +74,7 @@ class ResourceTableBuilder {
Visibility::Level level, bool allow_new = false);
ResourceTableBuilder& SetOverlayable(const android::StringPiece& name,
const OverlayableItem& overlayable);
+ ResourceTableBuilder& Add(NewResource&& res);
StringPool* string_pool();
std::unique_ptr<ResourceTable> Build();
diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp
index b54c155ddc2f..23c22185a53f 100644
--- a/tools/aapt2/test/Common.cpp
+++ b/tools/aapt2/test/Common.cpp
@@ -21,7 +21,7 @@ using android::ConfigDescription;
namespace aapt {
namespace test {
-struct DummyDiagnosticsImpl : public IDiagnostics {
+struct TestDiagnosticsImpl : public IDiagnostics {
void Log(Level level, DiagMessageActual& actual_msg) override {
switch (level) {
case Level::Note:
@@ -39,7 +39,7 @@ struct DummyDiagnosticsImpl : public IDiagnostics {
};
IDiagnostics* GetDiagnostics() {
- static DummyDiagnosticsImpl diag;
+ static TestDiagnosticsImpl diag;
return &diag;
}
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 553c43e6c469..5d8ded39e654 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -200,10 +200,11 @@ class StaticSymbolSourceBuilder {
private:
std::unique_ptr<SymbolTable::Symbol> CloneSymbol(SymbolTable::Symbol* sym) {
+ CloningValueTransformer cloner(nullptr);
std::unique_ptr<SymbolTable::Symbol> clone = util::make_unique<SymbolTable::Symbol>();
clone->id = sym->id;
if (sym->attribute) {
- clone->attribute = std::unique_ptr<Attribute>(sym->attribute->Clone(nullptr));
+ clone->attribute = std::unique_ptr<Attribute>(sym->attribute->Transform(cloner));
}
clone->is_public = sym->is_public;
return clone;
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index 5386802dbc8e..285e5a11b4c0 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -18,18 +18,17 @@
#include <dirent.h>
-#include "android-base/errors.h"
-#include "android-base/file.h"
-#include "android-base/stringprintf.h"
-#include "android-base/utf8.h"
-#include "androidfw/StringPiece.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
+#include <android-base/errors.h>
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/utf8.h>
+#include <androidfw/StringPiece.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
#include "cmd/Compile.h"
#include "cmd/Link.h"
#include "io/FileStream.h"
-#include "io/Util.h"
#include "util/Files.h"
using testing::Eq;
@@ -81,9 +80,6 @@ void TestDirectoryFixture::TearDown() {
}
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.";
-
// Create any intermediate directories specified in the path
auto pos = std::find(path.rbegin(), path.rend(), file::sDirSep);
if (pos != path.rend()) {
@@ -170,4 +166,74 @@ void CommandTestFixture::AssertLoadXml(LoadedApk* apk, const io::IData* data,
}
}
+ManifestBuilder::ManifestBuilder(CommandTestFixture* fixture) : fixture_(fixture) {
+}
+
+ManifestBuilder& ManifestBuilder::SetPackageName(const std::string& package_name) {
+ package_name_ = package_name;
+ return *this;
+}
+
+ManifestBuilder& ManifestBuilder::AddContents(const std::string& contents) {
+ contents_ += contents + "\n";
+ return *this;
+}
+
+std::string ManifestBuilder::Build(const std::string& file_path) {
+ const char* manifest_template = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="%s">
+ %s
+ </manifest>)";
+
+ fixture_->WriteFile(file_path, android::base::StringPrintf(
+ manifest_template, package_name_.c_str(), contents_.c_str()));
+ return file_path;
+}
+
+std::string ManifestBuilder::Build() {
+ return Build(fixture_->GetTestPath("AndroidManifest.xml"));
+}
+
+LinkCommandBuilder::LinkCommandBuilder(CommandTestFixture* fixture) : fixture_(fixture) {
+}
+
+LinkCommandBuilder& LinkCommandBuilder::SetManifestFile(const std::string& file) {
+ manifest_supplied_ = true;
+ args_.emplace_back("--manifest");
+ args_.emplace_back(file);
+ return *this;
+}
+
+LinkCommandBuilder& LinkCommandBuilder::AddFlag(const std::string& flag) {
+ args_.emplace_back(flag);
+ return *this;
+}
+
+LinkCommandBuilder& LinkCommandBuilder::AddCompiledResDir(const std::string& dir,
+ IDiagnostics* diag) {
+ if (auto files = file::FindFiles(dir, diag)) {
+ for (std::string& compile_file : files.value()) {
+ args_.emplace_back(file::BuildPath({dir, compile_file}));
+ }
+ }
+ return *this;
+}
+
+LinkCommandBuilder& LinkCommandBuilder::AddParameter(const std::string& param,
+ const std::string& value) {
+ args_.emplace_back(param);
+ args_.emplace_back(value);
+ return *this;
+}
+
+std::vector<std::string> LinkCommandBuilder::Build(const std::string& out_apk) {
+ if (!manifest_supplied_) {
+ SetManifestFile(ManifestBuilder(fixture_).Build());
+ }
+ args_.emplace_back("-o");
+ args_.emplace_back(out_apk);
+ return args_;
+}
+
} // namespace aapt \ No newline at end of file
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index 457d65e30b65..f8c4889aee3b 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -32,7 +32,7 @@ namespace aapt {
class TestDirectoryFixture : public ::testing::Test {
public:
TestDirectoryFixture() = default;
- virtual ~TestDirectoryFixture() = default;
+ ~TestDirectoryFixture() override = default;
// Creates the test directory or clears its contents if it contains previously created files.
void SetUp() override;
@@ -41,14 +41,14 @@ class TestDirectoryFixture : public ::testing::Test {
void TearDown() override;
// Retrieve the test directory of the fixture.
- const android::StringPiece GetTestDirectory() {
+ android::StringPiece GetTestDirectory() {
return temp_dir_;
}
// Retrieves the absolute path of the specified relative path in the test directory. Directories
// should be separated using forward slashes ('/'), and these slashes will be translated to
// backslashes when running Windows tests.
- const std::string GetTestPath(const android::StringPiece& path) {
+ std::string GetTestPath(const android::StringPiece& path) {
std::string base = temp_dir_;
for (android::StringPiece part : util::Split(path, '/')) {
file::AppendPath(&base, part);
@@ -68,7 +68,7 @@ class TestDirectoryFixture : public ::testing::Test {
class CommandTestFixture : public TestDirectoryFixture {
public:
CommandTestFixture() = default;
- virtual ~CommandTestFixture() = default;
+ ~CommandTestFixture() override = default;
// Wries the contents of the file to the specified path. The file is compiled and the flattened
// file is written to the out directory.
@@ -99,6 +99,33 @@ class CommandTestFixture : public TestDirectoryFixture {
DISALLOW_COPY_AND_ASSIGN(CommandTestFixture);
};
+struct ManifestBuilder {
+ explicit ManifestBuilder(CommandTestFixture* fixture);
+ ManifestBuilder& AddContents(const std::string& contents);
+ ManifestBuilder& SetPackageName(const std::string& package_name);
+ std::string Build(const std::string& file_path);
+ std::string Build();
+
+ private:
+ CommandTestFixture* fixture_;
+ std::string package_name_ = CommandTestFixture::kDefaultPackageName;
+ std::string contents_;
+};
+
+struct LinkCommandBuilder {
+ explicit LinkCommandBuilder(CommandTestFixture* fixture);
+ LinkCommandBuilder& AddCompiledResDir(const std::string& dir, IDiagnostics* diag);
+ LinkCommandBuilder& AddFlag(const std::string& flag);
+ LinkCommandBuilder& AddParameter(const std::string& param, const std::string& value);
+ LinkCommandBuilder& SetManifestFile(const std::string& manifest_path);
+ std::vector<std::string> Build(const std::string& out_apk_path);
+
+ private:
+ CommandTestFixture* fixture_;
+ std::vector<std::string> args_;
+ bool manifest_supplied_ = false;
+};
+
} // namespace aapt
#endif // AAPT_TEST_FIXTURE_H \ No newline at end of file
diff --git a/tools/aapt2/trace/TraceBuffer.h b/tools/aapt2/trace/TraceBuffer.h
index 8618e0eeb731..ba751dd72f41 100644
--- a/tools/aapt2/trace/TraceBuffer.h
+++ b/tools/aapt2/trace/TraceBuffer.h
@@ -40,7 +40,7 @@ public:
void BeginTrace(const std::string& tag);
void EndTrace();
-// A master trace is required to flush events to disk. Events are formatted in systrace
+// A main trace is required to flush events to disk. Events are formatted in systrace
// json format.
class FlushTrace {
public:
diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp
index 2057ddcc9e45..4c921f13a3ca 100644
--- a/tools/aapt2/util/Maybe_test.cpp
+++ b/tools/aapt2/util/Maybe_test.cpp
@@ -22,32 +22,32 @@
namespace aapt {
-struct Dummy {
- Dummy() {
+struct Fake {
+ Fake() {
data = new int;
*data = 1;
- std::cerr << "Construct Dummy{0x" << (void*)this << "} with data=0x"
+ std::cerr << "Construct Fake{0x" << (void*)this << "} with data=0x"
<< (void*)data << std::endl;
}
- Dummy(const Dummy& rhs) {
+ Fake(const Fake& rhs) {
data = nullptr;
if (rhs.data) {
data = new int;
*data = *rhs.data;
}
- std::cerr << "CopyConstruct Dummy{0x" << (void*)this << "} from Dummy{0x"
+ std::cerr << "CopyConstruct Fake{0x" << (void*)this << "} from Fake{0x"
<< (const void*)&rhs << "}" << std::endl;
}
- Dummy(Dummy&& rhs) {
+ Fake(Fake&& rhs) {
data = rhs.data;
rhs.data = nullptr;
- std::cerr << "MoveConstruct Dummy{0x" << (void*)this << "} from Dummy{0x"
+ std::cerr << "MoveConstruct Fake{0x" << (void*)this << "} from Fake{0x"
<< (const void*)&rhs << "}" << std::endl;
}
- Dummy& operator=(const Dummy& rhs) {
+ Fake& operator=(const Fake& rhs) {
delete data;
data = nullptr;
@@ -55,22 +55,22 @@ struct Dummy {
data = new int;
*data = *rhs.data;
}
- std::cerr << "CopyAssign Dummy{0x" << (void*)this << "} from Dummy{0x"
+ std::cerr << "CopyAssign Fake{0x" << (void*)this << "} from Fake{0x"
<< (const void*)&rhs << "}" << std::endl;
return *this;
}
- Dummy& operator=(Dummy&& rhs) {
+ Fake& operator=(Fake&& rhs) {
delete data;
data = rhs.data;
rhs.data = nullptr;
- std::cerr << "MoveAssign Dummy{0x" << (void*)this << "} from Dummy{0x"
+ std::cerr << "MoveAssign Fake{0x" << (void*)this << "} from Fake{0x"
<< (const void*)&rhs << "}" << std::endl;
return *this;
}
- ~Dummy() {
- std::cerr << "Destruct Dummy{0x" << (void*)this << "} with data=0x"
+ ~Fake() {
+ std::cerr << "Destruct Fake{0x" << (void*)this << "} with data=0x"
<< (void*)data << std::endl;
delete data;
}
@@ -100,15 +100,15 @@ TEST(MaybeTest, MakeSomething) {
}
TEST(MaybeTest, Lifecycle) {
- Maybe<Dummy> val = make_nothing<Dummy>();
+ Maybe<Fake> val = make_nothing<Fake>();
- Maybe<Dummy> val2 = make_value(Dummy());
+ Maybe<Fake> val2 = make_value(Fake());
}
TEST(MaybeTest, MoveAssign) {
- Maybe<Dummy> val;
+ Maybe<Fake> val;
{
- Maybe<Dummy> val2 = Dummy();
+ Maybe<Fake> val2 = Fake();
val = std::move(val2);
}
}
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 793a2368526b..d7a8e6fe6ada 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -38,6 +38,11 @@ using ::android::StringPiece16;
namespace aapt {
namespace util {
+// Package name and shared user id would be used as a part of the file name.
+// Limits size to 223 and reserves 32 for the OS.
+// See frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+constexpr static const size_t kMaxPackageNameSize = 223;
+
static std::vector<std::string> SplitAndTransform(
const StringPiece& str, char sep, const std::function<char(char)>& f) {
std::vector<std::string> parts;
@@ -169,9 +174,21 @@ static int IsAndroidNameImpl(const StringPiece& str) {
}
bool IsAndroidPackageName(const StringPiece& str) {
+ if (str.size() > kMaxPackageNameSize) {
+ return false;
+ }
return IsAndroidNameImpl(str) > 1 || str == "android";
}
+bool IsAndroidSharedUserId(const android::StringPiece& package_name,
+ const android::StringPiece& shared_user_id) {
+ if (shared_user_id.size() > kMaxPackageNameSize) {
+ return false;
+ }
+ return shared_user_id.empty() || IsAndroidNameImpl(shared_user_id) > 1 ||
+ package_name == "android";
+}
+
bool IsAndroidSplitName(const StringPiece& str) {
return IsAndroidNameImpl(str) > 0;
}
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index a956957eace8..c77aca31a810 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -81,6 +81,7 @@ bool IsJavaPackageName(const android::StringPiece& str);
// - First character of each component (separated by '.') must be an ASCII letter.
// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
// - Package must contain at least two components, unless it is 'android'.
+// - The maximum package name length is 223.
bool IsAndroidPackageName(const android::StringPiece& str);
// Tests that the string is a valid Android split name.
@@ -88,6 +89,15 @@ bool IsAndroidPackageName(const android::StringPiece& str);
// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
bool IsAndroidSplitName(const android::StringPiece& str);
+// Tests that the string is a valid Android shared user id.
+// - First character of each component (separated by '.') must be an ASCII letter.
+// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
+// - Must contain at least two components, unless package name is 'android'.
+// - The maximum shared user id length is 223.
+// - Treat empty string as valid, it's the case of no shared user id.
+bool IsAndroidSharedUserId(const android::StringPiece& package_name,
+ const android::StringPiece& shared_user_id);
+
// Converts the class name to a fully qualified class name from the given
// `package`. Ex:
//
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index d4e3bec24bd1..4ebcb115306f 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -27,6 +27,17 @@ using ::testing::SizeIs;
namespace aapt {
+// Test that a max package name size 223 is valid.
+static const std::string kMaxPackageName =
+ "com.foo.nameRw8ajIGbYmqPuO0K7TYJFsI2pjlDAS0pYOYQlJvtQux"
+ "SoBKV1hMyNh4XfmcMj8OgPHfFaTXeKEHFMdGQHpw9Dz9Uqr8h1krgJLRv2aXyPCsGdVwBJzfZ4COVRiX3sc9O"
+ "CUrTTvZe6wXlgKb5Qz5qdkTBZ5euzGeoyZwestDTBIgT5exAl5efnznwzceS7VsIntgY10UUQvaoTsLBO6l";
+// Test that a long package name size 224 is invalid.
+static const std::string kLongPackageName =
+ "com.foo.nameRw8ajIGbYmqPuO0K7TYJFsI2pjlDAS0pYOYQlJvtQu"
+ "xSoBKV1hMyNh4XfmcMj8OgPHfFaTXeKEHFMdGQHpw9Dz9Uqr8h1krgJLRv2aXyPCsGdVwBJzfZ4COVRiX3sc9O"
+ "CUrTTvZe6wXlgKb5Qz5qdkTBZ5euzGeoyZwestDTBIgT5exAl5efnznwzceS7VsIntgY10UUQvaoTsLBO6le";
+
TEST(UtilTest, TrimOnlyWhitespace) {
const StringPiece trimmed = util::TrimWhitespace("\n ");
EXPECT_TRUE(trimmed.empty());
@@ -108,6 +119,7 @@ TEST(UtilTest, IsAndroidPackageName) {
EXPECT_TRUE(util::IsAndroidPackageName("com.foo.test_thing"));
EXPECT_TRUE(util::IsAndroidPackageName("com.foo.testing_thing_"));
EXPECT_TRUE(util::IsAndroidPackageName("com.foo.test_99_"));
+ EXPECT_TRUE(util::IsAndroidPackageName(kMaxPackageName));
EXPECT_FALSE(util::IsAndroidPackageName("android._test"));
EXPECT_FALSE(util::IsAndroidPackageName("com"));
@@ -116,6 +128,27 @@ TEST(UtilTest, IsAndroidPackageName) {
EXPECT_FALSE(util::IsAndroidPackageName(".android"));
EXPECT_FALSE(util::IsAndroidPackageName(".."));
EXPECT_FALSE(util::IsAndroidPackageName("cøm.foo"));
+ EXPECT_FALSE(util::IsAndroidPackageName(kLongPackageName));
+}
+
+TEST(UtilTest, IsAndroidSharedUserId) {
+ EXPECT_TRUE(util::IsAndroidSharedUserId("android", "foo"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "android.test"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.test_thing"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.testing_thing_"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.test_99_"));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", ""));
+ EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", kMaxPackageName));
+
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "android._test"));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "com"));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "_android"));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "android."));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", ".android"));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", ".."));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "cøm.foo"));
+ EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", kLongPackageName));
}
TEST(UtilTest, FullyQualifiedClassName) {
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index cb844f085ecc..ea42d26358a8 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -21,23 +21,34 @@ using ::android::StringPiece;
namespace aapt {
namespace xml {
-static bool wrapper_one(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) {
+static bool wrapper_one(const XmlNodeAction::ActionFunc& f, Element* el,
+ const XmlActionExecutorPolicy& policy, SourcePathDiagnostics*) {
return f(el);
}
-static bool wrapper_two(XmlNodeAction::ActionFuncWithDiag& f, Element* el,
- SourcePathDiagnostics* diag) {
+static bool wrapper_two(const XmlNodeAction::ActionFuncWithDiag& f, Element* el,
+ const XmlActionExecutorPolicy& policy, SourcePathDiagnostics* diag) {
return f(el, diag);
}
+static bool wrapper_three(const XmlNodeAction::ActionFuncWithPolicyAndDiag& f, Element* el,
+ const XmlActionExecutorPolicy& policy, SourcePathDiagnostics* diag) {
+ return f(el, policy, diag);
+}
+
void XmlNodeAction::Action(XmlNodeAction::ActionFunc f) {
- actions_.emplace_back(std::bind(
- wrapper_one, std::move(f), std::placeholders::_1, std::placeholders::_2));
+ actions_.emplace_back(std::bind(wrapper_one, std::move(f), std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
}
void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithDiag f) {
- actions_.emplace_back(std::bind(
- wrapper_two, std::move(f), std::placeholders::_1, std::placeholders::_2));
+ actions_.emplace_back(std::bind(wrapper_two, std::move(f), std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
+}
+
+void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithPolicyAndDiag f) {
+ actions_.emplace_back(std::bind(wrapper_three, std::move(f), std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
}
static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) {
@@ -51,8 +62,8 @@ static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) {
bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPiece>* bread_crumb,
SourcePathDiagnostics* diag, Element* el) const {
bool error = false;
- for (const ActionFuncWithDiag& action : actions_) {
- error |= !action(el, diag);
+ for (const ActionFuncWithPolicyAndDiag& action : actions_) {
+ error |= !action(el, policy, diag);
}
for (Element* child_el : el->GetChildElements()) {
@@ -74,11 +85,11 @@ bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPi
for (const StringPiece& element : *bread_crumb) {
error_msg << "<" << element << ">";
}
- if (policy == XmlActionExecutorPolicy::kWhitelistWarning) {
+ if (policy == XmlActionExecutorPolicy::kAllowListWarning) {
// Treat the error only as a warning.
diag->Warn(error_msg);
} else {
- // Policy is XmlActionExecutorPolicy::kWhitelist, we should fail.
+ // Policy is XmlActionExecutorPolicy::kAllowList, we should fail.
diag->Error(error_msg);
error = true;
}
@@ -94,7 +105,7 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di
Element* el = doc->root.get();
if (!el) {
- if (policy == XmlActionExecutorPolicy::kWhitelist) {
+ if (policy == XmlActionExecutorPolicy::kAllowList) {
source_diag.Error(DiagMessage() << "no root XML tag found");
return false;
}
@@ -109,7 +120,7 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di
return iter->second.Execute(policy, &bread_crumb, &source_diag, el);
}
- if (policy == XmlActionExecutorPolicy::kWhitelist) {
+ if (policy == XmlActionExecutorPolicy::kAllowList) {
DiagMessage error_msg(el->line_number);
error_msg << "unexpected root element ";
PrintElementToDiagMessage(el, &error_msg);
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
index f689b2a3eaa8..78c43345deb7 100644
--- a/tools/aapt2/xml/XmlActionExecutor.h
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -37,18 +37,20 @@ enum class XmlActionExecutorPolicy {
// The actions defined must match and run. If an element is found that does not match an action,
// an error occurs.
// Note: namespaced elements are always ignored.
- kWhitelist,
+ kAllowList,
// The actions defined should match and run. if an element is found that does not match an
// action, a warning is printed.
// Note: namespaced elements are always ignored.
- kWhitelistWarning,
+ kAllowListWarning,
};
// Contains the actions to perform at this XML node. This is a recursive data structure that
// holds XmlNodeActions for child XML nodes.
class XmlNodeAction {
public:
+ using ActionFuncWithPolicyAndDiag =
+ std::function<bool(Element*, XmlActionExecutorPolicy, SourcePathDiagnostics*)>;
using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>;
using ActionFunc = std::function<bool(Element*)>;
@@ -61,6 +63,7 @@ class XmlNodeAction {
// Add an action to be performed at this XmlNodeAction.
void Action(ActionFunc f);
void Action(ActionFuncWithDiag);
+ void Action(ActionFuncWithPolicyAndDiag);
private:
friend class XmlActionExecutor;
@@ -69,7 +72,7 @@ class XmlNodeAction {
SourcePathDiagnostics* diag, Element* el) const;
std::map<std::string, XmlNodeAction> map_;
- std::vector<ActionFuncWithDiag> actions_;
+ std::vector<ActionFuncWithPolicyAndDiag> actions_;
};
// Allows the definition of actions to execute at specific XML elements defined by their hierarchy.
diff --git a/tools/aapt2/xml/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp
index d39854e5fe4e..d47b49590f5c 100644
--- a/tools/aapt2/xml/XmlActionExecutor_test.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp
@@ -60,10 +60,10 @@ TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) {
StdErrDiagnostics diag;
doc = test::BuildXmlDom("<manifest><application /><activity /></manifest>");
- ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get()));
+ ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kAllowList, &diag, doc.get()));
doc = test::BuildXmlDom("<manifest><application><activity /></application></manifest>");
- ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get()));
+ ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kAllowList, &diag, doc.get()));
}
} // namespace xml
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 005eeb936612..2cdcfe45b50e 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -374,6 +374,7 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string*
std::unique_ptr<XmlResource> XmlResource::Clone() const {
std::unique_ptr<XmlResource> cloned = util::make_unique<XmlResource>(file);
+ CloningValueTransformer cloner(&cloned->string_pool);
if (root != nullptr) {
cloned->root = root->CloneElement([&](const xml::Element& src, xml::Element* dst) {
dst->attributes.reserve(src.attributes.size());
@@ -384,7 +385,7 @@ std::unique_ptr<XmlResource> XmlResource::Clone() const {
cloned_attr.value = attr.value;
cloned_attr.compiled_attribute = attr.compiled_attribute;
if (attr.compiled_value != nullptr) {
- cloned_attr.compiled_value.reset(attr.compiled_value->Clone(&cloned->string_pool));
+ cloned_attr.compiled_value = attr.compiled_value->Transform(cloner);
}
dst->attributes.push_back(std::move(cloned_attr));
}
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index a023494ad8f7..182203d397c3 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -177,6 +177,10 @@ const std::string& XmlPullParser::element_name() const {
return event_queue_.front().data2;
}
+const std::vector<XmlPullParser::PackageDecl>& XmlPullParser::package_decls() const {
+ return package_aliases_;
+}
+
XmlPullParser::const_iterator XmlPullParser::begin_attributes() const {
return event_queue_.front().attributes.begin();
}
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index 6ebaa285745b..5da2d4b10a4b 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -123,6 +123,13 @@ class XmlPullParser : public IPackageDeclStack {
*/
Maybe<ExtractedPackage> TransformPackageAlias(const android::StringPiece& alias) const override;
+ struct PackageDecl {
+ std::string prefix;
+ ExtractedPackage package;
+ };
+
+ const std::vector<PackageDecl>& package_decls() const;
+
//
// Remaining methods are for retrieving information about attributes
// associated with a StartElement.
@@ -180,11 +187,6 @@ class XmlPullParser : public IPackageDeclStack {
const std::string empty_;
size_t depth_;
std::stack<std::string> namespace_uris_;
-
- struct PackageDecl {
- std::string prefix;
- ExtractedPackage package;
- };
std::vector<PackageDecl> package_aliases_;
};
diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh
index f25fcdcb7479..99aaa3c4d6e5 100755
--- a/tools/aosp/aosp_sha.sh
+++ b/tools/aosp/aosp_sha.sh
@@ -11,7 +11,7 @@ else
if (( count == 0 )); then
echo
fi
- echo -e "\033[0;31mThe source of truth for '$file' is in AOSP.\033[0m"
+ echo -e "\033[0;31;47mThe source of truth for '$file' is in AOSP.\033[0m"
(( count++ ))
done < <(git show --name-only --pretty=format: $1 | grep -- "$2")
if (( count != 0 )); then
diff --git a/tools/codegen/src/com/android/codegen/ClassInfo.kt b/tools/codegen/src/com/android/codegen/ClassInfo.kt
index bf95a2eb2193..056898c9eca1 100644
--- a/tools/codegen/src/com/android/codegen/ClassInfo.kt
+++ b/tools/codegen/src/com/android/codegen/ClassInfo.kt
@@ -1,12 +1,14 @@
package com.android.codegen
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.TypeDeclaration
open class ClassInfo(val classAst: ClassOrInterfaceDeclaration, val fileInfo: FileInfo) {
val fileAst = fileInfo.fileAst
val nestedClasses = classAst.members.filterIsInstance<ClassOrInterfaceDeclaration>()
+ val nestedTypes = classAst.members.filterIsInstance<TypeDeclaration<*>>()
val superInterfaces = classAst.implementedTypes.map { it.asString() }
val superClass = classAst.extendedTypes.getOrNull(0)
diff --git a/tools/codegen/src/com/android/codegen/ClassPrinter.kt b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
index b90e1bb3e7e7..8cae14a2d040 100644
--- a/tools/codegen/src/com/android/codegen/ClassPrinter.kt
+++ b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
@@ -145,7 +145,6 @@ class ClassPrinter(
}
return when {
cliArgs.contains("--hidden-$kebabCase") -> true
- this == FeatureFlag.BUILD_UPON -> FeatureFlag.BUILDER.hidden
else -> false
}
}
diff --git a/tools/codegen/src/com/android/codegen/Debug.kt b/tools/codegen/src/com/android/codegen/Debug.kt
new file mode 100644
index 000000000000..de3184468540
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/Debug.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.codegen
+
+import com.github.javaparser.ast.Node
+
+fun Node.dump(indent: String = ""): String {
+ return buildString {
+ append(indent)
+ appendln(dumpOneLineNoChildren())
+ childNodes.forEach { child ->
+ append(child.dump(indent + " "))
+ }
+ }
+}
+
+private fun Node.dumpOneLineNoChildren(): String {
+ val node = this
+ return buildString {
+ append(node::class.java.simpleName)
+ if (childNodes.isEmpty()) {
+ append(": ").append(node.toString())
+ }
+ }
+}
diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt
index 02ebaef90f0b..74392ddc30e6 100644
--- a/tools/codegen/src/com/android/codegen/FieldInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -220,11 +220,12 @@ data class FieldInfo(
isBinder(FieldInnerType!!) -> "BinderList"
else -> "ParcelableList"
}
+ isStrongBinder(Type) -> "StrongBinder"
isIInterface(Type) -> "StrongInterface"
- isBinder(Type) -> "StrongBinder"
else -> "TypedObject"
}.capitalize()
+ private fun isStrongBinder(type: String) = type == "Binder" || type == "IBinder"
private fun isBinder(type: String) = type == "Binder" || type == "IBinder" || isIInterface(type)
private fun isIInterface(type: String) = type.length >= 2 && type[0] == 'I' && type[1].isUpperCase()
} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/FileInfo.kt b/tools/codegen/src/com/android/codegen/FileInfo.kt
index 909472640f29..a1d0389b0041 100644
--- a/tools/codegen/src/com/android/codegen/FileInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FileInfo.kt
@@ -272,7 +272,7 @@ class FileInfo(
/** Debug info */
fun summary(): String = when(this) {
is Code -> "${javaClass.simpleName}(${lines.size} lines): ${lines.getOrNull(0)?.take(70) ?: ""}..."
- is DataClass -> "DataClass ${ast.nameAsString}:\n" +
+ is DataClass -> "DataClass ${ast.nameAsString} nested:${ast.nestedTypes.map { it.nameAsString }}:\n" +
chunks.joinToString("\n") { it.summary() } +
"\n//end ${ast.nameAsString}"
}
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index 5a96cf1d9bdb..5fc800b09ee9 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -3,7 +3,8 @@ package com.android.codegen
import com.github.javaparser.ast.body.FieldDeclaration
import com.github.javaparser.ast.body.MethodDeclaration
import com.github.javaparser.ast.body.VariableDeclarator
-import com.github.javaparser.ast.expr.*
+import com.github.javaparser.ast.expr.AnnotationExpr
+import com.github.javaparser.ast.expr.ArrayInitializerExpr
import java.io.File
@@ -13,10 +14,7 @@ import java.io.File
fun ClassPrinter.generateConstDefs() {
val consts = classAst.fields.filter {
it.isStatic && it.isFinal && it.variables.all { variable ->
- val initializer = variable.initializer.orElse(null)
- val isLiteral = initializer is LiteralExpr
- || (initializer is UnaryExpr && initializer.expression is LiteralExpr)
- isLiteral && variable.type.asString() in listOf("int", "String")
+ variable.type.asString() in listOf("int", "String")
} && it.annotations.none { it.nameAsString == DataClassSuppressConstDefs }
}.flatMap { field -> field.variables.map { it to field } }
val intConsts = consts.filter { it.first.type.asString() == "int" }
@@ -163,7 +161,12 @@ fun ClassPrinter.generateCopyConstructor() {
return
}
- +"/** Copy constructor */"
+ +"/**"
+ +" * Copy constructor"
+ if (FeatureFlag.COPY_CONSTRUCTOR.hidden) {
+ +" * @hide"
+ }
+ +" */"
+GENERATED_MEMBER_HEADER
"public $ClassName(@$NonNull $ClassName orig)" {
fields.forEachApply {
@@ -703,7 +706,7 @@ fun ClassPrinter.generateSetters() {
generateFieldJavadoc(forceHide = FeatureFlag.SETTERS.hidden)
+GENERATED_MEMBER_HEADER
- "public $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" {
+ "public @$NonNull $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" {
generateSetFrom("value")
+"return this;"
}
diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
index d6953c00fc0b..83108e5ae109 100644
--- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
+++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
@@ -41,7 +41,10 @@ private fun ClassPrinter.generateInputSignaturesForClass(classAst: ClassOrInterf
}
} + ("class ${classAst.nameAsString}" +
" extends ${classAst.extendedTypes.map { getFullClassName(it) }.ifEmpty { listOf("java.lang.Object") }.joinToString(", ")}" +
- " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]")
+ " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]") +
+ classAst.nestedNonDataClasses.flatMap { nestedClass ->
+ generateInputSignaturesForClass(nestedClass)
+ }
}
private fun ClassPrinter.annotationsToString(annotatedAst: NodeWithAnnotations<*>): String {
@@ -60,6 +63,7 @@ private fun ClassPrinter.annotationToString(ann: AnnotationExpr?): String {
append("@")
append(getFullClassName(ann.nameAsString))
if (ann is MarkerAnnotationExpr) return@buildString
+ if (!ann.nameAsString.startsWith("DataClass")) return@buildString
append("(")
@@ -125,7 +129,7 @@ private fun ClassPrinter.getFullClassName(className: String): String {
if (classAst.nameAsString == className) return thisPackagePrefix + classAst.nameAsString
- nestedClasses.find {
+ nestedTypes.find {
it.nameAsString == className
}?.let { return thisClassPrefix + it.nameAsString }
@@ -141,6 +145,8 @@ private fun ClassPrinter.getFullClassName(className: String): String {
if (className[0].isLowerCase()) return className //primitive
+ if (className[0] == '?') return className //wildcard
+
return thisPackagePrefix + className
}
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 6f740cd663e3..4da401951470 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.15"
+const val CODEGEN_VERSION = "1.0.23"
const val CANONICAL_BUILDER_CLASS = "Builder"
const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index a117aa09ab62..9ceb2042d74e 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -103,6 +103,10 @@ val TypeDeclaration<*>.nestedTypes get() = childNodes.filterIsInstance<TypeDecla
val TypeDeclaration<*>.nestedDataClasses get()
= nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>()
.filter { it.annotations.any { it.nameAsString.endsWith("DataClass") } }
+val TypeDeclaration<*>.nestedNonDataClasses get()
+ = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>()
+ .filter { it.annotations.none { it.nameAsString.endsWith("DataClass") } }
+ .filterNot { it.isInterface }
val TypeDeclaration<*>.startLine get() = range.get()!!.begin.line
inline fun <T> List<T>.forEachSequentialPair(action: (T, T?) -> Unit) {
diff --git a/tools/fonts/Android.bp b/tools/fonts/Android.bp
index 14c4b2c3fb03..eeb9e3ceda1e 100644
--- a/tools/fonts/Android.bp
+++ b/tools/fonts/Android.bp
@@ -46,3 +46,15 @@ python_binary_host {
"fontTools",
],
}
+
+python_binary_host {
+ name: "update_font_metadata",
+ defaults: ["fonts_python_defaults"],
+ main: "update_font_metadata.py",
+ srcs: [
+ "update_font_metadata.py",
+ ],
+ libs: [
+ "fontTools",
+ ],
+}
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index a4a315b7e371..1266ccee4df5 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -4,6 +4,7 @@ import collections
import copy
import glob
from os import path
+import re
import sys
from xml.etree import ElementTree
@@ -113,7 +114,7 @@ def get_variation_sequences_cmap(font):
def get_emoji_map(font):
# Add normal characters
emoji_map = copy.copy(get_best_cmap(font))
- reverse_cmap = {glyph: code for code, glyph in emoji_map.items()}
+ reverse_cmap = {glyph: code for code, glyph in emoji_map.items() if not contains_pua(code) }
# Add variation sequences
vs_dict = get_variation_sequences_cmap(font).uvsDict
@@ -199,8 +200,9 @@ def check_hyphens(hyphens_dir):
class FontRecord(object):
- def __init__(self, name, scripts, variant, weight, style, fallback_for, font):
+ def __init__(self, name, psName, scripts, variant, weight, style, fallback_for, font):
self.name = name
+ self.psName = psName
self.scripts = scripts
self.variant = variant
self.weight = weight
@@ -236,6 +238,7 @@ def parse_fonts_xml(fonts_xml_path):
assert variant in {None, 'elegant', 'compact'}, (
'Unexpected value for variant: %s' % variant)
+ trim_re = re.compile(r"^[ \n\r\t]*(.+)[ \n\r\t]*$")
for family in families:
name = family.get('name')
variant = family.get('variant')
@@ -251,6 +254,10 @@ def parse_fonts_xml(fonts_xml_path):
assert child.tag == 'font', (
'Unknown tag <%s>' % child.tag)
font_file = child.text.rstrip()
+
+ m = trim_re.match(font_file)
+ font_file = m.group(1)
+
weight = int(child.get('weight'))
assert weight % 100 == 0, (
'Font weight "%d" is not a multiple of 100.' % weight)
@@ -270,11 +277,12 @@ def parse_fonts_xml(fonts_xml_path):
if index:
index = int(index)
- if not path.exists(path.join(_fonts_dir, font_file)):
+ if not path.exists(path.join(_fonts_dir, m.group(1))):
continue # Missing font is a valid case. Just ignore the missing font files.
record = FontRecord(
name,
+ child.get('postScriptName'),
frozenset(scripts),
variant,
weight,
@@ -314,27 +322,54 @@ def get_emoji_font():
return emoji_fonts[0]
+def is_pua(x):
+ return 0xE000 <= x <= 0xF8FF or 0xF0000 <= x <= 0xFFFFD or 0x100000 <= x <= 0x10FFFD
+
+def contains_pua(sequence):
+ if type(sequence) is tuple:
+ return any([is_pua(x) for x in sequence])
+ else:
+ return is_pua(sequence)
+
+
+def check_emoji_compat():
+ ttf = open_font(get_emoji_font())
+ meta = ttf['meta']
+ assert meta, 'Compat font must have meta table'
+ assert 'Emji' in meta.data, 'meta table should have \'Emji\' data.'
+
def check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji):
coverage = get_emoji_map(emoji_font)
+
+ errors = []
+
for sequence in all_emoji:
- assert sequence in coverage, (
- '%s is not supported in the emoji font.' % printable(sequence))
+ if not sequence in coverage:
+ errors.append('%s is not supported in the emoji font.' % printable(sequence))
for sequence in coverage:
if sequence in {0x0000, 0x000D, 0x0020}:
# The font needs to support a few extra characters, which is OK
continue
- assert sequence in all_emoji, (
- 'Emoji font should not support %s.' % printable(sequence))
+
+ if contains_pua(sequence):
+ # The font needs to have some PUA for EmojiCompat library.
+ continue
+
+ if sequence not in all_emoji:
+ errors.append('%s support unexpected in the emoji font.' % printable(sequence))
for first, second in equivalent_emoji.items():
- assert coverage[first] == coverage[second], (
- '%s and %s should map to the same glyph.' % (
+ if first not in coverage or second not in coverage:
+ continue # sequence will be reported missing
+ if coverage[first] != coverage[second]:
+ errors.append('%s and %s should map to the same glyph.' % (
printable(first),
printable(second)))
for glyph in set(coverage.values()):
- maps_to_glyph = [seq for seq in coverage if coverage[seq] == glyph]
+ maps_to_glyph = [
+ seq for seq in coverage if coverage[seq] == glyph and not contains_pua(seq) ]
if len(maps_to_glyph) > 1:
# There are more than one sequences mapping to the same glyph. We
# need to make sure they were expected to be equivalent.
@@ -344,11 +379,13 @@ def check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji):
while equivalent_seq in equivalent_emoji:
equivalent_seq = equivalent_emoji[equivalent_seq]
equivalent_seqs.add(equivalent_seq)
- assert len(equivalent_seqs) == 1, (
- 'The sequences %s should not result in the same glyph %s' % (
+ if len(equivalent_seqs) != 1:
+ errors.append('The sequences %s should not result in the same glyph %s' % (
printable(equivalent_seqs),
glyph))
+ assert not errors, '%d emoji font errors:\n%s\n%d emoji font coverage errors' % (len(errors), '\n'.join(errors), len(errors))
+
def check_emoji_defaults(default_emoji):
missing_text_chars = _emoji_properties['Emoji'] - default_emoji
@@ -657,6 +694,37 @@ def check_cjk_punctuation():
break
assert_font_supports_none_of_chars(record.font, cjk_punctuation, name)
+def getPostScriptName(font):
+ font_file, index = font
+ font_path = path.join(_fonts_dir, font_file)
+ if index is not None:
+ # Use the first font file in the collection for resolving post script name.
+ ttf = ttLib.TTFont(font_path, fontNumber=0)
+ else:
+ ttf = ttLib.TTFont(font_path)
+
+ nameTable = ttf['name']
+ for name in nameTable.names:
+ if (name.nameID == 6 and name.platformID == 3 and name.platEncID == 1
+ and name.langID == 0x0409):
+ return str(name)
+
+def check_canonical_name():
+ for record in _all_fonts:
+ file_name, index = record.font
+
+ psName = getPostScriptName(record.font)
+ if record.psName:
+ # If fonts element has postScriptName attribute, it should match with the PostScript
+ # name in the name table.
+ assert psName == record.psName, ('postScriptName attribute %s should match with %s' % (
+ record.psName, psName))
+ else:
+ # If fonts element doesn't have postScriptName attribute, the file name should match
+ # with the PostScript name in the name table.
+ assert psName == file_name[:-4], ('file name %s should match with %s' % (
+ file_name, psName))
+
def main():
global _fonts_dir
@@ -675,11 +743,14 @@ def main():
check_cjk_punctuation()
+ check_canonical_name()
+
check_emoji = sys.argv[2]
if check_emoji == 'true':
ucd_path = sys.argv[3]
parse_ucd(ucd_path)
all_emoji, default_emoji, equivalent_emoji = compute_expected_emoji()
+ check_emoji_compat()
check_emoji_coverage(all_emoji, equivalent_emoji)
check_emoji_defaults(default_emoji)
diff --git a/tools/fonts/update_font_metadata.py b/tools/fonts/update_font_metadata.py
new file mode 100755
index 000000000000..c07a98a1e3d2
--- /dev/null
+++ b/tools/fonts/update_font_metadata.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+import argparse
+
+from fontTools import ttLib
+
+
+def update_font_revision(font, revisionSpec):
+ if revisionSpec.startswith('+'):
+ font['head'].fontRevision += float(revisionSpec[1:])
+ else:
+ font['head'].fontRevision = float(revisionSpec)
+
+
+def main():
+ args_parser = argparse.ArgumentParser(description='Update font file metadata')
+ args_parser.add_argument('--input', help='Input otf/ttf font file.')
+ args_parser.add_argument('--output', help='Output file for updated font file.')
+ args_parser.add_argument('--revision', help='Updated font revision. Use + to update revision based on the current revision')
+ args = args_parser.parse_args()
+
+ font = ttLib.TTFont(args.input)
+ update_font_revision(font, args.revision)
+ font.save(args.output)
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/powerstats/Android.bp b/tools/powerstats/Android.bp
new file mode 100644
index 000000000000..9c58daf0f922
--- /dev/null
+++ b/tools/powerstats/Android.bp
@@ -0,0 +1,19 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_binary_host {
+ name: "PowerStatsServiceProtoParser",
+ manifest: "PowerStatsServiceProtoParser_manifest.txt",
+ srcs: [
+ "*.java",
+ ],
+ static_libs: [
+ "platformprotos",
+ ],
+}
diff --git a/tools/powerstats/OWNERS b/tools/powerstats/OWNERS
index d68066bb8c40..12f13ea63db0 100644
--- a/tools/powerstats/OWNERS
+++ b/tools/powerstats/OWNERS
@@ -1 +1 @@
-include /services/core/java/com/android/server/power/OWNERS
+include /services/core/java/com/android/server/powerstats/OWNERS
diff --git a/tools/powerstats/PowerStatsServiceProtoParser.java b/tools/powerstats/PowerStatsServiceProtoParser.java
new file mode 100644
index 000000000000..04f9bf26b43c
--- /dev/null
+++ b/tools/powerstats/PowerStatsServiceProtoParser.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package com.android.server.powerstats;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * This class implements a utility to parse ODPM data out
+ * of incident reports contained in bugreports. The data
+ * is output to STDOUT in csv format.
+ */
+public class PowerStatsServiceProtoParser {
+ private static void printEnergyMeterInfo(PowerStatsServiceMeterProto proto) {
+ String csvHeader = new String();
+ for (int i = 0; i < proto.getChannelCount(); i++) {
+ ChannelProto energyMeterInfo = proto.getChannel(i);
+ csvHeader += "Index,Timestamp,Duration," + energyMeterInfo.getId()
+ + "/" + energyMeterInfo.getName() + "/" + energyMeterInfo.getSubsystem() + ",";
+ }
+ System.out.println(csvHeader);
+ }
+
+ private static void printEnergyMeasurements(PowerStatsServiceMeterProto proto) {
+ int energyMeterInfoCount = proto.getChannelCount();
+
+ if (energyMeterInfoCount > 0) {
+ int energyMeasurementCount = proto.getEnergyMeasurementCount();
+ int energyMeasurementSetCount = energyMeasurementCount / energyMeterInfoCount;
+
+ for (int i = 0; i < energyMeasurementSetCount; i++) {
+ String csvRow = new String();
+ for (int j = 0; j < energyMeterInfoCount; j++) {
+ EnergyMeasurementProto energyMeasurement =
+ proto.getEnergyMeasurement(i * energyMeterInfoCount + j);
+ csvRow += energyMeasurement.getId() + ","
+ + energyMeasurement.getTimestampMs() + ","
+ + energyMeasurement.getDurationMs() + ","
+ + energyMeasurement.getEnergyUws() + ",";
+ }
+ System.out.println(csvRow);
+ }
+ } else {
+ System.out.println("Error: energyMeterInfoCount is zero");
+ }
+ }
+
+ private static void printEnergyConsumer(PowerStatsServiceModelProto proto) {
+ String csvHeader = new String();
+ for (int i = 0; i < proto.getEnergyConsumerCount(); i++) {
+ EnergyConsumerProto energyConsumer = proto.getEnergyConsumer(i);
+ csvHeader += "Index,Timestamp," + energyConsumer.getId() + "/"
+ + energyConsumer.getOrdinal() + "/"
+ + energyConsumer.getType() + "/"
+ + energyConsumer.getName() + ",";
+ }
+ System.out.println(csvHeader);
+ }
+
+ private static void printEnergyConsumerResults(PowerStatsServiceModelProto proto) {
+ int energyConsumerCount = proto.getEnergyConsumerCount();
+
+ if (energyConsumerCount > 0) {
+ int energyConsumerResultCount = proto.getEnergyConsumerResultCount();
+ int energyConsumerResultSetCount = energyConsumerResultCount / energyConsumerCount;
+
+ for (int i = 0; i < energyConsumerResultSetCount; i++) {
+ String csvRow = new String();
+ for (int j = 0; j < energyConsumerCount; j++) {
+ EnergyConsumerResultProto energyConsumerResult =
+ proto.getEnergyConsumerResult(i * energyConsumerCount + j);
+ csvRow += energyConsumerResult.getId() + ","
+ + energyConsumerResult.getTimestampMs() + ","
+ + energyConsumerResult.getEnergyUws() + ",";
+ for (int k = 0; k < energyConsumerResult.getAttributionCount(); k++) {
+ final EnergyConsumerAttributionProto energyConsumerAttribution =
+ energyConsumerResult.getAttribution(k);
+ csvRow += energyConsumerAttribution.getUid() + ","
+ + energyConsumerAttribution.getEnergyUws() + ",";
+ }
+ }
+ System.out.println(csvRow);
+ }
+ } else {
+ System.out.println("Error: energyConsumerCount is zero");
+ }
+ }
+
+ private static void printPowerEntityInfo(PowerStatsServiceResidencyProto proto) {
+ String csvHeader = new String();
+ for (int i = 0; i < proto.getPowerEntityCount(); i++) {
+ PowerEntityProto powerEntity = proto.getPowerEntity(i);
+ csvHeader += powerEntity.getId() + "," + powerEntity.getName() + ",";
+ for (int j = 0; j < powerEntity.getStatesCount(); j++) {
+ StateProto state = powerEntity.getStates(j);
+ csvHeader += state.getId() + "," + state.getName() + ",";
+ }
+ }
+ System.out.println(csvHeader);
+ }
+
+ private static void printStateResidencyResult(PowerStatsServiceResidencyProto proto) {
+ for (int i = 0; i < proto.getStateResidencyResultCount(); i++) {
+ String csvRow = new String();
+
+ StateResidencyResultProto stateResidencyResult = proto.getStateResidencyResult(i);
+ csvRow += stateResidencyResult.getId() + ",";
+
+ for (int j = 0; j < stateResidencyResult.getStateResidencyDataCount(); j++) {
+ StateResidencyProto stateResidency = stateResidencyResult.getStateResidencyData(j);
+ csvRow += stateResidency.getId() + ","
+ + stateResidency.getTotalTimeInStateMs() + ","
+ + stateResidency.getTotalStateEntryCount() + ","
+ + stateResidency.getLastEntryTimestampMs() + ",";
+ }
+ System.out.println(csvRow);
+ }
+ }
+
+ private static void generateCsvFile(String pathToIncidentReport) {
+ try {
+ // Print power meter data.
+ IncidentReportMeterProto irMeterProto =
+ IncidentReportMeterProto.parseFrom(new FileInputStream(pathToIncidentReport));
+
+ if (irMeterProto.hasIncidentReport()) {
+ PowerStatsServiceMeterProto pssMeterProto = irMeterProto.getIncidentReport();
+ printEnergyMeterInfo(pssMeterProto);
+ printEnergyMeasurements(pssMeterProto);
+ } else {
+ System.out.println("Meter incident report not found. Exiting.");
+ }
+
+ // Print power model data.
+ IncidentReportModelProto irModelProto =
+ IncidentReportModelProto.parseFrom(new FileInputStream(pathToIncidentReport));
+
+ if (irModelProto.hasIncidentReport()) {
+ PowerStatsServiceModelProto pssModelProto = irModelProto.getIncidentReport();
+ printEnergyConsumer(pssModelProto);
+ printEnergyConsumerResults(pssModelProto);
+ } else {
+ System.out.println("Model incident report not found. Exiting.");
+ }
+
+ // Print state residency data.
+ IncidentReportResidencyProto irResidencyProto =
+ IncidentReportResidencyProto.parseFrom(
+ new FileInputStream(pathToIncidentReport));
+
+ if (irResidencyProto.hasIncidentReport()) {
+ PowerStatsServiceResidencyProto pssResidencyProto =
+ irResidencyProto.getIncidentReport();
+ printPowerEntityInfo(pssResidencyProto);
+ printStateResidencyResult(pssResidencyProto);
+ } else {
+ System.out.println("Residency incident report not found. Exiting.");
+ }
+
+ } catch (IOException e) {
+ System.out.println("Unable to open incident report file: " + pathToIncidentReport);
+ System.out.println(e);
+ }
+ }
+
+ /**
+ * This is the entry point to parse the ODPM data out of incident reports.
+ * It requires one argument which is the path to the incident_report.proto
+ * file captured in a bugreport.
+ *
+ * @param args Path to incident_report.proto passed in from command line.
+ */
+ public static void main(String[] args) {
+ if (args.length > 0) {
+ generateCsvFile(args[0]);
+ } else {
+ System.err.println("Usage: PowerStatsServiceProtoParser <incident_report.proto>");
+ System.err.println("Missing path to incident_report.proto. Exiting.");
+ System.exit(1);
+ }
+ }
+}
diff --git a/tools/powerstats/PowerStatsServiceProtoParser_manifest.txt b/tools/powerstats/PowerStatsServiceProtoParser_manifest.txt
new file mode 100644
index 000000000000..5df12118ce80
--- /dev/null
+++ b/tools/powerstats/PowerStatsServiceProtoParser_manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.server.powerstats.PowerStatsServiceProtoParser
diff --git a/tools/processors/intdef_mappings/Android.bp b/tools/processors/intdef_mappings/Android.bp
new file mode 100644
index 000000000000..82a5dac21160
--- /dev/null
+++ b/tools/processors/intdef_mappings/Android.bp
@@ -0,0 +1,42 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_plugin {
+ name: "intdef-annotation-processor",
+
+ processor_class: "android.processor.IntDefProcessor",
+
+ srcs: [
+ ":framework-annotations",
+ "src/**/*.java",
+ "src/**/*.kt"
+ ],
+
+ use_tools_jar: true,
+}
+
+java_test_host {
+ name: "intdef-annotation-processor-test",
+
+ srcs: [
+ "test/**/*.java",
+ "test/**/*.kt"
+ ],
+ java_resource_dirs: ["test/resources"],
+
+ static_libs: [
+ "compile-testing-prebuilt",
+ "truth-prebuilt",
+ "junit",
+ "guava",
+ "intdef-annotation-processor"
+ ],
+
+ test_suites: ["general-tests"],
+}
diff --git a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt
new file mode 100644
index 000000000000..4c1fa6ec40b3
--- /dev/null
+++ b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+package android.processor
+
+import android.annotation.IntDef
+import com.sun.source.tree.IdentifierTree
+import com.sun.source.tree.MemberSelectTree
+import com.sun.source.tree.NewArrayTree
+import com.sun.source.util.SimpleTreeVisitor
+import com.sun.source.util.Trees
+import java.io.IOException
+import java.io.Writer
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.AnnotationValue
+import javax.lang.model.element.TypeElement
+import javax.tools.Diagnostic.Kind
+import javax.tools.StandardLocation.CLASS_OUTPUT
+import kotlin.collections.set
+
+/**
+ * The IntDefProcessor is intended to generate a mapping from ints to their respective string
+ * identifier for each IntDef for use by Winscope or any other tool which requires such a mapping.
+ *
+ * The processor will run when building :framework-minus-apex-intdefs and dump all the IntDef
+ * mappings found in the files that make up the build target as json to outputPath.
+ */
+class IntDefProcessor : AbstractProcessor() {
+ private val outputName = "intDefMapping.json"
+
+ override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
+
+ // Define what the annotation we care about are for compiler optimization
+ override fun getSupportedAnnotationTypes() = LinkedHashSet<String>().apply {
+ add(IntDef::class.java.name)
+ }
+
+ override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
+ // There should only be one matching annotation definition for intDef
+ val annotationType = annotations.firstOrNull() ?: return false
+ val annotatedElements = roundEnv.getElementsAnnotatedWith(annotationType)
+
+ val annotationTypeToIntDefMapping = annotatedElements.associate { annotatedElement ->
+ val type = (annotatedElement as TypeElement).qualifiedName.toString()
+ val mapping = generateIntDefMapping(annotatedElement, annotationType)
+ val intDef = annotatedElement.getAnnotation(IntDef::class.java)
+ type to IntDefMapping(mapping, intDef.flag)
+ }
+
+ try {
+ outputToFile(annotationTypeToIntDefMapping)
+ } catch (e: IOException) {
+ error("Failed to write IntDef mappings :: $e")
+ }
+ return false
+ }
+
+ private fun generateIntDefMapping(
+ annotatedElement: TypeElement,
+ annotationType: TypeElement
+ ): Map<Int, String> {
+ // LinkedHashMap makes sure ordering is the same as in the code
+ val mapping = LinkedHashMap<Int, String>()
+
+ val annotationMirror = annotatedElement.annotationMirrors
+ // Should only ever be one matching this condition
+ .first { it.annotationType.asElement() == annotationType }
+
+ val value = annotationMirror.elementValues.entries
+ .first { entry -> entry.key.simpleName.contentEquals("value") }
+ .value
+
+ val trees = Trees.instance(processingEnv)
+ val tree = trees.getTree(annotatedElement, annotationMirror, value)
+
+ val identifiers = ArrayList<String>()
+ tree.accept(IdentifierVisitor(), identifiers)
+
+ val values = value.value as List<AnnotationValue>
+
+ for (i in identifiers.indices) {
+ mapping[values[i].value as Int] = identifiers[i]
+ }
+
+ return mapping
+ }
+
+ private class IdentifierVisitor : SimpleTreeVisitor<Void, ArrayList<String>>() {
+ override fun visitNewArray(node: NewArrayTree, indentifiers: ArrayList<String>): Void? {
+ for (initializer in node.initializers) {
+ initializer.accept(this, indentifiers)
+ }
+
+ return null
+ }
+
+ override fun visitMemberSelect(node: MemberSelectTree, indentifiers: ArrayList<String>):
+ Void? {
+ indentifiers.add(node.identifier.toString())
+
+ return null
+ }
+
+ override fun visitIdentifier(node: IdentifierTree, indentifiers: ArrayList<String>): Void? {
+ indentifiers.add(node.name.toString())
+
+ return null
+ }
+ }
+
+ @Throws(IOException::class)
+ private fun outputToFile(annotationTypeToIntDefMapping: Map<String, IntDefMapping>) {
+ val resource = processingEnv.filer.createResource(
+ CLASS_OUTPUT, "com.android.winscope", outputName)
+ val writer = resource.openWriter()
+ serializeTo(annotationTypeToIntDefMapping, writer)
+ writer.close()
+ }
+
+ private fun error(message: String) {
+ processingEnv.messager.printMessage(Kind.ERROR, message)
+ }
+
+ private fun note(message: String) {
+ processingEnv.messager.printMessage(Kind.NOTE, message)
+ }
+
+ class IntDefMapping(val mapping: Map<Int, String>, val flag: Boolean) {
+ val size
+ get() = this.mapping.size
+
+ val entries
+ get() = this.mapping.entries
+ }
+
+ companion object {
+ fun serializeTo(
+ annotationTypeToIntDefMapping: Map<String, IntDefMapping>,
+ writer: Writer
+ ) {
+ val indent = " "
+
+ writer.appendln("{")
+
+ val intDefTypesCount = annotationTypeToIntDefMapping.size
+ var currentIntDefTypesCount = 0
+ for ((field, intDefMapping) in annotationTypeToIntDefMapping) {
+ writer.appendln("""$indent"$field": {""")
+
+ // Start IntDef
+
+ writer.appendln("""$indent$indent"flag": ${intDefMapping.flag},""")
+
+ writer.appendln("""$indent$indent"values": {""")
+ intDefMapping.entries.joinTo(writer, separator = ",\n") { (value, identifier) ->
+ """$indent$indent$indent"$value": "$identifier""""
+ }
+ writer.appendln()
+ writer.appendln("$indent$indent}")
+
+ // End IntDef
+
+ writer.append("$indent}")
+ if (++currentIntDefTypesCount < intDefTypesCount) {
+ writer.appendln(",")
+ } else {
+ writer.appendln("")
+ }
+ }
+
+ writer.appendln("}")
+ }
+ }
+}
diff --git a/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt b/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt
new file mode 100644
index 000000000000..c0c159c98aac
--- /dev/null
+++ b/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+package android.processor
+
+import android.processor.IntDefProcessor.IntDefMapping
+import com.google.common.collect.ObjectArrays.concat
+import com.google.testing.compile.CompilationSubject.assertThat
+import com.google.testing.compile.Compiler.javac
+import com.google.testing.compile.JavaFileObjects
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import java.io.StringWriter
+import javax.tools.JavaFileObject
+import javax.tools.StandardLocation.CLASS_OUTPUT
+
+/**
+ * Tests for [IntDefProcessor]
+ */
+class IntDefProcessorTest {
+ private val mAnnotations = arrayOf<JavaFileObject>(
+ JavaFileObjects.forSourceLines("android.annotation.IntDef",
+ "package android.annotation;",
+ "import java.lang.annotation.Retention;",
+ "import java.lang.annotation.Target;",
+ "import static java.lang.annotation.ElementType.ANNOTATION_TYPE;",
+ "import static java.lang.annotation.RetentionPolicy.SOURCE;",
+ "@Retention(SOURCE)",
+ "@Target({ANNOTATION_TYPE})",
+ "public @interface IntDef {",
+ " String[] prefix() default {};",
+ " String[] suffix() default {};",
+ " int[] value() default {};",
+ " boolean flag() default false;",
+ "}")
+ )
+
+ @Test
+ public fun annotationProcessorGeneratesMapping() {
+ val sources: Array<JavaFileObject> = arrayOf(
+ JavaFileObjects.forSourceLines(
+ "com.android.server.accessibility.magnification.MagnificationGestureMatcher",
+ "package com.android.server.accessibility.magnification;",
+ "import android.annotation.IntDef;",
+ "import java.lang.annotation.Retention;",
+ "import java.lang.annotation.RetentionPolicy;",
+ "class MagnificationGestureMatcher {",
+ " private static final int GESTURE_BASE = 100;",
+ " public static final int GESTURE_TWO_FINGER_DOWN = GESTURE_BASE + 1;",
+ " public static final int GESTURE_SWIPE = GESTURE_BASE + 2;",
+ " @IntDef(prefix = {\"GESTURE_MAGNIFICATION_\"}, value = {",
+ " GESTURE_TWO_FINGER_DOWN,",
+ " GESTURE_SWIPE",
+ " })",
+ " @Retention(RetentionPolicy.SOURCE)",
+ " @interface GestureId {}",
+ "}"
+ ),
+ JavaFileObjects.forSourceLines(
+ "android.service.storage.ExternalStorageService",
+ "package android.service.storage;",
+ "import android.annotation.IntDef;",
+ "import java.lang.annotation.Retention;",
+ "import java.lang.annotation.RetentionPolicy;",
+ "class MagnificationGestureMatcher {",
+ " public static final int FLAG_SESSION_TYPE_FUSE = 1 << 0;",
+ " public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 1 << 1;",
+ " @IntDef(flag = true, prefix = {\"FLAG_SESSION_\"},",
+ " value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE})",
+ " @Retention(RetentionPolicy.SOURCE)",
+ " public @interface SessionFlag {}",
+ "}"
+ )
+ )
+
+ val expectedFile = """
+ {
+ "com.android.server.accessibility.magnification.MagnificationGestureMatcher.GestureId": {
+ "flag": false,
+ "values": {
+ "101": "GESTURE_TWO_FINGER_DOWN",
+ "102": "GESTURE_SWIPE"
+ }
+ },
+ "android.service.storage.MagnificationGestureMatcher.SessionFlag": {
+ "flag": true,
+ "values": {
+ "1": "FLAG_SESSION_TYPE_FUSE",
+ "2": "FLAG_SESSION_ATTRIBUTE_INDEXABLE"
+ }
+ }
+ }
+
+ """.trimIndent()
+
+ val filesToCompile = concat(mAnnotations, sources, JavaFileObject::class.java)
+
+ val compilation = javac()
+ .withProcessors(IntDefProcessor())
+ .compile(filesToCompile.toMutableList())
+
+ assertThat(compilation).succeeded()
+ assertThat(compilation).generatedFile(CLASS_OUTPUT, "com.android.winscope",
+ "intDefMapping.json").contentsAsUtf8String().isEqualTo(expectedFile)
+ }
+
+ @Test
+ public fun serializesMappingCorrectly() {
+ val map = linkedMapOf(
+ "SimpleIntDef" to IntDefMapping(linkedMapOf(
+ 0x0001 to "VAL_1",
+ 0x0002 to "VAL_2",
+ 0x0003 to "VAL_3"
+ ), flag = false),
+ "Flags" to IntDefMapping(linkedMapOf(
+ 0b0001 to "PRIVATE_FLAG_1",
+ 0b0010 to "PRIVATE_FLAG_2",
+ 0b0100 to "PRIVATE_FLAG_3"
+ ), flag = true)
+ )
+
+ val writer = StringWriter()
+ IntDefProcessor.serializeTo(map, writer)
+
+ val actualOutput = writer.toString()
+ val expectedOutput = """
+ {
+ "SimpleIntDef": {
+ "flag": false,
+ "values": {
+ "1": "VAL_1",
+ "2": "VAL_2",
+ "3": "VAL_3"
+ }
+ },
+ "Flags": {
+ "flag": true,
+ "values": {
+ "1": "PRIVATE_FLAG_1",
+ "2": "PRIVATE_FLAG_2",
+ "4": "PRIVATE_FLAG_3"
+ }
+ }
+ }
+
+ """.trimIndent()
+
+ assertEquals(actualOutput, expectedOutput)
+ }
+} \ No newline at end of file
diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
index 51faa49a86cc..1aec9b812e61 100644
--- a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
+++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
@@ -98,8 +98,10 @@ class StaleDataclassProcessor: AbstractProcessor() {
private fun elemToString(elem: Element): String {
return buildString {
- append(elem.modifiers.joinToString(" ") { it.name.toLowerCase() }).append(" ")
- append(elem.annotationMirrors.joinToString(" ")).append(" ")
+ append(elem.modifiers.joinToString(" ") { it.name.toLowerCase() })
+ append(" ")
+ append(elem.annotationMirrors.joinToString(" ", transform = { annotationToString(it) }))
+ append(" ")
if (elem is Symbol) {
if (elem.type is Type.MethodType) {
append((elem.type as Type.MethodType).returnType)
@@ -112,6 +114,14 @@ class StaleDataclassProcessor: AbstractProcessor() {
}
}
+ private fun annotationToString(ann: AnnotationMirror): String {
+ return if (ann.annotationType.toString().startsWith("com.android.internal.util.DataClass")) {
+ ann.toString()
+ } else {
+ ann.toString().substringBefore("(")
+ }
+ }
+
private fun processSingleFile(elementAnnotatedWithGenerated: Element) {
val classElement = elementAnnotatedWithGenerated.enclosingElement
diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp
index ea3cd427a130..039bb4e2788c 100644
--- a/tools/protologtool/Android.bp
+++ b/tools/protologtool/Android.bp
@@ -11,9 +11,9 @@ java_library_host {
name: "protologtool-lib",
srcs: [
"src/com/android/protolog/tool/**/*.kt",
+ ":protolog-common-src",
],
static_libs: [
- "protolog-common",
"javaparser",
"platformprotos",
"jsonlib",
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
index a59038fc99a0..645c5672da64 100644
--- a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
@@ -16,16 +16,15 @@
package com.android.protolog.tool
+import com.android.internal.protolog.ProtoLogFileProto
+import com.android.internal.protolog.ProtoLogMessage
+import com.android.internal.protolog.common.InvalidFormatStringException
+import com.android.internal.protolog.common.LogDataType
import com.android.json.stream.JsonReader
-import com.android.server.protolog.common.InvalidFormatStringException
-import com.android.server.protolog.common.LogDataType
-import com.android.server.protolog.ProtoLogMessage
-import com.android.server.protolog.ProtoLogFileProto
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
import java.io.PrintStream
-import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
index 75493b6427cb..42b628b0e262 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
@@ -17,7 +17,7 @@
package com.android.protolog.tool
import com.android.protolog.tool.Constants.ENUM_VALUES_METHOD
-import com.android.server.protolog.common.IProtoLogGroup
+import com.android.internal.protolog.common.IProtoLogGroup
import java.io.File
import java.net.URLClassLoader
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index 36ea41129450..27e61a139451 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -16,7 +16,7 @@
package com.android.protolog.tool
-import com.android.server.protolog.common.LogDataType
+import com.android.internal.protolog.common.LogDataType
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.NodeList
@@ -89,7 +89,7 @@ class SourceTransformer(
// Out: ProtoLog.e(GROUP, 1234, 0, null, arg)
newCall.arguments.add(2, IntegerLiteralExpr(typeMask))
// Replace call to a stub method with an actual implementation.
- // Out: com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, null, arg)
+ // Out: ProtoLogImpl.e(GROUP, 1234, null, arg)
newCall.setScope(protoLogImplClassNode)
// Create a call to ProtoLog$Cache.GROUP_enabled
// Out: com.android.server.protolog.ProtoLog$Cache.GROUP_enabled
@@ -119,9 +119,9 @@ class SourceTransformer(
}
blockStmt.addStatement(ExpressionStmt(newCall))
// Create an IF-statement with the previously created condition.
- // Out: if (com.android.server.protolog.ProtoLogImpl.isEnabled(GROUP)) {
+ // Out: if (ProtoLogImpl.isEnabled(GROUP)) {
// long protoLogParam0 = arg;
- // com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
+ // ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
// }
ifStmt = IfStmt(isLogEnabled, blockStmt, null)
} else {
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
index cf36651c3e39..3cfbb435a764 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
@@ -31,7 +31,7 @@ class CommandOptionsTest {
private const val TEST_PROTOLOG_CLASS = "com.android.server.wm.ProtoLog"
private const val TEST_PROTOLOGIMPL_CLASS = "com.android.server.wm.ProtoLogImpl"
private const val TEST_PROTOLOGCACHE_CLASS = "com.android.server.wm.ProtoLog\$Cache"
- private const val TEST_PROTOLOGGROUP_CLASS = "com.android.server.wm.ProtoLogGroup"
+ private const val TEST_PROTOLOGGROUP_CLASS = "com.android.internal.protolog.ProtoLogGroup"
private const val TEST_PROTOLOGGROUP_JAR = "out/soong/.intermediates/frameworks/base/" +
"services/core/services.core.wm.protologgroups/android_common/javac/" +
"services.core.wm.protologgroups.jar"
diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
index dd8a0b1c50b4..0d2b91d6cfb8 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
@@ -33,8 +33,8 @@ class EndToEndTest {
val output = run(
src = "frameworks/base/org/example/Example.java" to """
package org.example;
- import com.android.server.protolog.common.ProtoLog;
- import static com.android.server.wm.ProtoLogGroup.GROUP;
+ import com.android.internal.protolog.common.ProtoLog;
+ import static com.android.internal.protolog.ProtoLogGroup.GROUP;
class Example {
void method() {
@@ -46,11 +46,11 @@ class EndToEndTest {
""".trimIndent(),
logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"),
commandOptions = CommandOptions(arrayOf("transform-protolog-calls",
- "--protolog-class", "com.android.server.protolog.common.ProtoLog",
- "--protolog-impl-class", "com.android.server.protolog.ProtoLogImpl",
+ "--protolog-class", "com.android.internal.protolog.common.ProtoLog",
+ "--protolog-impl-class", "com.android.internal.protolog.ProtoLogImpl",
"--protolog-cache-class",
- "com.android.server.protolog.ProtoLog${"\$\$"}Cache",
- "--loggroups-class", "com.android.server.wm.ProtoLogGroup",
+ "com.android.server.wm.ProtoLogCache",
+ "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup",
"--loggroups-jar", "not_required.jar",
"--output-srcjar", "out.srcjar",
"frameworks/base/org/example/Example.java"))
@@ -64,8 +64,8 @@ class EndToEndTest {
val output = run(
src = "frameworks/base/org/example/Example.java" to """
package org.example;
- import com.android.server.protolog.common.ProtoLog;
- import static com.android.server.wm.ProtoLogGroup.GROUP;
+ import com.android.internal.protolog.common.ProtoLog;
+ import static com.android.internal.protolog.ProtoLogGroup.GROUP;
class Example {
void method() {
@@ -77,8 +77,8 @@ class EndToEndTest {
""".trimIndent(),
logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"),
commandOptions = CommandOptions(arrayOf("generate-viewer-config",
- "--protolog-class", "com.android.server.protolog.common.ProtoLog",
- "--loggroups-class", "com.android.server.wm.ProtoLogGroup",
+ "--protolog-class", "com.android.internal.protolog.common.ProtoLog",
+ "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup",
"--loggroups-jar", "not_required.jar",
"--viewer-conf", "out.json",
"frameworks/base/org/example/Example.java"))
diff --git a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
index 04a3bfa499d8..67a31da87081 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
@@ -17,8 +17,8 @@
package com.android.protolog.tool
import com.android.json.stream.JsonReader
-import com.android.server.protolog.ProtoLogMessage
-import com.android.server.protolog.ProtoLogFileProto
+import com.android.internal.protolog.ProtoLogMessage
+import com.android.internal.protolog.ProtoLogFileProto
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp
index 9fcf034536fc..0423b7abd685 100644
--- a/tools/validatekeymaps/Android.bp
+++ b/tools/validatekeymaps/Android.bp
@@ -29,12 +29,21 @@ cc_binary_host {
"libutils",
"libcutils",
"liblog",
+ "libui-types",
],
+ target: {
+ linux_glibc: {
+ static_libs: [
+ // libbinder is only available for linux
+ "libbinder",
+ ],
+ },
+ },
// This tool is prebuilt if we're doing an app-only build.
product_variables: {
unbundled_build: {
- enabled: false,
+ enabled: false,
},
},
}
diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp
index 877715a66f6d..991b28071515 100644
--- a/tools/validatekeymaps/Main.cpp
+++ b/tools/validatekeymaps/Main.cpp
@@ -26,15 +26,15 @@
using namespace android;
-static const char* kProgName = "validatekeymaps";
+static const char* PROG_NAME = "validatekeymaps";
static bool gQuiet = false;
-enum FileType {
- FILETYPE_UNKNOWN,
- FILETYPE_KEYLAYOUT,
- FILETYPE_KEYCHARACTERMAP,
- FILETYPE_VIRTUALKEYDEFINITION,
- FILETYPE_INPUTDEVICECONFIGURATION,
+enum class FileType {
+ UNKNOWN,
+ KEY_LAYOUT,
+ KEY_CHARACTER_MAP,
+ VIRTUAL_KEY_DEFINITION,
+ INPUT_DEVICE_CONFIGURATION,
};
static void log(const char* fmt, ...) {
@@ -57,33 +57,32 @@ static void error(const char* fmt, ...) {
static void usage() {
error("Keymap Validation Tool\n\n");
error("Usage:\n");
- error(
- " %s [-q] [*.kl] [*.kcm] [*.idc] [virtualkeys.*] [...]\n"
- " Validates the specified key layouts, key character maps, \n"
- " input device configurations, or virtual key definitions.\n\n"
- " -q Quiet; do not write anything to standard out.\n",
- kProgName);
+ error(" %s [-q] [*.kl] [*.kcm] [*.idc] [virtualkeys.*] [...]\n"
+ " Validates the specified key layouts, key character maps, \n"
+ " input device configurations, or virtual key definitions.\n\n"
+ " -q Quiet; do not write anything to standard out.\n",
+ PROG_NAME);
}
static FileType getFileType(const char* filename) {
const char *extension = strrchr(filename, '.');
if (extension) {
if (strcmp(extension, ".kl") == 0) {
- return FILETYPE_KEYLAYOUT;
+ return FileType::KEY_LAYOUT;
}
if (strcmp(extension, ".kcm") == 0) {
- return FILETYPE_KEYCHARACTERMAP;
+ return FileType::KEY_CHARACTER_MAP;
}
if (strcmp(extension, ".idc") == 0) {
- return FILETYPE_INPUTDEVICECONFIGURATION;
+ return FileType::INPUT_DEVICE_CONFIGURATION;
}
}
if (strstr(filename, "virtualkeys.")) {
- return FILETYPE_VIRTUALKEYDEFINITION;
+ return FileType::VIRTUAL_KEY_DEFINITION;
}
- return FILETYPE_UNKNOWN;
+ return FileType::UNKNOWN;
}
static bool validateFile(const char* filename) {
@@ -91,50 +90,49 @@ static bool validateFile(const char* filename) {
FileType fileType = getFileType(filename);
switch (fileType) {
- case FILETYPE_UNKNOWN:
- error("Supported file types: *.kl, *.kcm, virtualkeys.*\n\n");
- return false;
-
- case FILETYPE_KEYLAYOUT: {
- sp<KeyLayoutMap> map;
- status_t status = KeyLayoutMap::load(filename, &map);
- if (status) {
- error("Error %d parsing key layout file.\n\n", status);
+ case FileType::UNKNOWN:
+ error("Supported file types: *.kl, *.kcm, virtualkeys.*\n\n");
return false;
+
+ case FileType::KEY_LAYOUT: {
+ base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(filename);
+ if (!ret.ok()) {
+ error("Error %s parsing key layout file.\n\n", ret.error().message().c_str());
+ return false;
+ }
+ break;
}
- break;
- }
- case FILETYPE_KEYCHARACTERMAP: {
- sp<KeyCharacterMap> map;
- status_t status = KeyCharacterMap::load(filename,
- KeyCharacterMap::FORMAT_ANY, &map);
- if (status) {
- error("Error %d parsing key character map file.\n\n", status);
- return false;
+ case FileType::KEY_CHARACTER_MAP: {
+ base::Result<std::shared_ptr<KeyCharacterMap>> ret =
+ KeyCharacterMap::load(filename, KeyCharacterMap::Format::ANY);
+ if (!ret.ok()) {
+ error("Error %s parsing key character map file.\n\n",
+ ret.error().message().c_str());
+ return false;
+ }
+ break;
}
- break;
- }
- case FILETYPE_INPUTDEVICECONFIGURATION: {
- PropertyMap* map;
- status_t status = PropertyMap::load(String8(filename), &map);
- if (status) {
- error("Error %d parsing input device configuration file.\n\n", status);
- return false;
+ case FileType::INPUT_DEVICE_CONFIGURATION: {
+ android::base::Result<std::unique_ptr<PropertyMap>> propertyMap =
+ PropertyMap::load(String8(filename));
+ if (!propertyMap.ok()) {
+ error("Error %d parsing input device configuration file.\n\n",
+ propertyMap.error().code());
+ return false;
+ }
+ break;
}
- delete map;
- break;
- }
- case FILETYPE_VIRTUALKEYDEFINITION: {
- std::unique_ptr<VirtualKeyMap> map = VirtualKeyMap::load(filename);
- if (!map) {
- error("Error while parsing virtual key definition file.\n\n");
- return false;
+ case FileType::VIRTUAL_KEY_DEFINITION: {
+ std::unique_ptr<VirtualKeyMap> map = VirtualKeyMap::load(filename);
+ if (!map) {
+ error("Error while parsing virtual key definition file.\n\n");
+ return false;
+ }
+ break;
}
- break;
- }
}
return true;
diff --git a/tools/xmlpersistence/Android.bp b/tools/xmlpersistence/Android.bp
new file mode 100644
index 000000000000..0b6dba626794
--- /dev/null
+++ b/tools/xmlpersistence/Android.bp
@@ -0,0 +1,20 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_binary_host {
+ name: "xmlpersistence_cli",
+ manifest: "manifest.txt",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "javaparser-symbol-solver",
+ "javapoet",
+ ],
+}
diff --git a/tools/xmlpersistence/OWNERS b/tools/xmlpersistence/OWNERS
new file mode 100644
index 000000000000..4f4d06a32676
--- /dev/null
+++ b/tools/xmlpersistence/OWNERS
@@ -0,0 +1 @@
+zhanghai@google.com
diff --git a/tools/xmlpersistence/manifest.txt b/tools/xmlpersistence/manifest.txt
new file mode 100644
index 000000000000..6d9771998efc
--- /dev/null
+++ b/tools/xmlpersistence/manifest.txt
@@ -0,0 +1 @@
+Main-class: MainKt
diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt
new file mode 100644
index 000000000000..b2c5f4ac767b
--- /dev/null
+++ b/tools/xmlpersistence/src/main/kotlin/Generator.kt
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.NameAllocator
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeSpec
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.nio.charset.StandardCharsets
+import java.time.Year
+import java.util.Objects
+import javax.lang.model.element.Modifier
+
+// JavaPoet only supports line comments, and can't add a newline after file level comments.
+val FILE_HEADER = """
+ /*
+ * Copyright (C) ${Year.now().value} 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.
+ */
+
+ // Generated by xmlpersistence. DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ // @formatter:off
+""".trimIndent() + "\n\n"
+
+private val atomicFileType = ClassName.get("android.util", "AtomicFile")
+
+fun generate(persistence: PersistenceInfo): JavaFile {
+ val distinctClassFields = persistence.root.allClassFields.distinctBy { it.type }
+ val type = TypeSpec.classBuilder(persistence.name)
+ .addJavadoc(
+ """
+ Generated class implementing XML persistence for${'$'}W{@link $1T}.
+ <p>
+ This class provides atomicity for persistence via {@link $2T}, however it does not provide
+ thread safety, so please bring your own synchronization mechanism.
+ """.trimIndent(), persistence.root.type, atomicFileType
+ )
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addField(generateFileField())
+ .addMethod(generateConstructor())
+ .addMethod(generateReadMethod(persistence.root))
+ .addMethod(generateParseMethod(persistence.root))
+ .addMethods(distinctClassFields.map { generateParseClassMethod(it) })
+ .addMethod(generateWriteMethod(persistence.root))
+ .addMethod(generateSerializeMethod(persistence.root))
+ .addMethods(distinctClassFields.map { generateSerializeClassMethod(it) })
+ .addMethod(generateDeleteMethod())
+ .build()
+ return JavaFile.builder(persistence.root.type.packageName(), type)
+ .skipJavaLangImports(true)
+ .indent(" ")
+ .build()
+}
+
+private val nonNullType = ClassName.get("android.annotation", "NonNull")
+
+private fun generateFileField(): FieldSpec =
+ FieldSpec.builder(atomicFileType, "mFile", Modifier.PRIVATE, Modifier.FINAL)
+ .addAnnotation(nonNullType)
+ .build()
+
+private fun generateConstructor(): MethodSpec =
+ MethodSpec.constructorBuilder()
+ .addJavadoc(
+ """
+ Create an instance of this class.
+
+ @param file the XML file for persistence
+ """.trimIndent()
+ )
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(
+ ParameterSpec.builder(File::class.java, "file").addAnnotation(nonNullType).build()
+ )
+ .addStatement("mFile = new \$1T(file)", atomicFileType)
+ .build()
+
+private val nullableType = ClassName.get("android.annotation", "Nullable")
+
+private val xmlPullParserType = ClassName.get("org.xmlpull.v1", "XmlPullParser")
+
+private val xmlType = ClassName.get("android.util", "Xml")
+
+private val xmlPullParserExceptionType = ClassName.get("org.xmlpull.v1", "XmlPullParserException")
+
+private fun generateReadMethod(rootField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder("read")
+ .addJavadoc(
+ """
+ Read${'$'}W{@link $1T}${'$'}Wfrom${'$'}Wthe${'$'}WXML${'$'}Wfile.
+
+ @return the persisted${'$'}W{@link $1T},${'$'}Wor${'$'}W{@code null}${'$'}Wif${'$'}Wthe${'$'}WXML${'$'}Wfile${'$'}Wdoesn't${'$'}Wexist
+ @throws IllegalArgumentException if an error occurred while reading
+ """.trimIndent(), rootField.type
+ )
+ .addAnnotation(nullableType)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(rootField.type)
+ .addControlFlow("try (\$1T inputStream = mFile.openRead())", FileInputStream::class.java) {
+ addStatement("final \$1T parser = \$2T.newPullParser()", xmlPullParserType, xmlType)
+ addStatement("parser.setInput(inputStream, null)")
+ addStatement("return parse(parser)")
+ nextControlFlow("catch (\$1T e)", FileNotFoundException::class.java)
+ addStatement("return null")
+ nextControlFlow(
+ "catch (\$1T | \$2T e)", IOException::class.java, xmlPullParserExceptionType
+ )
+ addStatement("throw new IllegalArgumentException(e)")
+ }
+ .build()
+
+private val ClassFieldInfo.allClassFields: List<ClassFieldInfo>
+ get() =
+ mutableListOf<ClassFieldInfo>().apply {
+ this += this@allClassFields
+ for (field in fields) {
+ when (field) {
+ is ClassFieldInfo -> this += field.allClassFields
+ is ListFieldInfo -> this += field.element.allClassFields
+ }
+ }
+ }
+
+private fun generateParseMethod(rootField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder("parse")
+ .addAnnotation(nonNullType)
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
+ .returns(rootField.type)
+ .addParameter(
+ ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build()
+ )
+ .addExceptions(listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType))
+ .apply {
+ addStatement("int type")
+ addStatement("int depth")
+ addStatement("int innerDepth = parser.getDepth() + 1")
+ addControlFlow(
+ "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W"
+ + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))",
+ xmlPullParserType
+ ) {
+ addControlFlow(
+ "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType
+ ) {
+ addStatement("continue")
+ }
+ addControlFlow(
+ "if (\$1T.equals(parser.getName(),\$W\$2S))", Objects::class.java,
+ rootField.tagName
+ ) {
+ addStatement("return \$1L(parser)", rootField.parseMethodName)
+ }
+ }
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Missing root tag <${rootField.tagName}>"
+ )
+ }
+ .build()
+
+private fun generateParseClassMethod(classField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder(classField.parseMethodName)
+ .addAnnotation(nonNullType)
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
+ .returns(classField.type)
+ .addParameter(
+ ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build()
+ )
+ .apply {
+ val (attributeFields, tagFields) = classField.fields
+ .partition { it is PrimitiveFieldInfo || it is StringFieldInfo }
+ if (tagFields.isNotEmpty()) {
+ addExceptions(
+ listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType)
+ )
+ }
+ val nameAllocator = NameAllocator().apply {
+ newName("parser")
+ newName("type")
+ newName("depth")
+ newName("innerDepth")
+ }
+ for (field in attributeFields) {
+ val variableName = nameAllocator.newName(field.variableName, field)
+ when (field) {
+ is PrimitiveFieldInfo -> {
+ val stringVariableName =
+ nameAllocator.newName("${field.variableName}String")
+ addStatement(
+ "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)",
+ stringVariableName, field.attributeName
+ )
+ if (field.isRequired) {
+ addControlFlow("if (\$1L == null)", stringVariableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Missing attribute \"${field.attributeName}\""
+ )
+ }
+ }
+ val boxedType = field.type.box()
+ val parseTypeMethodName = if (field.type.isPrimitive) {
+ "parse${field.type.toString().capitalize()}"
+ } else {
+ "valueOf"
+ }
+ if (field.isRequired) {
+ addStatement(
+ "final \$1T \$2L =\$W\$3T.\$4L($5L)", field.type, variableName,
+ boxedType, parseTypeMethodName, stringVariableName
+ )
+ } else {
+ addStatement(
+ "final \$1T \$2L =\$W$3L != null ?\$W\$4T.\$5L($3L)\$W: null",
+ field.type, variableName, stringVariableName, boxedType,
+ parseTypeMethodName
+ )
+ }
+ }
+ is StringFieldInfo ->
+ addStatement(
+ "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)",
+ variableName, field.attributeName
+ )
+ else -> error(field)
+ }
+ }
+ if (tagFields.isNotEmpty()) {
+ for (field in tagFields) {
+ val variableName = nameAllocator.newName(field.variableName, field)
+ when (field) {
+ is ClassFieldInfo ->
+ addStatement("\$1T \$2L =\$Wnull", field.type, variableName)
+ is ListFieldInfo ->
+ addStatement(
+ "final \$1T \$2L =\$Wnew \$3T<>()", field.type, variableName,
+ ArrayList::class.java
+ )
+ else -> error(field)
+ }
+ }
+ addStatement("int type")
+ addStatement("int depth")
+ addStatement("int innerDepth = parser.getDepth() + 1")
+ addControlFlow(
+ "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W"
+ + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))",
+ xmlPullParserType
+ ) {
+ addControlFlow(
+ "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType
+ ) {
+ addStatement("continue")
+ }
+ addControlFlow("switch (parser.getName())") {
+ for (field in tagFields) {
+ addControlFlow("case \$1S:", field.tagName) {
+ val variableName = nameAllocator.get(field)
+ when (field) {
+ is ClassFieldInfo -> {
+ addControlFlow("if (\$1L != null)", variableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Duplicate tag \"${field.tagName}\""
+ )
+ }
+ addStatement(
+ "\$1L =\$W\$2L(parser)", variableName,
+ field.parseMethodName
+ )
+ addStatement("break")
+ }
+ is ListFieldInfo -> {
+ val elementNameAllocator = nameAllocator.clone()
+ val elementVariableName = elementNameAllocator.newName(
+ field.element.xmlName!!.toLowerCamelCase()
+ )
+ addStatement(
+ "final \$1T \$2L =\$W\$3L(parser)", field.element.type,
+ elementVariableName, field.element.parseMethodName
+ )
+ addStatement(
+ "\$1L.add(\$2L)", variableName, elementVariableName
+ )
+ addStatement("break")
+ }
+ else -> error(field)
+ }
+ }
+ }
+ }
+ }
+ }
+ for (field in tagFields.filter { it is ClassFieldInfo && it.isRequired }) {
+ addControlFlow("if ($1L == null)", nameAllocator.get(field)) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)", "Missing tag <${field.tagName}>"
+ )
+ }
+ }
+ addStatement(
+ classField.fields.joinToString(",\$W", "return new \$1T(", ")") {
+ nameAllocator.get(it)
+ }, classField.type
+ )
+ }
+ .build()
+
+private val ClassFieldInfo.parseMethodName: String
+ get() = "parse${type.simpleName().toUpperCamelCase()}"
+
+private val xmlSerializerType = ClassName.get("org.xmlpull.v1", "XmlSerializer")
+
+private fun generateWriteMethod(rootField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder("write")
+ .apply {
+ val nameAllocator = NameAllocator().apply {
+ newName("outputStream")
+ newName("serializer")
+ }
+ val parameterName = nameAllocator.newName(rootField.variableName)
+ addJavadoc(
+ """
+ Write${'$'}W{@link $1T}${'$'}Wto${'$'}Wthe${'$'}WXML${'$'}Wfile.
+
+ @param $2L the${'$'}W{@link ${'$'}1T}${'$'}Wto${'$'}Wpersist
+ """.trimIndent(), rootField.type, parameterName
+ )
+ addAnnotation(nullableType)
+ addModifiers(Modifier.PUBLIC)
+ addParameter(
+ ParameterSpec.builder(rootField.type, parameterName)
+ .addAnnotation(nonNullType)
+ .build()
+ )
+ addStatement("\$1T outputStream = null", FileOutputStream::class.java)
+ addControlFlow("try") {
+ addStatement("outputStream = mFile.startWrite()")
+ addStatement(
+ "final \$1T serializer =\$W\$2T.newSerializer()", xmlSerializerType, xmlType
+ )
+ addStatement(
+ "serializer.setOutput(outputStream, \$1T.UTF_8.name())",
+ StandardCharsets::class.java
+ )
+ addStatement(
+ "serializer.setFeature(\$1S, true)",
+ "http://xmlpull.org/v1/doc/features.html#indent-output"
+ )
+ addStatement("serializer.startDocument(null, true)")
+ addStatement("serialize(serializer,\$W\$1L)", parameterName)
+ addStatement("serializer.endDocument()")
+ addStatement("mFile.finishWrite(outputStream)")
+ nextControlFlow("catch (Exception e)")
+ addStatement("e.printStackTrace()")
+ addStatement("mFile.failWrite(outputStream)")
+ }
+ }
+ .build()
+
+private fun generateSerializeMethod(rootField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder("serialize")
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
+ .addParameter(
+ ParameterSpec.builder(xmlSerializerType, "serializer")
+ .addAnnotation(nonNullType)
+ .build()
+ )
+ .apply {
+ val nameAllocator = NameAllocator().apply { newName("serializer") }
+ val parameterName = nameAllocator.newName(rootField.variableName)
+ addParameter(
+ ParameterSpec.builder(rootField.type, parameterName)
+ .addAnnotation(nonNullType)
+ .build()
+ )
+ addException(IOException::class.java)
+ addStatement("serializer.startTag(null, \$1S)", rootField.tagName)
+ addStatement("\$1L(serializer, \$2L)", rootField.serializeMethodName, parameterName)
+ addStatement("serializer.endTag(null, \$1S)", rootField.tagName)
+ }
+ .build()
+
+private fun generateSerializeClassMethod(classField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder(classField.serializeMethodName)
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
+ .addParameter(
+ ParameterSpec.builder(xmlSerializerType, "serializer")
+ .addAnnotation(nonNullType)
+ .build()
+ )
+ .apply {
+ val nameAllocator = NameAllocator().apply {
+ newName("serializer")
+ newName("i")
+ }
+ val parameterName = nameAllocator.newName(classField.serializeParameterName)
+ addParameter(
+ ParameterSpec.builder(classField.type, parameterName)
+ .addAnnotation(nonNullType)
+ .build()
+ )
+ addException(IOException::class.java)
+ val (attributeFields, tagFields) = classField.fields
+ .partition { it is PrimitiveFieldInfo || it is StringFieldInfo }
+ for (field in attributeFields) {
+ val variableName = "$parameterName.${field.name}"
+ if (!field.isRequired) {
+ beginControlFlow("if (\$1L != null)", variableName)
+ }
+ when (field) {
+ is PrimitiveFieldInfo -> {
+ if (field.isRequired && !field.type.isPrimitive) {
+ addControlFlow("if (\$1L == null)", variableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Field \"${field.name}\" is null"
+ )
+ }
+ }
+ val stringVariableName =
+ nameAllocator.newName("${field.variableName}String")
+ addStatement(
+ "final String \$1L =\$WString.valueOf(\$2L)", stringVariableName,
+ variableName
+ )
+ addStatement(
+ "serializer.attribute(null, \$1S, \$2L)", field.attributeName,
+ stringVariableName
+ )
+ }
+ is StringFieldInfo -> {
+ if (field.isRequired) {
+ addControlFlow("if (\$1L == null)", variableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Field \"${field.name}\" is null"
+ )
+ }
+ }
+ addStatement(
+ "serializer.attribute(null, \$1S, \$2L)", field.attributeName,
+ variableName
+ )
+ }
+ else -> error(field)
+ }
+ if (!field.isRequired) {
+ endControlFlow()
+ }
+ }
+ for (field in tagFields) {
+ val variableName = "$parameterName.${field.name}"
+ if (field.isRequired) {
+ addControlFlow("if (\$1L == null)", variableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Field \"${field.name}\" is null"
+ )
+ }
+ }
+ when (field) {
+ is ClassFieldInfo -> {
+ addStatement("serializer.startTag(null, \$1S)", field.tagName)
+ addStatement(
+ "\$1L(serializer, \$2L)", field.serializeMethodName, variableName
+ )
+ addStatement("serializer.endTag(null, \$1S)", field.tagName)
+ }
+ is ListFieldInfo -> {
+ val sizeVariableName = nameAllocator.newName("${field.variableName}Size")
+ addStatement(
+ "final int \$1L =\$W\$2L.size()", sizeVariableName, variableName
+ )
+ addControlFlow("for (int i = 0;\$Wi < \$1L;\$Wi++)", sizeVariableName) {
+ val elementNameAllocator = nameAllocator.clone()
+ val elementVariableName = elementNameAllocator.newName(
+ field.element.xmlName!!.toLowerCamelCase()
+ )
+ addStatement(
+ "final \$1T \$2L =\$W\$3L.get(i)", field.element.type,
+ elementVariableName, variableName
+ )
+ addControlFlow("if (\$1L == null)", elementVariableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S\$W+ i\$W+ \$2S)",
+ "Field element \"${field.name}[", "]\" is null"
+ )
+ }
+ addStatement("serializer.startTag(null, \$1S)", field.element.tagName)
+ addStatement(
+ "\$1L(serializer,\$W\$2L)", field.element.serializeMethodName,
+ elementVariableName
+ )
+ addStatement("serializer.endTag(null, \$1S)", field.element.tagName)
+ }
+ }
+ else -> error(field)
+ }
+ }
+ }
+ .build()
+
+private val ClassFieldInfo.serializeMethodName: String
+ get() = "serialize${type.simpleName().toUpperCamelCase()}"
+
+private val ClassFieldInfo.serializeParameterName: String
+ get() = type.simpleName().toLowerCamelCase()
+
+private val FieldInfo.variableName: String
+ get() = name.toLowerCamelCase()
+
+private val FieldInfo.attributeName: String
+ get() {
+ check(this is PrimitiveFieldInfo || this is StringFieldInfo)
+ return xmlNameOrName.toLowerCamelCase()
+ }
+
+private val FieldInfo.tagName: String
+ get() {
+ check(this is ClassFieldInfo || this is ListFieldInfo)
+ return xmlNameOrName.toLowerKebabCase()
+ }
+
+private val FieldInfo.xmlNameOrName: String
+ get() = xmlName ?: name
+
+private fun generateDeleteMethod(): MethodSpec =
+ MethodSpec.methodBuilder("delete")
+ .addJavadoc("Delete the XML file, if any.")
+ .addModifiers(Modifier.PUBLIC)
+ .addStatement("mFile.delete()")
+ .build()
+
+private inline fun MethodSpec.Builder.addControlFlow(
+ controlFlow: String,
+ vararg args: Any,
+ block: MethodSpec.Builder.() -> Unit
+): MethodSpec.Builder {
+ beginControlFlow(controlFlow, *args)
+ block()
+ endControlFlow()
+ return this
+}
diff --git a/tools/xmlpersistence/src/main/kotlin/Main.kt b/tools/xmlpersistence/src/main/kotlin/Main.kt
new file mode 100644
index 000000000000..e271f8cb9361
--- /dev/null
+++ b/tools/xmlpersistence/src/main/kotlin/Main.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+import java.io.File
+import java.nio.file.Files
+
+fun main(args: Array<String>) {
+ val showUsage = args.isEmpty() || when (args.singleOrNull()) {
+ "-h", "--help" -> true
+ else -> false
+ }
+ if (showUsage) {
+ usage()
+ return
+ }
+
+ val files = args.flatMap {
+ File(it).walk().filter { it.isFile && it.extension == "java" }.map { it.toPath() }
+ }
+ val persistences = parse(files)
+ for (persistence in persistences) {
+ val file = generate(persistence)
+ Files.newBufferedWriter(persistence.path).use {
+ it.write(FILE_HEADER)
+ file.writeTo(it)
+ }
+ }
+}
+
+private fun usage() {
+ println("Usage: xmlpersistence <FILES>")
+}
diff --git a/tools/xmlpersistence/src/main/kotlin/Parser.kt b/tools/xmlpersistence/src/main/kotlin/Parser.kt
new file mode 100644
index 000000000000..3ea12a9aa389
--- /dev/null
+++ b/tools/xmlpersistence/src/main/kotlin/Parser.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+import com.github.javaparser.JavaParser
+import com.github.javaparser.ParseProblemException
+import com.github.javaparser.ParseResult
+import com.github.javaparser.ParserConfiguration
+import com.github.javaparser.ast.Node
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.FieldDeclaration
+import com.github.javaparser.ast.body.TypeDeclaration
+import com.github.javaparser.ast.expr.AnnotationExpr
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.NormalAnnotationExpr
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
+import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration
+import com.github.javaparser.resolution.types.ResolvedPrimitiveType
+import com.github.javaparser.resolution.types.ResolvedReferenceType
+import com.github.javaparser.symbolsolver.JavaSymbolSolver
+import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration
+import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver
+import com.github.javaparser.symbolsolver.resolution.typesolvers.MemoryTypeSolver
+import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import java.nio.file.Path
+import java.util.Optional
+
+class PersistenceInfo(
+ val name: String,
+ val root: ClassFieldInfo,
+ val path: Path
+)
+
+sealed class FieldInfo {
+ abstract val name: String
+ abstract val xmlName: String?
+ abstract val type: TypeName
+ abstract val isRequired: Boolean
+}
+
+class PrimitiveFieldInfo(
+ override val name: String,
+ override val xmlName: String?,
+ override val type: TypeName,
+ override val isRequired: Boolean
+) : FieldInfo()
+
+class StringFieldInfo(
+ override val name: String,
+ override val xmlName: String?,
+ override val isRequired: Boolean
+) : FieldInfo() {
+ override val type: TypeName = ClassName.get(String::class.java)
+}
+
+class ClassFieldInfo(
+ override val name: String,
+ override val xmlName: String?,
+ override val type: ClassName,
+ override val isRequired: Boolean,
+ val fields: List<FieldInfo>
+) : FieldInfo()
+
+class ListFieldInfo(
+ override val name: String,
+ override val xmlName: String?,
+ override val type: ParameterizedTypeName,
+ val element: ClassFieldInfo
+) : FieldInfo() {
+ override val isRequired: Boolean = true
+}
+
+fun parse(files: List<Path>): List<PersistenceInfo> {
+ val typeSolver = CombinedTypeSolver().apply { add(ReflectionTypeSolver()) }
+ val javaParser = JavaParser(ParserConfiguration()
+ .setSymbolResolver(JavaSymbolSolver(typeSolver)))
+ val compilationUnits = files.map { javaParser.parse(it).getOrThrow() }
+ val memoryTypeSolver = MemoryTypeSolver().apply {
+ for (compilationUnit in compilationUnits) {
+ for (typeDeclaration in compilationUnit.getNodesByClass<TypeDeclaration<*>>()) {
+ val name = typeDeclaration.fullyQualifiedName.getOrNull() ?: continue
+ addDeclaration(name, typeDeclaration.resolve())
+ }
+ }
+ }
+ typeSolver.add(memoryTypeSolver)
+ return mutableListOf<PersistenceInfo>().apply {
+ for (compilationUnit in compilationUnits) {
+ val classDeclarations = compilationUnit
+ .getNodesByClass<ClassOrInterfaceDeclaration>()
+ .filter { !it.isInterface && (!it.isNestedType || it.isStatic) }
+ this += classDeclarations.mapNotNull { parsePersistenceInfo(it) }
+ }
+ }
+}
+
+private fun parsePersistenceInfo(classDeclaration: ClassOrInterfaceDeclaration): PersistenceInfo? {
+ val annotation = classDeclaration.getAnnotationByName("XmlPersistence").getOrNull()
+ ?: return null
+ val rootClassName = classDeclaration.nameAsString
+ val name = annotation.getMemberValue("value")?.stringLiteralValue
+ ?: "${rootClassName}Persistence"
+ val rootXmlName = classDeclaration.getAnnotationByName("XmlName").getOrNull()
+ ?.getMemberValue("value")?.stringLiteralValue
+ val root = parseClassFieldInfo(
+ rootXmlName ?: rootClassName, rootXmlName, true, classDeclaration
+ )
+ val path = classDeclaration.findCompilationUnit().get().storage.get().path
+ .resolveSibling("$name.java")
+ return PersistenceInfo(name, root, path)
+}
+
+private fun parseClassFieldInfo(
+ name: String,
+ xmlName: String?,
+ isRequired: Boolean,
+ classDeclaration: ClassOrInterfaceDeclaration
+): ClassFieldInfo {
+ val fields = classDeclaration.fields.filterNot { it.isStatic }.map { parseFieldInfo(it) }
+ val type = classDeclaration.resolve().typeName
+ return ClassFieldInfo(name, xmlName, type, isRequired, fields)
+}
+
+private fun parseFieldInfo(field: FieldDeclaration): FieldInfo {
+ require(field.isPublic && field.isFinal)
+ val variable = field.variables.single()
+ val name = variable.nameAsString
+ val annotations = field.annotations + variable.type.annotations
+ val annotation = annotations.getByName("XmlName")
+ val xmlName = annotation?.getMemberValue("value")?.stringLiteralValue
+ val isRequired = annotations.getByName("NonNull") != null
+ return when (val type = variable.type.resolve()) {
+ is ResolvedPrimitiveType -> {
+ val primitiveType = type.typeName
+ PrimitiveFieldInfo(name, xmlName, primitiveType, true)
+ }
+ is ResolvedReferenceType -> {
+ when (type.qualifiedName) {
+ Boolean::class.javaObjectType.name, Byte::class.javaObjectType.name,
+ Short::class.javaObjectType.name, Char::class.javaObjectType.name,
+ Integer::class.javaObjectType.name, Long::class.javaObjectType.name,
+ Float::class.javaObjectType.name, Double::class.javaObjectType.name ->
+ PrimitiveFieldInfo(name, xmlName, type.typeName, isRequired)
+ String::class.java.name -> StringFieldInfo(name, xmlName, isRequired)
+ List::class.java.name -> {
+ requireNotNull(xmlName)
+ val elementType = type.typeParametersValues().single()
+ require(elementType is ResolvedReferenceType)
+ val listType = ParameterizedTypeName.get(
+ ClassName.get(List::class.java), elementType.typeName
+ )
+ val element = parseClassFieldInfo(
+ "(element)", xmlName, true, elementType.classDeclaration
+ )
+ ListFieldInfo(name, xmlName, listType, element)
+ }
+ else -> parseClassFieldInfo(name, xmlName, isRequired, type.classDeclaration)
+ }
+ }
+ else -> error(type)
+ }
+}
+
+private fun <T> ParseResult<T>.getOrThrow(): T =
+ if (isSuccessful) {
+ result.get()
+ } else {
+ throw ParseProblemException(problems)
+ }
+
+private inline fun <reified T : Node> Node.getNodesByClass(): List<T> =
+ getNodesByClass(T::class.java)
+
+private fun <T : Node> Node.getNodesByClass(klass: Class<T>): List<T> = mutableListOf<T>().apply {
+ if (klass.isInstance(this@getNodesByClass)) {
+ this += klass.cast(this@getNodesByClass)
+ }
+ for (childNode in childNodes) {
+ this += childNode.getNodesByClass(klass)
+ }
+}
+
+private fun <T> Optional<T>.getOrNull(): T? = orElse(null)
+
+private fun List<AnnotationExpr>.getByName(name: String): AnnotationExpr? =
+ find { it.name.identifier == name }
+
+private fun AnnotationExpr.getMemberValue(name: String): Expression? =
+ when (this) {
+ is NormalAnnotationExpr -> pairs.find { it.nameAsString == name }?.value
+ is SingleMemberAnnotationExpr -> if (name == "value") memberValue else null
+ else -> null
+ }
+
+private val Expression.stringLiteralValue: String
+ get() {
+ require(this is StringLiteralExpr)
+ return value
+ }
+
+private val ResolvedReferenceType.classDeclaration: ClassOrInterfaceDeclaration
+ get() {
+ val resolvedClassDeclaration = typeDeclaration
+ require(resolvedClassDeclaration is JavaParserClassDeclaration)
+ return resolvedClassDeclaration.wrappedNode
+ }
+
+private val ResolvedPrimitiveType.typeName: TypeName
+ get() =
+ when (this) {
+ ResolvedPrimitiveType.BOOLEAN -> TypeName.BOOLEAN
+ ResolvedPrimitiveType.BYTE -> TypeName.BYTE
+ ResolvedPrimitiveType.SHORT -> TypeName.SHORT
+ ResolvedPrimitiveType.CHAR -> TypeName.CHAR
+ ResolvedPrimitiveType.INT -> TypeName.INT
+ ResolvedPrimitiveType.LONG -> TypeName.LONG
+ ResolvedPrimitiveType.FLOAT -> TypeName.FLOAT
+ ResolvedPrimitiveType.DOUBLE -> TypeName.DOUBLE
+ }
+
+// This doesn't support type parameters.
+private val ResolvedReferenceType.typeName: TypeName
+ get() = typeDeclaration.typeName
+
+private val ResolvedReferenceTypeDeclaration.typeName: ClassName
+ get() {
+ val packageName = packageName
+ val classNames = className.split(".")
+ val topLevelClassName = classNames.first()
+ val nestedClassNames = classNames.drop(1)
+ return ClassName.get(packageName, topLevelClassName, *nestedClassNames.toTypedArray())
+ }
diff --git a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt
new file mode 100644
index 000000000000..b4bdbba7170b
--- /dev/null
+++ b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+import java.util.Locale
+
+private val camelHumpBoundary = Regex(
+ "-"
+ + "|_"
+ + "|(?<=[0-9])(?=[^0-9])"
+ + "|(?<=[A-Z])(?=[^A-Za-z]|[A-Z][a-z])"
+ + "|(?<=[a-z])(?=[^a-z])"
+)
+
+private fun String.toCamelHumps(): List<String> = split(camelHumpBoundary)
+
+fun String.toUpperCamelCase(): String =
+ toCamelHumps().joinToString("") { it.toLowerCase(Locale.ROOT).capitalize(Locale.ROOT) }
+
+fun String.toLowerCamelCase(): String = toUpperCamelCase().decapitalize(Locale.ROOT)
+
+fun String.toUpperKebabCase(): String =
+ toCamelHumps().joinToString("-") { it.toUpperCase(Locale.ROOT) }
+
+fun String.toLowerKebabCase(): String =
+ toCamelHumps().joinToString("-") { it.toLowerCase(Locale.ROOT) }
+
+fun String.toUpperSnakeCase(): String =
+ toCamelHumps().joinToString("_") { it.toUpperCase(Locale.ROOT) }
+
+fun String.toLowerSnakeCase(): String =
+ toCamelHumps().joinToString("_") { it.toLowerCase(Locale.ROOT) }