diff options
Diffstat (limited to 'tools')
83 files changed, 2816 insertions, 599 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index ade0dc4d9c42..46ae2ecd9406 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -202,6 +202,9 @@ cc_binary_host { use_version_lib: true, static_libs: ["libaapt2"], defaults: ["aapt2_defaults"], + dist: { + targets: ["aapt2_artifacts"], + }, } // ========================================================== @@ -220,6 +223,6 @@ genrule { "cp $(in) $(genDir)/protos && " + "$(location :soong_zip) -o $(out) -C $(genDir)/protos -D $(genDir)/protos", dist: { - targets: ["sdk_repo"], + targets: ["sdk_repo", "aapt2_artifacts"], }, } diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index 45719ef474cd..e930b47f2901 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -267,8 +267,14 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table return false; } } else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) { + SerializeTableOptions proto_serialize_options; + proto_serialize_options.collapse_key_stringpool = + options.collapse_key_stringpool; + proto_serialize_options.name_collapse_exemptions = + options.name_collapse_exemptions; pb::ResourceTable pb_table; - SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics()); + SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics(), + proto_serialize_options); if (!io::CopyProtoToArchive(context, &pb_table, path, diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 4e051a37f3ed..8fe0eb3b51bf 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -274,6 +274,19 @@ inline std::string to_string(const ResourceId& id) { return id.to_string(); } +// Helper to compare resource IDs, moving dynamic IDs after framework IDs. +inline bool cmp_ids_dynamic_after_framework(const ResourceId& a, const ResourceId& b) { + // If one of a and b is from the framework package (package ID 0x01), and the + // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the + // framework ID. This ensures that when AssetManager resolves the dynamic IDs, + // they will be in sorted order as expected by AssetManager. + if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) || + (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) { + return b < a; + } + return a < b; +} + // // ResourceType implementation. // diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 931a14b1f650..3d9be59dd960 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -342,7 +342,7 @@ bool ResourceParser::FlattenXmlSubtree( } } - // Sanity check to make sure we processed all the nodes. + // Validity check to make sure we processed all the nodes. CHECK(node_stack.size() == 1u); CHECK(node_stack.back() == &root); diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 469128b1e50b..7dfc983b54ba 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -740,7 +740,7 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config if (type == ResourceType::kId) { if (res_value.dataType != android::Res_value::TYPE_REFERENCE && res_value.dataType != android::Res_value::TYPE_DYNAMIC_REFERENCE) { - // plain "id" resources are actually encoded as dummy values (aapt1 uses an empty string, + // plain "id" resources are actually encoded as unused values (aapt1 uses an empty string, // while aapt2 uses a false boolean). return util::make_unique<Id>(); } diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 32686538c10d..ff54fccda767 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -75,8 +75,10 @@ struct ResourcePathData { }; // Resource file paths are expected to look like: [--/res/]type[-config]/name -static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, const char dir_sep, - std::string* out_error) { +static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, + const char dir_sep, + std::string* out_error, + const CompileOptions& options) { std::vector<std::string> parts = util::Split(path, dir_sep); if (parts.size() < 2) { if (out_error) *out_error = "bad resource path"; @@ -121,7 +123,11 @@ static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, } } - return ResourcePathData{Source(path), dir_str.to_string(), name.to_string(), + const Source res_path = options.source_path + ? StringPiece(options.source_path.value()) + : StringPiece(path); + + return ResourcePathData{res_path, dir_str.to_string(), name.to_string(), extension.to_string(), config_str.to_string(), config}; } @@ -667,7 +673,8 @@ int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* // Extract resource type information from the full path std::string err_str; ResourcePathData path_data; - if (auto maybe_path_data = ExtractResourcePathData(path, inputs->GetDirSeparator(), &err_str)) { + if (auto maybe_path_data = ExtractResourcePathData( + path, inputs->GetDirSeparator(), &err_str, options)) { path_data = maybe_path_data.value(); } else { context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str); @@ -747,6 +754,11 @@ int CompileCommand::Action(const std::vector<std::string>& args) { context.GetDiagnostics()->Error(DiagMessage() << "only one of --dir and --zip can be specified"); return 1; + } else if ((options_.res_dir || options_.res_zip) && + options_.source_path && args.size() > 1) { + context.GetDiagnostics()->Error(DiagMessage(kPath) + << "Cannot use an overriding source path with multiple files."); + return 1; } else if (options_.res_dir) { if (!args.empty()) { context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified"); diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h index 1752a1adac24..1bc1f6651f85 100644 --- a/tools/aapt2/cmd/Compile.h +++ b/tools/aapt2/cmd/Compile.h @@ -28,6 +28,7 @@ namespace aapt { struct CompileOptions { std::string output_path; + Maybe<std::string> source_path; Maybe<std::string> res_dir; Maybe<std::string> res_zip; Maybe<std::string> generate_text_symbols_path; @@ -69,6 +70,9 @@ class CompileCommand : public Command { AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose); AddOptionalFlag("--trace-folder", "Generate systrace json trace fragment to specified folder.", &trace_folder_); + AddOptionalFlag("--source-path", + "Sets the compiled resource file source file path to the given string.", + &options_.source_path); } int Action(const std::vector<std::string>& args) override; diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index fb786a31360e..0aab94d3299f 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -24,6 +24,7 @@ #include "io/ZipArchive.h" #include "java/AnnotationProcessor.h" #include "test/Test.h" +#include "format/proto/ProtoDeserialize.h" namespace aapt { @@ -253,4 +254,90 @@ TEST_F(CompilerTest, DoNotTranslateTest) { AssertTranslations(this, "donottranslate_foo", expected_not_translatable); } +TEST_F(CompilerTest, RelativePathTest) { + StdErrDiagnostics diag; + const std::string res_path = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "res"}); + + const std::string path_values_colors = GetTestPath("values/colors.xml"); + WriteFile(path_values_colors, "<resources>" + "<color name=\"color_one\">#008577</color>" + "</resources>"); + + const std::string path_layout_layout_one = GetTestPath("layout/layout_one.xml"); + WriteFile(path_layout_layout_one, "<LinearLayout " + "xmlns:android=\"http://schemas.android.com/apk/res/android\">" + "<TextBox android:id=\"@+id/text_one\" android:background=\"@color/color_one\"/>" + "</LinearLayout>"); + + const std::string compiled_files_dir = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "compiled"}); + CHECK(file::mkdirs(compiled_files_dir.data())); + + const std::string path_values_colors_out = + BuildPath({compiled_files_dir,"values_colors.arsc.flat"}); + const std::string path_layout_layout_one_out = + BuildPath({compiled_files_dir, "layout_layout_one.flat"}); + ::android::base::utf8::unlink(path_values_colors_out.c_str()); + ::android::base::utf8::unlink(path_layout_layout_one_out.c_str()); + const std::string apk_path = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "out.apk"}); + + const std::string source_set_res = BuildPath({"main", "res"}); + const std::string relative_path_values_colors = + BuildPath({source_set_res, "values", "colors.xml"}); + const std::string relative_path_layout_layout_one = + BuildPath({source_set_res, "layout", "layout_one.xml"}); + + CompileCommand(&diag).Execute({ + path_values_colors, + "-o", + compiled_files_dir, + "--source-path", + relative_path_values_colors}, + &std::cerr); + + CompileCommand(&diag).Execute({ + path_layout_layout_one, + "-o", + compiled_files_dir, + "--source-path", + relative_path_layout_layout_one}, + &std::cerr); + + std::ifstream ifs_values(path_values_colors_out); + std::string content_values((std::istreambuf_iterator<char>(ifs_values)), + (std::istreambuf_iterator<char>())); + ASSERT_NE(content_values.find(relative_path_values_colors), -1); + ASSERT_EQ(content_values.find(path_values_colors), -1); + + Link({"-o", apk_path, "--manifest", GetDefaultManifest(), "--proto-format"}, + compiled_files_dir, &diag); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, &diag); + ResourceTable* resource_table = apk.get()->GetResourceTable(); + const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings = + resource_table->string_pool.strings(); + + ASSERT_EQ(pool_strings.size(), 2); + ASSERT_EQ(pool_strings[0]->value, "res/layout/layout_one.xml"); + ASSERT_EQ(pool_strings[1]->value, "res/layout-v1/layout_one.xml"); + + // Check resources.pb contains relative sources. + io::IFile* proto_file = + apk.get()->GetFileCollection()->FindFile("resources.pb"); + std::unique_ptr<io::InputStream> proto_stream = proto_file->OpenInputStream(); + io::ProtoInputStreamReader proto_reader(proto_stream.get()); + pb::ResourceTable pb_table; + proto_reader.ReadMessage(&pb_table); + + const std::string pool_strings_proto = pb_table.source_pool().data(); + + ASSERT_NE(pool_strings_proto.find(relative_path_values_colors), -1); + ASSERT_NE(pool_strings_proto.find(relative_path_layout_layout_one), -1); +} + } // namespace aapt diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index fd12d02434fa..118a76ff73a8 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -78,6 +78,8 @@ using ::android::base::StringPrintf; namespace aapt { +constexpr uint8_t kAndroidPackageId = 0x01; + class LinkContext : public IAaptContext { public: explicit LinkContext(IDiagnostics* diagnostics) @@ -1401,7 +1403,7 @@ class Linker { return MergeExportedSymbols(compiled_file.source, compiled_file.exported_symbols); } - // Takes a path to load as a ZIP file and merges the files within into the master ResourceTable. + // Takes a path to load as a ZIP file and merges the files within into the main ResourceTable. // If override is true, conflicting resources are allowed to override each other, in order of last // seen. // An io::IFileCollection is created from the ZIP file and added to the set of @@ -1432,7 +1434,7 @@ class Linker { return !error; } - // Takes a path to load and merge into the master ResourceTable. If override is true, + // Takes a path to load and merge into the main ResourceTable. If override is true, // conflicting resources are allowed to override each other, in order of last seen. // If the file path ends with .flata, .jar, .jack, or .zip the file is treated // as ZIP archive and the files within are merged individually. @@ -1449,7 +1451,7 @@ class Linker { return MergeFile(file, override); } - // Takes an AAPT Container file (.apc/.flat) to load and merge into the master ResourceTable. + // Takes an AAPT Container file (.apc/.flat) to load and merge into the main ResourceTable. // If override is true, conflicting resources are allowed to override each other, in order of last // seen. // All other file types are ignored. This is because these files could be coming from a zip, @@ -1579,37 +1581,35 @@ class Linker { } void AliasAdaptiveIcon(xml::XmlResource* manifest, ResourceTable* table) { - xml::Element* application = manifest->root->FindChild("", "application"); + const xml::Element* application = manifest->root->FindChild("", "application"); if (!application) { return; } - xml::Attribute* icon = application->FindAttribute(xml::kSchemaAndroid, "icon"); - xml::Attribute* round_icon = application->FindAttribute(xml::kSchemaAndroid, "roundIcon"); + const xml::Attribute* icon = application->FindAttribute(xml::kSchemaAndroid, "icon"); + const xml::Attribute* round_icon = application->FindAttribute(xml::kSchemaAndroid, "roundIcon"); if (!icon || !round_icon) { return; } // Find the icon resource defined within the application. - auto icon_reference = ValueCast<Reference>(icon->compiled_value.get()); + const auto icon_reference = ValueCast<Reference>(icon->compiled_value.get()); if (!icon_reference || !icon_reference->name) { return; } - auto package = table->FindPackageById(icon_reference->id.value().package_id()); - if (!package) { - return; - } - auto type = package->FindType(icon_reference->name.value().type); - if (!type) { - return; + + auto icon_name = ResourceNameRef(icon_reference->name.value()); + if (icon_name.package.empty()) { + icon_name.package = context_->GetCompilationPackage(); } - auto icon_entry = type->FindEntry(icon_reference->name.value().entry); - if (!icon_entry) { + + const auto icon_entry_result = table->FindResource(icon_name); + if (!icon_entry_result) { return; } int icon_max_sdk = 0; - for (auto& config_value : icon_entry->values) { + for (auto& config_value : icon_entry_result.value().entry->values) { icon_max_sdk = (icon_max_sdk < config_value->config.sdkVersion) ? config_value->config.sdkVersion : icon_max_sdk; } @@ -1619,25 +1619,23 @@ class Linker { } // Find the roundIcon resource defined within the application. - auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get()); + const auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get()); if (!round_icon_reference || !round_icon_reference->name) { return; } - package = table->FindPackageById(round_icon_reference->id.value().package_id()); - if (!package) { - return; - } - type = package->FindType(round_icon_reference->name.value().type); - if (!type) { - return; + + auto round_icon_name = ResourceNameRef(round_icon_reference->name.value()); + if (round_icon_name.package.empty()) { + round_icon_name.package = context_->GetCompilationPackage(); } - auto round_icon_entry = type->FindEntry(round_icon_reference->name.value().entry); - if (!round_icon_entry) { + + const auto round_icon_entry_result = table->FindResource(round_icon_name); + if (!round_icon_entry_result) { return; } int round_icon_max_sdk = 0; - for (auto& config_value : round_icon_entry->values) { + for (auto& config_value : round_icon_entry_result.value().entry->values) { round_icon_max_sdk = (round_icon_max_sdk < config_value->config.sdkVersion) ? config_value->config.sdkVersion : round_icon_max_sdk; } @@ -1648,7 +1646,7 @@ class Linker { } // Add an equivalent v26 entry to the roundIcon for each v26 variant of the regular icon. - for (auto& config_value : icon_entry->values) { + for (auto& config_value : icon_entry_result.value().entry->values) { if (config_value->config.sdkVersion < SDK_O) { continue; } @@ -1659,7 +1657,7 @@ class Linker { << "\" for round icon compatibility"); auto value = icon_reference->Clone(&table->string_pool); - auto round_config_value = round_icon_entry->FindOrCreateValue( + auto round_config_value = round_icon_entry_result.value().entry->FindOrCreateValue( config_value->config, config_value->product); round_config_value->value.reset(value); } @@ -1809,7 +1807,7 @@ class Linker { // Override the package ID when it is "android". if (context_->GetCompilationPackage() == "android") { - context_->SetPackageId(0x01); + context_->SetPackageId(kAndroidPackageId); // Verify we're building a regular app. if (context_->GetPackageType() != PackageType::kApp) { @@ -1866,7 +1864,8 @@ class Linker { if (context_->GetPackageType() != PackageType::kStaticLib) { PrivateAttributeMover mover; - if (!mover.Consume(context_, &final_table_)) { + if (context_->GetPackageId() == kAndroidPackageId && + !mover.Consume(context_, &final_table_)) { context_->GetDiagnostics()->Error(DiagMessage() << "failed moving private attributes"); return 1; } diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index e36668e5a043..5b18a3789d76 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -132,8 +132,8 @@ class Optimizer { if (context_->IsVerbose()) { context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK..."); } - if (!options_.resources_blacklist.empty()) { - ResourceFilter filter(options_.resources_blacklist); + if (!options_.resources_exclude_list.empty()) { + ResourceFilter filter(options_.resources_exclude_list); if (!filter.Consume(context_, apk->GetResourceTable())) { context_->GetDiagnostics()->Error(DiagMessage() << "failed filtering resources"); return 1; @@ -328,7 +328,7 @@ bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOpti } for (StringPiece directive : util::Tokenize(directives, ',')) { if (directive == "remove") { - options->resources_blacklist.insert(resource_name.ToResourceName()); + options->resources_exclude_list.insert(resource_name.ToResourceName()); } else if (directive == "no_collapse" || directive == "no_obfuscate") { options->table_flattener_options.name_collapse_exemptions.insert( resource_name.ToResourceName()); diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h index 5070ccc8afbf..3afc46b04af6 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -36,8 +36,8 @@ struct OptimizeOptions { // Details of the app extracted from the AndroidManifest.xml AppInfo app_info; - // Blacklist of unused resources that should be removed from the apk. - std::unordered_set<ResourceName> resources_blacklist; + // Exclude list of unused resources that should be removed from the apk. + std::unordered_set<ResourceName> resources_exclude_list; // Split APK options. TableSplitterOptions table_splitter_options; diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp index bc2e6990433c..4db2392b4eab 100644 --- a/tools/aapt2/compile/PngChunkFilter.cpp +++ b/tools/aapt2/compile/PngChunkFilter.cpp @@ -35,7 +35,7 @@ constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { ((uint32_t)d); } -// Whitelist of PNG chunk types that we want to keep in the resulting PNG. +// Allow list of PNG chunk types that we want to keep in the resulting PNG. enum PngChunkTypes { kPngChunkIHDR = u32(73, 72, 68, 82), kPngChunkIDAT = u32(73, 68, 65, 84), @@ -56,7 +56,7 @@ static uint32_t Peek32LE(const char* data) { return word; } -static bool IsPngChunkWhitelisted(uint32_t type) { +static bool IsPngChunkAllowed(uint32_t type) { switch (type) { case kPngChunkIHDR: case kPngChunkIDAT: @@ -128,7 +128,7 @@ bool PngChunkFilter::Next(const void** buffer, size_t* len) { // Do we strip this chunk? const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t)); - if (IsPngChunkWhitelisted(chunk_type)) { + if (IsPngChunkAllowed(chunk_type)) { // Advance the window to include this chunk. window_end_ += kMinChunkHeaderSize + chunk_len; diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index 2ef8b999a192..e5b3107877cb 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -187,7 +187,7 @@ TEST_F(ConfigurationParserTest, ForPath_NoFile) { TEST_F(ConfigurationParserTest, ExtractConfiguration) { Maybe<PostProcessingConfiguration> maybe_config = - ExtractConfiguration(kValidConfig, "dummy.xml", &diag_); + ExtractConfiguration(kValidConfig, "fake.xml", &diag_); PostProcessingConfiguration config = maybe_config.value(); diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 4a6bfd031284..8862405189c0 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -79,8 +79,8 @@ enum { ISGAME_ATTR = 0x10103f4, VERSION_ATTR = 0x01010519, CERT_DIGEST_ATTR = 0x01010548, - REQUIRED_FEATURE_ATTR = 0x01010557, - REQUIRED_NOT_FEATURE_ATTR = 0x01010558, + REQUIRED_FEATURE_ATTR = 0x01010554, + REQUIRED_NOT_FEATURE_ATTR = 0x01010555, IS_STATIC_ATTR = 0x0101055a, REQUIRED_SYSTEM_PROPERTY_NAME_ATTR = 0x01010565, REQUIRED_SYSTEM_PROPERTY_VALUE_ATTR = 0x01010566, @@ -188,7 +188,7 @@ class ManifestExtractor { /** Retrieves the resource assigned to the specified resource id if one exists. */ Value* FindValueById(const ResourceTable* table, const ResourceId& res_id, - const ConfigDescription& config = DummyConfig()) { + const ConfigDescription& config = DefaultConfig()) { if (table) { for (auto& package : table->packages) { if (package->id && package->id.value() == res_id.package_id()) { @@ -210,7 +210,7 @@ class ManifestExtractor { } /** Attempts to resolve the reference to a non-reference value. */ - Value* ResolveReference(Reference* ref, const ConfigDescription& config = DummyConfig()) { + Value* ResolveReference(Reference* ref, const ConfigDescription& config = DefaultConfig()) { const int kMaxIterations = 40; int i = 0; while (ref && ref->id && i++ < kMaxIterations) { @@ -231,10 +231,10 @@ class ManifestExtractor { * this will attempt to resolve the reference to an integer value. **/ int32_t* GetAttributeInteger(xml::Attribute* attr, - const ConfigDescription& config = DummyConfig()) { + const ConfigDescription& config = DefaultConfig()) { if (attr != nullptr) { if (attr->compiled_value) { - // Resolve references using the dummy configuration + // Resolve references using the configuration Value* value = attr->compiled_value.get(); if (ValueCast<Reference>(value)) { value = ResolveReference(ValueCast<Reference>(value), config); @@ -257,7 +257,7 @@ class ManifestExtractor { * exist or cannot be resolved to an integer value. **/ int32_t GetAttributeIntegerDefault(xml::Attribute* attr, int32_t def, - const ConfigDescription& config = DummyConfig()) { + const ConfigDescription& config = DefaultConfig()) { auto value = GetAttributeInteger(attr, config); if (value) { return *value; @@ -270,10 +270,10 @@ class ManifestExtractor { * this will attempt to resolve the reference to a string value. **/ const std::string* GetAttributeString(xml::Attribute* attr, - const ConfigDescription& config = DummyConfig()) { + const ConfigDescription& config = DefaultConfig()) { if (attr != nullptr) { if (attr->compiled_value) { - // Resolve references using the dummy configuration + // Resolve references using the configuration Value* value = attr->compiled_value.get(); if (ValueCast<Reference>(value)) { value = ResolveReference(ValueCast<Reference>(value), config); @@ -305,7 +305,7 @@ class ManifestExtractor { * exist or cannot be resolved to an string value. **/ std::string GetAttributeStringDefault(xml::Attribute* attr, std::string def, - const ConfigDescription& config = DummyConfig()) { + const ConfigDescription& config = DefaultConfig()) { auto value = GetAttributeString(attr, config); if (value) { return *value; @@ -322,7 +322,7 @@ class ManifestExtractor { friend Element; /** Creates a default configuration used to retrieve resources. */ - static ConfigDescription DummyConfig() { + static ConfigDescription DefaultConfig() { ConfigDescription config; config.orientation = android::ResTable_config::ORIENTATION_PORT; config.density = android::ResTable_config::DENSITY_MEDIUM; @@ -1063,17 +1063,23 @@ class UsesPermission : public ManifestExtractor::Element { public: UsesPermission() = default; std::string name; - std::string requiredFeature; - std::string requiredNotFeature; + std::vector<std::string> requiredFeatures; + std::vector<std::string> requiredNotFeatures; int32_t required = true; int32_t maxSdkVersion = -1; void Extract(xml::Element* element) override { name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); - requiredFeature = GetAttributeStringDefault( - FindAttribute(element, REQUIRED_FEATURE_ATTR), ""); - requiredNotFeature = GetAttributeStringDefault( - FindAttribute(element, REQUIRED_NOT_FEATURE_ATTR), ""); + std::string feature = + GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), ""); + if (!feature.empty()) { + requiredFeatures.push_back(feature); + } + feature = GetAttributeStringDefault(FindAttribute(element, REQUIRED_NOT_FEATURE_ATTR), ""); + if (!feature.empty()) { + requiredNotFeatures.push_back(feature); + } + required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); maxSdkVersion = GetAttributeIntegerDefault( FindAttribute(element, MAX_SDK_VERSION_ATTR), -1); @@ -1090,13 +1096,13 @@ class UsesPermission : public ManifestExtractor::Element { if (maxSdkVersion >= 0) { printer->Print(StringPrintf(" maxSdkVersion='%d'", maxSdkVersion)); } - if (!requiredFeature.empty()) { - printer->Print(StringPrintf(" requiredFeature='%s'", requiredFeature.data())); + printer->Print("\n"); + for (const std::string& requiredFeature : requiredFeatures) { + printer->Print(StringPrintf(" required-feature='%s'\n", requiredFeature.data())); } - if (!requiredNotFeature.empty()) { - printer->Print(StringPrintf(" requiredNotFeature='%s'", requiredNotFeature.data())); + for (const std::string& requiredNotFeature : requiredNotFeatures) { + printer->Print(StringPrintf(" required-not-feature='%s'\n", requiredNotFeature.data())); } - printer->Print("\n"); if (required == 0) { printer->Print(StringPrintf("optional-permission: name='%s'", name.data())); if (maxSdkVersion >= 0) { @@ -1116,6 +1122,38 @@ class UsesPermission : public ManifestExtractor::Element { } }; +/** Represents <required-feature> elements. **/ +class RequiredFeature : public ManifestExtractor::Element { + public: + RequiredFeature() = default; + std::string name; + + void Extract(xml::Element* element) override { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + auto parent_stack = extractor()->parent_stack(); + if (!name.empty() && ElementCast<UsesPermission>(parent_stack[0])) { + UsesPermission* uses_permission = ElementCast<UsesPermission>(parent_stack[0]); + uses_permission->requiredFeatures.push_back(name); + } + } +}; + +/** Represents <required-not-feature> elements. **/ +class RequiredNotFeature : public ManifestExtractor::Element { + public: + RequiredNotFeature() = default; + std::string name; + + void Extract(xml::Element* element) override { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + auto parent_stack = extractor()->parent_stack(); + if (!name.empty() && ElementCast<UsesPermission>(parent_stack[0])) { + UsesPermission* uses_permission = ElementCast<UsesPermission>(parent_stack[0]); + uses_permission->requiredNotFeatures.push_back(name); + } + } +}; + /** Represents <uses-permission-sdk-23> elements. **/ class UsesPermissionSdk23 : public ManifestExtractor::Element { public: @@ -1405,6 +1443,29 @@ class UsesStaticLibrary : public ManifestExtractor::Element { } }; +/** Represents <uses-native-library> elements. **/ +class UsesNativeLibrary : public ManifestExtractor::Element { + public: + UsesNativeLibrary() = default; + std::string name; + int required; + + void Extract(xml::Element* element) override { + auto parent_stack = extractor()->parent_stack(); + if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); + } + } + + void Print(text::Printer* printer) override { + if (!name.empty()) { + printer->Print(StringPrintf("uses-native-library%s:'%s'\n", + (required == 0) ? "-not-required" : "", name.data())); + } + } +}; + /** * Represents <meta-data> elements. These tags are only printed when a flag is passed in to * explicitly enable meta data printing. @@ -1822,7 +1883,8 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { for (xml::Element* child : element->GetChildElements()) { if (child->name == "uses-permission" || child->name == "uses-permission-sdk-23" || child->name == "permission") { - auto permission_element = ManifestExtractor::Element::Inflate(this, child); + // Inflate the element and its descendants + auto permission_element = Visit(child); manifest->AddChild(permission_element); } } @@ -1848,7 +1910,7 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { // Collect all the unique locales of the apk if (locales_.find(locale_str) == locales_.end()) { - ConfigDescription config = ManifestExtractor::DummyConfig(); + ConfigDescription config = ManifestExtractor::DefaultConfig(); config.setBcp47Locale(locale_str.data()); locales_.insert(std::make_pair(locale_str, config)); } @@ -1857,7 +1919,7 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { uint16_t density = (value->config.density == 0) ? (uint16_t) 160 : value->config.density; if (densities_.find(density) == densities_.end()) { - ConfigDescription config = ManifestExtractor::DummyConfig(); + ConfigDescription config = ManifestExtractor::DefaultConfig(); config.density = density; densities_.insert(std::make_pair(density, config)); } @@ -2214,37 +2276,40 @@ T* ElementCast(ManifestExtractor::Element* element) { } const std::unordered_map<std::string, bool> kTagCheck = { - {"action", std::is_base_of<Action, T>::value}, - {"activity", std::is_base_of<Activity, T>::value}, - {"application", std::is_base_of<Application, T>::value}, - {"category", std::is_base_of<Category, T>::value}, - {"compatible-screens", std::is_base_of<CompatibleScreens, T>::value}, - {"feature-group", std::is_base_of<FeatureGroup, T>::value}, - {"input-type", std::is_base_of<InputType, T>::value}, - {"intent-filter", std::is_base_of<IntentFilter, T>::value}, - {"meta-data", std::is_base_of<MetaData, T>::value}, - {"manifest", std::is_base_of<Manifest, T>::value}, - {"original-package", std::is_base_of<OriginalPackage, T>::value}, - {"overlay", std::is_base_of<Overlay, T>::value}, - {"package-verifier", std::is_base_of<PackageVerifier, T>::value}, - {"permission", std::is_base_of<Permission, T>::value}, - {"provider", std::is_base_of<Provider, T>::value}, - {"receiver", std::is_base_of<Receiver, T>::value}, - {"screen", std::is_base_of<Screen, T>::value}, - {"service", std::is_base_of<Service, T>::value}, - {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value}, - {"supports-input", std::is_base_of<SupportsInput, T>::value}, - {"supports-screens", std::is_base_of<SupportsScreen, T>::value}, - {"uses-configuration", std::is_base_of<UsesConfiguarion, T>::value}, - {"uses-feature", std::is_base_of<UsesFeature, T>::value}, - {"uses-permission", std::is_base_of<UsesPermission, T>::value}, - {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value}, - {"uses-library", std::is_base_of<UsesLibrary, T>::value}, - {"uses-package", std::is_base_of<UsesPackage, T>::value}, - {"static-library", std::is_base_of<StaticLibrary, T>::value}, - {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value}, - {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value}, - {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value}, + {"action", std::is_base_of<Action, T>::value}, + {"activity", std::is_base_of<Activity, T>::value}, + {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value}, + {"application", std::is_base_of<Application, T>::value}, + {"category", std::is_base_of<Category, T>::value}, + {"compatible-screens", std::is_base_of<CompatibleScreens, T>::value}, + {"feature-group", std::is_base_of<FeatureGroup, T>::value}, + {"input-type", std::is_base_of<InputType, T>::value}, + {"intent-filter", std::is_base_of<IntentFilter, T>::value}, + {"meta-data", std::is_base_of<MetaData, T>::value}, + {"manifest", std::is_base_of<Manifest, T>::value}, + {"original-package", std::is_base_of<OriginalPackage, T>::value}, + {"overlay", std::is_base_of<Overlay, T>::value}, + {"package-verifier", std::is_base_of<PackageVerifier, T>::value}, + {"permission", std::is_base_of<Permission, T>::value}, + {"provider", std::is_base_of<Provider, T>::value}, + {"receiver", std::is_base_of<Receiver, T>::value}, + {"required-feature", std::is_base_of<RequiredFeature, T>::value}, + {"required-not-feature", std::is_base_of<RequiredNotFeature, T>::value}, + {"screen", std::is_base_of<Screen, T>::value}, + {"service", std::is_base_of<Service, T>::value}, + {"static-library", std::is_base_of<StaticLibrary, T>::value}, + {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value}, + {"supports-input", std::is_base_of<SupportsInput, T>::value}, + {"supports-screens", std::is_base_of<SupportsScreen, T>::value}, + {"uses-configuration", std::is_base_of<UsesConfiguarion, T>::value}, + {"uses-feature", std::is_base_of<UsesFeature, T>::value}, + {"uses-library", std::is_base_of<UsesLibrary, T>::value}, + {"uses-native-library", std::is_base_of<UsesNativeLibrary, T>::value}, + {"uses-package", std::is_base_of<UsesPackage, T>::value}, + {"uses-permission", std::is_base_of<UsesPermission, T>::value}, + {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value}, + {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value}, + {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value}, }; auto check = kTagCheck.find(element->tag()); @@ -2264,38 +2329,41 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( const std::unordered_map<std::string, std::function<std::unique_ptr<ManifestExtractor::Element>()>> kTagCheck = { - {"action", &CreateType<Action>}, - {"activity", &CreateType<Activity>}, - {"application", &CreateType<Application>}, - {"category", &CreateType<Category>}, - {"compatible-screens", &CreateType<CompatibleScreens>}, - {"feature-group", &CreateType<FeatureGroup>}, - {"input-type", &CreateType<InputType>}, - {"intent-filter",&CreateType<IntentFilter>}, - {"manifest", &CreateType<Manifest>}, - {"meta-data", &CreateType<MetaData>}, - {"original-package", &CreateType<OriginalPackage>}, - {"overlay", &CreateType<Overlay>}, - {"package-verifier", &CreateType<PackageVerifier>}, - {"permission", &CreateType<Permission>}, - {"provider", &CreateType<Provider>}, - {"receiver", &CreateType<Receiver>}, - {"screen", &CreateType<Screen>}, - {"service", &CreateType<Service>}, - {"supports-gl-texture", &CreateType<SupportsGlTexture>}, - {"supports-input", &CreateType<SupportsInput>}, - {"supports-screens", &CreateType<SupportsScreen>}, - {"uses-configuration", &CreateType<UsesConfiguarion>}, - {"uses-feature", &CreateType<UsesFeature>}, - {"uses-permission", &CreateType<UsesPermission>}, - {"uses-permission-sdk-23", &CreateType<UsesPermissionSdk23>}, - {"uses-library", &CreateType<UsesLibrary>}, - {"static-library", &CreateType<StaticLibrary>}, - {"uses-static-library", &CreateType<UsesStaticLibrary>}, - {"uses-package", &CreateType<UsesPackage>}, - {"additional-certificate", &CreateType<AdditionalCertificate>}, - {"uses-sdk", &CreateType<UsesSdkBadging>}, - }; + {"action", &CreateType<Action>}, + {"activity", &CreateType<Activity>}, + {"additional-certificate", &CreateType<AdditionalCertificate>}, + {"application", &CreateType<Application>}, + {"category", &CreateType<Category>}, + {"compatible-screens", &CreateType<CompatibleScreens>}, + {"feature-group", &CreateType<FeatureGroup>}, + {"input-type", &CreateType<InputType>}, + {"intent-filter", &CreateType<IntentFilter>}, + {"manifest", &CreateType<Manifest>}, + {"meta-data", &CreateType<MetaData>}, + {"original-package", &CreateType<OriginalPackage>}, + {"overlay", &CreateType<Overlay>}, + {"package-verifier", &CreateType<PackageVerifier>}, + {"permission", &CreateType<Permission>}, + {"provider", &CreateType<Provider>}, + {"receiver", &CreateType<Receiver>}, + {"required-feature", &CreateType<RequiredFeature>}, + {"required-not-feature", &CreateType<RequiredNotFeature>}, + {"screen", &CreateType<Screen>}, + {"service", &CreateType<Service>}, + {"static-library", &CreateType<StaticLibrary>}, + {"supports-gl-texture", &CreateType<SupportsGlTexture>}, + {"supports-input", &CreateType<SupportsInput>}, + {"supports-screens", &CreateType<SupportsScreen>}, + {"uses-configuration", &CreateType<UsesConfiguarion>}, + {"uses-feature", &CreateType<UsesFeature>}, + {"uses-library", &CreateType<UsesLibrary>}, + {"uses-native-library", &CreateType<UsesNativeLibrary>}, + {"uses-package", &CreateType<UsesPackage>}, + {"uses-permission", &CreateType<UsesPermission>}, + {"uses-permission-sdk-23", &CreateType<UsesPermissionSdk23>}, + {"uses-sdk", &CreateType<UsesSdkBadging>}, + {"uses-static-library", &CreateType<UsesStaticLibrary>}, + }; // Attempt to map the xml tag to a element inflater std::unique_ptr<ManifestExtractor::Element> element; diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 4784ecf3d12c..eb0ade62d542 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -59,22 +59,10 @@ static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) { dst[i] = 0; } -static bool cmp_style_ids(ResourceId a, ResourceId b) { - // If one of a and b is from the framework package (package ID 0x01), and the - // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the - // framework ID. This ensures that when AssetManager resolves the dynamic IDs, - // they will be in sorted order as expected by AssetManager. - if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) || - (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) { - return b < a; - } - return a < b; -} - static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) { if (a.key.id) { if (b.key.id) { - return cmp_style_ids(a.key.id.value(), b.key.id.value()); + return cmp_ids_dynamic_after_framework(a.key.id.value(), b.key.id.value()); } return true; } else if (!b.key.id) { diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index 7eb8ebd9a043..06ac9e5dc5c4 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -449,9 +449,12 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr } for (const pb::Entry& pb_entry : pb_type.entry()) { - ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name()); + ResourceEntry* entry; if (pb_entry.has_entry_id()) { - entry->id = static_cast<uint16_t>(pb_entry.entry_id().id()); + auto entry_id = static_cast<uint16_t>(pb_entry.entry_id().id()); + entry = type->FindOrCreateEntry(pb_entry.name(), entry_id); + } else { + entry = type->FindOrCreateEntry(pb_entry.name()); } // Deserialize the symbol status (public/private with source and comments). @@ -490,8 +493,10 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr // Find the overlayable to which this item belongs pb::OverlayableItem pb_overlayable_item = pb_entry.overlayable_item(); if (pb_overlayable_item.overlayable_idx() >= overlayables.size()) { - *out_error = android::base::StringPrintf("invalid overlayable_idx value %d", - pb_overlayable_item.overlayable_idx()); + *out_error = + android::base::StringPrintf("invalid overlayable_idx value %d for entry %s/%s", + pb_overlayable_item.overlayable_idx(), + pb_type.name().c_str(), pb_entry.name().c_str()); return false; } diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index 831229ffa383..98c517510028 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -359,12 +359,21 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table } pb_type->set_name(to_string(type->type).to_string()); + // hardcoded string uses characters which make it an invalid resource name + static const char* obfuscated_resource_name = "0_resource_name_obfuscated"; for (const std::unique_ptr<ResourceEntry>& entry : type->entries) { pb::Entry* pb_entry = pb_type->add_entry(); if (entry->id) { pb_entry->mutable_entry_id()->set_id(entry->id.value()); } - pb_entry->set_name(entry->name); + ResourceName resource_name({}, type->type, entry->name); + if (options.collapse_key_stringpool && + options.name_collapse_exemptions.find(resource_name) == + options.name_collapse_exemptions.end()) { + pb_entry->set_name(obfuscated_resource_name); + } else { + pb_entry->set_name(entry->name); + } // Write the Visibility struct. pb::Visibility* pb_visibility = pb_entry->mutable_visibility(); diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h index 7a3ea9903732..b0d56307fbe4 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.h +++ b/tools/aapt2/format/proto/ProtoSerialize.h @@ -38,6 +38,15 @@ struct SerializeXmlOptions { struct SerializeTableOptions { /** Prevent serializing the source pool and source protos. */ bool exclude_sources = false; + + // When true, all the entry names in pb:ResourceTable are collapsed to a + // single entry name. When the proto table is converted to binary + // resources.arsc, the key string pool is collapsed to a single entry. All + // resource entries have name indices that point to this single value. + bool collapse_key_stringpool = false; + + // Set of resources to avoid collapsing to a single entry in key stringpool. + std::set<ResourceName> name_collapse_exemptions; }; // Serializes a Value to its protobuf representation. An optional StringPool will hold the diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index 1a7de6dc1c48..fe4c8aa065f1 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -24,6 +24,7 @@ using ::android::ConfigDescription; using ::android::StringPiece; using ::testing::Eq; using ::testing::IsEmpty; +using ::testing::IsNull; using ::testing::NotNull; using ::testing::SizeIs; using ::testing::StrEq; @@ -39,6 +40,13 @@ class MockFileCollection : public io::IFileCollection { MOCK_METHOD0(GetDirSeparator, char()); }; +ResourceEntry* GetEntry(ResourceTable* table, const ResourceNameRef& res_name, + uint32_t id) { + ResourceTablePackage* package = table->FindPackage(res_name.package); + ResourceTableType* type = package->FindType(res_name.type); + return type->FindEntry(res_name.entry, id); +} + TEST(ProtoSerializeTest, SerializeSinglePackage) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<ResourceTable> table = @@ -662,4 +670,167 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) { EXPECT_FALSE(actual_ref->is_dynamic); } +TEST(ProtoSerializeTest, CollapsingResourceNamesNoNameCollapseExemptionsSucceeds) { + const uint32_t id_one_id = 0x7f020000; + const uint32_t id_two_id = 0x7f020001; + const uint32_t id_three_id = 0x7f020002; + const uint32_t integer_three_id = 0x7f030000; + const uint32_t string_test_id = 0x7f040000; + const uint32_t layout_bar_id = 0x7f050000; + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddSimple("com.app.test:id/one", ResourceId(id_one_id)) + .AddSimple("com.app.test:id/two", ResourceId(id_two_id)) + .AddValue("com.app.test:id/three", ResourceId(id_three_id), + test::BuildReference("com.app.test:id/one", ResourceId(id_one_id))) + .AddValue("com.app.test:integer/one", ResourceId(integer_three_id), + util::make_unique<BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), + ResourceId(integer_three_id), + util::make_unique<BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:string/test", ResourceId(string_test_id), "foo") + .AddFileReference("com.app.test:layout/bar", ResourceId(layout_bar_id), + "res/layout/bar.xml") + .Build(); + + SerializeTableOptions options; + options.collapse_key_stringpool = true; + + pb::ResourceTable pb_table; + + SerializeTableToPb(*table, &pb_table, context->GetDiagnostics(), options); + test::TestFile file_a("res/layout/bar.xml"); + MockFileCollection files; + EXPECT_CALL(files, FindFile(Eq("res/layout/bar.xml"))) + .WillRepeatedly(::testing::Return(&file_a)); + ResourceTable new_table; + std::string error; + ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)) << error; + EXPECT_THAT(error, IsEmpty()); + + ResourceName real_id_resource( + "com.app.test", ResourceType::kId, "one"); + EXPECT_THAT(GetEntry(&new_table, real_id_resource, id_one_id), IsNull()); + + ResourceName obfuscated_id_resource( + "com.app.test", ResourceType::kId, "0_resource_name_obfuscated"); + + EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, + id_one_id), NotNull()); + EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, + id_two_id), NotNull()); + ResourceEntry* entry = GetEntry(&new_table, obfuscated_id_resource, id_three_id); + EXPECT_THAT(entry, NotNull()); + ResourceConfigValue* config_value = entry->FindValue({}); + Reference* ref = ValueCast<Reference>(config_value->value.get()); + EXPECT_THAT(ref->id.value(), Eq(id_one_id)); + + ResourceName obfuscated_integer_resource( + "com.app.test", ResourceType::kInteger, "0_resource_name_obfuscated"); + entry = GetEntry(&new_table, obfuscated_integer_resource, integer_three_id); + EXPECT_THAT(entry, NotNull()); + config_value = entry->FindValue({}); + BinaryPrimitive* bp = ValueCast<BinaryPrimitive>(config_value->value.get()); + EXPECT_THAT(bp->value.data, Eq(1u)); + + config_value = entry->FindValue(test::ParseConfigOrDie("v1")); + bp = ValueCast<BinaryPrimitive>(config_value->value.get()); + EXPECT_THAT(bp->value.data, Eq(2u)); + + ResourceName obfuscated_string_resource( + "com.app.test", ResourceType::kString, "0_resource_name_obfuscated"); + entry = GetEntry(&new_table, obfuscated_string_resource, string_test_id); + EXPECT_THAT(entry, NotNull()); + config_value = entry->FindValue({}); + String* s = ValueCast<String>(config_value->value.get()); + EXPECT_THAT(*(s->value), Eq("foo")); + + ResourceName obfuscated_layout_resource( + "com.app.test", ResourceType::kLayout, "0_resource_name_obfuscated"); + entry = GetEntry(&new_table, obfuscated_layout_resource, layout_bar_id); + EXPECT_THAT(entry, NotNull()); + config_value = entry->FindValue({}); + FileReference* f = ValueCast<FileReference>(config_value->value.get()); + EXPECT_THAT(*(f->path), Eq("res/layout/bar.xml")); +} + +TEST(ProtoSerializeTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) { + const uint32_t id_one_id = 0x7f020000; + const uint32_t id_two_id = 0x7f020001; + const uint32_t id_three_id = 0x7f020002; + const uint32_t integer_three_id = 0x7f030000; + const uint32_t string_test_id = 0x7f040000; + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddSimple("com.app.test:id/one", ResourceId(id_one_id)) + .AddSimple("com.app.test:id/two", ResourceId(id_two_id)) + .AddValue("com.app.test:id/three", ResourceId(id_three_id), + test::BuildReference("com.app.test:id/one", ResourceId(id_one_id))) + .AddValue("com.app.test:integer/one", ResourceId(integer_three_id), + util::make_unique<BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), + ResourceId(integer_three_id), + util::make_unique<BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:string/test", ResourceId(string_test_id), "foo") + .Build(); + + SerializeTableOptions options; + options.collapse_key_stringpool = true; + options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one")); + options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test")); + pb::ResourceTable pb_table; + + SerializeTableToPb(*table, &pb_table, context->GetDiagnostics(), options); + MockFileCollection files; + ResourceTable new_table; + std::string error; + ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)) << error; + EXPECT_THAT(error, IsEmpty()); + + EXPECT_THAT(GetEntry(&new_table, ResourceName("com.app.test", ResourceType::kId, "one"), + id_one_id), NotNull()); + ResourceName obfuscated_id_resource( + "com.app.test", ResourceType::kId, "0_resource_name_obfuscated"); + EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, id_one_id), IsNull()); + + ResourceName real_id_resource( + "com.app.test", ResourceType::kId, "two"); + EXPECT_THAT(GetEntry(&new_table, real_id_resource, id_two_id), IsNull()); + EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, id_two_id), NotNull()); + + ResourceEntry* entry = GetEntry(&new_table, obfuscated_id_resource, id_three_id); + EXPECT_THAT(entry, NotNull()); + ResourceConfigValue* config_value = entry->FindValue({}); + Reference* ref = ValueCast<Reference>(config_value->value.get()); + EXPECT_THAT(ref->id.value(), Eq(id_one_id)); + + // Note that this resource is also named "one", but it's a different type, so gets obfuscated. + ResourceName obfuscated_integer_resource( + "com.app.test", ResourceType::kInteger, "0_resource_name_obfuscated"); + entry = GetEntry(&new_table, obfuscated_integer_resource, integer_three_id); + EXPECT_THAT(entry, NotNull()); + config_value = entry->FindValue({}); + BinaryPrimitive* bp = ValueCast<BinaryPrimitive>(config_value->value.get()); + EXPECT_THAT(bp->value.data, Eq(1u)); + + config_value = entry->FindValue(test::ParseConfigOrDie("v1")); + bp = ValueCast<BinaryPrimitive>(config_value->value.get()); + EXPECT_THAT(bp->value.data, Eq(2u)); + + entry = GetEntry(&new_table, ResourceName("com.app.test", ResourceType::kString, "test"), + string_test_id); + EXPECT_THAT(entry, NotNull()); + config_value = entry->FindValue({}); + String* s = ValueCast<String>(config_value->value.get()); + EXPECT_THAT(*(s->value), Eq("foo")); +} + } // namespace aapt diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index dffad3b99c06..f0f839d968d5 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -218,13 +218,10 @@ struct StyleableAttr { static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) { const ResourceId lhs_id = lhs.attr_ref->id.value_or_default(ResourceId(0)); const ResourceId rhs_id = rhs.attr_ref->id.value_or_default(ResourceId(0)); - if (lhs_id < rhs_id) { - return true; - } else if (lhs_id > rhs_id) { - return false; - } else { + if (lhs_id == rhs_id) { return lhs.attr_ref->name.value() < rhs.attr_ref->name.value(); } + return cmp_ids_dynamic_after_framework(lhs_id, rhs_id); } void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 1e1fe4740c6b..04e20101a0dd 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -551,4 +551,39 @@ TEST(JavaClassGeneratorTest, OnlyGenerateRText) { ASSERT_TRUE(generator.Generate("android", nullptr)); } +TEST(JavaClassGeneratorTest, SortsDynamicAttributesAfterFrameworkAttributes) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x01) + .SetPackageId("lib", 0x00) + .AddValue("android:attr/framework_attr", ResourceId(0x01010000), + test::AttributeBuilder().Build()) + .AddValue("lib:attr/dynamic_attr", ResourceId(0x00010000), + test::AttributeBuilder().Build()) + .AddValue("lib:styleable/MyStyleable", ResourceId(0x00030000), + test::StyleableBuilder() + .AddItem("android:attr/framework_attr", ResourceId(0x01010000)) + .AddItem("lib:attr/dynamic_attr", ResourceId(0x00010000)) + .Build()) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"custom"}) + .SetCompilationPackage("custom") + .Build(); + JavaClassGenerator generator(context.get(), table.get(), {}); + + std::string output; + StringOutputStream out(&output); + EXPECT_TRUE(generator.Generate("lib", &out)); + out.Flush(); + + EXPECT_THAT(output, HasSubstr("public static final int[] MyStyleable={")); + EXPECT_THAT(output, HasSubstr("0x01010000, 0x00010000")); + EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_android_framework_attr=0;")); + EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_dynamic_attr=1;")); +} + } // namespace aapt diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp index ba9646f9aeb4..ec3c5431c7a3 100644 --- a/tools/aapt2/jni/aapt2_jni.cpp +++ b/tools/aapt2/jni/aapt2_jni.cpp @@ -139,5 +139,5 @@ JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink(JNIEnv* JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping( JNIEnv *env, jclass aapt_obj) { - // This is just a dummy method to see if the library has been loaded. + // This is just a no-op method to see if the library has been loaded. } diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index c813a446b8db..3d8c25ebcbdd 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -376,10 +376,6 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, }); manifest_action["instrumentation"]["meta-data"] = meta_data_action; - // TODO moltmann: Remove - manifest_action["feature"]; - manifest_action["feature"]["inherit-from"]; - manifest_action["attribution"]; manifest_action["attribution"]["inherit-from"]; manifest_action["original-package"]; @@ -397,6 +393,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["protected-broadcast"]; manifest_action["adopt-permissions"]; manifest_action["uses-permission"]; + manifest_action["uses-permission"]["required-feature"].Action(RequiredNameIsNotEmpty); + manifest_action["uses-permission"]["required-not-feature"].Action(RequiredNameIsNotEmpty); manifest_action["uses-permission-sdk-23"]; manifest_action["permission"]; manifest_action["permission"]["meta-data"] = meta_data_action; @@ -426,6 +424,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, application_action.Action(OptionalNameIsJavaClassName); application_action["uses-library"].Action(RequiredNameIsNotEmpty); + application_action["uses-native-library"].Action(RequiredNameIsNotEmpty); application_action["library"].Action(RequiredNameIsNotEmpty); application_action["profileable"]; @@ -573,8 +572,8 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { } xml::XmlActionExecutorPolicy policy = options_.warn_validation - ? xml::XmlActionExecutorPolicy::kWhitelistWarning - : xml::XmlActionExecutorPolicy::kWhitelist; + ? xml::XmlActionExecutorPolicy::kAllowListWarning + : xml::XmlActionExecutorPolicy::kAllowList; if (!executor.Execute(policy, context->GetDiagnostics(), doc)) { return false; } diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index c25e4503a208..ad56092754aa 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -31,11 +31,11 @@ namespace aapt { TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options) - : context_(context), master_table_(out_table), options_(options) { + : context_(context), main_table_(out_table), options_(options) { // Create the desired package that all tables will be merged into. - master_package_ = - master_table_->CreatePackage(context_->GetCompilationPackage(), context_->GetPackageId()); - CHECK(master_package_ != nullptr) << "package name or ID already taken"; + main_package_ = + main_table_->CreatePackage(context_->GetCompilationPackage(), context_->GetPackageId()); + CHECK(main_package_ != nullptr) << "package name or ID already taken"; } bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) { @@ -235,7 +235,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, bool error = false; for (auto& src_type : src_package->types) { - ResourceTableType* dst_type = master_package_->FindOrCreateType(src_type->type); + ResourceTableType* dst_type = main_package_->FindOrCreateType(src_type->type); if (!MergeType(context_, src, dst_type, src_type.get())) { error = true; continue; @@ -279,7 +279,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, if (dst_config_value) { CollisionResult collision_result = MergeConfigValue( context_, res_name, overlay, options_.override_styles_instead_of_overlaying, - dst_config_value, src_config_value.get(), &master_table_->string_pool); + dst_config_value, src_config_value.get(), &main_table_->string_pool); if (collision_result == CollisionResult::kConflict) { error = true; continue; @@ -298,7 +298,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, if (mangle_package) { new_file_ref = CloneAndMangleFile(src_package->name, *f); } else { - new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool)); + new_file_ref = std::unique_ptr<FileReference>(f->Clone(&main_table_->string_pool)); } dst_config_value->value = std::move(new_file_ref); @@ -307,7 +307,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, ? dst_config_value->value->GetComment() : Maybe<std::string>(); dst_config_value->value = std::unique_ptr<Value>( - src_config_value->value->Clone(&master_table_->string_pool)); + src_config_value->value->Clone(&main_table_->string_pool)); // Keep the comment from the original resource and ignore all comments from overlaying // resources @@ -328,14 +328,14 @@ std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile( std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string()); std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string(); std::unique_ptr<FileReference> new_file_ref = - util::make_unique<FileReference>(master_table_->string_pool.MakeRef(newPath)); + util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath)); new_file_ref->SetComment(file_ref.GetComment()); new_file_ref->SetSource(file_ref.GetSource()); new_file_ref->type = file_ref.type; new_file_ref->file = file_ref.file; return new_file_ref; } - return std::unique_ptr<FileReference>(file_ref.Clone(&master_table_->string_pool)); + return std::unique_ptr<FileReference>(file_ref.Clone(&main_table_->string_pool)); } bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) { diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index a35a134a887d..e01a0c186392 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -80,9 +80,9 @@ class TableMerger { DISALLOW_COPY_AND_ASSIGN(TableMerger); IAaptContext* context_; - ResourceTable* master_table_; + ResourceTable* main_table_; TableMergerOptions options_; - ResourceTablePackage* master_package_; + ResourceTablePackage* main_package_; std::set<std::string> merged_packages_; bool MergeImpl(const Source& src, ResourceTable* src_table, bool overlay, bool allow_new); diff --git a/tools/aapt2/link/XmlCompatVersioner.cpp b/tools/aapt2/link/XmlCompatVersioner.cpp index 20ebdc696814..6937ca961f06 100644 --- a/tools/aapt2/link/XmlCompatVersioner.cpp +++ b/tools/aapt2/link/XmlCompatVersioner.cpp @@ -143,8 +143,8 @@ std::vector<std::unique_ptr<xml::XmlResource>> XmlCompatVersioner::Process( // Iterate from smallest to largest API version. for (ApiVersion api : apis_referenced) { - std::set<ApiVersion> dummy; - versioned_docs.push_back(ProcessDoc(api, api_range.end, doc, &dummy)); + std::set<ApiVersion> tmp; + versioned_docs.push_back(ProcessDoc(api, api_range.end, doc, &tmp)); } return versioned_docs; } diff --git a/tools/aapt2/optimize/ResourceFilter.cpp b/tools/aapt2/optimize/ResourceFilter.cpp index 250b65197a7d..08c045bf68f7 100644 --- a/tools/aapt2/optimize/ResourceFilter.cpp +++ b/tools/aapt2/optimize/ResourceFilter.cpp @@ -20,8 +20,8 @@ namespace aapt { -ResourceFilter::ResourceFilter(const std::unordered_set<ResourceName>& blacklist) - : blacklist_(blacklist) { +ResourceFilter::ResourceFilter(const std::unordered_set<ResourceName>& exclude_list) + : exclude_list_(exclude_list) { } bool ResourceFilter::Consume(IAaptContext* context, ResourceTable* table) { @@ -29,7 +29,7 @@ bool ResourceFilter::Consume(IAaptContext* context, ResourceTable* table) { for (auto& type : package->types) { for (auto it = type->entries.begin(); it != type->entries.end(); ) { ResourceName resource = ResourceName({}, type->type, (*it)->name); - if (blacklist_.find(resource) != blacklist_.end()) { + if (exclude_list_.find(resource) != exclude_list_.end()) { it = type->entries.erase(it); } else { ++it; diff --git a/tools/aapt2/optimize/ResourceFilter.h b/tools/aapt2/optimize/ResourceFilter.h index d4baf654b0ff..a2645333e497 100644 --- a/tools/aapt2/optimize/ResourceFilter.h +++ b/tools/aapt2/optimize/ResourceFilter.h @@ -25,16 +25,16 @@ namespace aapt { -// Removes non-whitelisted entries from resource table. +// Removes exclude-listed entries from resource table. class ResourceFilter : public IResourceTableConsumer { public: - explicit ResourceFilter(const std::unordered_set<ResourceName>& blacklist); + explicit ResourceFilter(const std::unordered_set<ResourceName>& exclude_list); bool Consume(IAaptContext* context, ResourceTable* table) override; private: DISALLOW_COPY_AND_ASSIGN(ResourceFilter); - std::unordered_set<ResourceName> blacklist_; + std::unordered_set<ResourceName> exclude_list_; }; } // namespace aapt diff --git a/tools/aapt2/optimize/ResourceFilter_test.cpp b/tools/aapt2/optimize/ResourceFilter_test.cpp index ef57f9c56dab..34d8fd280fa9 100644 --- a/tools/aapt2/optimize/ResourceFilter_test.cpp +++ b/tools/aapt2/optimize/ResourceFilter_test.cpp @@ -31,22 +31,22 @@ TEST(ResourceFilterTest, SomeValuesAreFilteredOut) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddString("android:string/notblacklisted", ResourceId{}, default_config, "value") - .AddString("android:string/blacklisted", ResourceId{}, default_config, "value") - .AddString("android:string/notblacklisted2", ResourceId{}, default_config, "value") - .AddString("android:string/blacklisted2", ResourceId{}, default_config, "value") + .AddString("android:string/notexclude_listed", ResourceId{}, default_config, "value") + .AddString("android:string/exclude_listed", ResourceId{}, default_config, "value") + .AddString("android:string/notexclude_listed2", ResourceId{}, default_config, "value") + .AddString("android:string/exclude_listed2", ResourceId{}, default_config, "value") .Build(); - std::unordered_set<ResourceName> blacklist = { - ResourceName({}, ResourceType::kString, "blacklisted"), - ResourceName({}, ResourceType::kString, "blacklisted2"), + std::unordered_set<ResourceName> exclude_list = { + ResourceName({}, ResourceType::kString, "exclude_listed"), + ResourceName({}, ResourceType::kString, "exclude_listed2"), }; - ASSERT_TRUE(ResourceFilter(blacklist).Consume(context.get(), table.get())); - EXPECT_THAT(table, HasValue("android:string/notblacklisted", default_config)); - EXPECT_THAT(table, HasValue("android:string/notblacklisted2", default_config)); - EXPECT_THAT(table, Not(HasValue("android:string/blacklisted", default_config))); - EXPECT_THAT(table, Not(HasValue("android:string/blacklisted2", default_config))); + ASSERT_TRUE(ResourceFilter(exclude_list).Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/notexclude_listed", default_config)); + EXPECT_THAT(table, HasValue("android:string/notexclude_listed2", default_config)); + EXPECT_THAT(table, Not(HasValue("android:string/exclude_listed", default_config))); + EXPECT_THAT(table, Not(HasValue("android:string/exclude_listed2", default_config))); } TEST(ResourceFilterTest, TypeIsCheckedBeforeFiltering) { @@ -55,21 +55,21 @@ TEST(ResourceFilterTest, TypeIsCheckedBeforeFiltering) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddString("android:string/notblacklisted", ResourceId{}, default_config, "value") - .AddString("android:string/blacklisted", ResourceId{}, default_config, "value") - .AddString("android:drawable/notblacklisted", ResourceId{}, default_config, "value") - .AddString("android:drawable/blacklisted", ResourceId{}, default_config, "value") + .AddString("android:string/notexclude_listed", ResourceId{}, default_config, "value") + .AddString("android:string/exclude_listed", ResourceId{}, default_config, "value") + .AddString("android:drawable/notexclude_listed", ResourceId{}, default_config, "value") + .AddString("android:drawable/exclude_listed", ResourceId{}, default_config, "value") .Build(); - std::unordered_set<ResourceName> blacklist = { - ResourceName({}, ResourceType::kString, "blacklisted"), + std::unordered_set<ResourceName> exclude_list = { + ResourceName({}, ResourceType::kString, "exclude_listed"), }; - ASSERT_TRUE(ResourceFilter(blacklist).Consume(context.get(), table.get())); - EXPECT_THAT(table, HasValue("android:string/notblacklisted", default_config)); - EXPECT_THAT(table, HasValue("android:drawable/blacklisted", default_config)); - EXPECT_THAT(table, HasValue("android:drawable/notblacklisted", default_config)); - EXPECT_THAT(table, Not(HasValue("android:string/blacklisted", default_config))); + ASSERT_TRUE(ResourceFilter(exclude_list).Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/notexclude_listed", default_config)); + EXPECT_THAT(table, HasValue("android:drawable/exclude_listed", default_config)); + EXPECT_THAT(table, HasValue("android:drawable/notexclude_listed", default_config)); + EXPECT_THAT(table, Not(HasValue("android:string/exclude_listed", default_config))); } } // namespace aapt diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp index b54c155ddc2f..23c22185a53f 100644 --- a/tools/aapt2/test/Common.cpp +++ b/tools/aapt2/test/Common.cpp @@ -21,7 +21,7 @@ using android::ConfigDescription; namespace aapt { namespace test { -struct DummyDiagnosticsImpl : public IDiagnostics { +struct TestDiagnosticsImpl : public IDiagnostics { void Log(Level level, DiagMessageActual& actual_msg) override { switch (level) { case Level::Note: @@ -39,7 +39,7 @@ struct DummyDiagnosticsImpl : public IDiagnostics { }; IDiagnostics* GetDiagnostics() { - static DummyDiagnosticsImpl diag; + static TestDiagnosticsImpl diag; return &diag; } diff --git a/tools/aapt2/trace/TraceBuffer.h b/tools/aapt2/trace/TraceBuffer.h index 8618e0eeb731..ba751dd72f41 100644 --- a/tools/aapt2/trace/TraceBuffer.h +++ b/tools/aapt2/trace/TraceBuffer.h @@ -40,7 +40,7 @@ public: void BeginTrace(const std::string& tag); void EndTrace(); -// A master trace is required to flush events to disk. Events are formatted in systrace +// A main trace is required to flush events to disk. Events are formatted in systrace // json format. class FlushTrace { public: diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp index 2057ddcc9e45..4c921f13a3ca 100644 --- a/tools/aapt2/util/Maybe_test.cpp +++ b/tools/aapt2/util/Maybe_test.cpp @@ -22,32 +22,32 @@ namespace aapt { -struct Dummy { - Dummy() { +struct Fake { + Fake() { data = new int; *data = 1; - std::cerr << "Construct Dummy{0x" << (void*)this << "} with data=0x" + std::cerr << "Construct Fake{0x" << (void*)this << "} with data=0x" << (void*)data << std::endl; } - Dummy(const Dummy& rhs) { + Fake(const Fake& rhs) { data = nullptr; if (rhs.data) { data = new int; *data = *rhs.data; } - std::cerr << "CopyConstruct Dummy{0x" << (void*)this << "} from Dummy{0x" + std::cerr << "CopyConstruct Fake{0x" << (void*)this << "} from Fake{0x" << (const void*)&rhs << "}" << std::endl; } - Dummy(Dummy&& rhs) { + Fake(Fake&& rhs) { data = rhs.data; rhs.data = nullptr; - std::cerr << "MoveConstruct Dummy{0x" << (void*)this << "} from Dummy{0x" + std::cerr << "MoveConstruct Fake{0x" << (void*)this << "} from Fake{0x" << (const void*)&rhs << "}" << std::endl; } - Dummy& operator=(const Dummy& rhs) { + Fake& operator=(const Fake& rhs) { delete data; data = nullptr; @@ -55,22 +55,22 @@ struct Dummy { data = new int; *data = *rhs.data; } - std::cerr << "CopyAssign Dummy{0x" << (void*)this << "} from Dummy{0x" + std::cerr << "CopyAssign Fake{0x" << (void*)this << "} from Fake{0x" << (const void*)&rhs << "}" << std::endl; return *this; } - Dummy& operator=(Dummy&& rhs) { + Fake& operator=(Fake&& rhs) { delete data; data = rhs.data; rhs.data = nullptr; - std::cerr << "MoveAssign Dummy{0x" << (void*)this << "} from Dummy{0x" + std::cerr << "MoveAssign Fake{0x" << (void*)this << "} from Fake{0x" << (const void*)&rhs << "}" << std::endl; return *this; } - ~Dummy() { - std::cerr << "Destruct Dummy{0x" << (void*)this << "} with data=0x" + ~Fake() { + std::cerr << "Destruct Fake{0x" << (void*)this << "} with data=0x" << (void*)data << std::endl; delete data; } @@ -100,15 +100,15 @@ TEST(MaybeTest, MakeSomething) { } TEST(MaybeTest, Lifecycle) { - Maybe<Dummy> val = make_nothing<Dummy>(); + Maybe<Fake> val = make_nothing<Fake>(); - Maybe<Dummy> val2 = make_value(Dummy()); + Maybe<Fake> val2 = make_value(Fake()); } TEST(MaybeTest, MoveAssign) { - Maybe<Dummy> val; + Maybe<Fake> val; { - Maybe<Dummy> val2 = Dummy(); + Maybe<Fake> val2 = Fake(); val = std::move(val2); } } diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp index cb844f085ecc..fab17c949dd8 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -74,11 +74,11 @@ bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPi for (const StringPiece& element : *bread_crumb) { error_msg << "<" << element << ">"; } - if (policy == XmlActionExecutorPolicy::kWhitelistWarning) { + if (policy == XmlActionExecutorPolicy::kAllowListWarning) { // Treat the error only as a warning. diag->Warn(error_msg); } else { - // Policy is XmlActionExecutorPolicy::kWhitelist, we should fail. + // Policy is XmlActionExecutorPolicy::kAllowList, we should fail. diag->Error(error_msg); error = true; } @@ -94,7 +94,7 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di Element* el = doc->root.get(); if (!el) { - if (policy == XmlActionExecutorPolicy::kWhitelist) { + if (policy == XmlActionExecutorPolicy::kAllowList) { source_diag.Error(DiagMessage() << "no root XML tag found"); return false; } @@ -109,7 +109,7 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di return iter->second.Execute(policy, &bread_crumb, &source_diag, el); } - if (policy == XmlActionExecutorPolicy::kWhitelist) { + if (policy == XmlActionExecutorPolicy::kAllowList) { DiagMessage error_msg(el->line_number); error_msg << "unexpected root element "; PrintElementToDiagMessage(el, &error_msg); diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h index f689b2a3eaa8..a0ad1dadeddf 100644 --- a/tools/aapt2/xml/XmlActionExecutor.h +++ b/tools/aapt2/xml/XmlActionExecutor.h @@ -37,12 +37,12 @@ enum class XmlActionExecutorPolicy { // The actions defined must match and run. If an element is found that does not match an action, // an error occurs. // Note: namespaced elements are always ignored. - kWhitelist, + kAllowList, // The actions defined should match and run. if an element is found that does not match an // action, a warning is printed. // Note: namespaced elements are always ignored. - kWhitelistWarning, + kAllowListWarning, }; // Contains the actions to perform at this XML node. This is a recursive data structure that diff --git a/tools/aapt2/xml/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp index d39854e5fe4e..d47b49590f5c 100644 --- a/tools/aapt2/xml/XmlActionExecutor_test.cpp +++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp @@ -60,10 +60,10 @@ TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) { StdErrDiagnostics diag; doc = test::BuildXmlDom("<manifest><application /><activity /></manifest>"); - ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); + ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kAllowList, &diag, doc.get())); doc = test::BuildXmlDom("<manifest><application><activity /></application></manifest>"); - ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); + ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kAllowList, &diag, doc.get())); } } // namespace xml diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh index f25fcdcb7479..99aaa3c4d6e5 100755 --- a/tools/aosp/aosp_sha.sh +++ b/tools/aosp/aosp_sha.sh @@ -11,7 +11,7 @@ else if (( count == 0 )); then echo fi - echo -e "\033[0;31mThe source of truth for '$file' is in AOSP.\033[0m" + echo -e "\033[0;31;47mThe source of truth for '$file' is in AOSP.\033[0m" (( count++ )) done < <(git show --name-only --pretty=format: $1 | grep -- "$2") if (( count != 0 )); then diff --git a/tools/codegen/src/com/android/codegen/ClassInfo.kt b/tools/codegen/src/com/android/codegen/ClassInfo.kt index bf95a2eb2193..056898c9eca1 100644 --- a/tools/codegen/src/com/android/codegen/ClassInfo.kt +++ b/tools/codegen/src/com/android/codegen/ClassInfo.kt @@ -1,12 +1,14 @@ package com.android.codegen import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration +import com.github.javaparser.ast.body.TypeDeclaration open class ClassInfo(val classAst: ClassOrInterfaceDeclaration, val fileInfo: FileInfo) { val fileAst = fileInfo.fileAst val nestedClasses = classAst.members.filterIsInstance<ClassOrInterfaceDeclaration>() + val nestedTypes = classAst.members.filterIsInstance<TypeDeclaration<*>>() val superInterfaces = classAst.implementedTypes.map { it.asString() } val superClass = classAst.extendedTypes.getOrNull(0) diff --git a/tools/codegen/src/com/android/codegen/Debug.kt b/tools/codegen/src/com/android/codegen/Debug.kt new file mode 100644 index 000000000000..de3184468540 --- /dev/null +++ b/tools/codegen/src/com/android/codegen/Debug.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.codegen + +import com.github.javaparser.ast.Node + +fun Node.dump(indent: String = ""): String { + return buildString { + append(indent) + appendln(dumpOneLineNoChildren()) + childNodes.forEach { child -> + append(child.dump(indent + " ")) + } + } +} + +private fun Node.dumpOneLineNoChildren(): String { + val node = this + return buildString { + append(node::class.java.simpleName) + if (childNodes.isEmpty()) { + append(": ").append(node.toString()) + } + } +} diff --git a/tools/codegen/src/com/android/codegen/FileInfo.kt b/tools/codegen/src/com/android/codegen/FileInfo.kt index 909472640f29..a1d0389b0041 100644 --- a/tools/codegen/src/com/android/codegen/FileInfo.kt +++ b/tools/codegen/src/com/android/codegen/FileInfo.kt @@ -272,7 +272,7 @@ class FileInfo( /** Debug info */ fun summary(): String = when(this) { is Code -> "${javaClass.simpleName}(${lines.size} lines): ${lines.getOrNull(0)?.take(70) ?: ""}..." - is DataClass -> "DataClass ${ast.nameAsString}:\n" + + is DataClass -> "DataClass ${ast.nameAsString} nested:${ast.nestedTypes.map { it.nameAsString }}:\n" + chunks.joinToString("\n") { it.summary() } + "\n//end ${ast.nameAsString}" } diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt index 5a96cf1d9bdb..6c6d011cfede 100644 --- a/tools/codegen/src/com/android/codegen/Generators.kt +++ b/tools/codegen/src/com/android/codegen/Generators.kt @@ -3,7 +3,10 @@ package com.android.codegen import com.github.javaparser.ast.body.FieldDeclaration import com.github.javaparser.ast.body.MethodDeclaration import com.github.javaparser.ast.body.VariableDeclarator -import com.github.javaparser.ast.expr.* +import com.github.javaparser.ast.expr.AnnotationExpr +import com.github.javaparser.ast.expr.ArrayInitializerExpr +import com.github.javaparser.ast.expr.LiteralExpr +import com.github.javaparser.ast.expr.UnaryExpr import java.io.File @@ -13,10 +16,7 @@ import java.io.File fun ClassPrinter.generateConstDefs() { val consts = classAst.fields.filter { it.isStatic && it.isFinal && it.variables.all { variable -> - val initializer = variable.initializer.orElse(null) - val isLiteral = initializer is LiteralExpr - || (initializer is UnaryExpr && initializer.expression is LiteralExpr) - isLiteral && variable.type.asString() in listOf("int", "String") + variable.type.asString() in listOf("int", "String") } && it.annotations.none { it.nameAsString == DataClassSuppressConstDefs } }.flatMap { field -> field.variables.map { it to field } } val intConsts = consts.filter { it.first.type.asString() == "int" } @@ -703,7 +703,7 @@ fun ClassPrinter.generateSetters() { generateFieldJavadoc(forceHide = FeatureFlag.SETTERS.hidden) +GENERATED_MEMBER_HEADER - "public $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" { + "public @$NonNull $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" { generateSetFrom("value") +"return this;" } diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt index d6953c00fc0b..83108e5ae109 100644 --- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt +++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt @@ -41,7 +41,10 @@ private fun ClassPrinter.generateInputSignaturesForClass(classAst: ClassOrInterf } } + ("class ${classAst.nameAsString}" + " extends ${classAst.extendedTypes.map { getFullClassName(it) }.ifEmpty { listOf("java.lang.Object") }.joinToString(", ")}" + - " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]") + " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]") + + classAst.nestedNonDataClasses.flatMap { nestedClass -> + generateInputSignaturesForClass(nestedClass) + } } private fun ClassPrinter.annotationsToString(annotatedAst: NodeWithAnnotations<*>): String { @@ -60,6 +63,7 @@ private fun ClassPrinter.annotationToString(ann: AnnotationExpr?): String { append("@") append(getFullClassName(ann.nameAsString)) if (ann is MarkerAnnotationExpr) return@buildString + if (!ann.nameAsString.startsWith("DataClass")) return@buildString append("(") @@ -125,7 +129,7 @@ private fun ClassPrinter.getFullClassName(className: String): String { if (classAst.nameAsString == className) return thisPackagePrefix + classAst.nameAsString - nestedClasses.find { + nestedTypes.find { it.nameAsString == className }?.let { return thisClassPrefix + it.nameAsString } @@ -141,6 +145,8 @@ private fun ClassPrinter.getFullClassName(className: String): String { if (className[0].isLowerCase()) return className //primitive + if (className[0] == '?') return className //wildcard + return thisPackagePrefix + className } diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt index 6f740cd663e3..2e176c3d3bec 100644 --- a/tools/codegen/src/com/android/codegen/SharedConstants.kt +++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt @@ -1,7 +1,7 @@ package com.android.codegen const val CODEGEN_NAME = "codegen" -const val CODEGEN_VERSION = "1.0.15" +const val CODEGEN_VERSION = "1.0.20" const val CANONICAL_BUILDER_CLASS = "Builder" const val BASE_BUILDER_CLASS = "BaseBuilder" diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt index c19ae3b0b11f..7cfa7847fcff 100644 --- a/tools/codegen/src/com/android/codegen/Utils.kt +++ b/tools/codegen/src/com/android/codegen/Utils.kt @@ -103,6 +103,10 @@ val TypeDeclaration<*>.nestedTypes get() = childNodes.filterIsInstance<TypeDecla val TypeDeclaration<*>.nestedDataClasses get() = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>() .filter { it.annotations.any { it.nameAsString.endsWith("DataClass") } } +val TypeDeclaration<*>.nestedNonDataClasses get() + = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>() + .filter { it.annotations.none { it.nameAsString.endsWith("DataClass") } } + .filterNot { it.isInterface } val TypeDeclaration<*>.startLine get() = range.get()!!.begin.line inline fun <T> List<T>.forEachSequentialPair(action: (T, T?) -> Unit) { diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index a4a315b7e371..f0b759547a93 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -316,20 +316,25 @@ def get_emoji_font(): def check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji): coverage = get_emoji_map(emoji_font) + + errors = [] + for sequence in all_emoji: - assert sequence in coverage, ( - '%s is not supported in the emoji font.' % printable(sequence)) + if not sequence in coverage: + errors.append('%s is not supported in the emoji font.' % printable(sequence)) for sequence in coverage: if sequence in {0x0000, 0x000D, 0x0020}: # The font needs to support a few extra characters, which is OK continue - assert sequence in all_emoji, ( - 'Emoji font should not support %s.' % printable(sequence)) + if sequence not in all_emoji: + errors.append('%s support unexpected in the emoji font.' % printable(sequence)) for first, second in equivalent_emoji.items(): - assert coverage[first] == coverage[second], ( - '%s and %s should map to the same glyph.' % ( + if first not in coverage or second not in coverage: + continue # sequence will be reported missing + if coverage[first] != coverage[second]: + errors.append('%s and %s should map to the same glyph.' % ( printable(first), printable(second))) @@ -344,11 +349,13 @@ def check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji): while equivalent_seq in equivalent_emoji: equivalent_seq = equivalent_emoji[equivalent_seq] equivalent_seqs.add(equivalent_seq) - assert len(equivalent_seqs) == 1, ( - 'The sequences %s should not result in the same glyph %s' % ( + if len(equivalent_seqs) != 1: + errors.append('The sequences %s should not result in the same glyph %s' % ( printable(equivalent_seqs), glyph)) + assert not errors, '%d emoji font errors:\n%s\n%d emoji font coverage errors' % (len(errors), '\n'.join(errors), len(errors)) + def check_emoji_defaults(default_emoji): missing_text_chars = _emoji_properties['Emoji'] - default_emoji diff --git a/tools/powerstats/Android.bp b/tools/powerstats/Android.bp new file mode 100644 index 000000000000..af41144167a9 --- /dev/null +++ b/tools/powerstats/Android.bp @@ -0,0 +1,10 @@ +java_binary_host { + name: "PowerStatsServiceProtoParser", + manifest: "PowerStatsServiceProtoParser_manifest.txt", + srcs: [ + "*.java", + ], + static_libs: [ + "platformprotos", + ], +} diff --git a/tools/powerstats/PowerStatsServiceProtoParser.java b/tools/powerstats/PowerStatsServiceProtoParser.java new file mode 100644 index 000000000000..76edd6341b55 --- /dev/null +++ b/tools/powerstats/PowerStatsServiceProtoParser.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.powerstats; + +import java.io.FileInputStream; +import java.io.IOException; + +/** + * This class implements a utility to parse ODPM data out + * of incident reports contained in bugreports. The data + * is output to STDOUT in csv format. + */ +public class PowerStatsServiceProtoParser { + private static void printEnergyMeterInfo(PowerStatsServiceMeterProto proto) { + String csvHeader = new String(); + for (int i = 0; i < proto.getChannelInfoCount(); i++) { + ChannelInfoProto energyMeterInfo = proto.getChannelInfo(i); + csvHeader += "Index,Timestamp," + energyMeterInfo.getChannelId() + + "/" + energyMeterInfo.getChannelName() + ","; + } + System.out.println(csvHeader); + } + + private static void printEnergyMeasurements(PowerStatsServiceMeterProto proto) { + int energyMeterInfoCount = proto.getChannelInfoCount(); + + if (energyMeterInfoCount > 0) { + int energyMeasurementCount = proto.getEnergyMeasurementCount(); + int energyMeasurementSetCount = energyMeasurementCount / energyMeterInfoCount; + + for (int i = 0; i < energyMeasurementSetCount; i++) { + String csvRow = new String(); + for (int j = 0; j < energyMeterInfoCount; j++) { + EnergyMeasurementProto energyMeasurement = + proto.getEnergyMeasurement(i * energyMeterInfoCount + j); + csvRow += energyMeasurement.getChannelId() + "," + + energyMeasurement.getTimestampMs() + "," + + energyMeasurement.getEnergyUws() + ","; + } + System.out.println(csvRow); + } + } else { + System.out.println("Error: energyMeterInfoCount is zero"); + } + } + + private static void printEnergyConsumerId(PowerStatsServiceModelProto proto) { + String csvHeader = new String(); + for (int i = 0; i < proto.getEnergyConsumerIdCount(); i++) { + EnergyConsumerIdProto energyConsumerId = proto.getEnergyConsumerId(i); + csvHeader += "Index,Timestamp," + energyConsumerId.getEnergyConsumerId() + ","; + } + System.out.println(csvHeader); + } + + private static void printEnergyConsumerResults(PowerStatsServiceModelProto proto) { + int energyConsumerIdCount = proto.getEnergyConsumerIdCount(); + + if (energyConsumerIdCount > 0) { + int energyConsumerResultCount = proto.getEnergyConsumerResultCount(); + int energyConsumerResultSetCount = energyConsumerResultCount / energyConsumerIdCount; + + for (int i = 0; i < energyConsumerResultSetCount; i++) { + String csvRow = new String(); + for (int j = 0; j < energyConsumerIdCount; j++) { + EnergyConsumerResultProto energyConsumerResult = + proto.getEnergyConsumerResult(i * energyConsumerIdCount + j); + csvRow += energyConsumerResult.getEnergyConsumerId() + "," + + energyConsumerResult.getTimestampMs() + "," + + energyConsumerResult.getEnergyUws() + ","; + } + System.out.println(csvRow); + } + } else { + System.out.println("Error: energyConsumerIdCount is zero"); + } + } + + private static void generateCsvFile(String pathToIncidentReport) { + try { + // Print power meter data. + IncidentReportMeterProto irMeterProto = + IncidentReportMeterProto.parseFrom(new FileInputStream(pathToIncidentReport)); + + if (irMeterProto.hasIncidentReport()) { + PowerStatsServiceMeterProto pssMeterProto = irMeterProto.getIncidentReport(); + printEnergyMeterInfo(pssMeterProto); + printEnergyMeasurements(pssMeterProto); + } else { + System.out.println("Meter incident report not found. Exiting."); + } + + // Print power model data. + IncidentReportModelProto irModelProto = + IncidentReportModelProto.parseFrom(new FileInputStream(pathToIncidentReport)); + + if (irModelProto.hasIncidentReport()) { + PowerStatsServiceModelProto pssModelProto = irModelProto.getIncidentReport(); + printEnergyConsumerId(pssModelProto); + printEnergyConsumerResults(pssModelProto); + } else { + System.out.println("Model incident report not found. Exiting."); + } + } catch (IOException e) { + System.out.println("Unable to open incident report file: " + pathToIncidentReport); + System.out.println(e); + } + } + + /** + * This is the entry point to parse the ODPM data out of incident reports. + * It requires one argument which is the path to the incident_report.proto + * file captured in a bugreport. + * + * @param args Path to incident_report.proto passed in from command line. + */ + public static void main(String[] args) { + if (args.length > 0) { + generateCsvFile(args[0]); + } else { + System.err.println("Usage: PowerStatsServiceProtoParser <incident_report.proto>"); + System.err.println("Missing path to incident_report.proto. Exiting."); + System.exit(1); + } + } +} diff --git a/tools/powerstats/PowerStatsServiceProtoParser_manifest.txt b/tools/powerstats/PowerStatsServiceProtoParser_manifest.txt new file mode 100644 index 000000000000..5df12118ce80 --- /dev/null +++ b/tools/powerstats/PowerStatsServiceProtoParser_manifest.txt @@ -0,0 +1 @@ +Main-class: com.android.server.powerstats.PowerStatsServiceProtoParser diff --git a/tools/processors/intdef_mappings/Android.bp b/tools/processors/intdef_mappings/Android.bp new file mode 100644 index 000000000000..e255f7c784d3 --- /dev/null +++ b/tools/processors/intdef_mappings/Android.bp @@ -0,0 +1,33 @@ +java_plugin { + name: "intdef-annotation-processor", + + processor_class: "android.processor.IntDefProcessor", + + srcs: [ + ":framework-annotations", + "src/**/*.java", + "src/**/*.kt" + ], + + use_tools_jar: true, +} + +java_test_host { + name: "intdef-annotation-processor-test", + + srcs: [ + "test/**/*.java", + "test/**/*.kt" + ], + java_resource_dirs: ["test/resources"], + + static_libs: [ + "compile-testing-prebuilt", + "truth-prebuilt", + "junit", + "guava", + "intdef-annotation-processor" + ], + + test_suites: ["general-tests"], +}
\ No newline at end of file diff --git a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt new file mode 100644 index 000000000000..84faeea36eea --- /dev/null +++ b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.processor + +import android.annotation.IntDef +import com.sun.source.tree.IdentifierTree +import com.sun.source.tree.MemberSelectTree +import com.sun.source.tree.NewArrayTree +import com.sun.source.util.SimpleTreeVisitor +import com.sun.source.util.Trees +import java.io.IOException +import java.io.Writer +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.RoundEnvironment +import javax.lang.model.SourceVersion +import javax.lang.model.element.AnnotationValue +import javax.lang.model.element.TypeElement +import javax.tools.Diagnostic.Kind +import javax.tools.StandardLocation.CLASS_OUTPUT +import kotlin.collections.set + + +/** + * The IntDefProcessor is intended to generate a mapping from ints to their respective string + * identifier for each IntDef for use by Winscope or any other tool which requires such a mapping. + * + * The processor will run when building :frameworks-all and dump all the IntDef mappings found the + * files the make up :frameworks-all as json to outputPath. + */ +class IntDefProcessor : AbstractProcessor() { + private val outputName = "intDefMapping.json" + + override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest() + + // Define what the annotation we care about are for compiler optimization + override fun getSupportedAnnotationTypes() = LinkedHashSet<String>().apply { + add(IntDef::class.java.name) + } + + override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { + // There should only be one matching annotation definition for intDef + val annotationType = annotations.firstOrNull() ?: return false + val annotatedElements = roundEnv.getElementsAnnotatedWith(annotationType) + + val annotationTypeToIntDefMapping = annotatedElements.associate { annotatedElement -> + val type = (annotatedElement as TypeElement).qualifiedName.toString() + val mapping = generateIntDefMapping(annotatedElement, annotationType) + val intDef = annotatedElement.getAnnotation(IntDef::class.java) + type to IntDefMapping(mapping, intDef.flag) + } + + try { + outputToFile(annotationTypeToIntDefMapping) + } catch (e: IOException) { + error("Failed to write IntDef mappings :: $e") + } + return false + } + + private fun generateIntDefMapping( + annotatedElement: TypeElement, + annotationType: TypeElement + ): Map<Int, String> { + // LinkedHashMap makes sure ordering is the same as in the code + val mapping = LinkedHashMap<Int, String>() + + val annotationMirror = annotatedElement.annotationMirrors + // Should only ever be one matching this condition + .first { it.annotationType.asElement() == annotationType } + + val value = annotationMirror.elementValues.entries + .first { entry -> entry.key.simpleName.contentEquals("value") } + .value + + val trees = Trees.instance(processingEnv) + val tree = trees.getTree(annotatedElement, annotationMirror, value) + + val identifiers = ArrayList<String>() + tree.accept(IdentifierVisitor(), identifiers) + + val values = value.value as List<AnnotationValue> + + for (i in identifiers.indices) { + mapping[values[i].value as Int] = identifiers[i] + } + + return mapping + } + + private class IdentifierVisitor : SimpleTreeVisitor<Void, ArrayList<String>>() { + override fun visitNewArray(node: NewArrayTree, indentifiers: ArrayList<String>): Void? { + for (initializer in node.initializers) { + initializer.accept(this, indentifiers) + } + + return null + } + + override fun visitMemberSelect(node: MemberSelectTree, indentifiers: ArrayList<String>): + Void? { + indentifiers.add(node.identifier.toString()) + + return null + } + + override fun visitIdentifier(node: IdentifierTree, indentifiers: ArrayList<String>): Void? { + indentifiers.add(node.name.toString()) + + return null + } + } + + @Throws(IOException::class) + private fun outputToFile(annotationTypeToIntDefMapping: Map<String, IntDefMapping>) { + val resource = processingEnv.filer.createResource( + CLASS_OUTPUT, "com.android.winscope", outputName) + val writer = resource.openWriter() + serializeTo(annotationTypeToIntDefMapping, writer) + writer.close() + } + + private fun error(message: String) { + processingEnv.messager.printMessage(Kind.ERROR, message) + } + + private fun note(message: String) { + processingEnv.messager.printMessage(Kind.NOTE, message) + } + + class IntDefMapping(val mapping: Map<Int, String>, val flag: Boolean) { + val size + get() = this.mapping.size + + val entries + get() = this.mapping.entries + } + + companion object { + fun serializeTo( + annotationTypeToIntDefMapping: Map<String, IntDefMapping>, + writer: Writer + ) { + val indent = " " + + writer.appendln("{") + + val intDefTypesCount = annotationTypeToIntDefMapping.size + var currentIntDefTypesCount = 0 + for ((field, intDefMapping) in annotationTypeToIntDefMapping) { + writer.appendln("""$indent"$field": {""") + + // Start IntDef + + writer.appendln("""$indent$indent"flag": ${intDefMapping.flag},""") + + writer.appendln("""$indent$indent"values": {""") + intDefMapping.entries.joinTo(writer, separator = ",\n") { (value, identifier) -> + """$indent$indent$indent"$value": "$identifier"""" + } + writer.appendln() + writer.appendln("$indent$indent}") + + // End IntDef + + writer.append("$indent}") + if (++currentIntDefTypesCount < intDefTypesCount) { + writer.appendln(",") + } else { + writer.appendln("") + } + } + + writer.appendln("}") + } + } +} diff --git a/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt b/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt new file mode 100644 index 000000000000..c0c159c98aac --- /dev/null +++ b/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.processor + +import android.processor.IntDefProcessor.IntDefMapping +import com.google.common.collect.ObjectArrays.concat +import com.google.testing.compile.CompilationSubject.assertThat +import com.google.testing.compile.Compiler.javac +import com.google.testing.compile.JavaFileObjects +import junit.framework.Assert.assertEquals +import org.junit.Test +import java.io.StringWriter +import javax.tools.JavaFileObject +import javax.tools.StandardLocation.CLASS_OUTPUT + +/** + * Tests for [IntDefProcessor] + */ +class IntDefProcessorTest { + private val mAnnotations = arrayOf<JavaFileObject>( + JavaFileObjects.forSourceLines("android.annotation.IntDef", + "package android.annotation;", + "import java.lang.annotation.Retention;", + "import java.lang.annotation.Target;", + "import static java.lang.annotation.ElementType.ANNOTATION_TYPE;", + "import static java.lang.annotation.RetentionPolicy.SOURCE;", + "@Retention(SOURCE)", + "@Target({ANNOTATION_TYPE})", + "public @interface IntDef {", + " String[] prefix() default {};", + " String[] suffix() default {};", + " int[] value() default {};", + " boolean flag() default false;", + "}") + ) + + @Test + public fun annotationProcessorGeneratesMapping() { + val sources: Array<JavaFileObject> = arrayOf( + JavaFileObjects.forSourceLines( + "com.android.server.accessibility.magnification.MagnificationGestureMatcher", + "package com.android.server.accessibility.magnification;", + "import android.annotation.IntDef;", + "import java.lang.annotation.Retention;", + "import java.lang.annotation.RetentionPolicy;", + "class MagnificationGestureMatcher {", + " private static final int GESTURE_BASE = 100;", + " public static final int GESTURE_TWO_FINGER_DOWN = GESTURE_BASE + 1;", + " public static final int GESTURE_SWIPE = GESTURE_BASE + 2;", + " @IntDef(prefix = {\"GESTURE_MAGNIFICATION_\"}, value = {", + " GESTURE_TWO_FINGER_DOWN,", + " GESTURE_SWIPE", + " })", + " @Retention(RetentionPolicy.SOURCE)", + " @interface GestureId {}", + "}" + ), + JavaFileObjects.forSourceLines( + "android.service.storage.ExternalStorageService", + "package android.service.storage;", + "import android.annotation.IntDef;", + "import java.lang.annotation.Retention;", + "import java.lang.annotation.RetentionPolicy;", + "class MagnificationGestureMatcher {", + " public static final int FLAG_SESSION_TYPE_FUSE = 1 << 0;", + " public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 1 << 1;", + " @IntDef(flag = true, prefix = {\"FLAG_SESSION_\"},", + " value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE})", + " @Retention(RetentionPolicy.SOURCE)", + " public @interface SessionFlag {}", + "}" + ) + ) + + val expectedFile = """ + { + "com.android.server.accessibility.magnification.MagnificationGestureMatcher.GestureId": { + "flag": false, + "values": { + "101": "GESTURE_TWO_FINGER_DOWN", + "102": "GESTURE_SWIPE" + } + }, + "android.service.storage.MagnificationGestureMatcher.SessionFlag": { + "flag": true, + "values": { + "1": "FLAG_SESSION_TYPE_FUSE", + "2": "FLAG_SESSION_ATTRIBUTE_INDEXABLE" + } + } + } + + """.trimIndent() + + val filesToCompile = concat(mAnnotations, sources, JavaFileObject::class.java) + + val compilation = javac() + .withProcessors(IntDefProcessor()) + .compile(filesToCompile.toMutableList()) + + assertThat(compilation).succeeded() + assertThat(compilation).generatedFile(CLASS_OUTPUT, "com.android.winscope", + "intDefMapping.json").contentsAsUtf8String().isEqualTo(expectedFile) + } + + @Test + public fun serializesMappingCorrectly() { + val map = linkedMapOf( + "SimpleIntDef" to IntDefMapping(linkedMapOf( + 0x0001 to "VAL_1", + 0x0002 to "VAL_2", + 0x0003 to "VAL_3" + ), flag = false), + "Flags" to IntDefMapping(linkedMapOf( + 0b0001 to "PRIVATE_FLAG_1", + 0b0010 to "PRIVATE_FLAG_2", + 0b0100 to "PRIVATE_FLAG_3" + ), flag = true) + ) + + val writer = StringWriter() + IntDefProcessor.serializeTo(map, writer) + + val actualOutput = writer.toString() + val expectedOutput = """ + { + "SimpleIntDef": { + "flag": false, + "values": { + "1": "VAL_1", + "2": "VAL_2", + "3": "VAL_3" + } + }, + "Flags": { + "flag": true, + "values": { + "1": "PRIVATE_FLAG_1", + "2": "PRIVATE_FLAG_2", + "4": "PRIVATE_FLAG_3" + } + } + } + + """.trimIndent() + + assertEquals(actualOutput, expectedOutput) + } +}
\ No newline at end of file diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt index 51faa49a86cc..1aec9b812e61 100644 --- a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt +++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt @@ -98,8 +98,10 @@ class StaleDataclassProcessor: AbstractProcessor() { private fun elemToString(elem: Element): String { return buildString { - append(elem.modifiers.joinToString(" ") { it.name.toLowerCase() }).append(" ") - append(elem.annotationMirrors.joinToString(" ")).append(" ") + append(elem.modifiers.joinToString(" ") { it.name.toLowerCase() }) + append(" ") + append(elem.annotationMirrors.joinToString(" ", transform = { annotationToString(it) })) + append(" ") if (elem is Symbol) { if (elem.type is Type.MethodType) { append((elem.type as Type.MethodType).returnType) @@ -112,6 +114,14 @@ class StaleDataclassProcessor: AbstractProcessor() { } } + private fun annotationToString(ann: AnnotationMirror): String { + return if (ann.annotationType.toString().startsWith("com.android.internal.util.DataClass")) { + ann.toString() + } else { + ann.toString().substringBefore("(") + } + } + private fun processSingleFile(elementAnnotatedWithGenerated: Element) { val classElement = elementAnnotatedWithGenerated.enclosingElement diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp index ce551bd0cc10..0be80d31990a 100644 --- a/tools/protologtool/Android.bp +++ b/tools/protologtool/Android.bp @@ -2,9 +2,9 @@ java_library_host { name: "protologtool-lib", srcs: [ "src/com/android/protolog/tool/**/*.kt", + ":protolog-common-src", ], static_libs: [ - "protolog-common", "javaparser", "platformprotos", "jsonlib", diff --git a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt index a59038fc99a0..645c5672da64 100644 --- a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt +++ b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt @@ -16,16 +16,15 @@ package com.android.protolog.tool +import com.android.internal.protolog.ProtoLogFileProto +import com.android.internal.protolog.ProtoLogMessage +import com.android.internal.protolog.common.InvalidFormatStringException +import com.android.internal.protolog.common.LogDataType import com.android.json.stream.JsonReader -import com.android.server.protolog.common.InvalidFormatStringException -import com.android.server.protolog.common.LogDataType -import com.android.server.protolog.ProtoLogMessage -import com.android.server.protolog.ProtoLogFileProto import java.io.BufferedReader import java.io.InputStream import java.io.InputStreamReader import java.io.PrintStream -import java.lang.Exception import java.text.SimpleDateFormat import java.util.Date import java.util.Locale diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt index 75493b6427cb..42b628b0e262 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt @@ -17,7 +17,7 @@ package com.android.protolog.tool import com.android.protolog.tool.Constants.ENUM_VALUES_METHOD -import com.android.server.protolog.common.IProtoLogGroup +import com.android.internal.protolog.common.IProtoLogGroup import java.io.File import java.net.URLClassLoader diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt index 36ea41129450..27e61a139451 100644 --- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt +++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt @@ -16,7 +16,7 @@ package com.android.protolog.tool -import com.android.server.protolog.common.LogDataType +import com.android.internal.protolog.common.LogDataType import com.github.javaparser.StaticJavaParser import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.ast.NodeList @@ -89,7 +89,7 @@ class SourceTransformer( // Out: ProtoLog.e(GROUP, 1234, 0, null, arg) newCall.arguments.add(2, IntegerLiteralExpr(typeMask)) // Replace call to a stub method with an actual implementation. - // Out: com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, null, arg) + // Out: ProtoLogImpl.e(GROUP, 1234, null, arg) newCall.setScope(protoLogImplClassNode) // Create a call to ProtoLog$Cache.GROUP_enabled // Out: com.android.server.protolog.ProtoLog$Cache.GROUP_enabled @@ -119,9 +119,9 @@ class SourceTransformer( } blockStmt.addStatement(ExpressionStmt(newCall)) // Create an IF-statement with the previously created condition. - // Out: if (com.android.server.protolog.ProtoLogImpl.isEnabled(GROUP)) { + // Out: if (ProtoLogImpl.isEnabled(GROUP)) { // long protoLogParam0 = arg; - // com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0); + // ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0); // } ifStmt = IfStmt(isLogEnabled, blockStmt, null) } else { diff --git a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt index cf36651c3e39..3cfbb435a764 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt @@ -31,7 +31,7 @@ class CommandOptionsTest { private const val TEST_PROTOLOG_CLASS = "com.android.server.wm.ProtoLog" private const val TEST_PROTOLOGIMPL_CLASS = "com.android.server.wm.ProtoLogImpl" private const val TEST_PROTOLOGCACHE_CLASS = "com.android.server.wm.ProtoLog\$Cache" - private const val TEST_PROTOLOGGROUP_CLASS = "com.android.server.wm.ProtoLogGroup" + private const val TEST_PROTOLOGGROUP_CLASS = "com.android.internal.protolog.ProtoLogGroup" private const val TEST_PROTOLOGGROUP_JAR = "out/soong/.intermediates/frameworks/base/" + "services/core/services.core.wm.protologgroups/android_common/javac/" + "services.core.wm.protologgroups.jar" diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt index dd8a0b1c50b4..0d2b91d6cfb8 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt @@ -33,8 +33,8 @@ class EndToEndTest { val output = run( src = "frameworks/base/org/example/Example.java" to """ package org.example; - import com.android.server.protolog.common.ProtoLog; - import static com.android.server.wm.ProtoLogGroup.GROUP; + import com.android.internal.protolog.common.ProtoLog; + import static com.android.internal.protolog.ProtoLogGroup.GROUP; class Example { void method() { @@ -46,11 +46,11 @@ class EndToEndTest { """.trimIndent(), logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"), commandOptions = CommandOptions(arrayOf("transform-protolog-calls", - "--protolog-class", "com.android.server.protolog.common.ProtoLog", - "--protolog-impl-class", "com.android.server.protolog.ProtoLogImpl", + "--protolog-class", "com.android.internal.protolog.common.ProtoLog", + "--protolog-impl-class", "com.android.internal.protolog.ProtoLogImpl", "--protolog-cache-class", - "com.android.server.protolog.ProtoLog${"\$\$"}Cache", - "--loggroups-class", "com.android.server.wm.ProtoLogGroup", + "com.android.server.wm.ProtoLogCache", + "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup", "--loggroups-jar", "not_required.jar", "--output-srcjar", "out.srcjar", "frameworks/base/org/example/Example.java")) @@ -64,8 +64,8 @@ class EndToEndTest { val output = run( src = "frameworks/base/org/example/Example.java" to """ package org.example; - import com.android.server.protolog.common.ProtoLog; - import static com.android.server.wm.ProtoLogGroup.GROUP; + import com.android.internal.protolog.common.ProtoLog; + import static com.android.internal.protolog.ProtoLogGroup.GROUP; class Example { void method() { @@ -77,8 +77,8 @@ class EndToEndTest { """.trimIndent(), logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"), commandOptions = CommandOptions(arrayOf("generate-viewer-config", - "--protolog-class", "com.android.server.protolog.common.ProtoLog", - "--loggroups-class", "com.android.server.wm.ProtoLogGroup", + "--protolog-class", "com.android.internal.protolog.common.ProtoLog", + "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup", "--loggroups-jar", "not_required.jar", "--viewer-conf", "out.json", "frameworks/base/org/example/Example.java")) diff --git a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt index 04a3bfa499d8..67a31da87081 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt @@ -17,8 +17,8 @@ package com.android.protolog.tool import com.android.json.stream.JsonReader -import com.android.server.protolog.ProtoLogMessage -import com.android.server.protolog.ProtoLogFileProto +import com.android.internal.protolog.ProtoLogMessage +import com.android.internal.protolog.ProtoLogFileProto import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp index e3b6db08c503..43387fc054b5 100644 --- a/tools/stats_log_api_gen/Android.bp +++ b/tools/stats_log_api_gen/Android.bp @@ -121,10 +121,26 @@ cc_library { ], target: { android: { - shared_libs: ["libstatssocket"], + shared_libs: [ + "libstatssocket", + "libstatspull", + ], + export_shared_lib_headers: [ + "libstatssocket", + "libstatspull", + ], }, host: { - static_libs: ["libstatssocket"], + static_libs: [ + "libstatssocket", + "libstatspull", + "statsd-aidl-ndk_platform", + ], + shared_libs: ["libbinder_ndk"], + export_static_lib_headers: [ + "libstatssocket", + "libstatspull", + ], }, }, } diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index a230de46dcf3..fe6ca558a0ee 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -25,6 +25,7 @@ namespace android { namespace stats_log_api_gen { +using google::protobuf::OneofDescriptor; using google::protobuf::EnumDescriptor; using google::protobuf::FieldDescriptor; using google::protobuf::FileDescriptor; @@ -71,7 +72,7 @@ static void print_error(const FieldDescriptor* field, const char* format, ...) { SourceLocation loc; if (field->GetSourceLocation(&loc)) { - // TODO: this will work if we can figure out how to pass + // TODO(b/162454173): this will work if we can figure out how to pass // --include_source_info to protoc fprintf(stderr, "%s:%d: ", file->name().c_str(), loc.start_line); } else { @@ -110,7 +111,6 @@ static java_type_t java_type(const FieldDescriptor* field) { case FieldDescriptor::TYPE_GROUP: return JAVA_TYPE_UNKNOWN; case FieldDescriptor::TYPE_MESSAGE: - // TODO: not the final package name if (field->message_type()->full_name() == "android.os.statsd.AttributionNode") { return JAVA_TYPE_ATTRIBUTION_CHAIN; } else if (field->message_type()->full_name() == "android.os.statsd.KeyValuePair") { @@ -146,7 +146,7 @@ static java_type_t java_type(const FieldDescriptor* field) { void collate_enums(const EnumDescriptor& enumDescriptor, AtomField* atomField) { for (int i = 0; i < enumDescriptor.value_count(); i++) { atomField->enumValues[enumDescriptor.value(i)->number()] = - enumDescriptor.value(i)->name().c_str(); + enumDescriptor.value(i)->name(); } } @@ -396,16 +396,14 @@ int collate_atom(const Descriptor* atom, AtomDecl* atomDecl, vector<java_type_t> collate_enums(*field->enum_type(), &atField); } - // Generate signature for pushed atoms - if (atomDecl->code < PULL_ATOM_START_ID) { - if (javaType == JAVA_TYPE_ENUM) { - // All enums are treated as ints when it comes to function signatures. - signature->push_back(JAVA_TYPE_INT); - } else if (javaType == JAVA_TYPE_OBJECT && isBinaryField) { - signature->push_back(JAVA_TYPE_BYTE_ARRAY); - } else { - signature->push_back(javaType); - } + // Generate signature for atom. + if (javaType == JAVA_TYPE_ENUM) { + // All enums are treated as ints when it comes to function signatures. + signature->push_back(JAVA_TYPE_INT); + } else if (javaType == JAVA_TYPE_OBJECT && isBinaryField) { + signature->push_back(JAVA_TYPE_BYTE_ARRAY); + } else { + signature->push_back(javaType); } atomDecl->fields.push_back(atField); @@ -518,8 +516,7 @@ int collate_atoms(const Descriptor* descriptor, const string& moduleName, Atoms* shared_ptr<AtomDecl> atomDecl = make_shared<AtomDecl>(atomField->number(), atomField->name(), atom->name()); - if (atomDecl->code < PULL_ATOM_START_ID && - atomField->options().GetExtension(os::statsd::truncate_timestamp)) { + if (atomField->options().GetExtension(os::statsd::truncate_timestamp)) { addAnnotationToAtomDecl(atomDecl.get(), ATOM_ID_FIELD_NUMBER, ANNOTATION_ID_TRUNCATE_TIMESTAMP, ANNOTATION_TYPE_BOOL, AnnotationValue(true)); @@ -530,14 +527,30 @@ int collate_atoms(const Descriptor* descriptor, const string& moduleName, Atoms* vector<java_type_t> signature; errorCount += collate_atom(atom, atomDecl.get(), &signature); - if (atomDecl->primaryFields.size() != 0 && atomDecl->exclusiveField == 0) { + if (!atomDecl->primaryFields.empty() && atomDecl->exclusiveField == 0) { print_error(atomField, "Cannot have a primary field without an exclusive field: %s\n", atomField->name().c_str()); errorCount++; continue; } - FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet = atoms->signatureInfoMap[signature]; + const OneofDescriptor* oneofAtom = atomField->containing_oneof(); + if (oneofAtom == nullptr) { + print_error(atomField, "Atom is not declared in a `oneof` field: %s\n", + atomField->name().c_str()); + errorCount++; + continue; + } else if ((oneofAtom->name() != ONEOF_PUSHED_ATOM_NAME) && + (oneofAtom->name() != ONEOF_PULLED_ATOM_NAME)) { + print_error(atomField, "Atom is neither a pushed nor pulled atom: %s\n", + atomField->name().c_str()); + errorCount++; + continue; + } + + FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet = oneofAtom->name() == + ONEOF_PUSHED_ATOM_NAME ? atoms->signatureInfoMap[signature] : + atoms->pulledAtomsSignatureInfoMap[signature]; populateFieldNumberToAtomDeclSet(atomDecl, &fieldNumberToAtomDeclSet); atoms->decls.insert(atomDecl); @@ -556,13 +569,25 @@ int collate_atoms(const Descriptor* descriptor, const string& moduleName, Atoms* } if (dbg) { + // Signatures for pushed atoms. printf("signatures = [\n"); for (SignatureInfoMap::const_iterator it = atoms->signatureInfoMap.begin(); it != atoms->signatureInfoMap.end(); it++) { printf(" "); for (vector<java_type_t>::const_iterator jt = it->first.begin(); jt != it->first.end(); jt++) { - printf(" %d", (int)*jt); + printf(" %d", static_cast<int>(*jt)); + } + printf("\n"); + } + + // Signatures for pull atoms. + for (SignatureInfoMap::const_iterator it = atoms->pulledAtomsSignatureInfoMap.begin(); + it != atoms->pulledAtomsSignatureInfoMap.end(); it++) { + printf(" "); + for (vector<java_type_t>::const_iterator jt = it->first.begin(); jt != it->first.end(); + jt++) { + printf(" %d", static_cast<int>(*jt)); } printf("\n"); } diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h index 10b34ecf5f54..5d196c4b8290 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -29,6 +29,7 @@ namespace android { namespace stats_log_api_gen { +using google::protobuf::OneofDescriptor; using google::protobuf::Descriptor; using google::protobuf::FieldDescriptor; using std::map; @@ -41,6 +42,14 @@ const int PULL_ATOM_START_ID = 10000; const int FIRST_UID_IN_CHAIN_ID = 0; +/** + * The types of oneof atoms. + * + * `OneofDescriptor::name()` returns the name of the oneof. + */ +const char ONEOF_PUSHED_ATOM_NAME[] = "pushed"; +const char ONEOF_PULLED_ATOM_NAME[] = "pulled"; + enum AnnotationId : uint8_t { ANNOTATION_ID_IS_UID = 1, ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2, @@ -54,7 +63,7 @@ enum AnnotationId : uint8_t { const int ATOM_ID_FIELD_NUMBER = -1; -const string DEFAULT_MODULE_NAME = "DEFAULT"; +const char DEFAULT_MODULE_NAME[] = "DEFAULT"; /** * The types for atom parameters. @@ -86,9 +95,9 @@ union AnnotationValue { int intValue; bool boolValue; - AnnotationValue(const int value) : intValue(value) { + explicit AnnotationValue(const int value) : intValue(value) { } - AnnotationValue(const bool value) : boolValue(value) { + explicit AnnotationValue(const bool value) : boolValue(value) { } }; @@ -184,6 +193,7 @@ using SignatureInfoMap = map<vector<java_type_t>, FieldNumberToAtomDeclSet>; struct Atoms { SignatureInfoMap signatureInfoMap; + SignatureInfoMap pulledAtomsSignatureInfoMap; AtomDeclSet decls; AtomDeclSet non_chained_decls; SignatureInfoMap nonChainedSignatureInfoMap; diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp index f4c937c3f599..6fcf267cf39c 100644 --- a/tools/stats_log_api_gen/java_writer.cpp +++ b/tools/stats_log_api_gen/java_writer.cpp @@ -42,6 +42,7 @@ static int write_java_q_logger_class(FILE* out, const SignatureInfoMap& signatur static void write_java_annotation_constants(FILE* out) { fprintf(out, " // Annotation constants.\n"); + const map<AnnotationId, string>& ANNOTATION_ID_CONSTANTS = get_annotation_id_constants(); for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) { fprintf(out, " public static final byte %s = %hhu;\n", name.c_str(), id); } @@ -56,6 +57,7 @@ static void write_annotations(FILE* out, int argIndex, return; } const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second; + const map<AnnotationId, string>& ANNOTATION_ID_CONSTANTS = get_annotation_id_constants(); for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) { const string atomConstant = make_constant_name(atomDecl->name); fprintf(out, " if (%s == code) {\n", atomConstant.c_str()); @@ -96,7 +98,154 @@ static void write_annotations(FILE* out, int argIndex, } } -static int write_java_methods(FILE* out, const SignatureInfoMap& signatureInfoMap, +static void write_method_signature(FILE* out, const vector<java_type_t>& signature, + const AtomDecl& attributionDecl) { + int argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); + arg++) { + if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { + for (const auto& chainField : attributionDecl.fields) { + fprintf(out, ", %s[] %s", java_type_name(chainField.javaType), + chainField.name.c_str()); + } + } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { + fprintf(out, ", android.util.SparseArray<Object> valueMap"); + } else { + fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); + } + argIndex++; + } +} + +static int write_method_body(FILE* out, const vector<java_type_t>& signature, + const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet, + const AtomDecl& attributionDecl, const string& indent) { + // Start StatsEvent.Builder. + fprintf(out, + "%s final StatsEvent.Builder builder = " + "StatsEvent.newBuilder();\n", + indent.c_str()); + + // Write atom code. + fprintf(out, "%s builder.setAtomId(code);\n", indent.c_str()); + write_annotations(out, ATOM_ID_FIELD_NUMBER, fieldNumberToAtomDeclSet); + + // Write the args. + int argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); + arg++) { + switch (*arg) { + case JAVA_TYPE_BOOLEAN: + fprintf(out, "%s builder.writeBoolean(arg%d);\n", indent.c_str(), + argIndex); + break; + case JAVA_TYPE_INT: + case JAVA_TYPE_ENUM: + fprintf(out, "%s builder.writeInt(arg%d);\n", indent.c_str(), argIndex); + break; + case JAVA_TYPE_FLOAT: + fprintf(out, "%s builder.writeFloat(arg%d);\n", indent.c_str(), + argIndex); + break; + case JAVA_TYPE_LONG: + fprintf(out, "%s builder.writeLong(arg%d);\n", indent.c_str(), argIndex); + break; + case JAVA_TYPE_STRING: + fprintf(out, "%s builder.writeString(arg%d);\n", indent.c_str(), + argIndex); + break; + case JAVA_TYPE_BYTE_ARRAY: + fprintf(out, + "%s builder.writeByteArray(null == arg%d ? new byte[0] : " + "arg%d);\n", + indent.c_str(), argIndex, argIndex); + break; + case JAVA_TYPE_ATTRIBUTION_CHAIN: { + const char* uidName = attributionDecl.fields.front().name.c_str(); + const char* tagName = attributionDecl.fields.back().name.c_str(); + + fprintf(out, "%s builder.writeAttributionChain(\n", indent.c_str()); + fprintf(out, "%s null == %s ? new int[0] : %s,\n", + indent.c_str(), uidName, uidName); + fprintf(out, "%s null == %s ? new String[0] : %s);\n", + indent.c_str(), tagName, tagName); + break; + } + case JAVA_TYPE_KEY_VALUE_PAIR: + fprintf(out, "\n"); + fprintf(out, "%s // Write KeyValuePairs.\n", indent.c_str()); + fprintf(out, "%s final int count = valueMap.size();\n", indent.c_str()); + fprintf(out, "%s android.util.SparseIntArray intMap = null;\n", + indent.c_str()); + fprintf(out, "%s android.util.SparseLongArray longMap = null;\n", + indent.c_str()); + fprintf(out, "%s android.util.SparseArray<String> stringMap = null;\n", + indent.c_str()); + fprintf(out, "%s android.util.SparseArray<Float> floatMap = null;\n", + indent.c_str()); + fprintf(out, "%s for (int i = 0; i < count; i++) {\n", indent.c_str()); + fprintf(out, "%s final int key = valueMap.keyAt(i);\n", + indent.c_str()); + fprintf(out, "%s final Object value = valueMap.valueAt(i);\n", + indent.c_str()); + fprintf(out, "%s if (value instanceof Integer) {\n", indent.c_str()); + fprintf(out, "%s if (null == intMap) {\n", indent.c_str()); + fprintf(out, + "%s intMap = new " + "android.util.SparseIntArray();\n", + indent.c_str()); + fprintf(out, "%s }\n", indent.c_str()); + fprintf(out, "%s intMap.put(key, (Integer) value);\n", + indent.c_str()); + fprintf(out, "%s } else if (value instanceof Long) {\n", + indent.c_str()); + fprintf(out, "%s if (null == longMap) {\n", indent.c_str()); + fprintf(out, + "%s longMap = new " + "android.util.SparseLongArray();\n", + indent.c_str()); + fprintf(out, "%s }\n", indent.c_str()); + fprintf(out, "%s longMap.put(key, (Long) value);\n", + indent.c_str()); + fprintf(out, "%s } else if (value instanceof String) {\n", + indent.c_str()); + fprintf(out, "%s if (null == stringMap) {\n", indent.c_str()); + fprintf(out, + "%s stringMap = new " + "android.util.SparseArray<>();\n", + indent.c_str()); + fprintf(out, "%s }\n", indent.c_str()); + fprintf(out, "%s stringMap.put(key, (String) value);\n", + indent.c_str()); + fprintf(out, "%s } else if (value instanceof Float) {\n", + indent.c_str()); + fprintf(out, "%s if (null == floatMap) {\n", indent.c_str()); + fprintf(out, + "%s floatMap = new " + "android.util.SparseArray<>();\n", + indent.c_str()); + fprintf(out, "%s }\n", indent.c_str()); + fprintf(out, "%s floatMap.put(key, (Float) value);\n", + indent.c_str()); + fprintf(out, "%s }\n", indent.c_str()); + fprintf(out, "%s }\n", indent.c_str()); + fprintf(out, + "%s builder.writeKeyValuePairs(" + "intMap, longMap, stringMap, floatMap);\n", + indent.c_str()); + break; + default: + // Unsupported types: OBJECT, DOUBLE. + fprintf(stderr, "Encountered unsupported type."); + return 1; + } + write_annotations(out, argIndex, fieldNumberToAtomDeclSet); + argIndex++; + } + return 0; +} + +static int write_java_pushed_methods(FILE* out, const SignatureInfoMap& signatureInfoMap, const AtomDecl& attributionDecl, const bool supportQ) { for (auto signatureInfoMapIt = signatureInfoMap.begin(); signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) { @@ -104,21 +253,7 @@ static int write_java_methods(FILE* out, const SignatureInfoMap& signatureInfoMa fprintf(out, " public static void write(int code"); const vector<java_type_t>& signature = signatureInfoMapIt->first; const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet = signatureInfoMapIt->second; - int argIndex = 1; - for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); - arg++) { - if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { - fprintf(out, ", %s[] %s", java_type_name(chainField.javaType), - chainField.name.c_str()); - } - } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { - fprintf(out, ", android.util.SparseArray<Object> valueMap"); - } else { - fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); - } - argIndex++; - } + write_method_signature(out, signature, attributionDecl); fprintf(out, ") {\n"); // Print method body. @@ -128,130 +263,13 @@ static int write_java_methods(FILE* out, const SignatureInfoMap& signatureInfoMa indent = " "; } - // Start StatsEvent.Builder. - fprintf(out, - "%s final StatsEvent.Builder builder = " - "StatsEvent.newBuilder();\n", - indent.c_str()); - - // Write atom code. - fprintf(out, "%s builder.setAtomId(code);\n", indent.c_str()); - write_annotations(out, ATOM_ID_FIELD_NUMBER, fieldNumberToAtomDeclSet); - - // Write the args. - argIndex = 1; - for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); - arg++) { - switch (*arg) { - case JAVA_TYPE_BOOLEAN: - fprintf(out, "%s builder.writeBoolean(arg%d);\n", indent.c_str(), - argIndex); - break; - case JAVA_TYPE_INT: - case JAVA_TYPE_ENUM: - fprintf(out, "%s builder.writeInt(arg%d);\n", indent.c_str(), argIndex); - break; - case JAVA_TYPE_FLOAT: - fprintf(out, "%s builder.writeFloat(arg%d);\n", indent.c_str(), - argIndex); - break; - case JAVA_TYPE_LONG: - fprintf(out, "%s builder.writeLong(arg%d);\n", indent.c_str(), argIndex); - break; - case JAVA_TYPE_STRING: - fprintf(out, "%s builder.writeString(arg%d);\n", indent.c_str(), - argIndex); - break; - case JAVA_TYPE_BYTE_ARRAY: - fprintf(out, - "%s builder.writeByteArray(null == arg%d ? new byte[0] : " - "arg%d);\n", - indent.c_str(), argIndex, argIndex); - break; - case JAVA_TYPE_ATTRIBUTION_CHAIN: { - const char* uidName = attributionDecl.fields.front().name.c_str(); - const char* tagName = attributionDecl.fields.back().name.c_str(); - - fprintf(out, "%s builder.writeAttributionChain(\n", indent.c_str()); - fprintf(out, "%s null == %s ? new int[0] : %s,\n", - indent.c_str(), uidName, uidName); - fprintf(out, "%s null == %s ? new String[0] : %s);\n", - indent.c_str(), tagName, tagName); - break; - } - case JAVA_TYPE_KEY_VALUE_PAIR: - fprintf(out, "\n"); - fprintf(out, "%s // Write KeyValuePairs.\n", indent.c_str()); - fprintf(out, "%s final int count = valueMap.size();\n", indent.c_str()); - fprintf(out, "%s android.util.SparseIntArray intMap = null;\n", - indent.c_str()); - fprintf(out, "%s android.util.SparseLongArray longMap = null;\n", - indent.c_str()); - fprintf(out, "%s android.util.SparseArray<String> stringMap = null;\n", - indent.c_str()); - fprintf(out, "%s android.util.SparseArray<Float> floatMap = null;\n", - indent.c_str()); - fprintf(out, "%s for (int i = 0; i < count; i++) {\n", indent.c_str()); - fprintf(out, "%s final int key = valueMap.keyAt(i);\n", - indent.c_str()); - fprintf(out, "%s final Object value = valueMap.valueAt(i);\n", - indent.c_str()); - fprintf(out, "%s if (value instanceof Integer) {\n", indent.c_str()); - fprintf(out, "%s if (null == intMap) {\n", indent.c_str()); - fprintf(out, - "%s intMap = new " - "android.util.SparseIntArray();\n", - indent.c_str()); - fprintf(out, "%s }\n", indent.c_str()); - fprintf(out, "%s intMap.put(key, (Integer) value);\n", - indent.c_str()); - fprintf(out, "%s } else if (value instanceof Long) {\n", - indent.c_str()); - fprintf(out, "%s if (null == longMap) {\n", indent.c_str()); - fprintf(out, - "%s longMap = new " - "android.util.SparseLongArray();\n", - indent.c_str()); - fprintf(out, "%s }\n", indent.c_str()); - fprintf(out, "%s longMap.put(key, (Long) value);\n", - indent.c_str()); - fprintf(out, "%s } else if (value instanceof String) {\n", - indent.c_str()); - fprintf(out, "%s if (null == stringMap) {\n", indent.c_str()); - fprintf(out, - "%s stringMap = new " - "android.util.SparseArray<>();\n", - indent.c_str()); - fprintf(out, "%s }\n", indent.c_str()); - fprintf(out, "%s stringMap.put(key, (String) value);\n", - indent.c_str()); - fprintf(out, "%s } else if (value instanceof Float) {\n", - indent.c_str()); - fprintf(out, "%s if (null == floatMap) {\n", indent.c_str()); - fprintf(out, - "%s floatMap = new " - "android.util.SparseArray<>();\n", - indent.c_str()); - fprintf(out, "%s }\n", indent.c_str()); - fprintf(out, "%s floatMap.put(key, (Float) value);\n", - indent.c_str()); - fprintf(out, "%s }\n", indent.c_str()); - fprintf(out, "%s }\n", indent.c_str()); - fprintf(out, - "%s builder.writeKeyValuePairs(" - "intMap, longMap, stringMap, floatMap);\n", - indent.c_str()); - break; - default: - // Unsupported types: OBJECT, DOUBLE. - fprintf(stderr, "Encountered unsupported type."); - return 1; - } - write_annotations(out, argIndex, fieldNumberToAtomDeclSet); - argIndex++; + int ret = write_method_body(out, signature, fieldNumberToAtomDeclSet, + attributionDecl, indent); + if (ret != 0) { + return ret; } - fprintf(out, "\n"); + fprintf(out, "%s builder.usePooledBuffer();\n", indent.c_str()); fprintf(out, "%s StatsLog.write(builder.build());\n", indent.c_str()); @@ -259,7 +277,7 @@ static int write_java_methods(FILE* out, const SignatureInfoMap& signatureInfoMa if (supportQ) { fprintf(out, " } else {\n"); fprintf(out, " QLogger.write(code"); - argIndex = 1; + int argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { @@ -285,6 +303,34 @@ static int write_java_methods(FILE* out, const SignatureInfoMap& signatureInfoMa return 0; } +static int write_java_pulled_methods(FILE* out, const SignatureInfoMap& signatureInfoMap, + const AtomDecl& attributionDecl) { + for (auto signatureInfoMapIt = signatureInfoMap.begin(); + signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) { + // Print method signature. + fprintf(out, " public static StatsEvent buildStatsEvent(int code"); + const vector<java_type_t>& signature = signatureInfoMapIt->first; + const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet = signatureInfoMapIt->second; + write_method_signature(out, signature, attributionDecl); + fprintf(out, ") {\n"); + + // Print method body. + string indent(""); + int ret = write_method_body(out, signature, fieldNumberToAtomDeclSet, + attributionDecl, indent); + if (ret != 0) { + return ret; + } + fprintf(out, "\n"); + + fprintf(out, "%s return builder.build();\n", indent.c_str()); + + fprintf(out, " }\n"); // method + fprintf(out, "\n"); + } + return 0; +} + int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl, const string& javaClass, const string& javaPackage, const bool supportQ, const bool supportWorkSource) { @@ -317,8 +363,10 @@ int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl& attribut // Print write methods. fprintf(out, " // Write methods\n"); - errors += write_java_methods(out, atoms.signatureInfoMap, attributionDecl, supportQ); + errors += write_java_pushed_methods(out, atoms.signatureInfoMap, attributionDecl, supportQ); errors += write_java_non_chained_methods(out, atoms.nonChainedSignatureInfoMap); + errors += write_java_pulled_methods(out, atoms.pulledAtomsSignatureInfoMap, + attributionDecl); if (supportWorkSource) { errors += write_java_work_source_methods(out, atoms.signatureInfoMap); } diff --git a/tools/stats_log_api_gen/java_writer.h b/tools/stats_log_api_gen/java_writer.h index 8b3b50588efc..afd992be6c5e 100644 --- a/tools/stats_log_api_gen/java_writer.h +++ b/tools/stats_log_api_gen/java_writer.h @@ -14,7 +14,8 @@ * limitations under the License. */ -#pragma once +#ifndef ANDROID_STATS_LOG_API_GEN_JAVA_WRITER_H +#define ANDROID_STATS_LOG_API_GEN_JAVA_WRITER_H #include <stdio.h> #include <string.h> @@ -28,11 +29,11 @@ namespace android { namespace stats_log_api_gen { -using namespace std; - int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl, const string& javaClass, const string& javaPackage, const bool supportQ, const bool supportWorkSource); } // namespace stats_log_api_gen } // namespace android + +#endif // ANDROID_STATS_LOG_API_GEN_JAVA_WRITER_H diff --git a/tools/stats_log_api_gen/java_writer_q.cpp b/tools/stats_log_api_gen/java_writer_q.cpp index d21e2708b724..be7cb4aeb3f8 100644 --- a/tools/stats_log_api_gen/java_writer_q.cpp +++ b/tools/stats_log_api_gen/java_writer_q.cpp @@ -65,7 +65,7 @@ int write_java_methods_q_schema(FILE* out, const SignatureInfoMap& signatureInfo for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { + for (const auto& chainField : attributionDecl.fields) { fprintf(out, ", %s[] %s", java_type_name(chainField.javaType), chainField.name.c_str()); } @@ -407,7 +407,7 @@ void write_java_helpers_for_q_schema_methods(FILE* out, const AtomDecl& attribut if (requiredHelpers & JAVA_MODULE_REQUIRES_ATTRIBUTION) { fprintf(out, "%sprivate static void writeAttributionChain(byte[] buff, int pos", indent.c_str()); - for (auto chainField : attributionDecl.fields) { + for (const auto& chainField : attributionDecl.fields) { fprintf(out, ", %s[] %s", java_type_name(chainField.javaType), chainField.name.c_str()); } fprintf(out, ") {\n"); diff --git a/tools/stats_log_api_gen/java_writer_q.h b/tools/stats_log_api_gen/java_writer_q.h index c511a8436416..622ef3e37bad 100644 --- a/tools/stats_log_api_gen/java_writer_q.h +++ b/tools/stats_log_api_gen/java_writer_q.h @@ -14,7 +14,8 @@ * limitations under the License. */ -#pragma once +#ifndef ANDROID_STATS_LOG_API_GEN_JAVA_WRITER_Q_H +#define ANDROID_STATS_LOG_API_GEN_JAVA_WRITER_Q_H #include <stdio.h> #include <string.h> @@ -28,8 +29,6 @@ namespace android { namespace stats_log_api_gen { -using namespace std; - void write_java_q_logging_constants(FILE* out, const string& indent); int write_java_methods_q_schema(FILE* out, const SignatureInfoMap& signatureInfoMap, @@ -44,3 +43,5 @@ int write_stats_log_java_q_for_module(FILE* out, const Atoms& atoms, } // namespace stats_log_api_gen } // namespace android + +#endif // ANDROID_STATS_LOG_API_GEN_JAVA_WRITER_Q_H diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index b888ce904b31..28302493a8e1 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -15,9 +15,6 @@ #include "native_writer.h" #include "utils.h" -using namespace google::protobuf; -using namespace std; - namespace android { namespace stats_log_api_gen { @@ -145,7 +142,7 @@ static int run(int argc, char const* const* argv) { index++; } - if (cppFilename.size() == 0 && headerFilename.size() == 0 && javaFilename.size() == 0) { + if (cppFilename.empty() && headerFilename.empty() && javaFilename.empty()) { print_usage(); return 1; } @@ -175,9 +172,9 @@ static int run(int argc, char const* const* argv) { &attributionSignature); // Write the .cpp file - if (cppFilename.size() != 0) { + if (!cppFilename.empty()) { FILE* out = fopen(cppFilename.c_str(), "w"); - if (out == NULL) { + if (out == nullptr) { fprintf(stderr, "Unable to open file for write: %s\n", cppFilename.c_str()); return 1; } @@ -198,9 +195,9 @@ static int run(int argc, char const* const* argv) { } // Write the .h file - if (headerFilename.size() != 0) { + if (!headerFilename.empty()) { FILE* out = fopen(headerFilename.c_str(), "w"); - if (out == NULL) { + if (out == nullptr) { fprintf(stderr, "Unable to open file for write: %s\n", headerFilename.c_str()); return 1; } @@ -214,24 +211,24 @@ static int run(int argc, char const* const* argv) { } // Write the .java file - if (javaFilename.size() != 0) { - if (javaClass.size() == 0) { + if (!javaFilename.empty()) { + if (javaClass.empty()) { fprintf(stderr, "Must supply --javaClass if supplying a Java filename"); return 1; } - if (javaPackage.size() == 0) { + if (javaPackage.empty()) { fprintf(stderr, "Must supply --javaPackage if supplying a Java filename"); return 1; } - if (moduleName.size() == 0) { + if (moduleName.empty()) { fprintf(stderr, "Must supply --module if supplying a Java filename"); return 1; } FILE* out = fopen(javaFilename.c_str(), "w"); - if (out == NULL) { + if (out == nullptr) { fprintf(stderr, "Unable to open file for write: %s\n", javaFilename.c_str()); return 1; } diff --git a/tools/stats_log_api_gen/native_writer.cpp b/tools/stats_log_api_gen/native_writer.cpp index 0c6c0099e459..b4fb8dd8321b 100644 --- a/tools/stats_log_api_gen/native_writer.cpp +++ b/tools/stats_log_api_gen/native_writer.cpp @@ -24,6 +24,7 @@ namespace stats_log_api_gen { static void write_native_annotation_constants(FILE* out) { fprintf(out, "// Annotation constants.\n"); + const map<AnnotationId, string>& ANNOTATION_ID_CONSTANTS = get_annotation_id_constants(); for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) { fprintf(out, "const uint8_t %s = %hhu;\n", name.c_str(), id); } @@ -39,6 +40,7 @@ static void write_annotations(FILE* out, int argIndex, return; } const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second; + const map<AnnotationId, string>& ANNOTATION_ID_CONSTANTS = get_annotation_id_constants(); for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) { const string atomConstant = make_constant_name(atomDecl->name); fprintf(out, " if (%s == code) {\n", atomConstant.c_str()); @@ -60,8 +62,6 @@ static void write_annotations(FILE* out, int argIndex, } break; case ANNOTATION_TYPE_BOOL: - // TODO(b/151786433): Write annotation constant name instead of - // annotation id literal. fprintf(out, " %saddBoolAnnotation(%s%s, %s);\n", methodPrefix.c_str(), methodSuffix.c_str(), annotationConstant.c_str(), annotation->value.boolValue ? "true" : "false"); @@ -82,21 +82,78 @@ static void write_annotations(FILE* out, int argIndex, } } -static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, +static int write_native_method_body(FILE* out, vector<java_type_t>& signature, + const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet, + const AtomDecl& attributionDecl) { + int argIndex = 1; + fprintf(out, " AStatsEvent_setAtomId(event, code);\n"); + write_annotations(out, ATOM_ID_FIELD_NUMBER, fieldNumberToAtomDeclSet, "AStatsEvent_", + "event, "); + for (vector<java_type_t>::const_iterator arg = signature.begin(); + arg != signature.end(); arg++) { + switch (*arg) { + case JAVA_TYPE_ATTRIBUTION_CHAIN: { + const char* uidName = attributionDecl.fields.front().name.c_str(); + const char* tagName = attributionDecl.fields.back().name.c_str(); + fprintf(out, + " AStatsEvent_writeAttributionChain(event, " + "reinterpret_cast<const uint32_t*>(%s), %s.data(), " + "static_cast<uint8_t>(%s_length));\n", + uidName, tagName, uidName); + break; + } + case JAVA_TYPE_BYTE_ARRAY: + fprintf(out, + " AStatsEvent_writeByteArray(event, " + "reinterpret_cast<const uint8_t*>(arg%d.arg), " + "arg%d.arg_length);\n", + argIndex, argIndex); + break; + case JAVA_TYPE_BOOLEAN: + fprintf(out, " AStatsEvent_writeBool(event, arg%d);\n", argIndex); + break; + case JAVA_TYPE_INT: // Fall through. + case JAVA_TYPE_ENUM: + fprintf(out, " AStatsEvent_writeInt32(event, arg%d);\n", argIndex); + break; + case JAVA_TYPE_FLOAT: + fprintf(out, " AStatsEvent_writeFloat(event, arg%d);\n", argIndex); + break; + case JAVA_TYPE_LONG: + fprintf(out, " AStatsEvent_writeInt64(event, arg%d);\n", argIndex); + break; + case JAVA_TYPE_STRING: + fprintf(out, " AStatsEvent_writeString(event, arg%d);\n", argIndex); + break; + default: + // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS + fprintf(stderr, "Encountered unsupported type."); + return 1; + } + write_annotations(out, argIndex, fieldNumberToAtomDeclSet, "AStatsEvent_", + "event, "); + argIndex++; + } + return 0; +} + +static int write_native_stats_write_methods(FILE* out, const SignatureInfoMap& signatureInfoMap, const AtomDecl& attributionDecl, const bool supportQ) { fprintf(out, "\n"); - for (auto signatureInfoMapIt = atoms.signatureInfoMap.begin(); - signatureInfoMapIt != atoms.signatureInfoMap.end(); signatureInfoMapIt++) { + for (auto signatureInfoMapIt = signatureInfoMap.begin(); + signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) { vector<java_type_t> signature = signatureInfoMapIt->first; const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet = signatureInfoMapIt->second; // Key value pairs not supported in native. - if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) { + if (std::find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != + signature.end()) { continue; } - write_native_method_signature(out, "int stats_write", signature, attributionDecl, " {"); + write_native_method_signature(out, "int stats_write(", signature, attributionDecl, " {"); - int argIndex = 1; + // Write method body. if (supportQ) { + int argIndex = 1; fprintf(out, " StatsEventCompat event;\n"); fprintf(out, " event.setAtomId(code);\n"); write_annotations(out, ATOM_ID_FIELD_NUMBER, fieldNumberToAtomDeclSet, "event.", ""); @@ -138,78 +195,37 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms, write_annotations(out, argIndex, fieldNumberToAtomDeclSet, "event.", ""); argIndex++; } - fprintf(out, " return event.writeToSocket();\n"); + fprintf(out, " return event.writeToSocket();\n"); // end method body. } else { fprintf(out, " AStatsEvent* event = AStatsEvent_obtain();\n"); - fprintf(out, " AStatsEvent_setAtomId(event, code);\n"); - write_annotations(out, ATOM_ID_FIELD_NUMBER, fieldNumberToAtomDeclSet, "AStatsEvent_", - "event, "); - for (vector<java_type_t>::const_iterator arg = signature.begin(); - arg != signature.end(); arg++) { - switch (*arg) { - case JAVA_TYPE_ATTRIBUTION_CHAIN: { - const char* uidName = attributionDecl.fields.front().name.c_str(); - const char* tagName = attributionDecl.fields.back().name.c_str(); - fprintf(out, - " AStatsEvent_writeAttributionChain(event, " - "reinterpret_cast<const uint32_t*>(%s), %s.data(), " - "static_cast<uint8_t>(%s_length));\n", - uidName, tagName, uidName); - break; - } - case JAVA_TYPE_BYTE_ARRAY: - fprintf(out, - " AStatsEvent_writeByteArray(event, " - "reinterpret_cast<const uint8_t*>(arg%d.arg), " - "arg%d.arg_length);\n", - argIndex, argIndex); - break; - case JAVA_TYPE_BOOLEAN: - fprintf(out, " AStatsEvent_writeBool(event, arg%d);\n", argIndex); - break; - case JAVA_TYPE_INT: // Fall through. - case JAVA_TYPE_ENUM: - fprintf(out, " AStatsEvent_writeInt32(event, arg%d);\n", argIndex); - break; - case JAVA_TYPE_FLOAT: - fprintf(out, " AStatsEvent_writeFloat(event, arg%d);\n", argIndex); - break; - case JAVA_TYPE_LONG: - fprintf(out, " AStatsEvent_writeInt64(event, arg%d);\n", argIndex); - break; - case JAVA_TYPE_STRING: - fprintf(out, " AStatsEvent_writeString(event, arg%d);\n", argIndex); - break; - default: - // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS - fprintf(stderr, "Encountered unsupported type."); - return 1; - } - write_annotations(out, argIndex, fieldNumberToAtomDeclSet, "AStatsEvent_", - "event, "); - argIndex++; + int ret = write_native_method_body(out, signature, fieldNumberToAtomDeclSet, + attributionDecl); + if (ret != 0) { + return ret; } fprintf(out, " const int ret = AStatsEvent_write(event);\n"); fprintf(out, " AStatsEvent_release(event);\n"); - fprintf(out, " return ret;\n"); + fprintf(out, " return ret;\n"); // end method body. } - fprintf(out, "}\n\n"); + fprintf(out, "}\n\n"); // end method. } return 0; } -static void write_native_stats_write_non_chained_methods(FILE* out, const Atoms& atoms, +static void write_native_stats_write_non_chained_methods(FILE* out, + const SignatureInfoMap& signatureInfoMap, const AtomDecl& attributionDecl) { fprintf(out, "\n"); - for (auto signature_it = atoms.nonChainedSignatureInfoMap.begin(); - signature_it != atoms.nonChainedSignatureInfoMap.end(); signature_it++) { + for (auto signature_it = signatureInfoMap.begin(); + signature_it != signatureInfoMap.end(); signature_it++) { vector<java_type_t> signature = signature_it->first; // Key value pairs not supported in native. - if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) { + if (std::find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != + signature.end()) { continue; } - write_native_method_signature(out, "int stats_write_non_chained", signature, + write_native_method_signature(out, "int stats_write_non_chained(", signature, attributionDecl, " {"); vector<java_type_t> newSignature; @@ -235,6 +251,35 @@ static void write_native_stats_write_non_chained_methods(FILE* out, const Atoms& } } +static int write_native_build_stats_event_methods(FILE* out, + const SignatureInfoMap& signatureInfoMap, + const AtomDecl& attributionDecl) { + fprintf(out, "\n"); + for (auto signatureInfoMapIt = signatureInfoMap.begin(); + signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) { + vector<java_type_t> signature = signatureInfoMapIt->first; + const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet = signatureInfoMapIt->second; + // Key value pairs not supported in native. + if (std::find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != + signature.end()) { + continue; + } + write_native_method_signature(out, "void addAStatsEvent(AStatsEventList* pulled_data, ", + signature, attributionDecl, " {"); + + fprintf(out, " AStatsEvent* event = AStatsEventList_addStatsEvent(pulled_data);\n"); + int ret = write_native_method_body(out, signature, fieldNumberToAtomDeclSet, + attributionDecl); + if (ret != 0) { + return ret; + } + fprintf(out, " AStatsEvent_build(event);\n"); // end method body. + + fprintf(out, "}\n\n"); // end method. + } + return 0; +} + static void write_native_method_header(FILE* out, const string& methodName, const SignatureInfoMap& signatureInfoMap, const AtomDecl& attributionDecl) { @@ -243,7 +288,8 @@ static void write_native_method_header(FILE* out, const string& methodName, vector<java_type_t> signature = signatureInfoMapIt->first; // Key value pairs not supported in native. - if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) { + if (std::find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != + signature.end()) { continue; } write_native_method_signature(out, methodName, signature, attributionDecl, ";"); @@ -262,13 +308,22 @@ int write_stats_log_cpp(FILE* out, const Atoms& atoms, const AtomDecl& attributi fprintf(out, "#include <StatsEventCompat.h>\n"); } else { fprintf(out, "#include <stats_event.h>\n"); + + if (!atoms.pulledAtomsSignatureInfoMap.empty()) { + fprintf(out, "#include <stats_pull_atom_callback.h>\n"); + } } + + fprintf(out, "\n"); write_namespace(out, cppNamespace); - write_native_stats_write_methods(out, atoms, attributionDecl, supportQ); - write_native_stats_write_non_chained_methods(out, atoms, attributionDecl); + write_native_stats_write_methods(out, atoms.signatureInfoMap, attributionDecl, supportQ); + write_native_stats_write_non_chained_methods(out, atoms.nonChainedSignatureInfoMap, + attributionDecl); + write_native_build_stats_event_methods(out, atoms.pulledAtomsSignatureInfoMap, + attributionDecl); // Print footer fprintf(out, "\n"); @@ -288,6 +343,9 @@ int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl& attrib fprintf(out, "#include <vector>\n"); fprintf(out, "#include <map>\n"); fprintf(out, "#include <set>\n"); + if (!atoms.pulledAtomsSignatureInfoMap.empty()) { + fprintf(out, "#include <stats_pull_atom_callback.h>\n"); + } fprintf(out, "\n"); write_namespace(out, cppNamespace); @@ -337,12 +395,22 @@ int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl& attrib fprintf(out, "//\n"); fprintf(out, "// Write methods\n"); fprintf(out, "//\n"); - write_native_method_header(out, "int stats_write", atoms.signatureInfoMap, attributionDecl); + write_native_method_header(out, "int stats_write(", atoms.signatureInfoMap, attributionDecl); + fprintf(out, "\n"); fprintf(out, "//\n"); fprintf(out, "// Write flattened methods\n"); fprintf(out, "//\n"); - write_native_method_header(out, "int stats_write_non_chained", atoms.nonChainedSignatureInfoMap, + write_native_method_header(out, "int stats_write_non_chained(", atoms.nonChainedSignatureInfoMap, + attributionDecl); + fprintf(out, "\n"); + + // Print pulled atoms methods. + fprintf(out, "//\n"); + fprintf(out, "// Add AStatsEvent methods\n"); + fprintf(out, "//\n"); + write_native_method_header(out, "void addAStatsEvent(AStatsEventList* pulled_data, ", + atoms.pulledAtomsSignatureInfoMap, attributionDecl); fprintf(out, "\n"); diff --git a/tools/stats_log_api_gen/native_writer.h b/tools/stats_log_api_gen/native_writer.h index 264d4db29fc9..4e42d1fa2809 100644 --- a/tools/stats_log_api_gen/native_writer.h +++ b/tools/stats_log_api_gen/native_writer.h @@ -14,7 +14,8 @@ * limitations under the License. */ -#pragma once +#ifndef ANDROID_STATS_LOG_API_GEN_NATIVE_WRITER_H +#define ANDROID_STATS_LOG_API_GEN_NATIVE_WRITER_H #include <stdio.h> #include <string.h> @@ -24,8 +25,6 @@ namespace android { namespace stats_log_api_gen { -using namespace std; - int write_stats_log_cpp(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl, const string& cppNamespace, const string& importHeader, const bool supportQ); @@ -35,3 +34,5 @@ int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl& attrib } // namespace stats_log_api_gen } // namespace android + +#endif // ANDROID_STATS_LOG_API_GEN_NATIVE_WRITER_H diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto index aaa488e44fee..e658b62b8daa 100644 --- a/tools/stats_log_api_gen/test.proto +++ b/tools/stats_log_api_gen/test.proto @@ -58,7 +58,7 @@ message AllTypesAtom { } message Event { - oneof event { + oneof pushed { OutOfOrderAtom out_of_order_atom = 2; IntAtom int_atom = 1; AnotherIntAtom another_int_atom = 3; @@ -74,7 +74,7 @@ message BadTypesAtom { } message BadTypesEvent { - oneof event { + oneof pushed { BadTypesAtom bad_types_atom = 1; } } @@ -84,7 +84,7 @@ message BadSkippedFieldSingleAtom { } message BadSkippedFieldSingle { - oneof event { + oneof pushed { BadSkippedFieldSingleAtom bad = 1; } } @@ -96,7 +96,7 @@ message BadSkippedFieldMultipleAtom { } message BadSkippedFieldMultiple { - oneof event { + oneof pushed { BadSkippedFieldMultipleAtom bad = 1; } } @@ -107,11 +107,11 @@ message BadAttributionNodePositionAtom { } message BadAttributionNodePosition { - oneof event { BadAttributionNodePositionAtom bad = 1; } + oneof pushed { BadAttributionNodePositionAtom bad = 1; } } message GoodEventWithBinaryFieldAtom { - oneof event { GoodBinaryFieldAtom field1 = 1; } + oneof pushed { GoodBinaryFieldAtom field1 = 1; } } message ComplexField { @@ -124,7 +124,7 @@ message GoodBinaryFieldAtom { } message BadEventWithBinaryFieldAtom { - oneof event { BadBinaryFieldAtom field1 = 1; } + oneof pushed { BadBinaryFieldAtom field1 = 1; } } message BadBinaryFieldAtom { @@ -133,7 +133,7 @@ message BadBinaryFieldAtom { } message BadStateAtoms { - oneof event { + oneof pushed { BadStateAtom1 bad1 = 1; BadStateAtom2 bad2 = 2; BadStateAtom3 bad3 = 3; @@ -141,7 +141,7 @@ message BadStateAtoms { } message GoodStateAtoms { - oneof event { + oneof pushed { GoodStateAtom1 good1 = 1; GoodStateAtom2 good2 = 2; } @@ -204,7 +204,7 @@ message NoModuleAtom { } message ModuleAtoms { - oneof event { + oneof pushed { ModuleOneAtom module_one_atom = 1 [(android.os.statsd.module) = "module1"]; ModuleTwoAtom module_two_atom = 2 [(android.os.statsd.module) = "module2"]; ModuleOneAndTwoAtom module_one_and_two_atom = 3 [ @@ -213,3 +213,24 @@ message ModuleAtoms { NoModuleAtom no_module_atom = 4; } } + +message NotAPushNorPullAtom { + oneof event { + IntAtom int_atom = 1; + } +} + +message AtomNotInAOneof { + optional IntAtom int_atom = 1; +} + +message PushedAndPulledAtoms { + oneof pushed { + IntAtom int_atom_1 = 1; + } + + oneof pulled { + OutOfOrderAtom out_of_order_atom = 11; + AnotherIntAtom another_int_atom = 10; + } +} diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp index dbae58889333..6f78921d8f1c 100644 --- a/tools/stats_log_api_gen/test_collation.cpp +++ b/tools/stats_log_api_gen/test_collation.cpp @@ -24,7 +24,6 @@ namespace android { namespace stats_log_api_gen { using std::map; -using std::set; using std::vector; /** @@ -32,11 +31,11 @@ using std::vector; */ static bool map_contains_vector(const SignatureInfoMap& s, int count, ...) { va_list args; - vector<java_type_t> v; + vector<java_type_t> v(count); va_start(args, count); for (int i = 0; i < count; i++) { - v.push_back((java_type_t)va_arg(args, int)); + v[i] = static_cast<java_type_t>(va_arg(args, int)); } va_end(args); @@ -222,7 +221,7 @@ TEST(CollationTest, FailOnBadBinaryFieldAtom) { Atoms atoms; int errorCount = collate_atoms(BadEventWithBinaryFieldAtom::descriptor(), DEFAULT_MODULE_NAME, &atoms); - EXPECT_TRUE(errorCount > 0); + EXPECT_GT(errorCount, 0); } TEST(CollationTest, PassOnLogFromModuleAtom) { @@ -365,5 +364,69 @@ TEST(CollationTest, RecognizeModule1Atom) { EXPECT_TRUE(annotation->value.boolValue); } +/** + * Test that an atom is not a pushed nor pulled atom. + */ +TEST(CollationTest, InvalidAtomType) { + Atoms atoms; + int errorCount = collate_atoms(NotAPushNorPullAtom::descriptor(), DEFAULT_MODULE_NAME, &atoms); + + EXPECT_EQ(1, errorCount); +} + +/** + * Test that an atom was not declared in a `oneof` field. + */ +TEST(CollationTest, AtomNotDeclaredInAOneof) { + Atoms atoms; + int errorCount = collate_atoms(AtomNotInAOneof::descriptor(), DEFAULT_MODULE_NAME, &atoms); + + EXPECT_EQ(1, errorCount); +} + +/** + * Test a correct collation with pushed and pulled atoms. + */ +TEST(CollationTest, CollatePushedAndPulledAtoms) { + Atoms atoms; + int errorCount = collate_atoms(PushedAndPulledAtoms::descriptor(), DEFAULT_MODULE_NAME, &atoms); + + EXPECT_EQ(0, errorCount); + EXPECT_EQ(1ul, atoms.signatureInfoMap.size()); + EXPECT_EQ(2ul, atoms.pulledAtomsSignatureInfoMap.size()); + + // IntAtom + EXPECT_MAP_CONTAINS_SIGNATURE(atoms.signatureInfoMap, JAVA_TYPE_INT); + + // AnotherIntAtom + EXPECT_MAP_CONTAINS_SIGNATURE(atoms.pulledAtomsSignatureInfoMap, JAVA_TYPE_INT); + + // OutOfOrderAtom + EXPECT_MAP_CONTAINS_SIGNATURE(atoms.pulledAtomsSignatureInfoMap, JAVA_TYPE_INT, JAVA_TYPE_INT); + + EXPECT_EQ(3ul, atoms.decls.size()); + + AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); + EXPECT_EQ(1, (*atomIt)->code); + EXPECT_EQ("int_atom_1", (*atomIt)->name); + EXPECT_EQ("IntAtom", (*atomIt)->message); + EXPECT_NO_ENUM_FIELD((*atomIt)); + atomIt++; + + EXPECT_EQ(10, (*atomIt)->code); + EXPECT_EQ("another_int_atom", (*atomIt)->name); + EXPECT_EQ("AnotherIntAtom", (*atomIt)->message); + EXPECT_NO_ENUM_FIELD((*atomIt)); + atomIt++; + + EXPECT_EQ(11, (*atomIt)->code); + EXPECT_EQ("out_of_order_atom", (*atomIt)->name); + EXPECT_EQ("OutOfOrderAtom", (*atomIt)->message); + EXPECT_NO_ENUM_FIELD((*atomIt)); + atomIt++; + + EXPECT_EQ(atoms.decls.end(), atomIt); +} + } // namespace stats_log_api_gen } // namespace android diff --git a/tools/stats_log_api_gen/utils.cpp b/tools/stats_log_api_gen/utils.cpp index abb89133e58e..1eaf42acf153 100644 --- a/tools/stats_log_api_gen/utils.cpp +++ b/tools/stats_log_api_gen/utils.cpp @@ -16,11 +16,30 @@ #include "utils.h" -#include "android-base/strings.h" - namespace android { namespace stats_log_api_gen { +/** + * Inlining this method because "android-base/strings.h" is not available on + * google3. + */ +static vector<string> Split(const string& s, const string& delimiters) { + GOOGLE_CHECK_NE(delimiters.size(), 0U); + + vector<string> result; + + size_t base = 0; + size_t found; + while (true) { + found = s.find_first_of(delimiters, base); + result.push_back(s.substr(base, found - base)); + if (found == s.npos) break; + base = found + 1; + } + + return result; +} + static void build_non_chained_decl_map(const Atoms& atoms, std::map<int, AtomDeclSet::const_iterator>* decl_map) { for (AtomDeclSet::const_iterator atomIt = atoms.non_chained_decls.begin(); @@ -29,6 +48,20 @@ static void build_non_chained_decl_map(const Atoms& atoms, } } +const map<AnnotationId, string>& get_annotation_id_constants() { + static const map<AnnotationId, string>* ANNOTATION_ID_CONSTANTS = + new map<AnnotationId, string>{ + {ANNOTATION_ID_IS_UID, "ANNOTATION_ID_IS_UID"}, + {ANNOTATION_ID_TRUNCATE_TIMESTAMP, "ANNOTATION_ID_TRUNCATE_TIMESTAMP"}, + {ANNOTATION_ID_PRIMARY_FIELD, "ANNOTATION_ID_PRIMARY_FIELD"}, + {ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, "ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID"}, + {ANNOTATION_ID_EXCLUSIVE_STATE, "ANNOTATION_ID_EXCLUSIVE_STATE"}, + {ANNOTATION_ID_TRIGGER_STATE_RESET, "ANNOTATION_ID_TRIGGER_STATE_RESET"}, + {ANNOTATION_ID_STATE_NESTED, "ANNOTATION_ID_STATE_NESTED"}}; + + return *ANNOTATION_ID_CONSTANTS; +} + /** * Turn lower and camel case into upper case with underscores. */ @@ -102,15 +135,15 @@ const char* java_type_name(java_type_t type) { // Writes namespaces for the cpp and header files, returning the number of // namespaces written. void write_namespace(FILE* out, const string& cppNamespaces) { - vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ","); - for (string cppNamespace : cppNamespaceVec) { + vector<string> cppNamespaceVec = Split(cppNamespaces, ","); + for (const string& cppNamespace : cppNamespaceVec) { fprintf(out, "namespace %s {\n", cppNamespace.c_str()); } } // Writes namespace closing brackets for cpp and header files. void write_closing_namespace(FILE* out, const string& cppNamespaces) { - vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ","); + vector<string> cppNamespaceVec = Split(cppNamespaces, ","); for (auto it = cppNamespaceVec.rbegin(); it != cppNamespaceVec.rend(); ++it) { fprintf(out, "} // namespace %s\n", it->c_str()); } @@ -123,7 +156,7 @@ static void write_cpp_usage(FILE* out, const string& method_name, const string& for (vector<AtomField>::const_iterator field = atom->fields.begin(); field != atom->fields.end(); field++) { if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { + for (const auto& chainField : attributionDecl.fields) { if (chainField.javaType == JAVA_TYPE_STRING) { fprintf(out, ", const std::vector<%s>& %s", cpp_type_name(chainField.javaType), chainField.name.c_str()); @@ -182,15 +215,15 @@ void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& fprintf(out, "\n"); } -void write_native_method_signature(FILE* out, const string& methodName, +void write_native_method_signature(FILE* out, const string& signaturePrefix, const vector<java_type_t>& signature, const AtomDecl& attributionDecl, const string& closer) { - fprintf(out, "%s(int32_t code", methodName.c_str()); + fprintf(out, "%sint32_t code", signaturePrefix.c_str()); int argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { + for (const auto& chainField : attributionDecl.fields) { if (chainField.javaType == JAVA_TYPE_STRING) { fprintf(out, ", const std::vector<%s>& %s", cpp_type_name(chainField.javaType), chainField.name.c_str()); @@ -222,7 +255,7 @@ void write_native_method_call(FILE* out, const string& methodName, for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { + for (const auto& chainField : attributionDecl.fields) { if (chainField.javaType == JAVA_TYPE_STRING) { fprintf(out, ", %s", chainField.name.c_str()); } else { diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h index 73e0cb838227..13a7e2d8a54e 100644 --- a/tools/stats_log_api_gen/utils.h +++ b/tools/stats_log_api_gen/utils.h @@ -14,7 +14,8 @@ * limitations under the License. */ -#pragma once +#ifndef ANDROID_STATS_LOG_API_GEN_UTILS_H +#define ANDROID_STATS_LOG_API_GEN_UTILS_H #include <stdio.h> #include <string.h> @@ -28,23 +29,14 @@ namespace android { namespace stats_log_api_gen { -using namespace std; - -const string DEFAULT_CPP_NAMESPACE = "android,util"; -const string DEFAULT_CPP_HEADER_IMPORT = "statslog.h"; +const char DEFAULT_CPP_NAMESPACE[] = "android,util"; +const char DEFAULT_CPP_HEADER_IMPORT[] = "statslog.h"; const int JAVA_MODULE_REQUIRES_FLOAT = 0x01; const int JAVA_MODULE_REQUIRES_ATTRIBUTION = 0x02; const int JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS = 0x04; -const map<AnnotationId, string> ANNOTATION_ID_CONSTANTS = { - {ANNOTATION_ID_IS_UID, "ANNOTATION_ID_IS_UID"}, - {ANNOTATION_ID_TRUNCATE_TIMESTAMP, "ANNOTATION_ID_TRUNCATE_TIMESTAMP"}, - {ANNOTATION_ID_PRIMARY_FIELD, "ANNOTATION_ID_PRIMARY_FIELD"}, - {ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, "ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID"}, - {ANNOTATION_ID_EXCLUSIVE_STATE, "ANNOTATION_ID_EXCLUSIVE_STATE"}, - {ANNOTATION_ID_TRIGGER_STATE_RESET, "ANNOTATION_ID_TRIGGER_STATE_RESET"}, - {ANNOTATION_ID_STATE_NESTED, "ANNOTATION_ID_STATE_NESTED"}}; +const map<AnnotationId, string>& get_annotation_id_constants(); string make_constant_name(const string& str); @@ -59,7 +51,7 @@ void write_closing_namespace(FILE* out, const string& cppNamespaces); void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl); -void write_native_method_signature(FILE* out, const string& methodName, +void write_native_method_signature(FILE* out, const string& signaturePrefix, const vector<java_type_t>& signature, const AtomDecl& attributionDecl, const string& closer); @@ -81,3 +73,5 @@ int write_java_work_source_methods(FILE* out, const SignatureInfoMap& signatureI } // namespace stats_log_api_gen } // namespace android + +#endif // ANDROID_STATS_LOG_API_GEN_UTILS_H diff --git a/tools/stringslint/stringslint.py b/tools/stringslint/stringslint.py index afe91cda37b0..15088fc81e88 100644 --- a/tools/stringslint/stringslint.py +++ b/tools/stringslint/stringslint.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +#-*- coding: utf-8 -*- # Copyright (C) 2018 The Android Open Source Project # @@ -33,9 +34,6 @@ In general: import re, sys, codecs import lxml.etree as ET -reload(sys) -sys.setdefaultencoding('utf8') - BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False): @@ -118,7 +116,7 @@ def lint(path): raw = f.read() if len(raw.strip()) == 0: return warnings - tree = ET.fromstring(raw) + tree = ET.fromstring(bytes(raw, encoding='utf-8')) root = tree #tree.getroot() last_comment = None @@ -231,6 +229,6 @@ for b in before: if len(after) > 0: for a in sorted(after.keys()): - print after[a] - print + print(after[a]) + print() sys.exit(1) diff --git a/tools/stringslint/stringslint_sha.sh b/tools/stringslint/stringslint_sha.sh index bd80bb4e6f3f..bd0569873197 100755 --- a/tools/stringslint/stringslint_sha.sh +++ b/tools/stringslint/stringslint_sha.sh @@ -1,5 +1,5 @@ #!/bin/bash LOCAL_DIR="$( dirname ${BASH_SOURCE} )" git show --name-only --pretty=format: $1 | grep values/strings.xml | while read file; do - python $LOCAL_DIR/stringslint.py <(git show $1:$file) <(git show $1^:$file) + python3 $LOCAL_DIR/stringslint.py <(git show $1:$file) <(git show $1^:$file) done diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp index 819e75ba1fed..15b8b4105713 100644 --- a/tools/validatekeymaps/Android.bp +++ b/tools/validatekeymaps/Android.bp @@ -20,12 +20,21 @@ cc_binary_host { "libutils", "libcutils", "liblog", + "libui-types", ], + target: { + linux_glibc: { + static_libs: [ + // libbinder is only available for linux + "libbinder", + ], + }, + }, // This tool is prebuilt if we're doing an app-only build. product_variables: { unbundled_build: { - enabled: false, + enabled: false, }, }, } diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp index 877715a66f6d..3865076e8484 100644 --- a/tools/validatekeymaps/Main.cpp +++ b/tools/validatekeymaps/Main.cpp @@ -96,34 +96,32 @@ static bool validateFile(const char* filename) { return false; case FILETYPE_KEYLAYOUT: { - sp<KeyLayoutMap> map; - status_t status = KeyLayoutMap::load(filename, &map); - if (status) { - error("Error %d parsing key layout file.\n\n", status); + base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(filename); + if (!ret) { + error("Error %s parsing key layout file.\n\n", ret.error().message().c_str()); return false; } break; } case FILETYPE_KEYCHARACTERMAP: { - sp<KeyCharacterMap> map; - status_t status = KeyCharacterMap::load(filename, - KeyCharacterMap::FORMAT_ANY, &map); - if (status) { - error("Error %d parsing key character map file.\n\n", status); + base::Result<std::shared_ptr<KeyCharacterMap>> ret = + KeyCharacterMap::load(filename, KeyCharacterMap::Format::ANY); + if (!ret) { + error("Error %s parsing key character map file.\n\n", ret.error().message().c_str()); return false; } break; } case FILETYPE_INPUTDEVICECONFIGURATION: { - PropertyMap* map; - status_t status = PropertyMap::load(String8(filename), &map); - if (status) { - error("Error %d parsing input device configuration file.\n\n", status); + android::base::Result<std::unique_ptr<PropertyMap>> propertyMap = + PropertyMap::load(String8(filename)); + if (!propertyMap.ok()) { + error("Error %d parsing input device configuration file.\n\n", + propertyMap.error().code()); return false; } - delete map; break; } diff --git a/tools/xmlpersistence/Android.bp b/tools/xmlpersistence/Android.bp new file mode 100644 index 000000000000..d58d0dcdc45a --- /dev/null +++ b/tools/xmlpersistence/Android.bp @@ -0,0 +1,11 @@ +java_binary_host { + name: "xmlpersistence_cli", + manifest: "manifest.txt", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "javaparser-symbol-solver", + "javapoet", + ], +} diff --git a/tools/xmlpersistence/OWNERS b/tools/xmlpersistence/OWNERS new file mode 100644 index 000000000000..4f4d06a32676 --- /dev/null +++ b/tools/xmlpersistence/OWNERS @@ -0,0 +1 @@ +zhanghai@google.com diff --git a/tools/xmlpersistence/manifest.txt b/tools/xmlpersistence/manifest.txt new file mode 100644 index 000000000000..6d9771998efc --- /dev/null +++ b/tools/xmlpersistence/manifest.txt @@ -0,0 +1 @@ +Main-class: MainKt diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt new file mode 100644 index 000000000000..b2c5f4ac767b --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/Generator.kt @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.FieldSpec +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.NameAllocator +import com.squareup.javapoet.ParameterSpec +import com.squareup.javapoet.TypeSpec +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.nio.charset.StandardCharsets +import java.time.Year +import java.util.Objects +import javax.lang.model.element.Modifier + +// JavaPoet only supports line comments, and can't add a newline after file level comments. +val FILE_HEADER = """ + /* + * Copyright (C) ${Year.now().value} The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + // Generated by xmlpersistence. DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // @formatter:off +""".trimIndent() + "\n\n" + +private val atomicFileType = ClassName.get("android.util", "AtomicFile") + +fun generate(persistence: PersistenceInfo): JavaFile { + val distinctClassFields = persistence.root.allClassFields.distinctBy { it.type } + val type = TypeSpec.classBuilder(persistence.name) + .addJavadoc( + """ + Generated class implementing XML persistence for${'$'}W{@link $1T}. + <p> + This class provides atomicity for persistence via {@link $2T}, however it does not provide + thread safety, so please bring your own synchronization mechanism. + """.trimIndent(), persistence.root.type, atomicFileType + ) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addField(generateFileField()) + .addMethod(generateConstructor()) + .addMethod(generateReadMethod(persistence.root)) + .addMethod(generateParseMethod(persistence.root)) + .addMethods(distinctClassFields.map { generateParseClassMethod(it) }) + .addMethod(generateWriteMethod(persistence.root)) + .addMethod(generateSerializeMethod(persistence.root)) + .addMethods(distinctClassFields.map { generateSerializeClassMethod(it) }) + .addMethod(generateDeleteMethod()) + .build() + return JavaFile.builder(persistence.root.type.packageName(), type) + .skipJavaLangImports(true) + .indent(" ") + .build() +} + +private val nonNullType = ClassName.get("android.annotation", "NonNull") + +private fun generateFileField(): FieldSpec = + FieldSpec.builder(atomicFileType, "mFile", Modifier.PRIVATE, Modifier.FINAL) + .addAnnotation(nonNullType) + .build() + +private fun generateConstructor(): MethodSpec = + MethodSpec.constructorBuilder() + .addJavadoc( + """ + Create an instance of this class. + + @param file the XML file for persistence + """.trimIndent() + ) + .addModifiers(Modifier.PUBLIC) + .addParameter( + ParameterSpec.builder(File::class.java, "file").addAnnotation(nonNullType).build() + ) + .addStatement("mFile = new \$1T(file)", atomicFileType) + .build() + +private val nullableType = ClassName.get("android.annotation", "Nullable") + +private val xmlPullParserType = ClassName.get("org.xmlpull.v1", "XmlPullParser") + +private val xmlType = ClassName.get("android.util", "Xml") + +private val xmlPullParserExceptionType = ClassName.get("org.xmlpull.v1", "XmlPullParserException") + +private fun generateReadMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("read") + .addJavadoc( + """ + Read${'$'}W{@link $1T}${'$'}Wfrom${'$'}Wthe${'$'}WXML${'$'}Wfile. + + @return the persisted${'$'}W{@link $1T},${'$'}Wor${'$'}W{@code null}${'$'}Wif${'$'}Wthe${'$'}WXML${'$'}Wfile${'$'}Wdoesn't${'$'}Wexist + @throws IllegalArgumentException if an error occurred while reading + """.trimIndent(), rootField.type + ) + .addAnnotation(nullableType) + .addModifiers(Modifier.PUBLIC) + .returns(rootField.type) + .addControlFlow("try (\$1T inputStream = mFile.openRead())", FileInputStream::class.java) { + addStatement("final \$1T parser = \$2T.newPullParser()", xmlPullParserType, xmlType) + addStatement("parser.setInput(inputStream, null)") + addStatement("return parse(parser)") + nextControlFlow("catch (\$1T e)", FileNotFoundException::class.java) + addStatement("return null") + nextControlFlow( + "catch (\$1T | \$2T e)", IOException::class.java, xmlPullParserExceptionType + ) + addStatement("throw new IllegalArgumentException(e)") + } + .build() + +private val ClassFieldInfo.allClassFields: List<ClassFieldInfo> + get() = + mutableListOf<ClassFieldInfo>().apply { + this += this@allClassFields + for (field in fields) { + when (field) { + is ClassFieldInfo -> this += field.allClassFields + is ListFieldInfo -> this += field.element.allClassFields + } + } + } + +private fun generateParseMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("parse") + .addAnnotation(nonNullType) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(rootField.type) + .addParameter( + ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build() + ) + .addExceptions(listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType)) + .apply { + addStatement("int type") + addStatement("int depth") + addStatement("int innerDepth = parser.getDepth() + 1") + addControlFlow( + "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W" + + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))", + xmlPullParserType + ) { + addControlFlow( + "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType + ) { + addStatement("continue") + } + addControlFlow( + "if (\$1T.equals(parser.getName(),\$W\$2S))", Objects::class.java, + rootField.tagName + ) { + addStatement("return \$1L(parser)", rootField.parseMethodName) + } + } + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Missing root tag <${rootField.tagName}>" + ) + } + .build() + +private fun generateParseClassMethod(classField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder(classField.parseMethodName) + .addAnnotation(nonNullType) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(classField.type) + .addParameter( + ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build() + ) + .apply { + val (attributeFields, tagFields) = classField.fields + .partition { it is PrimitiveFieldInfo || it is StringFieldInfo } + if (tagFields.isNotEmpty()) { + addExceptions( + listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType) + ) + } + val nameAllocator = NameAllocator().apply { + newName("parser") + newName("type") + newName("depth") + newName("innerDepth") + } + for (field in attributeFields) { + val variableName = nameAllocator.newName(field.variableName, field) + when (field) { + is PrimitiveFieldInfo -> { + val stringVariableName = + nameAllocator.newName("${field.variableName}String") + addStatement( + "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)", + stringVariableName, field.attributeName + ) + if (field.isRequired) { + addControlFlow("if (\$1L == null)", stringVariableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Missing attribute \"${field.attributeName}\"" + ) + } + } + val boxedType = field.type.box() + val parseTypeMethodName = if (field.type.isPrimitive) { + "parse${field.type.toString().capitalize()}" + } else { + "valueOf" + } + if (field.isRequired) { + addStatement( + "final \$1T \$2L =\$W\$3T.\$4L($5L)", field.type, variableName, + boxedType, parseTypeMethodName, stringVariableName + ) + } else { + addStatement( + "final \$1T \$2L =\$W$3L != null ?\$W\$4T.\$5L($3L)\$W: null", + field.type, variableName, stringVariableName, boxedType, + parseTypeMethodName + ) + } + } + is StringFieldInfo -> + addStatement( + "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)", + variableName, field.attributeName + ) + else -> error(field) + } + } + if (tagFields.isNotEmpty()) { + for (field in tagFields) { + val variableName = nameAllocator.newName(field.variableName, field) + when (field) { + is ClassFieldInfo -> + addStatement("\$1T \$2L =\$Wnull", field.type, variableName) + is ListFieldInfo -> + addStatement( + "final \$1T \$2L =\$Wnew \$3T<>()", field.type, variableName, + ArrayList::class.java + ) + else -> error(field) + } + } + addStatement("int type") + addStatement("int depth") + addStatement("int innerDepth = parser.getDepth() + 1") + addControlFlow( + "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W" + + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))", + xmlPullParserType + ) { + addControlFlow( + "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType + ) { + addStatement("continue") + } + addControlFlow("switch (parser.getName())") { + for (field in tagFields) { + addControlFlow("case \$1S:", field.tagName) { + val variableName = nameAllocator.get(field) + when (field) { + is ClassFieldInfo -> { + addControlFlow("if (\$1L != null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Duplicate tag \"${field.tagName}\"" + ) + } + addStatement( + "\$1L =\$W\$2L(parser)", variableName, + field.parseMethodName + ) + addStatement("break") + } + is ListFieldInfo -> { + val elementNameAllocator = nameAllocator.clone() + val elementVariableName = elementNameAllocator.newName( + field.element.xmlName!!.toLowerCamelCase() + ) + addStatement( + "final \$1T \$2L =\$W\$3L(parser)", field.element.type, + elementVariableName, field.element.parseMethodName + ) + addStatement( + "\$1L.add(\$2L)", variableName, elementVariableName + ) + addStatement("break") + } + else -> error(field) + } + } + } + } + } + } + for (field in tagFields.filter { it is ClassFieldInfo && it.isRequired }) { + addControlFlow("if ($1L == null)", nameAllocator.get(field)) { + addStatement( + "throw new IllegalArgumentException(\$1S)", "Missing tag <${field.tagName}>" + ) + } + } + addStatement( + classField.fields.joinToString(",\$W", "return new \$1T(", ")") { + nameAllocator.get(it) + }, classField.type + ) + } + .build() + +private val ClassFieldInfo.parseMethodName: String + get() = "parse${type.simpleName().toUpperCamelCase()}" + +private val xmlSerializerType = ClassName.get("org.xmlpull.v1", "XmlSerializer") + +private fun generateWriteMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("write") + .apply { + val nameAllocator = NameAllocator().apply { + newName("outputStream") + newName("serializer") + } + val parameterName = nameAllocator.newName(rootField.variableName) + addJavadoc( + """ + Write${'$'}W{@link $1T}${'$'}Wto${'$'}Wthe${'$'}WXML${'$'}Wfile. + + @param $2L the${'$'}W{@link ${'$'}1T}${'$'}Wto${'$'}Wpersist + """.trimIndent(), rootField.type, parameterName + ) + addAnnotation(nullableType) + addModifiers(Modifier.PUBLIC) + addParameter( + ParameterSpec.builder(rootField.type, parameterName) + .addAnnotation(nonNullType) + .build() + ) + addStatement("\$1T outputStream = null", FileOutputStream::class.java) + addControlFlow("try") { + addStatement("outputStream = mFile.startWrite()") + addStatement( + "final \$1T serializer =\$W\$2T.newSerializer()", xmlSerializerType, xmlType + ) + addStatement( + "serializer.setOutput(outputStream, \$1T.UTF_8.name())", + StandardCharsets::class.java + ) + addStatement( + "serializer.setFeature(\$1S, true)", + "http://xmlpull.org/v1/doc/features.html#indent-output" + ) + addStatement("serializer.startDocument(null, true)") + addStatement("serialize(serializer,\$W\$1L)", parameterName) + addStatement("serializer.endDocument()") + addStatement("mFile.finishWrite(outputStream)") + nextControlFlow("catch (Exception e)") + addStatement("e.printStackTrace()") + addStatement("mFile.failWrite(outputStream)") + } + } + .build() + +private fun generateSerializeMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("serialize") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .addParameter( + ParameterSpec.builder(xmlSerializerType, "serializer") + .addAnnotation(nonNullType) + .build() + ) + .apply { + val nameAllocator = NameAllocator().apply { newName("serializer") } + val parameterName = nameAllocator.newName(rootField.variableName) + addParameter( + ParameterSpec.builder(rootField.type, parameterName) + .addAnnotation(nonNullType) + .build() + ) + addException(IOException::class.java) + addStatement("serializer.startTag(null, \$1S)", rootField.tagName) + addStatement("\$1L(serializer, \$2L)", rootField.serializeMethodName, parameterName) + addStatement("serializer.endTag(null, \$1S)", rootField.tagName) + } + .build() + +private fun generateSerializeClassMethod(classField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder(classField.serializeMethodName) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .addParameter( + ParameterSpec.builder(xmlSerializerType, "serializer") + .addAnnotation(nonNullType) + .build() + ) + .apply { + val nameAllocator = NameAllocator().apply { + newName("serializer") + newName("i") + } + val parameterName = nameAllocator.newName(classField.serializeParameterName) + addParameter( + ParameterSpec.builder(classField.type, parameterName) + .addAnnotation(nonNullType) + .build() + ) + addException(IOException::class.java) + val (attributeFields, tagFields) = classField.fields + .partition { it is PrimitiveFieldInfo || it is StringFieldInfo } + for (field in attributeFields) { + val variableName = "$parameterName.${field.name}" + if (!field.isRequired) { + beginControlFlow("if (\$1L != null)", variableName) + } + when (field) { + is PrimitiveFieldInfo -> { + if (field.isRequired && !field.type.isPrimitive) { + addControlFlow("if (\$1L == null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Field \"${field.name}\" is null" + ) + } + } + val stringVariableName = + nameAllocator.newName("${field.variableName}String") + addStatement( + "final String \$1L =\$WString.valueOf(\$2L)", stringVariableName, + variableName + ) + addStatement( + "serializer.attribute(null, \$1S, \$2L)", field.attributeName, + stringVariableName + ) + } + is StringFieldInfo -> { + if (field.isRequired) { + addControlFlow("if (\$1L == null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Field \"${field.name}\" is null" + ) + } + } + addStatement( + "serializer.attribute(null, \$1S, \$2L)", field.attributeName, + variableName + ) + } + else -> error(field) + } + if (!field.isRequired) { + endControlFlow() + } + } + for (field in tagFields) { + val variableName = "$parameterName.${field.name}" + if (field.isRequired) { + addControlFlow("if (\$1L == null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Field \"${field.name}\" is null" + ) + } + } + when (field) { + is ClassFieldInfo -> { + addStatement("serializer.startTag(null, \$1S)", field.tagName) + addStatement( + "\$1L(serializer, \$2L)", field.serializeMethodName, variableName + ) + addStatement("serializer.endTag(null, \$1S)", field.tagName) + } + is ListFieldInfo -> { + val sizeVariableName = nameAllocator.newName("${field.variableName}Size") + addStatement( + "final int \$1L =\$W\$2L.size()", sizeVariableName, variableName + ) + addControlFlow("for (int i = 0;\$Wi < \$1L;\$Wi++)", sizeVariableName) { + val elementNameAllocator = nameAllocator.clone() + val elementVariableName = elementNameAllocator.newName( + field.element.xmlName!!.toLowerCamelCase() + ) + addStatement( + "final \$1T \$2L =\$W\$3L.get(i)", field.element.type, + elementVariableName, variableName + ) + addControlFlow("if (\$1L == null)", elementVariableName) { + addStatement( + "throw new IllegalArgumentException(\$1S\$W+ i\$W+ \$2S)", + "Field element \"${field.name}[", "]\" is null" + ) + } + addStatement("serializer.startTag(null, \$1S)", field.element.tagName) + addStatement( + "\$1L(serializer,\$W\$2L)", field.element.serializeMethodName, + elementVariableName + ) + addStatement("serializer.endTag(null, \$1S)", field.element.tagName) + } + } + else -> error(field) + } + } + } + .build() + +private val ClassFieldInfo.serializeMethodName: String + get() = "serialize${type.simpleName().toUpperCamelCase()}" + +private val ClassFieldInfo.serializeParameterName: String + get() = type.simpleName().toLowerCamelCase() + +private val FieldInfo.variableName: String + get() = name.toLowerCamelCase() + +private val FieldInfo.attributeName: String + get() { + check(this is PrimitiveFieldInfo || this is StringFieldInfo) + return xmlNameOrName.toLowerCamelCase() + } + +private val FieldInfo.tagName: String + get() { + check(this is ClassFieldInfo || this is ListFieldInfo) + return xmlNameOrName.toLowerKebabCase() + } + +private val FieldInfo.xmlNameOrName: String + get() = xmlName ?: name + +private fun generateDeleteMethod(): MethodSpec = + MethodSpec.methodBuilder("delete") + .addJavadoc("Delete the XML file, if any.") + .addModifiers(Modifier.PUBLIC) + .addStatement("mFile.delete()") + .build() + +private inline fun MethodSpec.Builder.addControlFlow( + controlFlow: String, + vararg args: Any, + block: MethodSpec.Builder.() -> Unit +): MethodSpec.Builder { + beginControlFlow(controlFlow, *args) + block() + endControlFlow() + return this +} diff --git a/tools/xmlpersistence/src/main/kotlin/Main.kt b/tools/xmlpersistence/src/main/kotlin/Main.kt new file mode 100644 index 000000000000..e271f8cb9361 --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/Main.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.File +import java.nio.file.Files + +fun main(args: Array<String>) { + val showUsage = args.isEmpty() || when (args.singleOrNull()) { + "-h", "--help" -> true + else -> false + } + if (showUsage) { + usage() + return + } + + val files = args.flatMap { + File(it).walk().filter { it.isFile && it.extension == "java" }.map { it.toPath() } + } + val persistences = parse(files) + for (persistence in persistences) { + val file = generate(persistence) + Files.newBufferedWriter(persistence.path).use { + it.write(FILE_HEADER) + file.writeTo(it) + } + } +} + +private fun usage() { + println("Usage: xmlpersistence <FILES>") +} diff --git a/tools/xmlpersistence/src/main/kotlin/Parser.kt b/tools/xmlpersistence/src/main/kotlin/Parser.kt new file mode 100644 index 000000000000..3ea12a9aa389 --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/Parser.kt @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.github.javaparser.JavaParser +import com.github.javaparser.ParseProblemException +import com.github.javaparser.ParseResult +import com.github.javaparser.ParserConfiguration +import com.github.javaparser.ast.Node +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration +import com.github.javaparser.ast.body.FieldDeclaration +import com.github.javaparser.ast.body.TypeDeclaration +import com.github.javaparser.ast.expr.AnnotationExpr +import com.github.javaparser.ast.expr.Expression +import com.github.javaparser.ast.expr.NormalAnnotationExpr +import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr +import com.github.javaparser.ast.expr.StringLiteralExpr +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration +import com.github.javaparser.resolution.types.ResolvedPrimitiveType +import com.github.javaparser.resolution.types.ResolvedReferenceType +import com.github.javaparser.symbolsolver.JavaSymbolSolver +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration +import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver +import com.github.javaparser.symbolsolver.resolution.typesolvers.MemoryTypeSolver +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.ParameterizedTypeName +import com.squareup.javapoet.TypeName +import java.nio.file.Path +import java.util.Optional + +class PersistenceInfo( + val name: String, + val root: ClassFieldInfo, + val path: Path +) + +sealed class FieldInfo { + abstract val name: String + abstract val xmlName: String? + abstract val type: TypeName + abstract val isRequired: Boolean +} + +class PrimitiveFieldInfo( + override val name: String, + override val xmlName: String?, + override val type: TypeName, + override val isRequired: Boolean +) : FieldInfo() + +class StringFieldInfo( + override val name: String, + override val xmlName: String?, + override val isRequired: Boolean +) : FieldInfo() { + override val type: TypeName = ClassName.get(String::class.java) +} + +class ClassFieldInfo( + override val name: String, + override val xmlName: String?, + override val type: ClassName, + override val isRequired: Boolean, + val fields: List<FieldInfo> +) : FieldInfo() + +class ListFieldInfo( + override val name: String, + override val xmlName: String?, + override val type: ParameterizedTypeName, + val element: ClassFieldInfo +) : FieldInfo() { + override val isRequired: Boolean = true +} + +fun parse(files: List<Path>): List<PersistenceInfo> { + val typeSolver = CombinedTypeSolver().apply { add(ReflectionTypeSolver()) } + val javaParser = JavaParser(ParserConfiguration() + .setSymbolResolver(JavaSymbolSolver(typeSolver))) + val compilationUnits = files.map { javaParser.parse(it).getOrThrow() } + val memoryTypeSolver = MemoryTypeSolver().apply { + for (compilationUnit in compilationUnits) { + for (typeDeclaration in compilationUnit.getNodesByClass<TypeDeclaration<*>>()) { + val name = typeDeclaration.fullyQualifiedName.getOrNull() ?: continue + addDeclaration(name, typeDeclaration.resolve()) + } + } + } + typeSolver.add(memoryTypeSolver) + return mutableListOf<PersistenceInfo>().apply { + for (compilationUnit in compilationUnits) { + val classDeclarations = compilationUnit + .getNodesByClass<ClassOrInterfaceDeclaration>() + .filter { !it.isInterface && (!it.isNestedType || it.isStatic) } + this += classDeclarations.mapNotNull { parsePersistenceInfo(it) } + } + } +} + +private fun parsePersistenceInfo(classDeclaration: ClassOrInterfaceDeclaration): PersistenceInfo? { + val annotation = classDeclaration.getAnnotationByName("XmlPersistence").getOrNull() + ?: return null + val rootClassName = classDeclaration.nameAsString + val name = annotation.getMemberValue("value")?.stringLiteralValue + ?: "${rootClassName}Persistence" + val rootXmlName = classDeclaration.getAnnotationByName("XmlName").getOrNull() + ?.getMemberValue("value")?.stringLiteralValue + val root = parseClassFieldInfo( + rootXmlName ?: rootClassName, rootXmlName, true, classDeclaration + ) + val path = classDeclaration.findCompilationUnit().get().storage.get().path + .resolveSibling("$name.java") + return PersistenceInfo(name, root, path) +} + +private fun parseClassFieldInfo( + name: String, + xmlName: String?, + isRequired: Boolean, + classDeclaration: ClassOrInterfaceDeclaration +): ClassFieldInfo { + val fields = classDeclaration.fields.filterNot { it.isStatic }.map { parseFieldInfo(it) } + val type = classDeclaration.resolve().typeName + return ClassFieldInfo(name, xmlName, type, isRequired, fields) +} + +private fun parseFieldInfo(field: FieldDeclaration): FieldInfo { + require(field.isPublic && field.isFinal) + val variable = field.variables.single() + val name = variable.nameAsString + val annotations = field.annotations + variable.type.annotations + val annotation = annotations.getByName("XmlName") + val xmlName = annotation?.getMemberValue("value")?.stringLiteralValue + val isRequired = annotations.getByName("NonNull") != null + return when (val type = variable.type.resolve()) { + is ResolvedPrimitiveType -> { + val primitiveType = type.typeName + PrimitiveFieldInfo(name, xmlName, primitiveType, true) + } + is ResolvedReferenceType -> { + when (type.qualifiedName) { + Boolean::class.javaObjectType.name, Byte::class.javaObjectType.name, + Short::class.javaObjectType.name, Char::class.javaObjectType.name, + Integer::class.javaObjectType.name, Long::class.javaObjectType.name, + Float::class.javaObjectType.name, Double::class.javaObjectType.name -> + PrimitiveFieldInfo(name, xmlName, type.typeName, isRequired) + String::class.java.name -> StringFieldInfo(name, xmlName, isRequired) + List::class.java.name -> { + requireNotNull(xmlName) + val elementType = type.typeParametersValues().single() + require(elementType is ResolvedReferenceType) + val listType = ParameterizedTypeName.get( + ClassName.get(List::class.java), elementType.typeName + ) + val element = parseClassFieldInfo( + "(element)", xmlName, true, elementType.classDeclaration + ) + ListFieldInfo(name, xmlName, listType, element) + } + else -> parseClassFieldInfo(name, xmlName, isRequired, type.classDeclaration) + } + } + else -> error(type) + } +} + +private fun <T> ParseResult<T>.getOrThrow(): T = + if (isSuccessful) { + result.get() + } else { + throw ParseProblemException(problems) + } + +private inline fun <reified T : Node> Node.getNodesByClass(): List<T> = + getNodesByClass(T::class.java) + +private fun <T : Node> Node.getNodesByClass(klass: Class<T>): List<T> = mutableListOf<T>().apply { + if (klass.isInstance(this@getNodesByClass)) { + this += klass.cast(this@getNodesByClass) + } + for (childNode in childNodes) { + this += childNode.getNodesByClass(klass) + } +} + +private fun <T> Optional<T>.getOrNull(): T? = orElse(null) + +private fun List<AnnotationExpr>.getByName(name: String): AnnotationExpr? = + find { it.name.identifier == name } + +private fun AnnotationExpr.getMemberValue(name: String): Expression? = + when (this) { + is NormalAnnotationExpr -> pairs.find { it.nameAsString == name }?.value + is SingleMemberAnnotationExpr -> if (name == "value") memberValue else null + else -> null + } + +private val Expression.stringLiteralValue: String + get() { + require(this is StringLiteralExpr) + return value + } + +private val ResolvedReferenceType.classDeclaration: ClassOrInterfaceDeclaration + get() { + val resolvedClassDeclaration = typeDeclaration + require(resolvedClassDeclaration is JavaParserClassDeclaration) + return resolvedClassDeclaration.wrappedNode + } + +private val ResolvedPrimitiveType.typeName: TypeName + get() = + when (this) { + ResolvedPrimitiveType.BOOLEAN -> TypeName.BOOLEAN + ResolvedPrimitiveType.BYTE -> TypeName.BYTE + ResolvedPrimitiveType.SHORT -> TypeName.SHORT + ResolvedPrimitiveType.CHAR -> TypeName.CHAR + ResolvedPrimitiveType.INT -> TypeName.INT + ResolvedPrimitiveType.LONG -> TypeName.LONG + ResolvedPrimitiveType.FLOAT -> TypeName.FLOAT + ResolvedPrimitiveType.DOUBLE -> TypeName.DOUBLE + } + +// This doesn't support type parameters. +private val ResolvedReferenceType.typeName: TypeName + get() = typeDeclaration.typeName + +private val ResolvedReferenceTypeDeclaration.typeName: ClassName + get() { + val packageName = packageName + val classNames = className.split(".") + val topLevelClassName = classNames.first() + val nestedClassNames = classNames.drop(1) + return ClassName.get(packageName, topLevelClassName, *nestedClassNames.toTypedArray()) + } diff --git a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt new file mode 100644 index 000000000000..b4bdbba7170b --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Locale + +private val camelHumpBoundary = Regex( + "-" + + "|_" + + "|(?<=[0-9])(?=[^0-9])" + + "|(?<=[A-Z])(?=[^A-Za-z]|[A-Z][a-z])" + + "|(?<=[a-z])(?=[^a-z])" +) + +private fun String.toCamelHumps(): List<String> = split(camelHumpBoundary) + +fun String.toUpperCamelCase(): String = + toCamelHumps().joinToString("") { it.toLowerCase(Locale.ROOT).capitalize(Locale.ROOT) } + +fun String.toLowerCamelCase(): String = toUpperCamelCase().decapitalize(Locale.ROOT) + +fun String.toUpperKebabCase(): String = + toCamelHumps().joinToString("-") { it.toUpperCase(Locale.ROOT) } + +fun String.toLowerKebabCase(): String = + toCamelHumps().joinToString("-") { it.toLowerCase(Locale.ROOT) } + +fun String.toUpperSnakeCase(): String = + toCamelHumps().joinToString("_") { it.toUpperCase(Locale.ROOT) } + +fun String.toLowerSnakeCase(): String = + toCamelHumps().joinToString("_") { it.toLowerCase(Locale.ROOT) } |