summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/Package.cpp11
-rw-r--r--tools/aapt/StringPool.cpp2
-rw-r--r--tools/aapt2/Debug.cpp120
-rw-r--r--tools/aapt2/Resource.h17
-rw-r--r--tools/aapt2/ResourceParser.cpp102
-rw-r--r--tools/aapt2/ResourceParser.h7
-rw-r--r--tools/aapt2/ResourceTable.cpp101
-rw-r--r--tools/aapt2/ResourceTable.h17
-rw-r--r--tools/aapt2/ResourceValues.cpp1
-rw-r--r--tools/aapt2/ResourceValues.h12
-rw-r--r--tools/aapt2/Resources.proto10
-rw-r--r--tools/aapt2/ResourcesInternal.proto5
-rw-r--r--tools/aapt2/cmd/Command.cpp27
-rw-r--r--tools/aapt2/cmd/Command_test.cpp24
-rw-r--r--tools/aapt2/cmd/Compile.cpp85
-rw-r--r--tools/aapt2/cmd/Compile.h5
-rw-r--r--tools/aapt2/cmd/Diff.cpp28
-rw-r--r--tools/aapt2/cmd/Link.cpp5
-rw-r--r--tools/aapt2/cmd/Util.cpp38
-rw-r--r--tools/aapt2/cmd/Util.h6
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.cpp40
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize.cpp30
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp18
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/drawable/removedpng.pngbin0 -> 5634 bytes
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/layout/layout2.xml6
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/values/bools.xml4
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.trueFlag)/layout/layout3.xml10
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml6
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml11
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.falseFlag)/bools.xml4
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.trueFlag)/bools.xml4
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(test.package.trueFlag)/bools.xml4
-rw-r--r--tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/styles.xml21
-rw-r--r--tools/aapt2/link/FlaggedResources_test.cpp50
-rw-r--r--tools/aapt2/link/TableMerger.cpp41
-rw-r--r--tools/aapt2/test/Common.cpp17
-rw-r--r--tools/aapt2/test/Common.h26
-rw-r--r--tools/aapt2/test/Fixture.cpp7
-rw-r--r--tools/aapt2/test/Fixture.h3
-rw-r--r--tools/aapt2/util/Util.h9
-rw-r--r--tools/processors/property_cache/Android.bp57
-rw-r--r--tools/processors/property_cache/OWNERS3
-rw-r--r--tools/processors/property_cache/TEST_MAPPING7
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java138
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java40
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java101
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java40
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java152
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java103
-rw-r--r--tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java64
-rw-r--r--tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java134
-rw-r--r--tools/processors/property_cache/test/resources/Custom.java127
-rw-r--r--tools/processors/property_cache/test/resources/CustomCache.java384
-rw-r--r--tools/processors/property_cache/test/resources/Default.java125
-rw-r--r--tools/processors/property_cache/test/resources/DefaultCache.java402
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt3
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt8
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt15
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt3
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt3
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt10
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt3
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt16
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt18
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/ViewerConfigProtoBuilderTest.kt86
65 files changed, 2814 insertions, 162 deletions
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index 5e0f87f0dcaf..60c4bf5c4131 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -292,13 +292,12 @@ bool processFile(Bundle* bundle, ZipFile* zip,
}
if (!hasData) {
const String8& srcName = file->getSourceFile();
- time_t fileModWhen;
- fileModWhen = getFileModDate(srcName.c_str());
- if (fileModWhen == (time_t) -1) { // file existence tested earlier,
- return false; // not expecting an error here
+ auto fileModWhen = getFileModDate(srcName.c_str());
+ if (fileModWhen == kInvalidModDate) { // file existence tested earlier,
+ return false; // not expecting an error here
}
-
- if (fileModWhen > entry->getModWhen()) {
+
+ if (toTimeT(fileModWhen) > entry->getModWhen()) {
// mark as deleted so add() will succeed
if (bundle->getVerbose()) {
printf(" (removing old '%s')\n", storageName.c_str());
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index 1af8d6f67bd3..b2e48bd74e8a 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -40,7 +40,7 @@ void strcpy16_htod(uint16_t* dst, const char16_t* src)
void printStringPool(const ResStringPool* pool)
{
if (pool->getError() == NO_INIT) {
- printf("String pool is unitialized.\n");
+ printf("String pool is uninitialized.\n");
return;
} else if (pool->getError() != NO_ERROR) {
printf("String pool is corrupt/invalid.\n");
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index df1d51e37660..e24fe07f959b 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -21,10 +21,13 @@
#include <format/binary/ResChunkPullParser.h>
#include <algorithm>
+#include <array>
#include <map>
#include <memory>
#include <queue>
#include <set>
+#include <span>
+#include <utility>
#include <vector>
#include "ResourceTable.h"
@@ -346,6 +349,21 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
value->value->Accept(&body_printer);
printer->Undent();
}
+ printer->Println("Flag disabled values:");
+ for (const auto& value : entry.flag_disabled_values) {
+ printer->Print("(");
+ printer->Print(value->config.to_string());
+ printer->Print(") ");
+ value->value->Accept(&headline_printer);
+ if (options.show_sources && !value->value->GetSource().path.empty()) {
+ printer->Print(" src=");
+ printer->Print(value->value->GetSource().to_string());
+ }
+ printer->Println();
+ printer->Indent();
+ value->value->Accept(&body_printer);
+ printer->Undent();
+ }
printer->Undent();
}
}
@@ -430,7 +448,7 @@ void Debug::DumpResStringPool(const android::ResStringPool* pool, text::Printer*
using namespace android;
if (pool->getError() == NO_INIT) {
- printer->Print("String pool is unitialized.\n");
+ printer->Print("String pool is uninitialized.\n");
return;
} else if (pool->getError() != NO_ERROR) {
printer->Print("String pool is corrupt/invalid.\n");
@@ -665,17 +683,91 @@ class ChunkPrinter {
item->PrettyPrint(printer_);
printer_->Print(")");
}
+ }
- printer_->Print("\n");
+ void PrintQualifiers(uint32_t qualifiers) const {
+ if (qualifiers == 0) {
+ printer_->Print("0");
+ return;
+ }
+
+ printer_->Print(StringPrintf("0x%04x: ", qualifiers));
+ static constinit std::array kValues = {
+ std::pair{ResTable_config::CONFIG_MCC, "mcc"},
+ std::pair{ResTable_config::CONFIG_MNC, "mnc"},
+ std::pair{ResTable_config::CONFIG_LOCALE, "locale"},
+ std::pair{ResTable_config::CONFIG_TOUCHSCREEN, "touchscreen"},
+ std::pair{ResTable_config::CONFIG_KEYBOARD, "keyboard"},
+ std::pair{ResTable_config::CONFIG_KEYBOARD_HIDDEN, "keyboard_hidden"},
+ std::pair{ResTable_config::CONFIG_NAVIGATION, "navigation"},
+ std::pair{ResTable_config::CONFIG_ORIENTATION, "orientation"},
+ std::pair{ResTable_config::CONFIG_DENSITY, "screen_density"},
+ std::pair{ResTable_config::CONFIG_SCREEN_SIZE, "screen_size"},
+ std::pair{ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE, "screen_smallest_size"},
+ std::pair{ResTable_config::CONFIG_VERSION, "version"},
+ std::pair{ResTable_config::CONFIG_SCREEN_LAYOUT, "screen_layout"},
+ std::pair{ResTable_config::CONFIG_UI_MODE, "ui_mode"},
+ std::pair{ResTable_config::CONFIG_LAYOUTDIR, "layout_dir"},
+ std::pair{ResTable_config::CONFIG_SCREEN_ROUND, "screen_round"},
+ std::pair{ResTable_config::CONFIG_COLOR_MODE, "color_mode"},
+ std::pair{ResTable_config::CONFIG_GRAMMATICAL_GENDER, "grammatical_gender"}};
+ const char* delimiter = "";
+ for (auto&& pair : kValues) {
+ if (qualifiers & pair.first) {
+ printer_->Print(StringPrintf("%s%s", delimiter, pair.second));
+ delimiter = "|";
+ }
+ }
+ }
+
+ bool PrintTypeSpec(const ResTable_typeSpec* chunk) const {
+ printer_->Print(StringPrintf(" id: 0x%02x", android::util::DeviceToHost32(chunk->id)));
+ printer_->Print(StringPrintf(" types: %u", android::util::DeviceToHost16(chunk->typesCount)));
+ printer_->Print(
+ StringPrintf(" entry configs: %u\n", android::util::DeviceToHost32(chunk->entryCount)));
+ printer_->Print("Entry qualifier masks:\n");
+ printer_->Indent();
+ std::span<const uint32_t> masks(reinterpret_cast<const uint32_t*>(GetChunkData(&chunk->header)),
+ GetChunkDataLen(&chunk->header) / sizeof(uint32_t));
+ int i = 0;
+ int non_empty_count = 0;
+ for (auto dev_mask : masks) {
+ auto mask = android::util::DeviceToHost32(dev_mask);
+ if (mask == 0) {
+ i++;
+ continue;
+ }
+ ++non_empty_count;
+ printer_->Print(StringPrintf("#0x%02x = ", i++));
+ if (mask & ResTable_typeSpec::SPEC_PUBLIC) {
+ mask &= ~ResTable_typeSpec::SPEC_PUBLIC;
+ printer_->Print("(PUBLIC) ");
+ }
+ if (mask & ResTable_typeSpec::SPEC_STAGED_API) {
+ mask &= ~ResTable_typeSpec::SPEC_STAGED_API;
+ printer_->Print("(STAGED) ");
+ }
+ PrintQualifiers(mask);
+ printer_->Print("\n");
+ }
+ if (non_empty_count > 0) {
+ printer_->Print("\n");
+ } else {
+ printer_->Print("(all empty)\n");
+ }
+ printer_->Undent();
+ return true;
}
bool PrintTableType(const ResTable_type* chunk) {
printer_->Print(StringPrintf(" id: 0x%02x", android::util::DeviceToHost32(chunk->id)));
- printer_->Print(StringPrintf(
- " name: %s",
- android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1)
- .c_str()));
+ const auto name =
+ android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1);
+ printer_->Print(StringPrintf(" name: %s", name.c_str()));
printer_->Print(StringPrintf(" flags: 0x%02x", android::util::DeviceToHost32(chunk->flags)));
+ printer_->Print(android::util::DeviceToHost32(chunk->flags) & ResTable_type::FLAG_SPARSE
+ ? " (SPARSE)"
+ : " (DENSE)");
printer_->Print(
StringPrintf(" entryCount: %u", android::util::DeviceToHost32(chunk->entryCount)));
printer_->Print(
@@ -685,8 +777,7 @@ class ChunkPrinter {
config.copyFromDtoH(chunk->config);
printer_->Print(StringPrintf(" config: %s\n", config.to_string().c_str()));
- const ResourceType* type = ParseResourceType(
- android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1));
+ const ResourceType* type = ParseResourceType(name);
printer_->Indent();
@@ -725,11 +816,8 @@ class ChunkPrinter {
for (size_t i = 0; i < map_entry_count; i++) {
PrintResValue(&(maps[i].value), config, type);
- printer_->Print(StringPrintf(
- " name: %s name-id:%d\n",
- android::util::GetString(key_pool_, android::util::DeviceToHost32(maps[i].name.ident))
- .c_str(),
- android::util::DeviceToHost32(maps[i].name.ident)));
+ printer_->Print(StringPrintf(" name-id: 0x%08x\n",
+ android::util::DeviceToHost32(maps[i].name.ident)));
}
} else {
printer_->Print("\n");
@@ -737,6 +825,8 @@ class ChunkPrinter {
// Print the value of the entry
Res_value value = entry->value();
PrintResValue(&value, config, type);
+
+ printer_->Print("\n");
}
printer_->Undent();
@@ -849,6 +939,10 @@ class ChunkPrinter {
PrintTableType(reinterpret_cast<const ResTable_type*>(chunk));
break;
+ case RES_TABLE_TYPE_SPEC_TYPE:
+ PrintTypeSpec(reinterpret_cast<const ResTable_typeSpec*>(chunk));
+ break;
+
default:
printer_->Print("\n");
break;
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index a274f047586c..0d261abd728d 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -71,6 +71,17 @@ enum class ResourceType {
enum class FlagStatus { NoFlag = 0, Disabled = 1, Enabled = 2 };
+struct FeatureFlagAttribute {
+ std::string name;
+ bool negated = false;
+
+ std::string ToString() {
+ return (negated ? "!" : "") + name;
+ }
+
+ bool operator==(const FeatureFlagAttribute& o) const = default;
+};
+
android::StringPiece to_string(ResourceType type);
/**
@@ -232,6 +243,12 @@ struct ResourceFile {
// Exported symbols
std::vector<SourcedResourceName> exported_symbols;
+
+ // Flag status
+ FlagStatus flag_status = FlagStatus::NoFlag;
+
+ // Flag
+ std::optional<FeatureFlagAttribute> flag;
};
/**
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index a5aecc855707..fb576df248be 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -107,9 +107,10 @@ struct ParsedResource {
Visibility::Level visibility_level = Visibility::Level::kUndefined;
bool staged_api = false;
bool allow_new = false;
- FlagStatus flag_status = FlagStatus::NoFlag;
std::optional<OverlayableItem> overlayable_item;
std::optional<StagedId> staged_alias;
+ std::optional<FeatureFlagAttribute> flag;
+ FlagStatus flag_status = FlagStatus::NoFlag;
std::string comment;
std::unique_ptr<Value> value;
@@ -151,6 +152,7 @@ static bool AddResourcesToTable(ResourceTable* table, android::IDiagnostics* dia
}
if (res->value != nullptr) {
+ res->value->SetFlag(res->flag);
res->value->SetFlagStatus(res->flag_status);
// Attach the comment, source and config to the value.
res->value->SetComment(std::move(res->comment));
@@ -162,8 +164,6 @@ static bool AddResourcesToTable(ResourceTable* table, android::IDiagnostics* dia
res_builder.SetStagedId(res->staged_alias.value());
}
- res_builder.SetFlagStatus(res->flag_status);
-
bool error = false;
if (!res->name.entry.empty()) {
if (!table->AddResource(res_builder.Build(), diag)) {
@@ -546,12 +546,26 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
{"symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
});
- std::string resource_type = parser->element_name();
- auto flag_status = GetFlagStatus(parser);
- if (!flag_status) {
- return false;
+ std::string_view resource_type = parser->element_name();
+ if (auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"))) {
+ if (options_.flag) {
+ diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+ << "Resource flag are not allowed both in the path and in the file");
+ return false;
+ }
+ out_resource->flag = std::move(flag);
+ std::string error;
+ auto flag_status = GetFlagStatus(out_resource->flag, options_.feature_flag_values, &error);
+ if (flag_status) {
+ out_resource->flag_status = flag_status.value();
+ } else {
+ diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << error);
+ return false;
+ }
+ } else if (options_.flag) {
+ out_resource->flag = options_.flag;
+ out_resource->flag_status = options_.flag_status;
}
- out_resource->flag_status = flag_status.value();
// The value format accepted for this resource.
uint32_t resource_format = 0u;
@@ -567,7 +581,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
// Items have their type encoded in the type attribute.
if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
- resource_type = std::string(maybe_type.value());
+ resource_type = maybe_type.value();
} else {
diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
<< "<item> must have a 'type' attribute");
@@ -590,7 +604,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
// Bags have their type encoded in the type attribute.
if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
- resource_type = std::string(maybe_type.value());
+ resource_type = maybe_type.value();
} else {
diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
<< "<bag> must have a 'type' attribute");
@@ -733,33 +747,6 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
return false;
}
-std::optional<FlagStatus> ResourceParser::GetFlagStatus(xml::XmlPullParser* parser) {
- auto flag_status = FlagStatus::NoFlag;
-
- std::optional<StringPiece> flag = xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag");
- if (flag) {
- auto flag_it = options_.feature_flag_values.find(flag.value());
- if (flag_it == options_.feature_flag_values.end()) {
- diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
- << "Resource flag value undefined");
- return {};
- }
- const auto& flag_properties = flag_it->second;
- if (!flag_properties.read_only) {
- diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
- << "Only read only flags may be used with resources");
- return {};
- }
- if (!flag_properties.enabled.has_value()) {
- diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
- << "Only flags with a value may be used with resources");
- return {};
- }
- flag_status = flag_properties.enabled.value() ? FlagStatus::Enabled : FlagStatus::Disabled;
- }
- return flag_status;
-}
-
bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
ParsedResource* out_resource,
const uint32_t format) {
@@ -1542,13 +1529,34 @@ bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) {
ResolvePackage(parser, &maybe_key.value());
maybe_key.value().SetSource(source);
+ auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"));
+
std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString);
if (!value) {
diag_->Error(android::DiagMessage(source) << "could not parse style item");
return false;
}
- style->entries.push_back(Style::Entry{std::move(maybe_key.value()), std::move(value)});
+ if (flag) {
+ if (options_.flag) {
+ diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+ << "Resource flag are not allowed both in the path and in the file");
+ return false;
+ }
+ std::string error;
+ auto flag_status = GetFlagStatus(flag, options_.feature_flag_values, &error);
+ if (flag_status) {
+ value->SetFlagStatus(flag_status.value());
+ value->SetFlag(std::move(flag));
+ } else {
+ diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << error);
+ return false;
+ }
+ }
+
+ if (value->GetFlagStatus() != FlagStatus::Disabled) {
+ style->entries.push_back(Style::Entry{std::move(maybe_key.value()), std::move(value)});
+ }
return true;
}
@@ -1666,21 +1674,25 @@ bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser,
const std::string& element_namespace = parser->element_namespace();
const std::string& element_name = parser->element_name();
if (element_namespace.empty() && element_name == "item") {
- auto flag_status = GetFlagStatus(parser);
- if (!flag_status) {
- error = true;
- continue;
- }
+ auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"));
std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
if (!item) {
diag_->Error(android::DiagMessage(item_source) << "could not parse array item");
error = true;
continue;
}
- item->SetFlagStatus(flag_status.value());
+ item->SetFlag(flag);
+ std::string err;
+ auto status = GetFlagStatus(flag, options_.feature_flag_values, &err);
+ if (status) {
+ item->SetFlagStatus(status.value());
+ } else {
+ diag_->Error(android::DiagMessage(item_source) << err);
+ error = true;
+ continue;
+ }
item->SetSource(item_source);
array->elements.emplace_back(std::move(item));
-
} else if (!ShouldIgnoreElement(element_namespace, element_name)) {
diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
<< "unknown tag <" << element_namespace << ":" << element_name << ">");
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 442dea89ef40..90690d522ef2 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -57,6 +57,11 @@ struct ResourceParserOptions {
std::optional<Visibility::Level> visibility;
FeatureFlagValues feature_flag_values;
+
+ // The flag that should be applied to all resources parsed
+ std::optional<FeatureFlagAttribute> flag;
+
+ FlagStatus flag_status = FlagStatus::NoFlag;
};
struct FlattenedXmlSubTree {
@@ -85,8 +90,6 @@ class ResourceParser {
private:
DISALLOW_COPY_AND_ASSIGN(ResourceParser);
- std::optional<FlagStatus> GetFlagStatus(xml::XmlPullParser* parser);
-
std::optional<FlattenedXmlSubTree> CreateFlattenSubTree(xml::XmlPullParser* parser);
// Parses the XML subtree as a StyleString (flattened XML representation for strings with
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 97514599c0b1..5435cba290fc 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -101,6 +101,21 @@ struct lt_config_key_ref {
}
};
+struct ConfigFlagKey {
+ const ConfigDescription* config;
+ StringPiece product;
+ const FeatureFlagAttribute& flag;
+};
+
+struct lt_config_flag_key_ref {
+ template <typename T>
+ bool operator()(const T& lhs, const ConfigFlagKey& rhs) const noexcept {
+ return std::tie(lhs->config, lhs->product, lhs->value->GetFlag()->name,
+ lhs->value->GetFlag()->negated) <
+ std::tie(*rhs.config, rhs.product, rhs.flag.name, rhs.flag.negated);
+ }
+};
+
} // namespace
ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
@@ -213,6 +228,25 @@ std::vector<ResourceConfigValue*> ResourceEntry::FindAllValues(const ConfigDescr
return results;
}
+ResourceConfigValue* ResourceEntry::FindOrCreateFlagDisabledValue(
+ const FeatureFlagAttribute& flag, const android::ConfigDescription& config,
+ android::StringPiece product) {
+ auto iter = std::lower_bound(flag_disabled_values.begin(), flag_disabled_values.end(),
+ ConfigFlagKey{&config, product, flag}, lt_config_flag_key_ref());
+ if (iter != flag_disabled_values.end()) {
+ ResourceConfigValue* value = iter->get();
+ const auto value_flag = value->value->GetFlag().value();
+ if (value_flag.name == flag.name && value_flag.negated == flag.negated &&
+ value->config == config && value->product == product) {
+ return value;
+ }
+ }
+ ResourceConfigValue* newValue =
+ flag_disabled_values.insert(iter, util::make_unique<ResourceConfigValue>(config, product))
+ ->get();
+ return newValue;
+}
+
bool ResourceEntry::HasDefaultValue() const {
// The default config should be at the top of the list, since the list is sorted.
return !values.empty() && values.front()->config == ConfigDescription::DefaultConfig();
@@ -375,13 +409,14 @@ struct EntryViewComparer {
}
};
-void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package,
- const ResourceTableType* type, const std::string& entry_name,
- const std::optional<ResourceId>& id, const Visibility& visibility,
- const std::optional<AllowNew>& allow_new,
- const std::optional<OverlayableItem>& overlayable_item,
- const std::optional<StagedId>& staged_id,
- const std::vector<std::unique_ptr<ResourceConfigValue>>& values) {
+void InsertEntryIntoTableView(
+ ResourceTableView& table, const ResourceTablePackage* package, const ResourceTableType* type,
+ const std::string& entry_name, const std::optional<ResourceId>& id,
+ const Visibility& visibility, const std::optional<AllowNew>& allow_new,
+ const std::optional<OverlayableItem>& overlayable_item,
+ const std::optional<StagedId>& staged_id,
+ const std::vector<std::unique_ptr<ResourceConfigValue>>& values,
+ const std::vector<std::unique_ptr<ResourceConfigValue>>& flag_disabled_values) {
SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
SortedVectorInserter<ResourceTableEntryView, EntryViewComparer> entry_inserter;
@@ -408,6 +443,9 @@ void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePacka
for (auto& value : values) {
new_entry.values.emplace_back(value.get());
}
+ for (auto& value : flag_disabled_values) {
+ new_entry.flag_disabled_values.emplace_back(value.get());
+ }
entry_inserter.Insert(view_type->entries, std::move(new_entry));
}
@@ -426,6 +464,21 @@ const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescrip
return nullptr;
}
+const ResourceConfigValue* ResourceTableEntryView::FindFlagDisabledValue(
+ const FeatureFlagAttribute& flag, const ConfigDescription& config,
+ android::StringPiece product) const {
+ auto iter = std::lower_bound(flag_disabled_values.begin(), flag_disabled_values.end(),
+ ConfigFlagKey{&config, product, flag}, lt_config_flag_key_ref());
+ if (iter != values.end()) {
+ const ResourceConfigValue* value = *iter;
+ if (value->value->GetFlag() == flag && value->config == config &&
+ StringPiece(value->product) == product) {
+ return value;
+ }
+ }
+ return nullptr;
+}
+
ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptions& options) const {
ResourceTableView view;
for (const auto& package : packages) {
@@ -433,13 +486,13 @@ ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptio
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);
+ entry->staged_id, entry->values, entry->flag_disabled_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);
+ entry->values, entry->flag_disabled_values);
}
}
}
@@ -587,6 +640,25 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag)
entry->staged_id = res.staged_id.value();
}
+ if (res.value != nullptr && res.value->GetFlagStatus() == FlagStatus::Disabled) {
+ auto disabled_config_value =
+ entry->FindOrCreateFlagDisabledValue(res.value->GetFlag().value(), res.config, res.product);
+ if (!disabled_config_value->value) {
+ // Resource does not exist, add it now.
+ // Must clone the value since it might be in the values vector as well
+ CloningValueTransformer cloner(&string_pool);
+ disabled_config_value->value = res.value->Transform(cloner);
+ } else {
+ diag->Error(android::DiagMessage(source)
+ << "duplicate value for resource '" << res.name << "' " << "with config '"
+ << res.config << "' and flag '"
+ << (res.value->GetFlag().value().negated ? "!" : "")
+ << res.value->GetFlag().value().name << "'");
+ diag->Error(android::DiagMessage(source) << "resource previously defined here");
+ return false;
+ }
+ }
+
if (res.value != nullptr) {
auto config_value = entry->FindOrCreateValue(res.config, res.product);
if (!config_value->value) {
@@ -595,9 +667,9 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag)
} else {
// When validation is enabled, ensure that a resource cannot have multiple values defined for
// the same configuration unless protected by flags.
- auto result =
- validate ? ResolveFlagCollision(config_value->value->GetFlagStatus(), res.flag_status)
- : CollisionResult::kKeepBoth;
+ auto result = validate ? ResolveFlagCollision(config_value->value->GetFlagStatus(),
+ res.value->GetFlagStatus())
+ : CollisionResult::kKeepBoth;
if (result == CollisionResult::kConflict) {
result = ResolveValueCollision(config_value->value.get(), res.value.get());
}
@@ -771,11 +843,6 @@ NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) {
return *this;
}
-NewResourceBuilder& NewResourceBuilder::SetFlagStatus(FlagStatus flag_status) {
- res_.flag_status = flag_status;
- return *this;
-}
-
NewResource NewResourceBuilder::Build() {
return std::move(res_);
}
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index cba6b70cfbd6..b0e185536d16 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -136,6 +136,9 @@ class ResourceEntry {
// The resource's values for each configuration.
std::vector<std::unique_ptr<ResourceConfigValue>> values;
+ // The resource's values that are behind disabled flags.
+ std::vector<std::unique_ptr<ResourceConfigValue>> flag_disabled_values;
+
explicit ResourceEntry(android::StringPiece name) : name(name) {
}
@@ -148,6 +151,13 @@ class ResourceEntry {
android::StringPiece product);
std::vector<ResourceConfigValue*> FindAllValues(const android::ConfigDescription& config);
+ // Either returns the existing ResourceConfigValue in the disabled list with the given flag,
+ // config, and product or creates a new one and returns that. In either case the returned value
+ // does not have the flag set on the value so it must be set by the caller.
+ ResourceConfigValue* FindOrCreateFlagDisabledValue(const FeatureFlagAttribute& flag,
+ const android::ConfigDescription& config,
+ android::StringPiece product = {});
+
template <typename Func>
std::vector<ResourceConfigValue*> FindValuesIf(Func f) {
std::vector<ResourceConfigValue*> results;
@@ -215,9 +225,14 @@ struct ResourceTableEntryView {
std::optional<OverlayableItem> overlayable_item;
std::optional<StagedId> staged_id;
std::vector<const ResourceConfigValue*> values;
+ std::vector<const ResourceConfigValue*> flag_disabled_values;
const ResourceConfigValue* FindValue(const android::ConfigDescription& config,
android::StringPiece product = {}) const;
+
+ const ResourceConfigValue* FindFlagDisabledValue(const FeatureFlagAttribute& flag,
+ const android::ConfigDescription& config,
+ android::StringPiece product = {}) const;
};
struct ResourceTableTypeView {
@@ -269,7 +284,6 @@ struct NewResource {
std::optional<AllowNew> allow_new;
std::optional<StagedId> staged_id;
bool allow_mangled = false;
- FlagStatus flag_status = FlagStatus::NoFlag;
};
struct NewResourceBuilder {
@@ -283,7 +297,6 @@ struct NewResourceBuilder {
NewResourceBuilder& SetAllowNew(AllowNew allow_new);
NewResourceBuilder& SetStagedId(StagedId id);
NewResourceBuilder& SetAllowMangled(bool allow_mangled);
- NewResourceBuilder& SetFlagStatus(FlagStatus flag_status);
NewResource Build();
private:
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index b75e87c90128..723cfc0e035b 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -1102,6 +1102,7 @@ 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());
+ new_value->SetFlag(value->GetFlag());
new_value->SetFlagStatus(value->GetFlagStatus());
return new_value;
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index a1b1839b19ef..e000c653b87a 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -65,10 +65,21 @@ class Value {
return translatable_;
}
+ void SetFlag(std::optional<FeatureFlagAttribute> val) {
+ flag_ = val;
+ }
+
+ std::optional<FeatureFlagAttribute> GetFlag() const {
+ return flag_;
+ }
+
void SetFlagStatus(FlagStatus val) {
flag_status_ = val;
}
+ // If the value is behind a flag this returns whether that flag was enabled when the value was
+ // parsed by comparing it to the flags passed on the command line to aapt2 (taking into account
+ // negation if necessary). If there was no flag, FlagStatus::NoFlag is returned instead.
FlagStatus GetFlagStatus() const {
return flag_status_;
}
@@ -128,6 +139,7 @@ class Value {
std::string comment_;
bool weak_ = false;
bool translatable_ = true;
+ std::optional<FeatureFlagAttribute> flag_;
FlagStatus flag_status_ = FlagStatus::NoFlag;
private:
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 5c6408940b34..fe9b4a8843ca 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -240,6 +240,9 @@ message Entry {
// The staged resource ID of this finalized resource.
StagedId staged_id = 7;
+
+ // The set of values defined for this entry which are behind disabled flags
+ repeated ConfigValue flag_disabled_config_value = 8;
}
// A Configuration/Value pair.
@@ -283,6 +286,8 @@ message Item {
// The status of the flag the value is behind if any
uint32 flag_status = 8;
+ bool flag_negated = 9;
+ string flag_name = 10;
}
// A CompoundValue is an abstract type. It represents a value that is a made of other values.
@@ -297,6 +302,11 @@ message CompoundValue {
Plural plural = 5;
MacroBody macro = 6;
}
+
+ // The status of the flag the value is behind if any
+ uint32 flag_status = 7;
+ bool flag_negated = 8;
+ string flag_name = 9;
}
// Message holding a boolean, so it can be optionally encoded.
diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto
index b0ed3da33368..f4735a2f6ce7 100644
--- a/tools/aapt2/ResourcesInternal.proto
+++ b/tools/aapt2/ResourcesInternal.proto
@@ -49,4 +49,9 @@ message CompiledFile {
// Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file).
repeated Symbol exported_symbol = 5;
+
+ // The status of the flag the file is behind if any
+ uint32 flag_status = 6;
+ bool flag_negated = 7;
+ string flag_name = 8;
}
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index 514651e92c27..449d93dd8c0b 100644
--- a/tools/aapt2/cmd/Command.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -213,15 +213,28 @@ int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_err
bool match = false;
for (Flag& flag : flags_) {
- if (arg == flag.name) {
+ // Allow both "--arg value" and "--arg=value" syntax.
+ if (arg.starts_with(flag.name) &&
+ (arg.size() == flag.name.size() || (flag.num_args > 0 && arg[flag.name.size()] == '='))) {
if (flag.num_args > 0) {
- i++;
- if (i >= args.size()) {
- *out_error << flag.name << " missing argument.\n\n";
- Usage(out_error);
- return false;
+ if (arg.size() == flag.name.size()) {
+ i++;
+ if (i >= args.size()) {
+ *out_error << flag.name << " missing argument.\n\n";
+ Usage(out_error);
+ return 1;
+ }
+ arg = args[i];
+ } else {
+ arg.remove_prefix(flag.name.size() + 1);
+ // Disallow empty arguments after '='.
+ if (arg.empty()) {
+ *out_error << flag.name << " has empty argument.\n\n";
+ Usage(out_error);
+ return 1;
+ }
}
- flag.action(args[i]);
+ flag.action(arg);
} else {
flag.action({});
}
diff --git a/tools/aapt2/cmd/Command_test.cpp b/tools/aapt2/cmd/Command_test.cpp
index 7aa1aa017f7b..20d87e0025c3 100644
--- a/tools/aapt2/cmd/Command_test.cpp
+++ b/tools/aapt2/cmd/Command_test.cpp
@@ -19,6 +19,7 @@
#include "test/Test.h"
using ::testing::Eq;
+using namespace std::literals;
namespace aapt {
@@ -94,4 +95,27 @@ TEST(CommandTest, LongFullyQualifiedPathWindows) {
}
#endif
+TEST(CommandTest, OptionsWithValues) {
+ TestCommand command;
+ std::string flag;
+ command.AddRequiredFlag("--flag", "", &flag);
+
+ ASSERT_EQ(0, command.Execute({"--flag"s, "1"s}, &std::cerr));
+ EXPECT_STREQ("1", flag.c_str());
+
+ ASSERT_EQ(0, command.Execute({"--flag=1"s}, &std::cerr));
+ EXPECT_STREQ("1", flag.c_str());
+
+ ASSERT_EQ(0, command.Execute({"--flag"s, "=2"s}, &std::cerr));
+ EXPECT_STREQ("=2", flag.c_str());
+
+ ASSERT_EQ(0, command.Execute({"--flag"s, "--flag"s}, &std::cerr));
+ EXPECT_STREQ("--flag", flag.c_str());
+
+ EXPECT_NE(0, command.Execute({"--flag"s}, &std::cerr));
+ EXPECT_NE(0, command.Execute({"--flag="s}, &std::cerr));
+ EXPECT_NE(0, command.Execute({"--flag1=2"s}, &std::cerr));
+ EXPECT_NE(0, command.Execute({"--flag1"s, "2"s}, &std::cerr));
+}
+
} // namespace aapt \ No newline at end of file
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 2a978a5153ca..a5e18d35a256 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -67,6 +67,7 @@ struct ResourcePathData {
std::string resource_dir;
std::string name;
std::string extension;
+ std::string flag_name;
// Original config str. We keep this because when we parse the config, we may add on
// version qualifiers. We want to preserve the original input so the output is easily
@@ -81,6 +82,22 @@ static std::optional<ResourcePathData> ExtractResourcePathData(const std::string
std::string* out_error,
const CompileOptions& options) {
std::vector<std::string> parts = util::Split(path, dir_sep);
+
+ std::string flag_name;
+ // Check for a flag
+ for (auto iter = parts.begin(); iter != parts.end();) {
+ if (iter->starts_with("flag(") && iter->ends_with(")")) {
+ if (!flag_name.empty()) {
+ if (out_error) *out_error = "resource path cannot contain more than one flag directory";
+ return {};
+ }
+ flag_name = iter->substr(5, iter->size() - 6);
+ iter = parts.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
if (parts.size() < 2) {
if (out_error) *out_error = "bad resource path";
return {};
@@ -131,6 +148,7 @@ static std::optional<ResourcePathData> ExtractResourcePathData(const std::string
std::string(dir_str),
std::string(name),
std::string(extension),
+ std::move(flag_name),
std::string(config_str),
config};
}
@@ -142,6 +160,9 @@ static std::string BuildIntermediateContainerFilename(const ResourcePathData& da
name << "-" << data.config_str;
}
name << "_" << data.name;
+ if (!data.flag_name.empty()) {
+ name << ".(" << data.flag_name << ")";
+ }
if (!data.extension.empty()) {
name << "." << data.extension;
}
@@ -163,7 +184,6 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,
<< "failed to open file: " << fin->GetError());
return false;
}
-
// Parse the values file from XML.
xml::XmlPullParser xml_parser(fin.get());
@@ -176,6 +196,18 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,
// If visibility was forced, we need to use it when creating a new resource and also error if
// we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
parser_options.visibility = options.visibility;
+ parser_options.flag = ParseFlag(path_data.flag_name);
+
+ if (parser_options.flag) {
+ std::string error;
+ auto flag_status = GetFlagStatus(parser_options.flag, options.feature_flag_values, &error);
+ if (flag_status) {
+ parser_options.flag_status = std::move(flag_status.value());
+ } else {
+ context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error);
+ return false;
+ }
+ }
ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config,
parser_options);
@@ -402,6 +434,18 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,
xmlres->file.config = path_data.config;
xmlres->file.source = path_data.source;
xmlres->file.type = ResourceFile::Type::kProtoXml;
+ xmlres->file.flag = ParseFlag(path_data.flag_name);
+
+ if (xmlres->file.flag) {
+ std::string error;
+ auto flag_status = GetFlagStatus(xmlres->file.flag, options.feature_flag_values, &error);
+ if (flag_status) {
+ xmlres->file.flag_status = flag_status.value();
+ } else {
+ context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error);
+ return false;
+ }
+ }
// Collect IDs that are defined here.
XmlIdCollector collector;
@@ -491,6 +535,27 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options,
res_file.source = path_data.source;
res_file.type = ResourceFile::Type::kPng;
+ if (!path_data.flag_name.empty()) {
+ FeatureFlagAttribute flag;
+ auto name = path_data.flag_name;
+ if (name.starts_with('!')) {
+ flag.negated = true;
+ flag.name = name.substr(1);
+ } else {
+ flag.name = name;
+ }
+ res_file.flag = flag;
+
+ std::string error;
+ auto flag_status = GetFlagStatus(flag, options.feature_flag_values, &error);
+ if (flag_status) {
+ res_file.flag_status = flag_status.value();
+ } else {
+ context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error);
+ return false;
+ }
+ }
+
{
auto data = file->OpenAsData();
if (!data) {
@@ -540,8 +605,9 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options,
}
// Write the crunched PNG.
- if (!android::WritePng(image.get(), nine_patch.get(), &crunched_png_buffer_out, {},
- &source_diag, context->IsVerbose())) {
+ if (!android::WritePng(image.get(), nine_patch.get(), &crunched_png_buffer_out,
+ {.compression_level = options.png_compression_level_int}, &source_diag,
+ context->IsVerbose())) {
return false;
}
@@ -859,6 +925,19 @@ int CompileCommand::Action(const std::vector<std::string>& args) {
}
}
+ if (!options_.png_compression_level) {
+ options_.png_compression_level_int = 9;
+ } else {
+ if (options_.png_compression_level->size() != 1 ||
+ options_.png_compression_level->front() < '0' ||
+ options_.png_compression_level->front() > '9') {
+ context.GetDiagnostics()->Error(
+ android::DiagMessage() << "PNG compression level should be a number in [0..9] range");
+ return 1;
+ }
+ options_.png_compression_level_int = options_.png_compression_level->front() - '0';
+ }
+
return Compile(&context, file_collection.get(), archive_writer.get(), options_);
}
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index 70c8791524c8..e244546476b0 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -47,6 +47,8 @@ struct CompileOptions {
bool verbose = false;
std::optional<std::string> product_;
FeatureFlagValues feature_flag_values;
+ std::optional<std::string> png_compression_level;
+ int png_compression_level_int = 9;
};
/** Parses flags and compiles resources to be used in linking. */
@@ -65,6 +67,9 @@ class CompileCommand : public Command {
AddOptionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
"(en-XA and ar-XB)", &options_.pseudolocalize);
AddOptionalSwitch("--no-crunch", "Disables PNG processing", &options_.no_png_crunch);
+ AddOptionalFlag("--png-compression-level",
+ "Set the zlib compression level for crunched PNG images, [0-9], 9 by default.",
+ &options_.png_compression_level);
AddOptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
&options_.legacy_mode);
AddOptionalSwitch("--preserve-visibility-of-styleables",
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 6da3176b2bee..d3750a6100d3 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -138,6 +138,22 @@ static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
}
}
+ for (const ResourceConfigValue* config_value_a : entry_a.flag_disabled_values) {
+ auto config_value_b = entry_b.FindFlagDisabledValue(config_value_a->value->GetFlag().value(),
+ config_value_a->config);
+ if (!config_value_b) {
+ std::stringstream str_stream;
+ str_stream << "missing disabled value " << pkg_a.name << ":" << type_a.named_type << "/"
+ << entry_a.name << " config=" << config_value_a->config
+ << " flag=" << config_value_a->value->GetFlag()->ToString();
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ } else {
+ 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 (const ResourceConfigValue* config_value_b : entry_b.values) {
auto config_value_a = entry_a.FindValue(config_value_b->config);
@@ -149,6 +165,18 @@ static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
diff = true;
}
}
+ for (const ResourceConfigValue* config_value_b : entry_b.flag_disabled_values) {
+ auto config_value_a = entry_a.FindFlagDisabledValue(config_value_b->value->GetFlag().value(),
+ config_value_b->config);
+ if (!config_value_a) {
+ std::stringstream str_stream;
+ str_stream << "new disabled config " << pkg_b.name << ":" << type_b.named_type << "/"
+ << entry_b.name << " config=" << config_value_b->config
+ << " flag=" << config_value_b->value->GetFlag()->ToString();
+ EmitDiffLine(apk_b->GetSource(), str_stream.str());
+ diff = true;
+ }
+ }
return diff;
}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 498e431097ad..232b4024abd2 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1574,7 +1574,10 @@ class Linker {
// 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.
// Otherwise the file is processed on its own.
- bool MergePath(const std::string& path, bool override) {
+ bool MergePath(std::string path, bool override) {
+ if (path.size() > 2 && util::StartsWith(path, "'") && util::EndsWith(path, "'")) {
+ path = path.substr(1, path.size() - 2);
+ }
if (util::EndsWith(path, ".flata") || util::EndsWith(path, ".jar") ||
util::EndsWith(path, ".jack") || util::EndsWith(path, ".zip")) {
return MergeArchive(path, override);
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 7739171b347f..08f8f0d85807 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -34,6 +34,44 @@ using ::android::base::StringPrintf;
namespace aapt {
+std::optional<FeatureFlagAttribute> ParseFlag(std::optional<std::string_view> flag_text) {
+ if (!flag_text || flag_text->empty()) {
+ return {};
+ }
+ FeatureFlagAttribute flag;
+ if (flag_text->starts_with('!')) {
+ flag.negated = true;
+ flag.name = flag_text->substr(1);
+ } else {
+ flag.name = flag_text.value();
+ }
+ return flag;
+}
+
+std::optional<FlagStatus> GetFlagStatus(const std::optional<FeatureFlagAttribute>& flag,
+ const FeatureFlagValues& feature_flag_values,
+ std::string* out_err) {
+ if (!flag) {
+ return FlagStatus::NoFlag;
+ }
+ auto flag_it = feature_flag_values.find(flag->name);
+ if (flag_it == feature_flag_values.end()) {
+ *out_err = "Resource flag value undefined: " + flag->name;
+ return {};
+ }
+ const auto& flag_properties = flag_it->second;
+ if (!flag_properties.read_only) {
+ *out_err = "Only read only flags may be used with resources: " + flag->name;
+ return {};
+ }
+ if (!flag_properties.enabled.has_value()) {
+ *out_err = "Only flags with a value may be used with resources: " + flag->name;
+ return {};
+ }
+ return (flag_properties.enabled.value() != flag->negated) ? FlagStatus::Enabled
+ : FlagStatus::Disabled;
+}
+
std::optional<uint16_t> ParseTargetDensityParameter(StringPiece arg, android::IDiagnostics* diag) {
ConfigDescription preferred_density_config;
if (!ConfigDescription::Parse(arg, &preferred_density_config)) {
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 6b8813b34082..d32e532b86a8 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -49,6 +49,12 @@ struct FeatureFlagProperties {
using FeatureFlagValues = std::map<std::string, FeatureFlagProperties, std::less<>>;
+std::optional<FeatureFlagAttribute> ParseFlag(std::optional<std::string_view> flag_text);
+
+std::optional<FlagStatus> GetFlagStatus(const std::optional<FeatureFlagAttribute>& flag,
+ const FeatureFlagValues& feature_flag_values,
+ std::string* out_err);
+
// Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc).
// Returns Nothing and logs a human friendly error message if the string was not legal.
std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg,
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 55f5e5668a16..91ec3485ac3b 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -536,6 +536,32 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
&out_table->string_pool, files, out_error);
+
+ if (config_value->value == nullptr) {
+ return false;
+ }
+ }
+
+ // flag disabled
+ for (const pb::ConfigValue& pb_config_value : pb_entry.flag_disabled_config_value()) {
+ const pb::Configuration& pb_config = pb_config_value.config();
+
+ ConfigDescription config;
+ if (!DeserializeConfigFromPb(pb_config, &config, out_error)) {
+ return false;
+ }
+
+ ResourceConfigValue* config_value = entry->FindOrCreateFlagDisabledValue(
+ FeatureFlagAttribute{.name = pb_config_value.value().item().flag_name(),
+ .negated = pb_config_value.value().item().flag_negated()},
+ config, pb_config.product());
+ if (config_value->value != nullptr) {
+ *out_error = "duplicate configuration in resource table";
+ return false;
+ }
+
+ config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
+ &out_table->string_pool, files, out_error);
if (config_value->value == nullptr) {
return false;
}
@@ -615,6 +641,12 @@ bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
out_file->source.path = pb_file.source_path();
out_file->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+ out_file->flag_status = (FlagStatus)pb_file.flag_status();
+ if (!pb_file.flag_name().empty()) {
+ out_file->flag =
+ FeatureFlagAttribute{.name = pb_file.flag_name(), .negated = pb_file.flag_negated()};
+ }
+
std::string config_error;
if (!DeserializeConfigFromPb(pb_file.config(), &out_file->config, &config_error)) {
std::ostringstream error;
@@ -748,7 +780,6 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
if (value == nullptr) {
return {};
}
-
} else if (pb_value.has_compound_value()) {
const pb::CompoundValue& pb_compound_value = pb_value.compound_value();
switch (pb_compound_value.value_case()) {
@@ -863,6 +894,9 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
LOG(FATAL) << "unknown compound value: " << (int)pb_compound_value.value_case();
break;
}
+ value->SetFlagStatus((FlagStatus)pb_compound_value.flag_status());
+ value->SetFlag(FeatureFlagAttribute{.name = pb_compound_value.flag_name(),
+ .negated = pb_compound_value.flag_negated()});
} else {
LOG(FATAL) << "unknown value: " << (int)pb_value.value_case();
return {};
@@ -1018,6 +1052,10 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
DeserializeItemFromPbInternal(pb_item, src_pool, config, value_pool, files, out_error);
if (item) {
item->SetFlagStatus((FlagStatus)pb_item.flag_status());
+ if (!pb_item.flag_name().empty()) {
+ item->SetFlag(
+ FeatureFlagAttribute{.name = pb_item.flag_name(), .negated = pb_item.flag_negated()});
+ }
}
return item;
}
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 5772b3b0b3e6..fcc77d5a9d6d 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -427,6 +427,14 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table
SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(),
source_pool.get());
}
+
+ for (const ResourceConfigValue* config_value : entry.flag_disabled_values) {
+ pb::ConfigValue* pb_config_value = pb_entry->add_flag_disabled_config_value();
+ SerializeConfig(config_value->config, pb_config_value->mutable_config());
+ pb_config_value->mutable_config()->set_product(config_value->product);
+ SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(),
+ source_pool.get());
+ }
}
}
}
@@ -721,6 +729,18 @@ void SerializeValueToPb(const Value& value, pb::Value* out_value, android::Strin
}
if (out_value->has_item()) {
out_value->mutable_item()->set_flag_status((uint32_t)value.GetFlagStatus());
+ if (value.GetFlag()) {
+ const auto& flag = value.GetFlag();
+ out_value->mutable_item()->set_flag_negated(flag->negated);
+ out_value->mutable_item()->set_flag_name(flag->name);
+ }
+ } else if (out_value->has_compound_value()) {
+ out_value->mutable_compound_value()->set_flag_status((uint32_t)value.GetFlagStatus());
+ if (value.GetFlag()) {
+ const auto& flag = value.GetFlag();
+ out_value->mutable_compound_value()->set_flag_negated(flag->negated);
+ out_value->mutable_compound_value()->set_flag_name(flag->name);
+ }
}
}
@@ -730,6 +750,11 @@ void SerializeItemToPb(const Item& item, pb::Item* out_item) {
item.Accept(&serializer);
out_item->MergeFrom(value.item());
out_item->set_flag_status((uint32_t)item.GetFlagStatus());
+ if (item.GetFlag()) {
+ const auto& flag = item.GetFlag();
+ out_item->set_flag_negated(flag->negated);
+ out_item->set_flag_name(flag->name);
+ }
}
void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) {
@@ -737,6 +762,11 @@ void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledF
out_file->set_source_path(file.source.path);
out_file->set_type(SerializeFileReferenceTypeToPb(file.type));
SerializeConfig(file.config, out_file->mutable_config());
+ out_file->set_flag_status((uint32_t)file.flag_status);
+ if (file.flag) {
+ out_file->set_flag_negated(file.flag->negated);
+ out_file->set_flag_name(file.flag->name);
+ }
for (const SourcedResourceName& exported : file.exported_symbols) {
pb::internal::CompiledFile_Symbol* pb_symbol = out_file->add_exported_symbol();
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
index c456e5c296d2..1b0f99753ce6 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
@@ -30,14 +30,32 @@ genrule {
"res/values/bools2.xml",
"res/values/ints.xml",
"res/values/strings.xml",
+ "res/values/styles.xml",
"res/layout/layout1.xml",
+ "res/layout/layout3.xml",
+ "res/flag(test.package.falseFlag)/values/bools.xml",
+ "res/flag(test.package.falseFlag)/layout/layout2.xml",
+ "res/flag(test.package.falseFlag)/drawable/removedpng.png",
+ "res/flag(test.package.trueFlag)/layout/layout3.xml",
+ "res/values/flag(test.package.trueFlag)/bools.xml",
+ "res/values/flag(!test.package.trueFlag)/bools.xml",
+ "res/values/flag(!test.package.falseFlag)/bools.xml",
],
out: [
+ "drawable_removedpng.(test.package.falseFlag).png.flat",
"values_bools.arsc.flat",
+ "values_bools.(test.package.falseFlag).arsc.flat",
+ "values_bools.(test.package.trueFlag).arsc.flat",
+ "values_bools.(!test.package.falseFlag).arsc.flat",
+ "values_bools.(!test.package.trueFlag).arsc.flat",
"values_bools2.arsc.flat",
"values_ints.arsc.flat",
"values_strings.arsc.flat",
+ "values_styles.arsc.flat",
"layout_layout1.xml.flat",
+ "layout_layout2.(test.package.falseFlag).xml.flat",
+ "layout_layout3.xml.flat",
+ "layout_layout3.(test.package.trueFlag).xml.flat",
],
cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
"--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/drawable/removedpng.png b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/drawable/removedpng.png
new file mode 100644
index 000000000000..8a9e6984be96
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/drawable/removedpng.png
Binary files differ
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/layout/layout2.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/layout/layout2.xml
new file mode 100644
index 000000000000..dec5de72925a
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/layout/layout2.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+</LinearLayout> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/values/bools.xml
new file mode 100644
index 000000000000..c46c4d4d8546
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/values/bools.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="bool7">false</bool>
+</resources> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.trueFlag)/layout/layout3.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.trueFlag)/layout/layout3.xml
new file mode 100644
index 000000000000..5aeee0ee1e28
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.trueFlag)/layout/layout3.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <TextView android:id="@+id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="foobar" />
+</LinearLayout> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml
new file mode 100644
index 000000000000..dec5de72925a
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+</LinearLayout> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
index 1ed0c8a5f1e6..35975ed1274a 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
@@ -9,4 +9,15 @@
<bool name="bool3">false</bool>
<bool name="bool4" android:featureFlag="test.package.falseFlag">true</bool>
+
+ <bool name="bool5">false</bool>
+ <bool name="bool5" android:featureFlag="!test.package.falseFlag">true</bool>
+
+ <bool name="bool6">true</bool>
+ <bool name="bool6" android:featureFlag="!test.package.trueFlag">false</bool>
+
+ <bool name="bool7">true</bool>
+ <bool name="bool8">false</bool>
+ <bool name="bool9">true</bool>
+ <bool name="bool10">false</bool>
</resources> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.falseFlag)/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.falseFlag)/bools.xml
new file mode 100644
index 000000000000..a63749c6ed7e
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.falseFlag)/bools.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="bool10">true</bool>
+</resources> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.trueFlag)/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.trueFlag)/bools.xml
new file mode 100644
index 000000000000..bb5526e69f97
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.trueFlag)/bools.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="bool9">false</bool>
+</resources> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(test.package.trueFlag)/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(test.package.trueFlag)/bools.xml
new file mode 100644
index 000000000000..eba780e88c9a
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(test.package.trueFlag)/bools.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="bool8">true</bool>
+</resources> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/styles.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/styles.xml
new file mode 100644
index 000000000000..604129c26fef
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/styles.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="style1">
+ <item name="android:windowIsTranslucent">true</item>
+ </style>
+ <style name="style1" android:featureFlag="test.package.falseFlag">
+ <item name="android:windowIsTranslucent">false</item>
+ </style>
+
+ <style name="style2">
+ <item name="android:windowIsTranslucent">false</item>
+ </style>
+ <style name="style2" android:featureFlag="test.package.trueFlag">
+ <item name="android:windowIsTranslucent">true</item>
+ </style>
+
+ <style name="style3">
+ <item name="android:windowIsTranslucent" android:featureFlag="!test.package.trueFlag">false</item>
+ <item name="android:windowIsTranslucent" android:featureFlag="test.package.trueFlag">true</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
index 3db37c2fa6f8..629300838bbe 100644
--- a/tools/aapt2/link/FlaggedResources_test.cpp
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -17,6 +17,7 @@
#include "LoadedApk.h"
#include "cmd/Dump.h"
#include "io/StringStream.h"
+#include "test/Common.h"
#include "test/Test.h"
#include "text/Printer.h"
@@ -75,6 +76,10 @@ TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTable) {
std::string output;
DumpResourceTableToString(loaded_apk.get(), &output);
+ ASSERT_EQ(output.find("bool4"), std::string::npos);
+ ASSERT_EQ(output.find("str1"), std::string::npos);
+ ASSERT_EQ(output.find("layout2"), std::string::npos);
+ ASSERT_EQ(output.find("removedpng"), std::string::npos);
}
TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) {
@@ -86,6 +91,8 @@ TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) {
ASSERT_EQ(output.find("bool4"), std::string::npos);
ASSERT_EQ(output.find("str1"), std::string::npos);
+ ASSERT_EQ(output.find("layout2"), std::string::npos);
+ ASSERT_EQ(output.find("removedpng"), std::string::npos);
}
TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) {
@@ -98,4 +105,47 @@ TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) {
ASSERT_NE(r_contents.find("public static final int str1"), std::string::npos);
}
+TEST_F(FlaggedResourcesTest, TwoValuesSameDisabledFlag) {
+ test::TestDiagnosticsImpl diag;
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ ASSERT_FALSE(CompileFile(
+ GetTestPath("res/values/values.xml"),
+ R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <bool name="bool1" android:featureFlag="test.package.falseFlag">false</bool>
+ <bool name="bool1" android:featureFlag="test.package.falseFlag">true</bool>
+ </resources>)",
+ compiled_files_dir, &diag,
+ {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"}));
+ ASSERT_TRUE(diag.GetLog().contains("duplicate value for resource 'bool/bool1'"));
+}
+
+TEST_F(FlaggedResourcesTest, TwoValuesSameDisabledFlagDifferentFiles) {
+ test::TestDiagnosticsImpl diag;
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ ASSERT_TRUE(CompileFile(
+ GetTestPath("res/values/values1.xml"),
+ R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <bool name="bool1" android:featureFlag="test.package.falseFlag">false</bool>
+ </resources>)",
+ compiled_files_dir, &diag,
+ {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"}));
+ ASSERT_TRUE(CompileFile(
+ GetTestPath("res/values/values2.xml"),
+ R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <bool name="bool1" android:featureFlag="test.package.falseFlag">true</bool>
+ </resources>)",
+ compiled_files_dir, &diag,
+ {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"}));
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest",
+ GetDefaultManifest(),
+ "-o",
+ out_apk,
+ };
+
+ ASSERT_FALSE(Link(link_args, compiled_files_dir, &diag));
+ ASSERT_TRUE(diag.GetLog().contains("duplicate value for resource 'bool1'"));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 37a039e9528f..1d4adc4a57d8 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -207,14 +207,13 @@ static ResourceTable::CollisionResult MergeConfigValue(
Value* dst_value = dst_config_value->value.get();
Value* src_value = src_config_value->value.get();
- CollisionResult collision_result;
- if (overlay) {
- collision_result =
- ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
- } else {
- collision_result =
- ResourceTable::ResolveFlagCollision(dst_value->GetFlagStatus(), src_value->GetFlagStatus());
- if (collision_result == CollisionResult::kConflict) {
+ CollisionResult collision_result =
+ ResourceTable::ResolveFlagCollision(dst_value->GetFlagStatus(), src_value->GetFlagStatus());
+ if (collision_result == CollisionResult::kConflict) {
+ if (overlay) {
+ collision_result =
+ ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
+ } else {
collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
}
}
@@ -321,6 +320,30 @@ bool TableMerger::DoMerge(const android::Source& src, ResourceTablePackage* src_
}
}
}
+
+ // disabled values
+ for (auto& src_config_value : src_entry->flag_disabled_values) {
+ auto dst_config_value = dst_entry->FindOrCreateFlagDisabledValue(
+ src_config_value->value->GetFlag().value(), src_config_value->config,
+ src_config_value->product);
+ if (!dst_config_value->value) {
+ // Resource does not exist, add it now.
+ // Must clone the value since it might be in the values vector as well
+ CloningValueTransformer cloner(&main_table_->string_pool);
+ dst_config_value->value = src_config_value->value->Transform(cloner);
+ } else {
+ error = true;
+ context_->GetDiagnostics()->Error(
+ android::DiagMessage(src_config_value->value->GetSource())
+ << "duplicate value for resource '" << src_entry->name << "' " << "with config '"
+ << src_config_value->config << "' and flag '"
+ << (src_config_value->value->GetFlag()->negated ? "!" : "")
+ << src_config_value->value->GetFlag()->name << "'");
+ context_->GetDiagnostics()->Note(
+ android::DiagMessage(dst_config_value->value->GetSource())
+ << "resource previously defined here");
+ }
+ }
}
}
return !error;
@@ -353,6 +376,8 @@ bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFi
file_ref->SetSource(file_desc.source);
file_ref->type = file_desc.type;
file_ref->file = file;
+ file_ref->SetFlagStatus(file_desc.flag_status);
+ file_ref->SetFlag(file_desc.flag);
ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package);
pkg->FindOrCreateType(file_desc.name.type)
diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp
index cdf245341844..c7dd4c90e67f 100644
--- a/tools/aapt2/test/Common.cpp
+++ b/tools/aapt2/test/Common.cpp
@@ -21,23 +21,6 @@ using android::ConfigDescription;
namespace aapt {
namespace test {
-struct TestDiagnosticsImpl : public android::IDiagnostics {
- void Log(Level level, android::DiagMessageActual& actual_msg) override {
- switch (level) {
- case Level::Note:
- return;
-
- case Level::Warn:
- std::cerr << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl;
- break;
-
- case Level::Error:
- std::cerr << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl;
- break;
- }
- }
-};
-
android::IDiagnostics* GetDiagnostics() {
static TestDiagnosticsImpl diag;
return &diag;
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 04379804d8ee..b06c4329488e 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -37,6 +37,32 @@
namespace aapt {
namespace test {
+struct TestDiagnosticsImpl : public android::IDiagnostics {
+ void Log(Level level, android::DiagMessageActual& actual_msg) override {
+ switch (level) {
+ case Level::Note:
+ return;
+
+ case Level::Warn:
+ std::cerr << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl;
+ log << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl;
+ break;
+
+ case Level::Error:
+ std::cerr << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl;
+ log << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl;
+ break;
+ }
+ }
+
+ std::string GetLog() {
+ return log.str();
+ }
+
+ private:
+ std::ostringstream log;
+};
+
android::IDiagnostics* GetDiagnostics();
inline ResourceName ParseNameOrDie(android::StringPiece str) {
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index b91abe572306..570bcf16c92c 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -91,10 +91,13 @@ void TestDirectoryFixture::WriteFile(const std::string& path, const std::string&
}
bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents,
- android::StringPiece out_dir, android::IDiagnostics* diag) {
+ android::StringPiece out_dir, android::IDiagnostics* diag,
+ const std::vector<android::StringPiece>& additional_args) {
WriteFile(path, contents);
CHECK(file::mkdirs(out_dir.data()));
- return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0;
+ std::vector<android::StringPiece> args = {path, "-o", out_dir, "-v"};
+ args.insert(args.end(), additional_args.begin(), additional_args.end());
+ return CompileCommand(diag).Execute(args, &std::cerr) == 0;
}
bool CommandTestFixture::Link(const std::vector<std::string>& args, android::IDiagnostics* diag) {
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index 14298d1678f0..178d01156f32 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -73,7 +73,8 @@ class CommandTestFixture : public TestDirectoryFixture {
// Wries the contents of the file to the specified path. The file is compiled and the flattened
// file is written to the out directory.
bool CompileFile(const std::string& path, const std::string& contents,
- android::StringPiece flat_out_dir, android::IDiagnostics* diag);
+ android::StringPiece flat_out_dir, android::IDiagnostics* diag,
+ const std::vector<android::StringPiece>& additional_args = {});
// Executes the link command with the specified arguments.
bool Link(const std::vector<std::string>& args, android::IDiagnostics* diag);
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 40ff5b633d97..03da460affb5 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -111,8 +111,8 @@ const char* GetToolName();
// Retrieves the build fingerprint of aapt2.
std::string GetToolFingerprint();
-template <typename T>
-typename std::enable_if<std::is_arithmetic<T>::value, int>::type compare(const T& a, const T& b) {
+template <std::integral T>
+int compare(T a, T b) {
if (a < b) {
return -1;
} else if (a > b) {
@@ -123,10 +123,7 @@ typename std::enable_if<std::is_arithmetic<T>::value, int>::type compare(const T
// Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
// This will be present in C++14 and can be removed then.
-template <typename T, class... Args>
-std::unique_ptr<T> make_unique(Args&&... args) {
- return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
-}
+using std::make_unique;
// Writes a set of items to the std::ostream, joining the times with the provided separator.
template <typename Container>
diff --git a/tools/processors/property_cache/Android.bp b/tools/processors/property_cache/Android.bp
new file mode 100644
index 000000000000..81fab7a4c862
--- /dev/null
+++ b/tools/processors/property_cache/Android.bp
@@ -0,0 +1,57 @@
+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"],
+ default_team: "trendy_team_framework_android_multiuser",
+}
+
+java_library_host {
+ name: "libcached-property-annotation-processor",
+ srcs: [
+ ":framework-annotations",
+ "src/**/*.java",
+ ],
+ static_libs: [
+ "codegen-version-info",
+ "android.multiuser.flags-aconfig-java-host",
+ "guava",
+ ],
+ use_tools_jar: true,
+}
+
+java_plugin {
+ name: "cached-property-annotation-processor",
+ processor_class: "android.processor.property_cache.CachedPropertyProcessor",
+ static_libs: ["libcached-property-annotation-processor"],
+}
+
+java_aconfig_library {
+ name: "android.multiuser.flags-aconfig-java-host",
+ aconfig_declarations: "android.multiuser.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_test_host {
+ name: "cached-property-annotation-processor-test",
+ srcs: ["test/java/**/*.java"],
+ java_resources: [":CachedPropertyAnnotationJavaTestSource"],
+ static_libs: [
+ "compile-testing-prebuilt",
+ "truth",
+ "junit",
+ "guava",
+ "libcached-property-annotation-processor",
+ ],
+ test_suites: ["general-tests"],
+}
+
+filegroup {
+ name: "CachedPropertyAnnotationJavaTestSource",
+ srcs: ["test/resources/*.java"],
+ path: "test/resources/",
+ visibility: ["//visibility:private"],
+}
diff --git a/tools/processors/property_cache/OWNERS b/tools/processors/property_cache/OWNERS
new file mode 100644
index 000000000000..78650168807e
--- /dev/null
+++ b/tools/processors/property_cache/OWNERS
@@ -0,0 +1,3 @@
+include /ACTIVITY_MANAGER_OWNERS
+include /BROADCASTS_OWNERS
+include /MULTIUSER_OWNERS \ No newline at end of file
diff --git a/tools/processors/property_cache/TEST_MAPPING b/tools/processors/property_cache/TEST_MAPPING
new file mode 100644
index 000000000000..7177abc8c52d
--- /dev/null
+++ b/tools/processors/property_cache/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "cached-property-annotation-processor-test"
+ }
+ ]
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java
new file mode 100644
index 000000000000..c665c840f376
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheConfig.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 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.property_cache;
+
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import com.google.common.base.CaseFormat;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+public class CacheConfig {
+ private final CacheModifiers mModifiers;
+ private final int mMaxSize;
+ private final String mModuleName;
+ private final String mApiName;
+ private final String mClassName;
+ private final String mQualifiedName;
+ private String mPropertyName;
+ private String mMethodName;
+ private int mNumberOfParams = 0;
+ private String mInputType = Constants.JAVA_LANG_VOID;
+ private String mResultType;
+
+ public CacheConfig(TypeElement classElement, ExecutableElement method) {
+ CachedPropertyDefaults classAnnotation = classElement.getAnnotation(
+ CachedPropertyDefaults.class);
+ CachedProperty methodAnnotation = method.getAnnotation(CachedProperty.class);
+
+ mModuleName = methodAnnotation.module().isEmpty() ? classAnnotation.module()
+ : methodAnnotation.module();
+ mClassName = classElement.getSimpleName().toString();
+ mQualifiedName = classElement.getQualifiedName().toString();
+ mModifiers = new CacheModifiers(methodAnnotation.modsFlagOnOrNone());
+ mMethodName = method.getSimpleName().toString();
+ mPropertyName = getPropertyName(mMethodName);
+ mApiName = methodAnnotation.api().isEmpty() ? getUniqueApiName(mClassName, mPropertyName)
+ : methodAnnotation.api();
+ mMaxSize = methodAnnotation.max() == -1 ? classAnnotation.max() : methodAnnotation.max();
+ mNumberOfParams = method.getParameters().size();
+ if (mNumberOfParams > 0) {
+ mInputType = primitiveTypeToObjectEquivalent(
+ method.getParameters().get(0).asType().toString());
+ }
+ mResultType = primitiveTypeToObjectEquivalent(method.getReturnType().toString());
+ }
+
+ public CacheModifiers getModifiers() {
+ return mModifiers;
+ }
+
+ public int getMaxSize() {
+ return mMaxSize;
+ }
+
+ public String getApiName() {
+ return mApiName;
+ }
+
+ public String getClassName() {
+ return mClassName;
+ }
+
+ public String getQualifiedName() {
+ return mQualifiedName;
+ }
+
+ public String getModuleName() {
+ return mModuleName;
+ }
+
+ public String getMethodName() {
+ return mMethodName;
+ }
+
+ public String getPropertyName() {
+ return mPropertyName;
+ }
+
+ public String getPropertyVariable() {
+ return (mModifiers.isStatic() ? "s" : "m") + mPropertyName;
+ }
+
+ private String getPropertyName(String methodName) {
+ if (methodName.startsWith("get")) {
+ return methodName.substring(3);
+ } else if (methodName.startsWith("is")) {
+ return methodName.substring(2);
+ } else {
+ return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, methodName);
+ }
+ }
+
+ public int getNumberOfParams() {
+ return mNumberOfParams;
+ }
+
+ public String getInputType() {
+ return mInputType;
+ }
+
+ public String getResultType() {
+ return mResultType;
+ }
+
+ /**
+ * This method returns the unique api name for a given class and property name.
+ * Property name is retrieved from the method name.
+ * Both names are combined and converted to lower snake case.
+ *
+ * @param className The name of the class that contains the property.
+ * @param propertyName The name of the property.
+ * @return The registration name for the property.
+ */
+ private String getUniqueApiName(String className, String propertyName) {
+ return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, className + propertyName);
+ }
+
+ private String primitiveTypeToObjectEquivalent(String simpleType) {
+ // checking against primitive types
+ return Constants.PRIMITIVE_TYPE_MAP.getOrDefault(simpleType, simpleType);
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java
new file mode 100644
index 000000000000..fda9b2c40e27
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CacheModifiers.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.property_cache;
+
+import com.android.internal.annotations.CacheModifier;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CacheModifiers {
+ private final boolean mIsStatic;
+ private static final String STATIC_MODIFIER_STRING = "static ";
+
+ CacheModifiers(CacheModifier[] modifierArray) {
+ final List<CacheModifier> modifiers = Arrays.asList(modifierArray);
+ mIsStatic = modifiers.contains(CacheModifier.STATIC);
+ }
+
+ public boolean isStatic() {
+ return mIsStatic;
+ }
+
+ public String getStaticModifier() {
+ return mIsStatic ? STATIC_MODIFIER_STRING : Constants.EMPTY_STRING;
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
new file mode 100644
index 000000000000..c438163948fc
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 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.property_cache;
+
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.JavaFileObject;
+
+public class CachedPropertyProcessor extends AbstractProcessor {
+
+ IpcDataCacheComposer mIpcDataCacheComposer =
+ new IpcDataCacheComposer();
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
+ public Set<String> getSupportedAnnotationTypes() {
+ return new HashSet<String>(
+ ImmutableSet.of(CachedPropertyDefaults.class.getCanonicalName()));
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ for (Element element : roundEnv.getElementsAnnotatedWith(CachedPropertyDefaults.class)) {
+ try {
+ generateCachedClass((TypeElement) element, processingEnv.getFiler());
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private void generateCachedClass(TypeElement classElement, Filer filer) throws IOException {
+ String packageName =
+ processingEnv
+ .getElementUtils()
+ .getPackageOf(classElement)
+ .getQualifiedName()
+ .toString();
+ String className = classElement.getSimpleName().toString() + "Cache";
+ JavaFileObject jfo = filer.createSourceFile(packageName + "." + className);
+ Writer writer = jfo.openWriter();
+ writer.write("package " + packageName + ";\n\n");
+ writer.write("import android.os.IpcDataCache;\n");
+ writer.write("\n /** \n * This class is auto-generated \n * @hide \n **/");
+ writer.write("\npublic class " + className + " {\n");
+
+ List<ExecutableElement> methods =
+ ElementFilter.methodsIn(classElement.getEnclosedElements());
+ String initCache = String.format(Constants.METHOD_COMMENT,
+ " - initialise all caches for class " + className)
+ + "\npublic static void initCache() {";
+ for (ExecutableElement method : methods) {
+ if (method.getAnnotation(CachedProperty.class) != null) {
+ mIpcDataCacheComposer.generatePropertyCache(writer, classElement, method);
+ initCache += "\n " + mIpcDataCacheComposer.generateInvalidatePropertyCall();
+ }
+ }
+ initCache += "\n}";
+ writer.write(initCache);
+ writer.write("\n}");
+ writer.write("\n");
+ writer.close();
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java b/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java
new file mode 100644
index 000000000000..03961bcaaba0
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/Constants.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.property_cache;
+
+import com.google.common.collect.ImmutableMap;
+
+public final class Constants {
+ public static final String EMPTY_STRING = "";
+ public static final String JAVA_LANG_VOID = "java.lang.Void";
+ public static final ImmutableMap<String, String> PRIMITIVE_TYPE_MAP =
+ ImmutableMap.of(
+ "int", "java.lang.Integer",
+ "boolean", "java.lang.Boolean",
+ "long", "java.lang.Long",
+ "float", "java.lang.Float",
+ "double", "java.lang.Double",
+ "byte", "java.lang.Byte",
+ "short", "java.lang.Short",
+ "char", "java.lang.Character");
+
+ public static final String METHOD_COMMENT = "\n /**"
+ + "\n * This method is auto-generated%s"
+ + "\n * "
+ + "\n * @hide"
+ + "\n */";
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java b/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java
new file mode 100644
index 000000000000..8526a04e9910
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/IpcDataCacheComposer.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 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.property_cache;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+public class IpcDataCacheComposer {
+
+ private static final String PROPERTY_DEFINITION_LINE = "private %s%s %s;\n";
+ private static final String METHOD_NAME_LINE = "\npublic %s%s %s(%s%s%s\n) {\n";
+ private static final String RETURN_IF_NOT_NULL_LINE =
+ "if (%s != null) {\n return %s.%s;\n }";
+
+ private CacheConfig mCacheConfig;
+
+ /**
+ * Generates code for property cache.
+ *
+ * @param writer writer to write code to.
+ * @param classElement class element to generate code for.
+ * @param method method element to generate code for.
+ * @throws IOException if writer throws IOException.
+ */
+ public void generatePropertyCache(Writer writer, TypeElement classElement,
+ ExecutableElement method) throws IOException {
+
+ mCacheConfig = new CacheConfig(classElement, method);
+
+ ParamComposer inputParam = new ParamComposer(null, null);
+ ParamComposer binderParam = new ParamComposer(
+ String.format("IpcDataCache.RemoteCall<%s, %s>", mCacheConfig.getInputType(),
+ mCacheConfig.getResultType()), "binderCall");
+
+ ParamComposer bypassParam = new ParamComposer(null, null); // empty if method have no params
+ String queryCall = "query(null)";
+ if (mCacheConfig.getNumberOfParams() > 0) {
+ bypassParam = new ParamComposer(
+ String.format("IpcDataCache.BypassCall<%s> ", mCacheConfig.getInputType()),
+ "bypassPredicate");
+ inputParam = new ParamComposer(mCacheConfig.getInputType(), "query");
+ queryCall = "query(query)";
+ }
+ String propertyClass =
+ "IpcDataCache<" + mCacheConfig.getInputType() + ", " + mCacheConfig.getResultType()
+ + ">";
+ String invalidateName = "invalidate" + mCacheConfig.getPropertyName();
+ String lockObject = mCacheConfig.getPropertyVariable() + "Lock";
+ writer.write("private " + mCacheConfig.getModifiers().getStaticModifier() + "final Object "
+ + lockObject + " = new Object();\n");
+ writer.write(String.format(PROPERTY_DEFINITION_LINE,
+ mCacheConfig.getModifiers().getStaticModifier(), propertyClass,
+ mCacheConfig.getPropertyVariable()));
+
+ writer.write(propertyInvalidatedCacheMethod(binderParam, bypassParam, inputParam, queryCall,
+ lockObject));
+
+ // If binder param is not empty then generate getter without binder param to be called
+ if (!bypassParam.getParam().isEmpty()) {
+ writer.write(propertyInvalidatedCacheMethod(binderParam, new ParamComposer(null, null),
+ inputParam, queryCall, lockObject));
+ }
+ writer.write(String.format(Constants.METHOD_COMMENT,
+ "- invalidate cache for {@link " + mCacheConfig.getQualifiedName() + "#"
+ + mCacheConfig.getMethodName() + "}"));
+ writer.write("\n public static final void " + invalidateName + "() {");
+ writer.write(
+ "\n IpcDataCache.invalidateCache(\"" + mCacheConfig.getModuleName() + "\", \""
+ + mCacheConfig.getApiName() + "\");");
+ writer.write("\n }");
+ writer.write("\n");
+ writer.write("\n");
+ }
+
+ /**
+ * Generates code to call cache invalidation.
+ *
+ * @return code string calling cache invalidation.
+ */
+ public String generateInvalidatePropertyCall() {
+ String invalidateName = "invalidate" + mCacheConfig.getPropertyName();
+ return mCacheConfig.getClassName() + "Cache." + invalidateName + "();";
+ }
+
+ /**
+ * Generates code for getter that returns cached value or calls binder and caches result.
+ *
+ * @param binderParam parameter for binder call.
+ * @param bypassParam parameter for bypass predicate.
+ * @param inputParam parameter for input value.
+ * @param queryCall cache query call syntax.
+ * @param lockObject object to synchronize on.
+ * @return String with code for method.
+ */
+ private String propertyInvalidatedCacheMethod(ParamComposer binderParam,
+ ParamComposer bypassParam, ParamComposer inputParam, String queryCall,
+ String lockObject) {
+ String result = "\n";
+ CacheModifiers modifiers = mCacheConfig.getModifiers();
+ String paramsComments = binderParam.getParamComment(
+ "lambda for remote call" + " {@link " + mCacheConfig.getQualifiedName() + "#"
+ + mCacheConfig.getMethodName() + " }") + bypassParam.getParamComment(
+ "lambda to bypass remote call") + inputParam.getParamComment(
+ "parameter to call remote lambda");
+ result += String.format(Constants.METHOD_COMMENT, paramsComments);
+ result += String.format(METHOD_NAME_LINE, modifiers.getStaticModifier(),
+ mCacheConfig.getResultType(), mCacheConfig.getMethodName(),
+ binderParam.getParam(), bypassParam.getNextParam(),
+ inputParam.getNextParam());
+ result += String.format(RETURN_IF_NOT_NULL_LINE, mCacheConfig.getPropertyVariable(),
+ mCacheConfig.getPropertyVariable(), queryCall);
+ result += "\n synchronized (" + lockObject + " ) {";
+ result += "\n if (" + mCacheConfig.getPropertyVariable() + " == null) {";
+ result += "\n " + mCacheConfig.getPropertyVariable() + " = new IpcDataCache" + "("
+ + generateCreateIpcConfig() + ", " + binderParam.getName()
+ + bypassParam.getNextName() + ");\n";
+ result += "\n }";
+ result += "\n }";
+ result += "\n return " + mCacheConfig.getPropertyVariable() + "." + queryCall + ";";
+ result += "\n }";
+ result += "\n";
+ return result;
+ }
+
+ /**
+ * Generates code for new IpcDataCache.Config object for given configuration.
+ *
+ * @return String with code for new IpcDataCache.Config object.
+ */
+ public String generateCreateIpcConfig() {
+ return "new IpcDataCache.Config(" + mCacheConfig.getMaxSize() + ", " + "\""
+ + mCacheConfig.getModuleName() + "\"" + ", " + "\"" + mCacheConfig.getApiName()
+ + "\"" + ", " + "\"" + mCacheConfig.getPropertyName() + "\"" + ")";
+ }
+}
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java b/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java
new file mode 100644
index 000000000000..307443aea730
--- /dev/null
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/ParamComposer.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 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.property_cache;
+
+public class ParamComposer {
+ private String mType;
+ private String mName;
+
+ /** Creates ParamComposer with given type and name.
+ *
+ * @param type type of parameter.
+ * @param name name of parameter.
+ */
+ public ParamComposer(String type, String name) {
+ mType = type;
+ mName = name;
+ }
+
+ /** Returns name of parameter.
+ *
+ * @return name of parameter.
+ */
+ public String getName() {
+ if (mName != null) {
+ return mName;
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /** Returns name of parameter for next parameter followed by comma.
+ *
+ * @return name of parameter for next parameter if exists, empty string otherwise.
+ */
+ public String getNextName() {
+ if (!getName().isEmpty()) {
+ return ", " + getName();
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns type of parameter.
+ *
+ * @return type of parameter.
+ */
+ public String getType() {
+ if (mType != null) {
+ return mType;
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns type and name of parameter.
+ *
+ * @return type and name of parameter if exists, empty string otherwise.
+ */
+ public String getParam() {
+ if (!getType().isEmpty() && !getName().isEmpty()) {
+ return getType() + " " + getName();
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns type and name of parameter for next parameter followed by comma.
+ *
+ * @return type and name of parameter for next parameter if exists, empty string otherwise.
+ */
+ public String getNextParam() {
+ if (!getType().isEmpty() && !getName().isEmpty()) {
+ return ", " + getParam();
+ }
+ return Constants.EMPTY_STRING;
+ }
+
+ /**
+ * Returns comment for parameter.
+ *
+ * @param description of parameter.
+ * @return comment for parameter if exists, empty string otherwise.
+ */
+ public String getParamComment(String description) {
+ if (!getType().isEmpty() && !getName().isEmpty()) {
+ return "\n * @param " + getName() + " - " + description;
+ }
+ return Constants.EMPTY_STRING;
+ }
+}
diff --git a/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java b/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java
new file mode 100644
index 000000000000..1e23c78d4816
--- /dev/null
+++ b/tools/processors/property_cache/test/java/android/processor/property_cache/CachedPropertyProcessorTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 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.property_cache.test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+
+import android.processor.property_cache.CachedPropertyProcessor;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+
+/** Tests the {@link CachedPropertyProcessor}. */
+@RunWith(JUnit4.class)
+public class CachedPropertyProcessorTest {
+ private final Compiler mCompiler =
+ Compiler.javac().withProcessors(new CachedPropertyProcessor());
+
+ @Test
+ public void testDefaultValues() {
+ JavaFileObject expectedJava = JavaFileObjects.forResource("DefaultCache.java");
+
+ Compilation compilation = mCompiler.compile(JavaFileObjects.forResource("Default.java"));
+ assertThat(compilation).succeeded();
+ assertThat(compilation)
+ .generatedFile(StandardLocation.SOURCE_OUTPUT,
+ "android/processor/property_cache/test/DefaultCache.java")
+ .hasSourceEquivalentTo(expectedJava);
+ }
+
+ @Test
+ public void testCustomValues() {
+ JavaFileObject expectedJava = JavaFileObjects.forResource("CustomCache.java");
+
+ Compilation compilation = mCompiler.compile(JavaFileObjects.forResource("Custom.java"));
+ assertThat(compilation).succeeded();
+ assertThat(compilation)
+ .generatedFile(StandardLocation.SOURCE_OUTPUT,
+ "android/processor/property_cache/test/CustomCache.java")
+ .hasSourceEquivalentTo(expectedJava);
+ }
+}
diff --git a/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java b/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java
new file mode 100644
index 000000000000..e5ef48c14436
--- /dev/null
+++ b/tools/processors/property_cache/test/java/android/processor/property_cache/shadows/IpcDataCache.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 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.os;
+
+// Mocked class for generation compilation tests purposes only.
+public class IpcDataCache<Input, Output> {
+ public static class Config {
+ public Config(int max, String module, String api, String name) {
+ }
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - shadow parameter from IpcDataCache in Frameworks.
+ * @return null
+ */
+ public Output query(Input query) {
+ return null;
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param key - shadow parameter from IpcDataCache in Frameworks;
+ */
+ public static void invalidateCache(String key) {
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - shadow parameter from IpcDataCache in Frameworks;
+ * @return null
+ */
+ public Output recompute(Input query) {
+ return null;
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache in android framework.
+ * @param query - shadow parameter from IpcDataCache in Frameworks;
+ * @return false
+ */
+ public boolean bypass(Input query) {
+ return false;
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param module - parameter equivalent to IpcDataCache in android framework.
+ * @param key - parameter equivalent to IpcDataCache in android framework.
+ * @return module + key sttring
+ */
+ public static String createPropertyName(String module, String key) {
+ return module + key;
+ }
+
+ public abstract static class QueryHandler<Input, Output> {
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.QueryHandler in android framework.
+ * @return expected value
+ */
+ public abstract Output apply(Input query);
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.QueryHandler in android framework.
+ */
+ public boolean shouldBypassCache(Input query) {
+ return false;
+ }
+ }
+
+ public interface RemoteCall<Input, Output> {
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.RemoteCall in android framework.
+ */
+ Output apply(Input query);
+ }
+
+ public interface BypassCall<Input> {
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param query - parameter equivalent to IpcDataCache.BypassCall in android framework.
+ */
+ boolean apply(Input query);
+ }
+
+ public IpcDataCache(
+ int maxEntries,
+ String module,
+ String api,
+ String cacheName,
+ QueryHandler<Input, Output> computer) {
+ }
+
+ public IpcDataCache(Config config, QueryHandler<Input, Output> computer) {
+ }
+
+ public IpcDataCache(Config config, RemoteCall<Input, Output> computer) {
+ }
+
+ public IpcDataCache(Config config, RemoteCall<Input, Output> computer,
+ BypassCall<Input> bypassCall) {
+ }
+
+ /** Shadow method for generated code compilation tests purposes only.*/
+ public void invalidateCache() {
+ }
+
+
+ /** Shadow method for generated code compilation tests purposes only.
+ *
+ * @param module - shadow parameter from IpcDataCache in Frameworks.
+ * @param api - shadow parameter from IpcDataCache in Frameworks.
+ */
+ public static void invalidateCache(String module, String api) {
+ }
+
+}
diff --git a/tools/processors/property_cache/test/resources/Custom.java b/tools/processors/property_cache/test/resources/Custom.java
new file mode 100644
index 000000000000..05024da67e6c
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/Custom.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 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.property_cache.test;
+
+import com.android.internal.annotations.CacheModifier;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import java.util.Date;
+
+@CachedPropertyDefaults(max = 4, module = "bluetooth")
+public class Custom {
+ BirthdayManagerService mService = new BirthdayManagerService();
+ Object mCache = new CustomCache();
+
+ public Custom() {
+ CustomCache.initCache();
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return birthday date of given user Id
+ */
+ @CachedProperty()
+ public Date getBirthday(int userId) {
+ return CustomCache.getBirthday(mService::getBirthday, userId);
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days till birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC})
+ public int getDaysTillBirthday(int userId) {
+ return CustomCache.getDaysTillBirthday(mService::getDaysTillBirthday, userId);
+ }
+
+ /**
+ * Testing custom class values to generate non-static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days since birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {})
+ public int getDaysSinceBirthday(int userId) {
+ return ((CustomCache) mCache).getDaysSinceBirthday(mService::getDaysSinceBirthday, userId);
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache with max capasity of 1
+ *
+ * @return number of days till birthay of current user
+ */
+ @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC}, max = 1)
+ public int getDaysTillMyBirthday() {
+ return CustomCache.getDaysTillMyBirthday((Void) -> mService.getDaysTillMyBirthday());
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache with max capasity of 1 and custom
+ * api
+ *
+ * @return number of days since birthay of current user
+ */
+ @CachedProperty(modsFlagOnOrNone = {}, max = 1, api = "my_unique_key")
+ public int getDaysSinceMyBirthday() {
+ return ((CustomCache) mCache).getDaysSinceMyBirthday(
+ (Void) -> mService.getDaysSinceMyBirthday());
+ }
+
+ /**
+ * Testing custom class values to generate static IpcDataCache with custom module name
+ *
+ * @return birthday wishes of given user Id
+ */
+ @CachedProperty(module = "telephony")
+ public String getBirthdayWishesFromUser(int userId) {
+ return CustomCache.getBirthdayWishesFromUser(mService::getBirthdayWishesFromUser,
+ userId);
+ }
+
+ class BirthdayManagerService {
+ int mDaysTillBirthday = 182;
+
+ public Date getBirthday(int userId) {
+ return new Date(2024, 6, 1 + userId);
+ }
+
+ public int getDaysTillBirthday(int userId) {
+ return mDaysTillBirthday + userId;
+ }
+
+ public int getDaysSinceBirthday(int userId) {
+ return 365 - getDaysTillBirthday(userId);
+ }
+
+ public int getDaysTillMyBirthday() {
+ return 0;
+ }
+
+ public int getDaysSinceMyBirthday() {
+ return 365;
+ }
+
+ public String getBirthdayWishesFromUser(int userId) {
+ return "Happy Birthday!\n- " + userId;
+ }
+ }
+}
diff --git a/tools/processors/property_cache/test/resources/CustomCache.java b/tools/processors/property_cache/test/resources/CustomCache.java
new file mode 100644
index 000000000000..326467fb5639
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/CustomCache.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2024 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.property_cache.test;
+
+import android.os.IpcDataCache;
+
+/**
+ * This class is auto-generated
+ *
+ * @hide
+ **/
+public class CustomCache {
+ private static final Object sBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.util.Date> sBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_birthday", "Birthday"),
+ binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getBirthday }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ java.lang.Integer query) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_birthday", "Birthday"),
+ binderCall);
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_birthday");
+ }
+
+ private static final Object sDaysTillBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.Integer> sDaysTillBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getDaysTillBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_till_birthday",
+ "DaysTillBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysTillBirthday }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_till_birthday",
+ "DaysTillBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysTillBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_days_till_birthday");
+ }
+
+ private final Object mDaysSinceBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Integer, java.lang.Integer> mDaysSinceBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getDaysSinceBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_since_birthday",
+ "DaysSinceBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceBirthday
+ * }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(4, "bluetooth", "custom_days_since_birthday",
+ "DaysSinceBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_days_since_birthday");
+ }
+
+ private static final Object sDaysTillMyBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Void, java.lang.Integer> sDaysTillMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysTillMyBirthday
+ * }
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall) {
+ if (sDaysTillMyBirthday != null) {
+ return sDaysTillMyBirthday.query(null);
+ }
+ synchronized (sDaysTillMyBirthdayLock) {
+ if (sDaysTillMyBirthday == null) {
+ sDaysTillMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "bluetooth", "custom_days_till_my_birthday",
+ "DaysTillMyBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysTillMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillMyBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "custom_days_till_my_birthday");
+ }
+
+ private final Object mDaysSinceMyBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Void, java.lang.Integer> mDaysSinceMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceMyBirthday
+ * }
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall) {
+ if (mDaysSinceMyBirthday != null) {
+ return mDaysSinceMyBirthday.query(null);
+ }
+ synchronized (mDaysSinceMyBirthdayLock) {
+ if (mDaysSinceMyBirthday == null) {
+ mDaysSinceMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "bluetooth", "my_unique_key",
+ "DaysSinceMyBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getDaysSinceMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceMyBirthday() {
+ IpcDataCache.invalidateCache("bluetooth", "my_unique_key");
+ }
+
+ private static final Object sBirthdayWishesFromUserLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.String> sBirthdayWishesFromUser;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getBirthdayWishesFromUser
+ * }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(4, "telephony", "custom_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Custom#getBirthdayWishesFromUser }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ java.lang.Integer query) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(4, "telephony", "custom_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Custom#getBirthdayWishesFromUser}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthdayWishesFromUser() {
+ IpcDataCache.invalidateCache("telephony", "custom_birthday_wishes_from_user");
+ }
+
+
+ /**
+ * This method is auto-generated - initialise all caches for class CustomCache
+ *
+ * @hide
+ */
+ public static void initCache() {
+ CustomCache.invalidateBirthday();
+ CustomCache.invalidateDaysTillBirthday();
+ CustomCache.invalidateDaysSinceBirthday();
+ CustomCache.invalidateDaysTillMyBirthday();
+ CustomCache.invalidateDaysSinceMyBirthday();
+ CustomCache.invalidateBirthdayWishesFromUser();
+ }
+}
diff --git a/tools/processors/property_cache/test/resources/Default.java b/tools/processors/property_cache/test/resources/Default.java
new file mode 100644
index 000000000000..d2449aad656c
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/Default.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 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.property_cache.test;
+
+import com.android.internal.annotations.CacheModifier;
+import com.android.internal.annotations.CachedProperty;
+import com.android.internal.annotations.CachedPropertyDefaults;
+
+import java.util.Date;
+
+@CachedPropertyDefaults()
+public class Default {
+ BirthdayManagerService mService = new BirthdayManagerService();
+ Object mCache = new DefaultCache();
+
+ /** Testing default class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return birthday date of given user Id
+ */
+ @CachedProperty()
+ public Date getBirthday(int userId) {
+ return DefaultCache.getBirthday(mService::getBirthday, userId);
+ }
+
+ /** Testing default class values to generate static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days till birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {CacheModifier.STATIC})
+ public int getDaysTillBirthday(int userId) {
+ return DefaultCache.getDaysTillBirthday(mService::getDaysTillBirthday, userId);
+ }
+
+ /** Testing generate non-static IpcDataCache
+ *
+ * @param userId - user Id
+ * @return number of days since birthday of given user Id
+ */
+ @CachedProperty(modsFlagOnOrNone = {})
+ public int getDaysSinceBirthday(int userId) {
+ return ((DefaultCache) mCache).getDaysSinceBirthday(mService::getDaysSinceBirthday, userId);
+ }
+
+ /** Testing default class values to generate static IpcDataCache with max capacity of 1
+ *
+ * @return number of days till birthay of current user
+ */
+ @CachedProperty(
+ modsFlagOnOrNone = {CacheModifier.STATIC},
+ max = 1)
+ public int getDaysTillMyBirthday() {
+ return DefaultCache.getDaysTillMyBirthday((Void) -> mService.getDaysTillMyBirthday());
+ }
+
+ /** Testing default class values to generate static IpcDataCache with max capacity of 1 and
+ custom api
+ *
+ * @return number of days since birthay of current user
+ */
+ @CachedProperty(
+ modsFlagOnOrNone = {},
+ max = 1,
+ api = "my_unique_key")
+ public int getDaysSinceMyBirthday() {
+ return ((DefaultCache) mCache).getDaysSinceMyBirthday(
+ (Void) -> mService.getDaysSinceMyBirthday());
+ }
+
+ /** Testing default class values to generate static IpcDataCache with custom module name
+ *
+ * @return birthday wishes of given user Id
+ */
+ @CachedProperty(module = "telephony")
+ public String getBirthdayWishesFromUser(int userId) {
+ return DefaultCache.getBirthdayWishesFromUser(mService::getBirthdayWishesFromUser,
+ userId);
+ }
+
+ class BirthdayManagerService {
+
+ BirthdayManagerService() {
+ DefaultCache.initCache();
+ }
+
+ public Date getBirthday(int userId) {
+ return new Date();
+ }
+
+ public int getDaysTillBirthday(int userId) {
+ return 0;
+ }
+
+ public int getDaysSinceBirthday(int userId) {
+ return 0;
+ }
+
+ public int getDaysTillMyBirthday() {
+ return 0;
+ }
+
+ public int getDaysSinceMyBirthday() {
+ return 0;
+ }
+
+ public String getBirthdayWishesFromUser(int userId) {
+ return "Happy Birthday!\n- " + userId;
+ }
+ }
+}
diff --git a/tools/processors/property_cache/test/resources/DefaultCache.java b/tools/processors/property_cache/test/resources/DefaultCache.java
new file mode 100644
index 000000000000..9531118752bb
--- /dev/null
+++ b/tools/processors/property_cache/test/resources/DefaultCache.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2024 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.property_cache.test;
+
+import android.os.IpcDataCache;
+
+/**
+ * This class is auto-generated
+ *
+ * @hide
+ **/
+public class DefaultCache {
+ private static final Object sBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.util.Date> sBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server",
+ "default_birthday", "Birthday"),
+ binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getBirthday }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.util.Date getBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.util.Date> binderCall,
+ java.lang.Integer query
+ ) {
+ if (sBirthday != null) {
+ return sBirthday.query(query);
+ }
+ synchronized (sBirthdayLock) {
+ if (sBirthday == null) {
+ sBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server",
+ "default_birthday", "Birthday"),
+ binderCall);
+
+ }
+ }
+ return sBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_birthday");
+ }
+
+ private static final Object sDaysTillBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.Integer> sDaysTillBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getDaysTillBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_till_birthday",
+ "DaysTillBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getDaysTillBirthday
+ * }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query
+ ) {
+ if (sDaysTillBirthday != null) {
+ return sDaysTillBirthday.query(query);
+ }
+ synchronized (sDaysTillBirthdayLock) {
+ if (sDaysTillBirthday == null) {
+ sDaysTillBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_till_birthday",
+ "DaysTillBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysTillBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_days_till_birthday");
+ }
+
+ private final Object mDaysSinceBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Integer, java.lang.Integer> mDaysSinceBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getDaysSinceBirthday }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_since_birthday",
+ "DaysSinceBirthday"), binderCall, bypassPredicate);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getDaysSinceBirthday
+ * }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceBirthday(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.Integer> binderCall,
+ java.lang.Integer query
+ ) {
+ if (mDaysSinceBirthday != null) {
+ return mDaysSinceBirthday.query(query);
+ }
+ synchronized (mDaysSinceBirthdayLock) {
+ if (mDaysSinceBirthday == null) {
+ mDaysSinceBirthday = new IpcDataCache(
+ new IpcDataCache.Config(32, "system_server", "default_days_since_birthday",
+ "DaysSinceBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceBirthday.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysSinceBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_days_since_birthday");
+ }
+
+ private static final Object sDaysTillMyBirthdayLock = new Object();
+ private static IpcDataCache<java.lang.Void, java.lang.Integer> sDaysTillMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link android.processor.property_cache.test.Default#getDaysTillMyBirthday
+ * }
+ * @hide
+ */
+ public static java.lang.Integer getDaysTillMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall
+ ) {
+ if (sDaysTillMyBirthday != null) {
+ return sDaysTillMyBirthday.query(null);
+ }
+ synchronized (sDaysTillMyBirthdayLock) {
+ if (sDaysTillMyBirthday == null) {
+ sDaysTillMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "system_server", "default_days_till_my_birthday",
+ "DaysTillMyBirthday"), binderCall);
+
+ }
+ }
+ return sDaysTillMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysTillMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysTillMyBirthday() {
+ IpcDataCache.invalidateCache("system_server", "default_days_till_my_birthday");
+ }
+
+ private final Object mDaysSinceMyBirthdayLock = new Object();
+ private IpcDataCache<java.lang.Void, java.lang.Integer> mDaysSinceMyBirthday;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getDaysSinceMyBirthday }
+ * @hide
+ */
+ public java.lang.Integer getDaysSinceMyBirthday(
+ IpcDataCache.RemoteCall<java.lang.Void, java.lang.Integer> binderCall
+ ) {
+ if (mDaysSinceMyBirthday != null) {
+ return mDaysSinceMyBirthday.query(null);
+ }
+ synchronized (mDaysSinceMyBirthdayLock) {
+ if (mDaysSinceMyBirthday == null) {
+ mDaysSinceMyBirthday = new IpcDataCache(
+ new IpcDataCache.Config(1, "system_server", "my_unique_key",
+ "DaysSinceMyBirthday"), binderCall);
+
+ }
+ }
+ return mDaysSinceMyBirthday.query(null);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getDaysSinceMyBirthday}
+ *
+ * @hide
+ */
+ public static final void invalidateDaysSinceMyBirthday() {
+ IpcDataCache.invalidateCache("system_server", "my_unique_key");
+ }
+
+ private static final Object sBirthdayWishesFromUserLock = new Object();
+ private static IpcDataCache<java.lang.Integer, java.lang.String> sBirthdayWishesFromUser;
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ *
+ * android.processor.property_cache.test.Default#getBirthdayWishesFromUser
+ * }
+ * @param bypassPredicate - lambda to bypass remote call
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ IpcDataCache.BypassCall<java.lang.Integer> bypassPredicate, java.lang.Integer query
+ ) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(32, "telephony",
+ "default_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall, bypassPredicate);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+
+ /**
+ * This method is auto-generated
+ *
+ * @param binderCall - lambda for remote call
+ * {@link
+ * android.processor.property_cache.test.Default#getBirthdayWishesFromUser }
+ * @param query - parameter to call remote lambda
+ * @hide
+ */
+ public static java.lang.String getBirthdayWishesFromUser(
+ IpcDataCache.RemoteCall<java.lang.Integer, java.lang.String> binderCall,
+ java.lang.Integer query
+ ) {
+ if (sBirthdayWishesFromUser != null) {
+ return sBirthdayWishesFromUser.query(query);
+ }
+ synchronized (sBirthdayWishesFromUserLock) {
+ if (sBirthdayWishesFromUser == null) {
+ sBirthdayWishesFromUser = new IpcDataCache(
+ new IpcDataCache.Config(32, "telephony",
+ "default_birthday_wishes_from_user",
+ "BirthdayWishesFromUser"), binderCall);
+
+ }
+ }
+ return sBirthdayWishesFromUser.query(query);
+ }
+
+ /**
+ * This method is auto-generated- invalidate cache for
+ * {@link android.processor.property_cache.test.Default#getBirthdayWishesFromUser}
+ *
+ * @hide
+ */
+ public static final void invalidateBirthdayWishesFromUser() {
+ IpcDataCache.invalidateCache("telephony", "default_birthday_wishes_from_user");
+ }
+
+
+ /**
+ * This method is auto-generated - initialise all caches for class DefaultCache
+ *
+ * @hide
+ */
+ public static void initCache() {
+ DefaultCache.invalidateBirthday();
+ DefaultCache.invalidateDaysTillBirthday();
+ DefaultCache.invalidateDaysSinceBirthday();
+ DefaultCache.invalidateDaysTillMyBirthday();
+ DefaultCache.invalidateDaysSinceMyBirthday();
+ DefaultCache.invalidateBirthdayWishesFromUser();
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
index c3595b7ac288..272d8bb1793d 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
@@ -119,7 +119,8 @@ class ProtoLogCallProcessorImpl(
}
logCallVisitor?.processCall(call, messageString, getLevelForMethodName(
- call.name.toString(), call, context), groupMap.getValue(groupName))
+ call.name.toString(), call, context), groupMap.getValue(groupName),
+ context.lineNumber)
} else if (call.name.id == "init") {
// No processing
} else {
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt
index 8cd927a7cd0e..216241ac5a47 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt
@@ -20,5 +20,11 @@ import com.android.internal.protolog.common.LogLevel
import com.github.javaparser.ast.expr.MethodCallExpr
interface ProtoLogCallVisitor {
- fun processCall(call: MethodCallExpr, messageString: String, level: LogLevel, group: LogGroup)
+ fun processCall(
+ call: MethodCallExpr,
+ messageString: String,
+ level: LogLevel,
+ group: LogGroup,
+ lineNumber: Int
+ )
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 22858803b597..d8a2954545bb 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -16,6 +16,7 @@
package com.android.protolog.tool
+import com.android.internal.protolog.common.IProtoLog
import com.android.internal.protolog.common.LogLevel
import com.android.internal.protolog.common.ProtoLogToolInjected
import com.android.protolog.tool.CommandOptions.Companion.USAGE
@@ -68,7 +69,8 @@ object ProtoLogTool {
val messageString: String,
val logLevel: LogLevel,
val logGroup: LogGroup,
- val position: String
+ val position: String,
+ val lineNumber: Int,
)
private fun showHelpAndExit() {
@@ -319,6 +321,7 @@ object ProtoLogTool {
MethodCallExpr()
.setName("isEnabled")
.setArguments(NodeList(
+ NameExpr("protoLogInstance"),
FieldAccessExpr()
.setScope(NameExpr(protoLogGroupsClassName))
.setName(group.value.name),
@@ -332,6 +335,7 @@ object ProtoLogTool {
}
cacheClass.addMethod("update").setPrivate(true).setStatic(true)
+ .addParameter(IProtoLog::class.java, "protoLogInstance")
.setBody(updateBlockStmt)
classDeclaration.addMember(cacheClass)
@@ -368,7 +372,7 @@ object ProtoLogTool {
}
interface ProtologViewerConfigBuilder {
- fun build(statements: Map<LogCall, Long>): ByteArray
+ fun build(groups: Collection<LogGroup>, statements: Map<LogCall, Long>): ByteArray
}
private fun viewerConf(command: CommandOptions) {
@@ -416,7 +420,7 @@ object ProtoLogTool {
}
val outFile = injector.fileOutputStream(command.viewerConfigFileNameArg)
- outFile.write(configBuilder.build(logCallRegistry.getStatements()))
+ outFile.write(configBuilder.build(groups.values, logCallRegistry.getStatements()))
outFile.close()
}
@@ -432,9 +436,10 @@ object ProtoLogTool {
call: MethodCallExpr,
messageString: String,
level: LogLevel,
- group: LogGroup
+ group: LogGroup,
+ lineNumber: Int,
) {
- val logCall = LogCall(messageString, level, group, packagePath)
+ val logCall = LogCall(messageString, level, group, packagePath, lineNumber)
calls.add(logCall)
}
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index c478f5844de6..76df0674df65 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -91,7 +91,8 @@ class SourceTransformer(
call: MethodCallExpr,
messageString: String,
level: LogLevel,
- group: LogGroup
+ group: LogGroup,
+ lineNumber: Int,
) {
validateCall(call)
val processedCallStatement =
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
index 7714db212c9f..16a3d7cdda21 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
@@ -21,8 +21,7 @@ import com.android.protolog.tool.Constants.VERSION
import java.io.StringWriter
class ViewerConfigJsonBuilder : ProtoLogTool.ProtologViewerConfigBuilder {
- override fun build(statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
- val groups = statements.map { it.key.logGroup }.toSet()
+ override fun build(groups: Collection<LogGroup>, statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
val stringWriter = StringWriter()
val writer = JsonWriter(stringWriter)
writer.setIndent(" ")
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
index 245e802df49b..5af2d9440533 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
@@ -28,10 +28,14 @@ class ViewerConfigProtoBuilder : ProtoLogTool.ProtologViewerConfigBuilder {
* @return a byte array of a ProtoLogViewerConfig proto message encoding all the viewer
* configurations mapping protolog hashes to message information and log group information.
*/
- override fun build(statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
+ override fun build(groups: Collection<LogGroup>, statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
val configBuilder = ProtoLogViewerConfig.newBuilder()
- val groups = statements.map { it.key.logGroup }.toSet()
+ // TODO(b/373754057): We are passing all the groups now, because some groups might only be
+ // used by Kotlin code that is not processed, but for group that get enabled to log to
+ // logcat we try and load the viewer configurations for this group, so the group must exist
+ // in the viewer config. Once Kotlin is pre-processed or this logic changes we should only
+ // use the groups that are actually used as an optimization.
val groupIds = mutableMapOf<LogGroup, Int>()
groups.forEach {
groupIds.putIfAbsent(it, groupIds.size + 1)
@@ -55,7 +59,7 @@ class ViewerConfigProtoBuilder : ProtoLogTool.ProtologViewerConfigBuilder {
.setLevel(
ProtoLogLevel.forNumber(log.logLevel.id))
.setGroupId(groupId)
- .setLocation(log.position)
+ .setLocation("${log.position}:${log.lineNumber}")
)
}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt
index 5e50f71d75cc..004d97babbad 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt
@@ -42,7 +42,8 @@ class ProtoLogCallProcessorImplTest {
call: MethodCallExpr,
messageString: String,
level: LogLevel,
- group: LogGroup
+ group: LogGroup,
+ lineNumber: Int,
) {
calls.add(LogCall(call, messageString, level, group))
}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index 6cde7a72db53..674a907d68d9 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -158,7 +158,7 @@ class SourceTransformerTest {
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
- LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"), 123)
invocation.arguments[0] as CompilationUnit
}
@@ -195,11 +195,11 @@ class SourceTransformerTest {
val calls = code.findAll(MethodCallExpr::class.java)
visitor.processCall(calls[0], "test %d %f",
- LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"), 456)
visitor.processCall(calls[1], "test %d %f",
- LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"), 789)
visitor.processCall(calls[2], "test %d %f",
- LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"), 123)
invocation.arguments[0] as CompilationUnit
}
@@ -236,7 +236,7 @@ class SourceTransformerTest {
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
"test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
- true, true, "WM_TEST"))
+ true, true, "WM_TEST"), 123)
invocation.arguments[0] as CompilationUnit
}
@@ -273,7 +273,7 @@ class SourceTransformerTest {
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test",
- LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"), 456)
invocation.arguments[0] as CompilationUnit
}
@@ -307,7 +307,7 @@ class SourceTransformerTest {
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
- LogLevel.WARN, LogGroup("TEST_GROUP", true, false, "WM_TEST"))
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, false, "WM_TEST"), 789)
invocation.arguments[0] as CompilationUnit
}
@@ -344,7 +344,7 @@ class SourceTransformerTest {
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
"test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
- true, false, "WM_TEST"))
+ true, false, "WM_TEST"), 123)
invocation.arguments[0] as CompilationUnit
}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
index d27ae88fc488..bcbc8790e170 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
@@ -36,6 +36,8 @@ class ViewerConfigJsonBuilderTest {
private val GROUP_DISABLED = LogGroup("DEBUG_GROUP", false, true, TAG2)
private val GROUP_TEXT_DISABLED = LogGroup("DEBUG_GROUP", true, false, TAG2)
private const val PATH = "/tmp/test.java"
+
+ private val GROUPS = listOf(GROUP1, GROUP2, GROUP3)
}
private val configBuilder = ViewerConfigJsonBuilder()
@@ -48,12 +50,12 @@ class ViewerConfigJsonBuilderTest {
fun processClass() {
val logCallRegistry = ProtoLogTool.LogCallRegistry()
logCallRegistry.addLogCalls(listOf(
- LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
- LogCall(TEST2.messageString, LogLevel.DEBUG, GROUP2, PATH),
- LogCall(TEST3.messageString, LogLevel.ERROR, GROUP3, PATH)))
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH, 123),
+ LogCall(TEST2.messageString, LogLevel.DEBUG, GROUP2, PATH, 456),
+ LogCall(TEST3.messageString, LogLevel.ERROR, GROUP3, PATH, 789)))
val parsedConfig = parseConfig(
- configBuilder.build(logCallRegistry.getStatements()).toString(Charsets.UTF_8))
+ configBuilder.build(GROUPS, logCallRegistry.getStatements()).toString(Charsets.UTF_8))
assertEquals(3, parsedConfig.size)
assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH,
TEST1.messageString, LogLevel.INFO, GROUP1)])
@@ -67,12 +69,12 @@ class ViewerConfigJsonBuilderTest {
fun processClass_nonUnique() {
val logCallRegistry = ProtoLogTool.LogCallRegistry()
logCallRegistry.addLogCalls(listOf(
- LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
- LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
- LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH)))
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH, 123),
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH, 456),
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH, 789)))
val parsedConfig = parseConfig(
- configBuilder.build(logCallRegistry.getStatements()).toString(Charsets.UTF_8))
+ configBuilder.build(GROUPS, logCallRegistry.getStatements()).toString(Charsets.UTF_8))
assertEquals(1, parsedConfig.size)
assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH, TEST1.messageString,
LogLevel.INFO, GROUP1)])
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigProtoBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigProtoBuilderTest.kt
new file mode 100644
index 000000000000..dfc66405eec9
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigProtoBuilderTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 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.protolog.tool
+
+import com.android.internal.protolog.common.LogLevel
+import com.android.protolog.tool.ProtoLogTool.LogCall
+import com.google.common.truth.Truth
+import org.junit.Test
+import perfetto.protos.PerfettoTrace.ProtoLogViewerConfig
+
+class ViewerConfigProtoBuilderTest {
+ companion object {
+ private val TAG1 = "WM_TEST"
+ private val TAG2 = "WM_DEBUG"
+
+ private val TEST1 = ViewerConfigParser.ConfigEntry("test1", LogLevel.INFO.name,
+ TAG1
+ )
+ private val TEST2 = ViewerConfigParser.ConfigEntry("test2", LogLevel.DEBUG.name,
+ TAG2
+ )
+
+ private val GROUP1 = LogGroup("TEST_GROUP", true, true, TAG1)
+ private val GROUP2 = LogGroup("DEBUG_GROUP", true, true, TAG2)
+ private val GROUP3 = LogGroup("UNUSED_GROUP", true, true, TAG1)
+
+ private val GROUPS = listOf(
+ GROUP1,
+ GROUP2,
+ GROUP3
+ )
+
+ private const val PATH = "/tmp/test.java"
+ }
+
+ @Test
+ fun includesUnusedProtoLogGroups() {
+ // Added because of b/373754057. This test might need to be removed in the future.
+
+ val configBuilder = ViewerConfigProtoBuilder()
+
+ val logCallRegistry = ProtoLogTool.LogCallRegistry()
+ logCallRegistry.addLogCalls(listOf(
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH, 123),
+ LogCall(TEST2.messageString, LogLevel.INFO, GROUP2, PATH, 456),
+ ))
+
+ val rawProto = configBuilder.build(GROUPS, logCallRegistry.getStatements())
+
+ val viewerConfig = ProtoLogViewerConfig.parseFrom(rawProto)
+ Truth.assertThat(viewerConfig.groupsCount).isEqualTo(GROUPS.size)
+ Truth.assertThat(viewerConfig.messagesCount).isLessThan(GROUPS.size)
+ }
+
+ @Test
+ fun includesLineNumberInLocation() {
+ val configBuilder = ViewerConfigProtoBuilder()
+
+ val logCallRegistry = ProtoLogTool.LogCallRegistry()
+ logCallRegistry.addLogCalls(listOf(
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH, 123),
+ LogCall(TEST2.messageString, LogLevel.INFO, GROUP2, PATH, 456),
+ ))
+
+ val rawProto = configBuilder.build(GROUPS, logCallRegistry.getStatements())
+
+ val viewerConfig = ProtoLogViewerConfig.parseFrom(rawProto)
+
+ Truth.assertThat(viewerConfig.messagesList[0].location).isEqualTo("$PATH:123")
+ Truth.assertThat(viewerConfig.messagesList[1].location).isEqualTo("$PATH:456")
+ }
+} \ No newline at end of file