summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h5
-rw-r--r--tools/aapt2/ResourceParser.cpp112
-rw-r--r--tools/aapt2/ResourceParser.h1
-rw-r--r--tools/aapt2/ResourceParser_test.cpp25
-rw-r--r--tools/aapt2/ResourceTable.cpp139
-rw-r--r--tools/aapt2/ResourceTable.h5
-rw-r--r--tools/aapt2/Resources.proto5
-rw-r--r--tools/aapt2/cmd/Link_test.cpp124
-rw-r--r--tools/aapt2/compile/IdAssigner.cpp81
-rw-r--r--tools/aapt2/compile/IdAssigner_test.cpp79
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp6
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.cpp1
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize.cpp1
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize_test.cpp47
-rw-r--r--tools/aapt2/java/ClassDefinition.h13
-rw-r--r--tools/aapt2/java/JavaClassGenerator.cpp3
-rw-r--r--tools/aapt2/process/SymbolTable.cpp9
17 files changed, 504 insertions, 152 deletions
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 168a863df2bc..1e90b7c71376 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1379,6 +1379,11 @@ struct ResTable_typeSpec
enum : uint32_t {
// Additional flag indicating an entry is public.
SPEC_PUBLIC = 0x40000000u,
+
+ // Additional flag indicating the resource id for this resource may change in a future
+ // build. If this flag is set, the SPEC_PUBLIC flag is also set since the resource must be
+ // public to be exposed as an API to other applications.
+ SPEC_STAGED_API = 0x20000000u,
};
};
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index a447cef6dbbb..24c60b740bc3 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -42,6 +42,10 @@ using ::android::StringPiece;
using android::idmap2::policy::kPolicyStringToFlag;
namespace aapt {
+namespace {
+constexpr const char* kPublicGroupTag = "public-group";
+constexpr const char* kStagingPublicGroupTag = "staging-public-group";
+} // namespace
constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
@@ -102,6 +106,7 @@ struct ParsedResource {
ResourceId id;
Visibility::Level visibility_level = Visibility::Level::kUndefined;
+ bool staged_api = false;
bool allow_new = false;
Maybe<OverlayableItem> overlayable_item;
@@ -122,6 +127,7 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed
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;
res_builder.SetVisibility(visibility);
@@ -525,6 +531,7 @@ 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)},
{"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
{"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle,
std::placeholders::_2, std::placeholders::_3)},
@@ -653,7 +660,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 != "overlayable") {
if (!maybe_name) {
diag_->Error(DiagMessage(out_resource->source)
<< "<" << parser->element_name() << "> missing 'name' attribute");
@@ -890,54 +898,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 +948,72 @@ 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,
+ .id = next_id,
+ .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::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..af0db8c0ba2c 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -99,6 +99,7 @@ class ResourceParser {
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 ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 53bfa0b37475..4a509be56776 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -840,6 +840,31 @@ TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
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().entry->id);
+ 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) {
std::string input = R"(
<!-- private -->
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index cff98728c325..27f7bdd83c9e 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -47,11 +47,6 @@ bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, ResourceType
}
template <typename T>
-bool less_than_type_and_id(const T& lhs, const std::pair<ResourceType, Maybe<uint8_t>>& rhs) {
- return lhs.id != rhs.second ? lhs.id < rhs.second : lhs.type < rhs.first;
-}
-
-template <typename T>
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;
}
@@ -80,12 +75,6 @@ bool less_than_struct_with_name_and_id(const T& lhs,
return lhs.name.compare(0, lhs.name.size(), rhs.first.data(), rhs.first.size()) < 0;
}
-template <typename T, typename U>
-bool less_than_struct_with_name_and_id_pointer(const T* lhs,
- const std::pair<std::string_view, Maybe<U>>& rhs) {
- return less_than_struct_with_name_and_id(*lhs, rhs);
-}
-
template <typename T, typename Func, typename Elements>
T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) {
const auto iter =
@@ -307,51 +296,115 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
return CollisionResult::kConflict;
}
+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);
+ }
+
+ T* Insert(std::vector<T>& el, T&& value) {
+ auto [found, it] = LowerBound(el, value);
+ if (found) {
+ return &*it;
+ }
+ return &*el.insert(it, std::move(value));
+ }
+};
+
+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));
+ }
+};
+
+struct TypeViewComparer {
+ bool operator()(const ResourceTableTypeView& lhs, const ResourceTableTypeView& rhs) {
+ return lhs.id != rhs.id ? lhs.id < rhs.id : lhs.type < rhs.type;
+ }
+};
+
+struct EntryViewComparer {
+ bool operator()(const ResourceEntry* lhs, const ResourceEntry* rhs) {
+ return less_than_struct_with_name_and_id<ResourceEntry, ResourceId>(
+ *lhs, std::make_pair(rhs->name, rhs->id));
+ }
+};
+
ResourceTableView ResourceTable::GetPartitionedView() const {
ResourceTableView view;
+ SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
+ SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
+ SortedVectorInserter<const ResourceEntry*, EntryViewComparer> entry_inserter;
+
for (const auto& package : packages) {
for (const auto& type : package->types) {
for (const auto& entry : type->entries) {
- std::pair<std::string_view, Maybe<uint8_t>> package_key(package->name, {});
- std::pair<std::string_view, Maybe<ResourceId>> entry_key(entry->name, {});
- std::pair<ResourceType, Maybe<uint8_t>> type_key(type->type, {});
- if (entry->id) {
- // If the entry has a defined id, use the id to determine insertion position.
- package_key.second = entry->id.value().package_id();
- type_key.second = entry->id.value().type_id();
- entry_key.second = entry->id.value();
- }
+ ResourceTablePackageView new_package{
+ package->name, entry->id ? entry->id.value().package_id() : Maybe<uint8_t>{}};
+ auto view_package = package_inserter.Insert(view.packages, std::move(new_package));
- auto package_it =
- std::lower_bound(view.packages.begin(), view.packages.end(), package_key,
- less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>);
- if (package_it == view.packages.end() || package_it->name != package_key.first ||
- package_it->id != package_key.second) {
- ResourceTablePackageView new_package{std::string(package_key.first), package_key.second};
- package_it = view.packages.insert(package_it, new_package);
- }
-
- auto type_it = std::lower_bound(package_it->types.begin(), package_it->types.end(),
- type_key, less_than_type_and_id<ResourceTableTypeView>);
- if (type_it == package_it->types.end() || type_key.first != type_it->type ||
- type_it->id != type_key.second) {
- ResourceTableTypeView new_type{type_key.first, type_key.second};
- type_it = package_it->types.insert(type_it, new_type);
- }
+ ResourceTableTypeView new_type{type->type,
+ entry->id ? entry->id.value().type_id() : Maybe<uint8_t>{}};
+ auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));
if (entry->visibility.level == Visibility::Level::kPublic) {
// Only mark the type visibility level as public, it doesn't care about being private.
- type_it->visibility_level = Visibility::Level::kPublic;
+ view_type->visibility_level = Visibility::Level::kPublic;
}
- auto entry_it =
- std::lower_bound(type_it->entries.begin(), type_it->entries.end(), entry_key,
- less_than_struct_with_name_and_id_pointer<ResourceEntry, ResourceId>);
- type_it->entries.insert(entry_it, entry.get());
+ entry_inserter.Insert(view_type->entries, entry.get());
+ }
+ }
+ }
+
+ // 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;
+ 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;
}
+
+ // 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;
+ }
+
+ // 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);
}
}
+ 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));
+ }
+
return view;
}
@@ -424,6 +477,10 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) {
// This symbol definition takes precedence, replace.
entry->visibility = res.visibility.value();
}
+
+ if (res.visibility->staged_api) {
+ entry->visibility.staged_api = entry->visibility.staged_api;
+ }
}
if (res.overlayable.has_value()) {
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 49392a5db830..080ecc20558b 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.
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index b1e1a77e1224..4247ec5a3495 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
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 27cbe88fde38..dfdac6b9d93e 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -25,6 +25,7 @@
using testing::Eq;
using testing::HasSubstr;
using testing::Ne;
+using testing::NotNull;
namespace aapt {
@@ -400,4 +401,127 @@ TEST_F(LinkTest, SharedLibraryAttributeRJava) {
EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000"));
}
+TEST_F(LinkTest, StagedAndroidApi) {
+ StdErrDiagnostics 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>
+
+ <!-- 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>
+
+ <staging-public-group type="string" first-id="0x01fd0072">
+ <public name="staged_t_string" />
+ </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_t_string">Hello</string>
+ </resources>)";
+
+ const std::string app_values =
+ R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <attr name="bar" />
+ <declare-styleable name="ClientStyleable">
+ <attr name="android:finalized_res" />
+ <attr name="android:staged_s_res" />
+ <attr name="bar" />
+ </declare-styleable>
+ </resources>)";
+
+ const std::string android_res = GetTestPath("android-res");
+ ASSERT_TRUE(
+ CompileFile(GetTestPath("res/values/values.xml"), android_values, android_res, &diag));
+
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android_java");
+ // clang-format off
+ auto android_manifest = ManifestBuilder(this)
+ .SetPackageName("android")
+ .Build();
+
+ auto android_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(android_manifest)
+ .AddParameter("--private-symbols", "com.android.internal")
+ .AddParameter("--java", android_java)
+ .AddCompiledResDir(android_res, &diag)
+ .Build(android_apk);
+ // clang-format on
+ ASSERT_TRUE(Link(android_link_args, &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 int staged_s_res=0x01010050;"));
+ EXPECT_THAT(android_r_contents, HasSubstr(" public static int staged_s2_res=0x01ff0049;"));
+ EXPECT_THAT(android_r_contents, HasSubstr(" public static int staged_t_res=0x01fe0063;"));
+ EXPECT_THAT(android_r_contents, HasSubstr(" public static int staged_t_string=0x01fd0072;"));
+
+ // Build an app that uses the framework attribute in a declare-styleable
+ const std::string client_res = GetTestPath("app-res");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), app_values, client_res, &diag));
+
+ const std::string app_apk = GetTestPath("app.apk");
+ const std::string app_java = GetTestPath("app_java");
+ // clang-format off
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("--java", app_java)
+ .AddParameter("-I", android_apk)
+ .AddCompiledResDir(client_res, &diag)
+ .Build(app_apk);
+ // clang-format on
+ ASSERT_TRUE(Link(app_link_args, &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: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));
+
+ result = am.GetResourceId("android:string/staged_t_string");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01fd0072));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index 07db73dff2c5..9a50b263c006 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -77,6 +77,27 @@ struct TypeGroup {
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);
+ }
+};
+
+::std::ostream& operator<<(::std::ostream& out, const ResourceTypeKey& type) {
+ return out << type.type;
+}
+
struct IdAssignerContext {
IdAssignerContext(std::string package_name, uint8_t package_id)
: package_name_(std::move(package_name)), package_id_(package_id) {
@@ -85,7 +106,8 @@ struct IdAssignerContext {
// 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, IDiagnostics* diag);
+ 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);
@@ -93,8 +115,10 @@ struct IdAssignerContext {
private:
std::string package_name_;
uint8_t package_id_;
- std::map<ResourceType, TypeGroup> types_;
- NextIdFinder<uint8_t, ResourceType> type_id_finder_ = NextIdFinder<uint8_t, ResourceType>(1);
+ 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
@@ -106,7 +130,8 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
for (auto& entry : type->entries) {
const ResourceName name(package->name, type->type, entry->name);
if (entry->id) {
- if (!assigned_ids.ReserveId(name, entry->id.value(), context->GetDiagnostics())) {
+ if (!assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
+ context->GetDiagnostics())) {
return false;
}
}
@@ -116,7 +141,8 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
const auto iter = assigned_id_map_->find(name);
if (iter != assigned_id_map_->end()) {
const ResourceId assigned_id = iter->second;
- if (!assigned_ids.ReserveId(name, assigned_id, context->GetDiagnostics())) {
+ if (!assigned_ids.ReserveId(name, assigned_id, entry->visibility,
+ context->GetDiagnostics())) {
return false;
}
entry->id = assigned_id;
@@ -132,7 +158,8 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* 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;
- if (!assigned_ids.ReserveId(pre_assigned_name, pre_assigned_id, context->GetDiagnostics())) {
+ if (!assigned_ids.ReserveId(pre_assigned_name, pre_assigned_id, {},
+ context->GetDiagnostics())) {
return false;
}
}
@@ -165,7 +192,7 @@ Result<Id> NextIdFinder<Id, Key>::ReserveId(Key key, Id id) {
auto assign_result = pre_assigned_ids_.emplace(id, key);
if (!assign_result.second && assign_result.first->second != key) {
std::stringstream error;
- error << "ID " << id << " is already assigned to " << assign_result.first->second;
+ error << "ID is already assigned to " << assign_result.first->second;
return unexpected(error.str());
}
return id;
@@ -210,7 +237,7 @@ Result<std::monostate> TypeGroup::ReserveId(const ResourceName& name, ResourceId
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 " << id.type_id();
+ error << "type '" << name.type << "' already has ID " << std::hex << (int)id.type_id();
return unexpected(error.str());
}
@@ -234,24 +261,38 @@ Result<ResourceId> TypeGroup::NextId() {
return ResourceId(package_id_, type_id_, entry_id.value());
}
-bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, IDiagnostics* diag) {
+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 " << id.package_id());
+ << " because package already has ID " << std::hex
+ << (int)id.package_id());
return false;
}
- auto type = types_.find(name.type);
+ 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(name.type, id.type_id());
+ 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(name.type, TypeGroup(package_id_, id.type_id())).first;
+ type = types_.emplace(key, TypeGroup(package_id_, id.type_id())).first;
+ }
+
+ 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);
@@ -268,11 +309,19 @@ std::optional<ResourceId> IdAssignerContext::NextId(const ResourceName& name, ID
// 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_);
- auto type = types_.find(name.type);
- if (type == types_.end()) {
+
+ // 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)";
- type = types_.emplace(name.type, TypeGroup(package_id_, next_type_id.value())).first;
+ 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();
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
index 0065a224bfa7..663776645990 100644
--- a/tools/aapt2/compile/IdAssigner_test.cpp
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -98,6 +98,37 @@ TEST_F(IdAssignerTests, FailWhenNonUniqueIdsAssigned) {
ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
}
+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_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()));
+}
+
TEST_F(IdAssignerTests, AssignIdsWithIdMap) {
auto table = test::ResourceTableBuilder()
.AddSimple("android:attr/foo")
@@ -154,52 +185,24 @@ TEST_F(IdAssignerTests, ExaustEntryIdsLastIdIsPublic) {
}
::testing::AssertionResult VerifyIds(ResourceTable* table) {
- std::set<uint8_t> package_ids;
- auto table_view = table->GetPartitionedView();
- for (auto& package : table_view.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;
- }
- }
-
- for (auto& package : table_view.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";
- }
-
- 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;
- }
- }
-
- for (auto& type : package.types) {
- std::set<ResourceId> entry_ids;
- for (auto& entry : type.entries) {
+ std::set<ResourceId> seen_ids;
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ 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 << 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/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 5fea897d46e7..17d11a63553a 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -546,8 +546,12 @@ class PackageFlattener {
const uint16_t entry_id = entry->id.value().entry_id();
// Populate the config masks for this entry.
+ uint32_t& entry_config_masks = config_masks[entry_id];
if (entry->visibility.level == Visibility::Level::kPublic) {
- config_masks[entry_id] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
+ 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();
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index bfb92da27e4d..498d5a27d69d 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -456,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;
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 9842e253f063..f13f82da3102 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -378,6 +378,7 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table
// Write the Visibility struct.
pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
+ 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(),
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index ad5ed4db55b6..591ba14942cd 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -50,6 +50,53 @@ ResourceEntry* GetEntry(ResourceTable* table, const ResourceNameRef& res_name, R
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 =
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 995495ac56a8..d3648c8aae52 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;
@@ -77,7 +78,7 @@ class PrimitiveMember : public ClassMember {
ClassMember::Print(final, printer, strip_api_annotations);
printer->Print("public static ");
- if (final) {
+ if (final && !staged_api_) {
printer->Print("final ");
}
printer->Print("int ").Print(name_).Print("=").Print(to_string(val_)).Print(";");
@@ -88,14 +89,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;
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 039448e1b3e9..e1e2e0135cf7 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -460,7 +460,8 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso
const std::string field_name = TransformToFieldName(name.entry);
if (out_class_def != nullptr) {
- auto 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();
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index de72334e65a9..d385267fe5ed 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -199,7 +199,8 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName(
if (sr.entry->id) {
symbol->id = sr.entry->id.value();
- symbol->is_dynamic = (sr.entry->id.value().package_id() == 0);
+ symbol->is_dynamic =
+ (sr.entry->id.value().package_id() == 0) || sr.entry->visibility.staged_api;
}
if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
@@ -374,7 +375,8 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName(
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);
+ 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 {};
@@ -421,7 +423,8 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById(
if (s) {
s->is_public = (*flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
- s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package);
+ s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package) ||
+ (*flags & android::ResTable_typeSpec::SPEC_STAGED_API) != 0;
return s;
}
return {};