summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/Command.cpp4
-rw-r--r--tools/aapt/SdkConstants.h1
-rw-r--r--tools/aapt2/Android.bp5
-rw-r--r--tools/aapt2/AppInfo.h4
-rw-r--r--tools/aapt2/Configuration.proto1
-rw-r--r--tools/aapt2/Debug.cpp111
-rw-r--r--tools/aapt2/Debug.h1
-rw-r--r--tools/aapt2/Resource.h9
-rw-r--r--tools/aapt2/ResourceParser.cpp41
-rw-r--r--tools/aapt2/ResourceParser_test.cpp44
-rw-r--r--tools/aapt2/ResourceTable.cpp30
-rw-r--r--tools/aapt2/ResourceTable.h38
-rw-r--r--tools/aapt2/ResourceTable_test.cpp20
-rw-r--r--tools/aapt2/ResourceUtils.cpp19
-rw-r--r--tools/aapt2/ResourceUtils_test.cpp14
-rw-r--r--tools/aapt2/ResourceValues.cpp10
-rw-r--r--tools/aapt2/ResourceValues.h1
-rw-r--r--tools/aapt2/ResourceValues_test.cpp52
-rw-r--r--tools/aapt2/Resources.proto13
-rw-r--r--tools/aapt2/ResourcesInternal.proto1
-rw-r--r--tools/aapt2/SdkConstants.cpp2
-rw-r--r--tools/aapt2/SdkConstants.h1
-rw-r--r--tools/aapt2/cmd/Compile.cpp18
-rw-r--r--tools/aapt2/cmd/Compile_test.cpp2
-rw-r--r--tools/aapt2/cmd/Convert.cpp6
-rw-r--r--tools/aapt2/cmd/Diff.cpp6
-rw-r--r--tools/aapt2/cmd/Dump.cpp17
-rw-r--r--tools/aapt2/cmd/Dump.h12
-rw-r--r--tools/aapt2/cmd/Link.cpp203
-rw-r--r--tools/aapt2/cmd/Link.h31
-rw-r--r--tools/aapt2/cmd/Link_test.cpp244
-rw-r--r--tools/aapt2/cmd/Optimize.cpp56
-rw-r--r--tools/aapt2/cmd/Optimize.h17
-rw-r--r--tools/aapt2/cmd/Optimize_test.cpp68
-rw-r--r--tools/aapt2/cmd/Util.cpp4
-rw-r--r--tools/aapt2/cmd/Util_test.cpp22
-rw-r--r--tools/aapt2/dump/DumpManifest.cpp20
-rw-r--r--tools/aapt2/format/Archive.cpp9
-rw-r--r--tools/aapt2/format/Archive_test.cpp209
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.cpp32
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp62
-rw-r--r--tools/aapt2/format/binary/TableFlattener.h5
-rw-r--r--tools/aapt2/format/binary/TableFlattener_test.cpp124
-rw-r--r--tools/aapt2/format/binary/XmlFlattener_test.cpp6
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.cpp22
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize.cpp64
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize.h8
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize_test.cpp94
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/Android.mk2
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk29
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml23
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk28
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml17
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml25
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml43
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk28
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml28
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml25
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml25
-rw-r--r--tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml29
-rw-r--r--tools/aapt2/io/Util.cpp2
-rw-r--r--tools/aapt2/io/Util.h10
-rw-r--r--tools/aapt2/java/AnnotationProcessor.cpp31
-rw-r--r--tools/aapt2/java/AnnotationProcessor.h3
-rw-r--r--tools/aapt2/java/AnnotationProcessor_test.cpp15
-rw-r--r--tools/aapt2/java/JavaClassGenerator.cpp17
-rw-r--r--tools/aapt2/java/JavaClassGenerator_test.cpp12
-rw-r--r--tools/aapt2/java/ProguardRules.cpp46
-rw-r--r--tools/aapt2/java/ProguardRules.h6
-rw-r--r--tools/aapt2/java/ProguardRules_test.cpp21
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp46
-rw-r--r--tools/aapt2/link/ManifestFixer_test.cpp30
-rw-r--r--tools/aapt2/link/ReferenceLinker.cpp43
-rw-r--r--tools/aapt2/link/ReferenceLinker.h7
-rw-r--r--tools/aapt2/link/ReferenceLinker_test.cpp65
-rw-r--r--tools/aapt2/link/TableMerger.cpp37
-rw-r--r--tools/aapt2/link/TableMerger.h2
-rw-r--r--tools/aapt2/link/TableMerger_test.cpp137
-rw-r--r--tools/aapt2/link/XmlReferenceLinker.cpp11
-rw-r--r--tools/aapt2/link/XmlReferenceLinker_test.cpp63
-rw-r--r--tools/aapt2/optimize/MultiApkGenerator.cpp4
-rw-r--r--tools/aapt2/optimize/ResourceDeduper.cpp9
-rw-r--r--tools/aapt2/optimize/ResourceDeduper_test.cpp47
-rw-r--r--tools/aapt2/optimize/ResourcePathShortener.cpp33
-rw-r--r--tools/aapt2/optimize/ResourcePathShortener_test.cpp99
-rw-r--r--tools/aapt2/process/IResourceTableConsumer.h2
-rw-r--r--tools/aapt2/process/SymbolTable.cpp20
-rw-r--r--tools/aapt2/process/SymbolTable.h2
-rw-r--r--tools/aapt2/test/Context.h14
-rw-r--r--tools/aapt2/test/Fixture.cpp25
-rw-r--r--tools/aapt2/test/Fixture.h5
-rw-r--r--tools/bit/main.cpp10
-rwxr-xr-xtools/codegen/.gitignore2
-rw-r--r--tools/codegen/Android.bp18
-rw-r--r--tools/codegen/OWNERS1
-rw-r--r--tools/codegen/manifest.txt1
-rw-r--r--tools/codegen/src/com/android/codegen/ClassInfo.kt27
-rw-r--r--tools/codegen/src/com/android/codegen/ClassPrinter.kt234
-rw-r--r--tools/codegen/src/com/android/codegen/ConstDef.kt17
-rw-r--r--tools/codegen/src/com/android/codegen/FeatureFlag.kt27
-rw-r--r--tools/codegen/src/com/android/codegen/FieldInfo.kt230
-rw-r--r--tools/codegen/src/com/android/codegen/FileInfo.kt289
-rw-r--r--tools/codegen/src/com/android/codegen/Generators.kt949
-rw-r--r--tools/codegen/src/com/android/codegen/ImportsProvider.kt92
-rw-r--r--tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt151
-rwxr-xr-xtools/codegen/src/com/android/codegen/Main.kt136
-rw-r--r--tools/codegen/src/com/android/codegen/Printer.kt186
-rw-r--r--tools/codegen/src/com/android/codegen/SharedConstants.kt7
-rw-r--r--tools/codegen/src/com/android/codegen/Utils.kt146
-rw-r--r--tools/incident_section_gen/main.cpp13
-rw-r--r--tools/processors/staledataclass/Android.bp29
-rw-r--r--tools/processors/staledataclass/META-INF/services/javax.annotation.processing.Processor1
-rw-r--r--tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt227
-rw-r--r--tools/protologtool/Android.bp33
-rw-r--r--tools/protologtool/README.md119
-rw-r--r--tools/protologtool/TEST_MAPPING7
-rw-r--r--tools/protologtool/manifest.txt1
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt81
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt212
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/Constants.kt24
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/LogGroup.kt24
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/LogLevel.kt38
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/LogParser.kt115
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt26
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt113
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt23
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt50
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt250
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt236
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt124
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt122
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/exceptions.kt41
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt182
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt287
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt144
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt187
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt226
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt52
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt451
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt94
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/ViewerConfigParserTest.kt327
-rw-r--r--tools/stats_log_api_gen/.clang-format17
-rw-r--r--tools/stats_log_api_gen/Android.bp11
-rw-r--r--tools/stats_log_api_gen/Collation.cpp653
-rw-r--r--tools/stats_log_api_gen/Collation.h154
-rw-r--r--tools/stats_log_api_gen/atoms_info_writer.cpp164
-rw-r--r--tools/stats_log_api_gen/atoms_info_writer.h8
-rw-r--r--tools/stats_log_api_gen/java_writer.cpp354
-rw-r--r--tools/stats_log_api_gen/java_writer.h12
-rw-r--r--tools/stats_log_api_gen/java_writer_q.cpp630
-rw-r--r--tools/stats_log_api_gen/java_writer_q.h29
-rw-r--r--tools/stats_log_api_gen/main.cpp618
-rw-r--r--tools/stats_log_api_gen/native_writer.cpp251
-rw-r--r--tools/stats_log_api_gen/native_writer.h14
-rw-r--r--tools/stats_log_api_gen/native_writer_q.cpp276
-rw-r--r--tools/stats_log_api_gen/native_writer_q.h49
-rw-r--r--tools/stats_log_api_gen/test.proto83
-rw-r--r--tools/stats_log_api_gen/test_collation.cpp358
-rw-r--r--tools/stats_log_api_gen/utils.cpp310
-rw-r--r--tools/stats_log_api_gen/utils.h48
-rw-r--r--tools/streaming_proto/cpp/main.cpp14
-rw-r--r--tools/validatekeymaps/Main.cpp1
-rw-r--r--tools/validatekeymaps/OWNERS2
163 files changed, 10457 insertions, 2930 deletions
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 05375b0cb871..21386b88ce2c 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -787,7 +787,9 @@ int doDump(Bundle* bundle)
// The dynamicRefTable can be null if there are no resources for this asset cookie.
// This fine.
- const DynamicRefTable* dynamicRefTable = res.getDynamicRefTableForCookie(assetsCookie);
+ auto noop_destructor = [](const DynamicRefTable* /*ref_table */) { };
+ auto dynamicRefTable = std::shared_ptr<const DynamicRefTable>(
+ res.getDynamicRefTableForCookie(assetsCookie), noop_destructor);
Asset* asset = NULL;
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
index 27ffcdf52168..04fbbe1f1069 100644
--- a/tools/aapt/SdkConstants.h
+++ b/tools/aapt/SdkConstants.h
@@ -45,6 +45,7 @@ enum {
SDK_O_MR1 = 27,
SDK_P = 28,
SDK_Q = 29,
+ SDK_R = 30,
};
#endif // H_AAPT_SDK_CONSTANTS
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index a2709bd545b2..ade0dc4d9c42 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -57,9 +57,10 @@ cc_defaults {
"libziparchive",
"libpng",
"libbase",
- "libprotobuf-cpp-lite",
+ "libprotobuf-cpp-full",
"libz",
"libbuildversion",
+ "libidmap2_policies",
],
stl: "libc++_static",
group_static_libs: true,
@@ -198,6 +199,7 @@ cc_test_host {
cc_binary_host {
name: "aapt2",
srcs: ["Main.cpp"] + toolSources,
+ use_version_lib: true,
static_libs: ["libaapt2"],
defaults: ["aapt2_defaults"],
}
@@ -210,6 +212,7 @@ genrule {
tools: [":soong_zip"],
srcs: [
"Configuration.proto",
+ "ResourcesInternal.proto",
"Resources.proto",
],
out: ["aapt2-protos.zip"],
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h
index 75123537116f..d3ca357b0305 100644
--- a/tools/aapt2/AppInfo.h
+++ b/tools/aapt2/AppInfo.h
@@ -17,6 +17,7 @@
#ifndef AAPT_APP_INFO_H
#define AAPT_APP_INFO_H
+#include <set>
#include <string>
#include "util/Maybe.h"
@@ -42,6 +43,9 @@ struct AppInfo {
// The app's split name, if it is a split.
Maybe<std::string> split_name;
+
+ // The split names that this split depends on.
+ std::set<std::string> split_name_dependencies;
};
} // namespace aapt
diff --git a/tools/aapt2/Configuration.proto b/tools/aapt2/Configuration.proto
index fc636a43ec40..8a4644c9a219 100644
--- a/tools/aapt2/Configuration.proto
+++ b/tools/aapt2/Configuration.proto
@@ -19,7 +19,6 @@ syntax = "proto3";
package aapt.pb;
option java_package = "com.android.aapt";
-option optimize_for = LITE_RUNTIME;
// A description of the requirements a device must have in order for a
// resource to be matched and selected.
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 3da22b4fb9fa..1eb7d95f381a 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -32,10 +32,16 @@
#include "text/Printer.h"
#include "util/Util.h"
+#include "idmap2/Policies.h"
+
using ::aapt::text::Printer;
using ::android::StringPiece;
using ::android::base::StringPrintf;
+using android::idmap2::policy::kPolicyStringToFlag;
+
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
namespace aapt {
namespace {
@@ -178,19 +184,17 @@ class ValueBodyPrinter : public ConstValueVisitor {
void Visit(const Array* array) override {
const size_t count = array->elements.size();
printer_->Print("[");
- if (count > 0) {
- for (size_t i = 0u; i < count; i++) {
- if (i != 0u && i % 4u == 0u) {
- printer_->Println();
- printer_->Print(" ");
- }
- PrintItem(*array->elements[i]);
- if (i != count - 1) {
- printer_->Print(", ");
- }
+ for (size_t i = 0u; i < count; i++) {
+ if (i != 0u && i % 4u == 0u) {
+ printer_->Println();
+ printer_->Print(" ");
+ }
+ PrintItem(*array->elements[i]);
+ if (i != count - 1) {
+ printer_->Print(", ");
}
- printer_->Println("]");
}
+ printer_->Println("]");
}
void Visit(const Plural* plural) override {
@@ -248,6 +252,29 @@ class ValueBodyPrinter : public ConstValueVisitor {
Printer* printer_;
};
+std::string OverlayablePoliciesToString(PolicyFlags policies) {
+ std::string str;
+
+ uint32_t remaining = policies;
+ for (auto const& policy : kPolicyStringToFlag) {
+ if ((policies & policy.second) != policy.second) {
+ continue;
+ }
+ if (!str.empty()) {
+ str.append("|");
+ }
+ str.append(policy.first.data());
+ remaining &= ~policy.second;
+ }
+ if (remaining != 0) {
+ if (!str.empty()) {
+ str.append("|");
+ }
+ str.append(StringPrintf("0x%08x", remaining));
+ }
+ return !str.empty() ? str : "none";
+}
+
} // namespace
void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
@@ -314,6 +341,10 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
break;
}
+ if (entry->overlayable_item) {
+ printer->Print(" OVERLAYABLE");
+ }
+
printer->Println();
if (options.show_values) {
@@ -527,4 +558,62 @@ void Debug::DumpXml(const xml::XmlResource& doc, Printer* printer) {
doc.root->Accept(&xml_visitor);
}
+struct DumpOverlayableEntry {
+ std::string overlayable_section;
+ std::string policy_subsection;
+ std::string resource_name;
+};
+
+void Debug::DumpOverlayable(const ResourceTable& table, text::Printer* printer) {
+ std::vector<DumpOverlayableEntry> items;
+ for (const auto& package : table.packages) {
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ if (entry->overlayable_item) {
+ const auto& overlayable_item = entry->overlayable_item.value();
+ const auto overlayable_section = StringPrintf(R"(name="%s" actor="%s")",
+ overlayable_item.overlayable->name.c_str(),
+ overlayable_item.overlayable->actor.c_str());
+ const auto policy_subsection = StringPrintf(R"(policies="%s")",
+ OverlayablePoliciesToString(overlayable_item.policies).c_str());
+ const auto value =
+ StringPrintf("%s/%s", to_string(type->type).data(), entry->name.c_str());
+ items.push_back(DumpOverlayableEntry{overlayable_section, policy_subsection, value});
+ }
+ }
+ }
+ }
+
+ std::sort(items.begin(), items.end(),
+ [](const DumpOverlayableEntry& a, const DumpOverlayableEntry& b) {
+ if (a.overlayable_section != b.overlayable_section) {
+ return a.overlayable_section < b.overlayable_section;
+ }
+ if (a.policy_subsection != b.policy_subsection) {
+ return a.policy_subsection < b.policy_subsection;
+ }
+ return a.resource_name < b.resource_name;
+ });
+
+ std::string last_overlayable_section;
+ std::string last_policy_subsection;
+ for (const auto& item : items) {
+ if (last_overlayable_section != item.overlayable_section) {
+ printer->Println(item.overlayable_section);
+ last_overlayable_section = item.overlayable_section;
+ }
+ if (last_policy_subsection != item.policy_subsection) {
+ printer->Indent();
+ printer->Println(item.policy_subsection);
+ last_policy_subsection = item.policy_subsection;
+ printer->Undent();
+ }
+ printer->Indent();
+ printer->Indent();
+ printer->Println(item.resource_name);
+ printer->Undent();
+ printer->Undent();
+ }
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index a43197cacf7b..9443d606d7e5 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -39,6 +39,7 @@ struct Debug {
static void DumpHex(const void* data, size_t len);
static void DumpXml(const xml::XmlResource& doc, text::Printer* printer);
static void DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer);
+ static void DumpOverlayable(const ResourceTable& table, text::Printer* printer);
};
} // namespace aapt
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 67ba895e51d1..c49c370bcc44 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -147,10 +147,11 @@ struct ResourceId {
ResourceId(uint32_t res_id); // NOLINT(google-explicit-constructor)
ResourceId(uint8_t p, uint8_t t, uint16_t e);
- bool is_valid() const;
+ // Returns true if the ID is a valid ID that is not dynamic (package ID cannot be 0)
+ bool is_valid_static() const;
// Returns true if the ID is a valid ID or dynamic ID (package ID can be 0).
- bool is_valid_dynamic() const;
+ bool is_valid() const;
uint8_t package_id() const;
uint8_t type_id() const;
@@ -233,11 +234,11 @@ inline ResourceId::ResourceId(uint32_t res_id) : id(res_id) {}
inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e)
: id((p << 24) | (t << 16) | e) {}
-inline bool ResourceId::is_valid() const {
+inline bool ResourceId::is_valid_static() const {
return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
}
-inline bool ResourceId::is_valid_dynamic() const {
+inline bool ResourceId::is_valid() const {
return (id & 0x00ff0000u) != 0;
}
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index b34c3d59d6a5..931a14b1f650 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -32,11 +32,15 @@
#include "util/Util.h"
#include "xml/XmlPullParser.h"
+#include "idmap2/Policies.h"
+
using ::aapt::ResourceUtils::StringBuilder;
using ::aapt::text::Utf8Iterator;
using ::android::ConfigDescription;
using ::android::StringPiece;
+using android::idmap2::policy::kPolicyStringToFlag;
+
namespace aapt {
constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
@@ -770,16 +774,14 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
return std::move(string);
}
- // If the text is empty, and the value is not allowed to be a string, encode it as a @null.
- if (util::TrimWhitespace(raw_value).empty()) {
- return ResourceUtils::MakeNull();
- }
-
if (allow_raw_value) {
// We can't parse this so return a RawString if we are allowed.
return util::make_unique<RawString>(
table_->string_pool.MakeRef(util::TrimWhitespace(raw_value),
StringPool::Context(config_)));
+ } else if (util::TrimWhitespace(raw_value).empty()) {
+ // If the text is empty, and the value is not allowed to be a string, encode it as a @null.
+ return ResourceUtils::MakeNull();
}
return {};
}
@@ -1067,7 +1069,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
bool error = false;
std::string comment;
- OverlayableItem::PolicyFlags current_policies = OverlayableItem::Policy::kNone;
+ PolicyFlags current_policies = PolicyFlags::NONE;
const size_t start_depth = parser->depth();
while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
xml::XmlPullParser::Event event = parser->event();
@@ -1077,7 +1079,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
} else if (event == xml::XmlPullParser::Event::kEndElement
&& parser->depth() == start_depth + 1) {
// Clear the current policies when exiting the <policy> tags
- current_policies = OverlayableItem::Policy::kNone;
+ current_policies = PolicyFlags::NONE;
continue;
} else if (event == xml::XmlPullParser::Event::kComment) {
// Retrieve the comment of individual <item> tags
@@ -1092,7 +1094,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
const std::string& element_name = parser->element_name();
const std::string& element_namespace = parser->element_namespace();
if (element_namespace.empty() && element_name == "item") {
- if (current_policies == OverlayableItem::Policy::kNone) {
+ if (current_policies == PolicyFlags::NONE) {
diag_->Error(DiagMessage(element_source)
<< "<item> within an <overlayable> must be inside a <policy> block");
error = true;
@@ -1137,7 +1139,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
out_resource->child_resources.push_back(std::move(child_resource));
} else if (element_namespace.empty() && element_name == "policy") {
- if (current_policies != OverlayableItem::Policy::kNone) {
+ if (current_policies != PolicyFlags::NONE) {
// If the policy list is not empty, then we are currently inside a policy element
diag_->Error(DiagMessage(element_source) << "<policy> blocks cannot be recursively nested");
error = true;
@@ -1145,21 +1147,14 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
} else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
// Parse the polices separated by vertical bar characters to allow for specifying multiple
// policies. Items within the policy tag will have the specified policy.
- static const auto kPolicyMap =
- ImmutableMap<StringPiece, OverlayableItem::Policy>::CreatePreSorted({
- {"odm", OverlayableItem::Policy::kOdm},
- {"oem", OverlayableItem::Policy::kOem},
- {"product", OverlayableItem::Policy::kProduct},
- {"public", OverlayableItem::Policy::kPublic},
- {"signature", OverlayableItem::Policy::kSignature},
- {"system", OverlayableItem::Policy::kSystem},
- {"vendor", OverlayableItem::Policy::kVendor},
- });
-
for (const StringPiece& part : util::Tokenize(maybe_type.value(), '|')) {
StringPiece trimmed_part = util::TrimWhitespace(part);
- const auto policy = kPolicyMap.find(trimmed_part);
- if (policy == kPolicyMap.end()) {
+ const auto policy = std::find_if(kPolicyStringToFlag.begin(),
+ kPolicyStringToFlag.end(),
+ [trimmed_part](const auto& it) {
+ return trimmed_part == it.first;
+ });
+ if (policy == kPolicyStringToFlag.end()) {
diag_->Error(DiagMessage(element_source)
<< "<policy> has unsupported type '" << trimmed_part << "'");
error = true;
@@ -1391,7 +1386,7 @@ Maybe<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(
return Attribute::Symbol{
Reference(ResourceNameRef({}, ResourceType::kId, maybe_name.value())),
- val.data};
+ val.data, val.dataType};
}
bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 46ad7cbe49e9..9b70079a98c9 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -41,6 +41,8 @@ using ::testing::Pointee;
using ::testing::SizeIs;
using ::testing::StrEq;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
namespace aapt {
constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
@@ -401,7 +403,7 @@ TEST_F(ResourceParserTest, ParseEnumAttr) {
std::string input = R"(
<attr name="foo">
<enum name="bar" value="0"/>
- <enum name="bat" value="1"/>
+ <enum name="bat" value="0x1"/>
<enum name="baz" value="2"/>
</attr>)";
ASSERT_TRUE(TestParse(input));
@@ -414,14 +416,17 @@ TEST_F(ResourceParserTest, ParseEnumAttr) {
ASSERT_TRUE(enum_attr->symbols[0].symbol.name);
EXPECT_THAT(enum_attr->symbols[0].symbol.name.value().entry, Eq("bar"));
EXPECT_THAT(enum_attr->symbols[0].value, Eq(0u));
+ EXPECT_THAT(enum_attr->symbols[0].type, Eq(Res_value::TYPE_INT_DEC));
ASSERT_TRUE(enum_attr->symbols[1].symbol.name);
EXPECT_THAT(enum_attr->symbols[1].symbol.name.value().entry, Eq("bat"));
EXPECT_THAT(enum_attr->symbols[1].value, Eq(1u));
+ EXPECT_THAT(enum_attr->symbols[1].type, Eq(Res_value::TYPE_INT_HEX));
ASSERT_TRUE(enum_attr->symbols[2].symbol.name);
EXPECT_THAT(enum_attr->symbols[2].symbol.name.value().entry, Eq("baz"));
EXPECT_THAT(enum_attr->symbols[2].value, Eq(2u));
+ EXPECT_THAT(enum_attr->symbols[2].type, Eq(Res_value::TYPE_INT_DEC));
}
TEST_F(ResourceParserTest, ParseFlagAttr) {
@@ -956,7 +961,7 @@ TEST_F(ResourceParserTest, ParseOverlayable) {
OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SIGNATURE));
search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar"));
ASSERT_TRUE(search_result);
@@ -965,7 +970,7 @@ TEST_F(ResourceParserTest, ParseOverlayable) {
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SIGNATURE));
}
TEST_F(ResourceParserTest, ParseOverlayableRequiresName) {
@@ -1002,6 +1007,9 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
<policy type="oem">
<item type="string" name="buz" />
</policy>
+ <policy type="actor">
+ <item type="string" name="actor" />
+ </policy>
</overlayable>)";
ASSERT_TRUE(TestParse(input));
@@ -1011,7 +1019,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
ASSERT_TRUE(search_result.value().entry->overlayable_item);
OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION));
search_result = table_.FindResource(test::ParseNameOrDie("string/fiz"));
ASSERT_TRUE(search_result);
@@ -1019,7 +1027,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
ASSERT_TRUE(search_result.value().entry->overlayable_item);
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSystem));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SYSTEM_PARTITION));
search_result = table_.FindResource(test::ParseNameOrDie("string/fuz"));
ASSERT_TRUE(search_result);
@@ -1027,7 +1035,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
ASSERT_TRUE(search_result.value().entry->overlayable_item);
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kVendor));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::VENDOR_PARTITION));
search_result = table_.FindResource(test::ParseNameOrDie("string/faz"));
ASSERT_TRUE(search_result);
@@ -1035,7 +1043,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
ASSERT_TRUE(search_result.value().entry->overlayable_item);
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PUBLIC));
search_result = table_.FindResource(test::ParseNameOrDie("string/foz"));
ASSERT_TRUE(search_result);
@@ -1043,7 +1051,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
ASSERT_TRUE(search_result.value().entry->overlayable_item);
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SIGNATURE));
search_result = table_.FindResource(test::ParseNameOrDie("string/biz"));
ASSERT_TRUE(search_result);
@@ -1051,7 +1059,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
ASSERT_TRUE(search_result.value().entry->overlayable_item);
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kOdm));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::ODM_PARTITION));
search_result = table_.FindResource(test::ParseNameOrDie("string/buz"));
ASSERT_TRUE(search_result);
@@ -1059,7 +1067,15 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
ASSERT_TRUE(search_result.value().entry->overlayable_item);
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kOem));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::OEM_PARTITION));
+
+ search_result = table_.FindResource(test::ParseNameOrDie("string/actor"));
+ ASSERT_TRUE(search_result);
+ ASSERT_THAT(search_result.value().entry, NotNull());
+ ASSERT_TRUE(search_result.value().entry->overlayable_item);
+ result_overlayable_item = search_result.value().entry->overlayable_item.value();
+ EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::ACTOR_SIGNATURE));
}
TEST_F(ResourceParserTest, ParseOverlayableNoPolicyError) {
@@ -1122,8 +1138,8 @@ TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) {
ASSERT_TRUE(search_result.value().entry->overlayable_item);
OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kVendor
- | OverlayableItem::Policy::kPublic));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::VENDOR_PARTITION
+ | PolicyFlags::PUBLIC));
search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
ASSERT_TRUE(search_result);
@@ -1131,8 +1147,8 @@ TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) {
ASSERT_TRUE(search_result.value().entry->overlayable_item);
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct
- | OverlayableItem::Policy::kSystem));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION
+ | PolicyFlags::SYSTEM_PARTITION));
}
TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 836e199593fc..e0a9a31eee8b 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -267,8 +267,7 @@ bool ResourceEntry::HasDefaultValue() const {
// A DECL will override a USE without error. Two DECLs must match in their format for there to be
// no error.
ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* existing,
- Value* incoming,
- bool overlay) {
+ Value* incoming) {
Attribute* existing_attr = ValueCast<Attribute>(existing);
Attribute* incoming_attr = ValueCast<Attribute>(incoming);
if (!incoming_attr) {
@@ -282,7 +281,7 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
}
// The existing and incoming values are strong, this is an error
// if the values are not both attributes.
- return overlay ? CollisionResult::kTakeNew : CollisionResult::kConflict;
+ return CollisionResult::kConflict;
}
if (!existing_attr) {
@@ -293,7 +292,7 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
}
// The existing value is not an attribute and it is strong,
// so the incoming attribute value is an error.
- return overlay ? CollisionResult::kTakeNew : CollisionResult::kConflict;
+ return CollisionResult::kConflict;
}
CHECK(incoming_attr != nullptr && existing_attr != nullptr);
@@ -324,9 +323,8 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
return CollisionResult::kConflict;
}
-ResourceTable::CollisionResult ResourceTable::IgnoreCollision(Value* /* existing */,
- Value* /* incoming */,
- bool /* overlay */) {
+ResourceTable::CollisionResult ResourceTable::IgnoreCollision(Value* /** existing **/,
+ Value* /** incoming **/) {
return CollisionResult::kKeepBoth;
}
@@ -400,7 +398,7 @@ bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceI
// Check for package names appearing twice with two different package ids
ResourceTablePackage* package = FindOrCreatePackage(name.package);
- if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
+ if (res_id.is_valid() && package->id && package->id.value() != res_id.package_id()) {
diag->Error(DiagMessage(source)
<< "trying to add resource '" << name << "' with ID " << res_id
<< " but package '" << package->name << "' already has ID "
@@ -409,9 +407,9 @@ bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceI
}
// Whether or not to error on duplicate resources
- bool check_id = validate_resources_ && res_id.is_valid_dynamic();
+ bool check_id = validate_resources_ && res_id.is_valid();
// Whether or not to create a duplicate resource if the id does not match
- bool use_id = !validate_resources_ && res_id.is_valid_dynamic();
+ bool use_id = !validate_resources_ && res_id.is_valid();
ResourceTableType* type = package->FindOrCreateType(name.type, use_id ? res_id.type_id()
: Maybe<uint8_t>());
@@ -442,7 +440,7 @@ bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceI
// Resource does not exist, add it now.
config_value->value = std::move(value);
} else {
- switch (conflict_resolver(config_value->value.get(), value.get(), false /* overlay */)) {
+ switch (conflict_resolver(config_value->value.get(), value.get())) {
case CollisionResult::kKeepBoth:
// Insert the value ignoring for duplicate configurations
entry->values.push_back(util::make_unique<ResourceConfigValue>(config, product));
@@ -465,7 +463,7 @@ bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceI
}
}
- if (res_id.is_valid_dynamic()) {
+ if (res_id.is_valid()) {
package->id = res_id.package_id();
type->id = res_id.type_id();
entry->id = res_id.entry_id();
@@ -506,7 +504,7 @@ bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibil
// Check for package names appearing twice with two different package ids
ResourceTablePackage* package = FindOrCreatePackage(name.package);
- if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
+ if (res_id.is_valid() && package->id && package->id.value() != res_id.package_id()) {
diag->Error(DiagMessage(source)
<< "trying to add resource '" << name << "' with ID " << res_id
<< " but package '" << package->name << "' already has ID "
@@ -515,9 +513,9 @@ bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibil
}
// Whether or not to error on duplicate resources
- bool check_id = validate_resources_ && res_id.is_valid_dynamic();
+ bool check_id = validate_resources_ && res_id.is_valid();
// Whether or not to create a duplicate resource if the id does not match
- bool use_id = !validate_resources_ && res_id.is_valid_dynamic();
+ bool use_id = !validate_resources_ && res_id.is_valid();
ResourceTableType* type = package->FindOrCreateType(name.type, use_id ? res_id.type_id()
: Maybe<uint8_t>());
@@ -543,7 +541,7 @@ bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibil
return false;
}
- if (res_id.is_valid_dynamic()) {
+ if (res_id.is_valid()) {
package->id = res_id.package_id();
type->id = res_id.type_id();
entry->id = res_id.entry_id();
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index e8793800b148..93a7a314d6ba 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -36,6 +36,8 @@
#include <unordered_map>
#include <vector>
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
namespace aapt {
// The Public status of a resource.
@@ -75,36 +77,8 @@ struct Overlayable {
struct OverlayableItem {
explicit OverlayableItem(const std::shared_ptr<Overlayable>& overlayable)
: overlayable(overlayable) {}
-
- // Represents the types overlays that are allowed to overlay the resource.
- typedef uint32_t PolicyFlags;
- enum Policy : uint32_t {
- kNone = 0x00000000,
-
- // The resource can be overlaid by any overlay.
- kPublic = 0x00000001,
-
- // The resource can be overlaid by any overlay on the system partition.
- kSystem = 0x00000002,
-
- // The resource can be overlaid by any overlay on the vendor partition.
- kVendor = 0x00000004,
-
- // The resource can be overlaid by any overlay on the product partition.
- kProduct = 0x00000008,
-
- // The resource can be overlaid by any overlay signed with the same signature as its actor.
- kSignature = 0x00000010,
-
- // The resource can be overlaid by any overlay on the odm partition.
- kOdm = 0x00000020,
-
- // The resource can be overlaid by any overlay on the oem partition.
- kOem = 0x00000040,
- };
-
std::shared_ptr<Overlayable> overlayable;
- PolicyFlags policies = Policy::kNone;
+ PolicyFlags policies = PolicyFlags::NONE;
std::string comment;
Source source;
};
@@ -228,13 +202,13 @@ class ResourceTable {
enum class CollisionResult { kKeepBoth, kKeepOriginal, kConflict, kTakeNew };
- using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*, bool)>;
+ using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
// When a collision of resources occurs, this method decides which value to keep.
- static CollisionResult ResolveValueCollision(Value* existing, Value* incoming, bool overlay);
+ static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
// When a collision of resources occurs, this method keeps both values
- static CollisionResult IgnoreCollision(Value* existing, Value* incoming, bool overlay);
+ static CollisionResult IgnoreCollision(Value* existing, Value* incoming);
bool AddResource(const ResourceNameRef& name, const android::ConfigDescription& config,
const android::StringPiece& product, std::unique_ptr<Value> value,
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index b97dc6b205ca..9271a7e6bae1 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -30,6 +30,8 @@ using ::testing::Eq;
using ::testing::NotNull;
using ::testing::StrEq;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
namespace aapt {
TEST(ResourceTableTest, FailToAddResourceWithBadName) {
@@ -247,8 +249,8 @@ TEST(ResourceTableTest, SetOverlayable) {
auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme",
Source("res/values/overlayable.xml", 40));
OverlayableItem overlayable_item(overlayable);
- overlayable_item.policies |= OverlayableItem::Policy::kProduct;
- overlayable_item.policies |= OverlayableItem::Policy::kVendor;
+ overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
+ overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
overlayable_item.comment = "comment";
overlayable_item.source = Source("res/values/overlayable.xml", 42);
@@ -264,8 +266,8 @@ TEST(ResourceTableTest, SetOverlayable) {
EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme"));
EXPECT_THAT(result_overlayable_item.overlayable->source.path, Eq("res/values/overlayable.xml"));
EXPECT_THAT(result_overlayable_item.overlayable->source.line, 40);
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct
- | OverlayableItem::Policy::kVendor));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION
+ | PolicyFlags::VENDOR_PARTITION));
ASSERT_THAT(result_overlayable_item.comment, StrEq("comment"));
EXPECT_THAT(result_overlayable_item.source.path, Eq("res/values/overlayable.xml"));
EXPECT_THAT(result_overlayable_item.source.line, 42);
@@ -277,17 +279,17 @@ TEST(ResourceTableTest, SetMultipleOverlayableResources) {
const ResourceName foo = test::ParseNameOrDie("android:string/foo");
auto group = std::make_shared<Overlayable>("Name", "overlay://theme");
OverlayableItem overlayable(group);
- overlayable.policies = OverlayableItem::Policy::kProduct;
+ overlayable.policies = PolicyFlags::PRODUCT_PARTITION;
ASSERT_TRUE(table.SetOverlayable(foo, overlayable, test::GetDiagnostics()));
const ResourceName bar = test::ParseNameOrDie("android:string/bar");
OverlayableItem overlayable2(group);
- overlayable2.policies = OverlayableItem::Policy::kProduct;
+ overlayable2.policies = PolicyFlags::PRODUCT_PARTITION;
ASSERT_TRUE(table.SetOverlayable(bar, overlayable2, test::GetDiagnostics()));
const ResourceName baz = test::ParseNameOrDie("android:string/baz");
OverlayableItem overlayable3(group);
- overlayable3.policies = OverlayableItem::Policy::kVendor;
+ overlayable3.policies = PolicyFlags::VENDOR_PARTITION;
ASSERT_TRUE(table.SetOverlayable(baz, overlayable3, test::GetDiagnostics()));
}
@@ -296,12 +298,12 @@ TEST(ResourceTableTest, SetOverlayableDifferentResourcesDifferentName) {
const ResourceName foo = test::ParseNameOrDie("android:string/foo");
OverlayableItem overlayable_item(std::make_shared<Overlayable>("Name", "overlay://theme"));
- overlayable_item.policies = OverlayableItem::Policy::kProduct;
+ overlayable_item.policies = PolicyFlags::PRODUCT_PARTITION;
ASSERT_TRUE(table.SetOverlayable(foo, overlayable_item, test::GetDiagnostics()));
const ResourceName bar = test::ParseNameOrDie("android:string/bar");
OverlayableItem overlayable_item2(std::make_shared<Overlayable>("Name2", "overlay://theme"));
- overlayable_item2.policies = OverlayableItem::Policy::kProduct;
+ overlayable_item2.policies = PolicyFlags::PRODUCT_PARTITION;
ASSERT_TRUE(table.SetOverlayable(bar, overlayable_item2, test::GetDiagnostics()));
}
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index e0040e486a23..469128b1e50b 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -378,7 +378,7 @@ std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
const ResourceName& enum_symbol_resource_name = symbol.symbol.name.value();
if (trimmed_str == enum_symbol_resource_name.entry) {
android::Res_value value = {};
- value.dataType = android::Res_value::TYPE_INT_DEC;
+ value.dataType = symbol.type;
value.data = symbol.value;
return util::make_unique<BinaryPrimitive>(value);
}
@@ -516,7 +516,7 @@ Maybe<ResourceId> ParseResourceId(const StringPiece& str) {
if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
if (value.dataType == android::Res_value::TYPE_INT_HEX) {
ResourceId id(value.data);
- if (id.is_valid_dynamic()) {
+ if (id.is_valid()) {
return id;
}
}
@@ -738,7 +738,13 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config
const android::Res_value& res_value,
StringPool* dst_pool) {
if (type == ResourceType::kId) {
- return util::make_unique<Id>();
+ 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,
+ // while aapt2 uses a false boolean).
+ return util::make_unique<Id>();
+ }
+ // fall through to regular reference deserialization logic
}
const uint32_t data = util::DeviceToHost32(res_value.data);
@@ -794,7 +800,12 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config
}
// This is a normal reference.
- return util::make_unique<Reference>(data, ref_type);
+ auto reference = util::make_unique<Reference>(data, ref_type);
+ if (res_value.dataType == android::Res_value::TYPE_DYNAMIC_REFERENCE ||
+ res_value.dataType == android::Res_value::TYPE_DYNAMIC_ATTRIBUTE) {
+ reference->is_dynamic = true;
+ }
+ return reference;
} break;
}
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index c016cb44af00..b08bf9a1ff17 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -109,6 +109,20 @@ TEST(ResourceUtilsTest, ParsePrivateReference) {
EXPECT_TRUE(private_ref);
}
+TEST(ResourceUtilsTest, ParseBinaryDynamicReference) {
+ android::Res_value value = {};
+ value.data = util::HostToDevice32(0x01);
+ value.dataType = android::Res_value::TYPE_DYNAMIC_REFERENCE;
+ std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(ResourceType::kId,
+ android::ConfigDescription(),
+ android::ResStringPool(), value,
+ nullptr);
+
+ Reference* ref = ValueCast<Reference>(item.get());
+ EXPECT_TRUE(ref->is_dynamic);
+ EXPECT_EQ(ref->id.value().id, 0x01);
+}
+
TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
bool create = false;
bool private_ref = false;
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 696012786e6d..4f0fa8ae29ba 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -117,7 +117,7 @@ bool Reference::Equals(const Value* value) const {
bool Reference::Flatten(android::Res_value* out_value) const {
const ResourceId resid = id.value_or_default(ResourceId(0));
- const bool dynamic = resid.is_valid_dynamic() && is_dynamic;
+ const bool dynamic = resid.is_valid() && is_dynamic;
if (reference_type == Reference::Type::kResource) {
if (dynamic) {
@@ -159,7 +159,7 @@ void Reference::Print(std::ostream* out) const {
*out << name.value();
}
- if (id && id.value().is_valid_dynamic()) {
+ if (id && id.value().is_valid()) {
if (name) {
*out << " ";
}
@@ -196,7 +196,7 @@ static void PrettyPrintReferenceImpl(const Reference& ref, bool print_package, P
printer->Print("/");
printer->Print(name.entry);
}
- } else if (ref.id && ref.id.value().is_valid_dynamic()) {
+ } else if (ref.id && ref.id.value().is_valid()) {
printer->Print(ref.id.value().to_string());
}
}
@@ -574,10 +574,6 @@ bool Attribute::Equals(const Value* value) const {
}
bool Attribute::IsCompatibleWith(const Attribute& attr) const {
- if (Equals(&attr)) {
- return true;
- }
-
// If the high bits are set on any of these attribute type masks, then they are incompatible.
// We don't check that flags and enums are identical.
if ((type_mask & ~android::ResTable_map::TYPE_ANY) != 0 ||
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 168ad61784e7..fe0883be50aa 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -292,6 +292,7 @@ struct Attribute : public BaseValue<Attribute> {
struct Symbol {
Reference symbol;
uint32_t value;
+ uint8_t type;
friend std::ostream& operator<<(std::ostream& out, const Symbol& symbol);
};
diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp
index dbf51143f720..c4a1108ac62a 100644
--- a/tools/aapt2/ResourceValues_test.cpp
+++ b/tools/aapt2/ResourceValues_test.cpp
@@ -284,58 +284,8 @@ TEST(ResourcesValuesTest, AttributeIsCompatible) {
EXPECT_FALSE(attr_three.IsCompatibleWith(attr_one));
EXPECT_FALSE(attr_three.IsCompatibleWith(attr_two));
- EXPECT_TRUE(attr_three.IsCompatibleWith(attr_three));
+ EXPECT_FALSE(attr_three.IsCompatibleWith(attr_three));
EXPECT_FALSE(attr_three.IsCompatibleWith(attr_four));
-
- EXPECT_FALSE(attr_four.IsCompatibleWith(attr_one));
- EXPECT_FALSE(attr_four.IsCompatibleWith(attr_two));
- EXPECT_FALSE(attr_four.IsCompatibleWith(attr_three));
- EXPECT_TRUE(attr_four.IsCompatibleWith(attr_four));
-}
-
-TEST(ResourcesValuesTest, AttributeEnumIsCompatible) {
- Attribute attr_one(TYPE_ENUM);
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u});
-
- Attribute attr_two(TYPE_ENUM);
- attr_two.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_two.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u});
- EXPECT_TRUE(attr_one.IsCompatibleWith(attr_two));
-}
-
-TEST(ResourcesValuesTest, DifferentAttributeEnumDifferentNameIsNotCompatible) {
- Attribute attr_one(TYPE_ENUM);
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u});
-
- Attribute attr_two(TYPE_ENUM);
- attr_two.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/baz")), 0x07u});
- EXPECT_FALSE(attr_one.IsCompatibleWith(attr_two));
-}
-
-TEST(ResourcesValuesTest, DifferentAttributeEnumDifferentValueIsNotCompatible) {
- Attribute attr_one(TYPE_ENUM);
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_one.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u});
-
- Attribute attr_two(TYPE_ENUM);
- attr_two.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
- attr_two.symbols.push_back(
- Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x09u});
- EXPECT_FALSE(attr_one.IsCompatibleWith(attr_two));
}
} // namespace aapt
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index b2fc08423d34..ab9ce66b0ae3 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -21,7 +21,6 @@ import "frameworks/base/tools/aapt2/Configuration.proto";
package aapt.pb;
option java_package = "com.android.aapt";
-option optimize_for = LITE_RUNTIME;
// A string pool that wraps the binary form of the C++ class android::ResStringPool.
message StringPool {
@@ -168,6 +167,7 @@ message OverlayableItem {
SIGNATURE = 5;
ODM = 6;
OEM = 7;
+ ACTOR = 8;
}
// The location of the <item> declaration in source.
@@ -270,6 +270,11 @@ message CompoundValue {
}
}
+// Message holding a boolean, so it can be optionally encoded.
+message Boolean {
+ bool value = 1;
+}
+
// A value that is a reference to another resource. This reference can be by name or resource ID.
message Reference {
enum Type {
@@ -290,6 +295,9 @@ message Reference {
// Whether this reference is referencing a private resource (@*package:type/entry).
bool private = 4;
+
+ // Whether this reference is dynamic.
+ Boolean is_dynamic = 5;
}
// A value that represents an ID. This is just a placeholder, as ID values are used to occupy a
@@ -388,6 +396,9 @@ message Attribute {
// The value of the enum/flag.
uint32 value = 4;
+
+ // The data type of the enum/flag as defined in android::Res_value.
+ uint32 type = 5;
}
// Bitmask of formats allowed for an attribute.
diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto
index 520b242ee509..b0ed3da33368 100644
--- a/tools/aapt2/ResourcesInternal.proto
+++ b/tools/aapt2/ResourcesInternal.proto
@@ -22,7 +22,6 @@ import "frameworks/base/tools/aapt2/Resources.proto";
package aapt.pb.internal;
option java_package = "android.aapt.pb.internal";
-option optimize_for = LITE_RUNTIME;
// The top level message representing an external resource file (layout XML, PNG, etc).
// This is used to represent a compiled file before it is linked. Only useful to aapt2.
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 304bc4925831..e8873bf2d81b 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -57,6 +57,8 @@ static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
{0x0568, SDK_O},
{0x056d, SDK_O_MR1},
{0x0586, SDK_P},
+ {0x0606, SDK_Q},
+ {0x0617, SDK_R},
};
static bool less_entry_id(const std::pair<uint16_t, ApiVersion>& p, uint16_t entryId) {
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index a00d978565ad..aa9aa12d2cee 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -55,6 +55,7 @@ enum : ApiVersion {
SDK_O_MR1 = 27,
SDK_P = 28,
SDK_Q = 29,
+ SDK_R = 30,
};
ApiVersion FindAttributeSdkLevel(const ResourceId& id);
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 21719705838d..32686538c10d 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -630,6 +630,12 @@ class CompileContext : public IAaptContext {
return 0;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ UNIMPLEMENTED(FATAL) << "No Split Name Dependencies be needed in compile phase";
+ static std::set<std::string> empty;
+ return empty;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(CompileContext);
@@ -735,7 +741,6 @@ int CompileCommand::Action(const std::vector<std::string>& args) {
}
std::unique_ptr<io::IFileCollection> file_collection;
- std::unique_ptr<IArchiveWriter> archive_writer;
// Collect the resources files to compile
if (options_.res_dir && options_.res_zip) {
@@ -756,8 +761,6 @@ int CompileCommand::Action(const std::vector<std::string>& args) {
context.GetDiagnostics()->Error(DiagMessage(options_.res_dir.value()) << err);
return 1;
}
-
- archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
} else if (options_.res_zip) {
if (!args.empty()) {
context.GetDiagnostics()->Error(DiagMessage() << "files given but --zip specified");
@@ -772,8 +775,6 @@ int CompileCommand::Action(const std::vector<std::string>& args) {
context.GetDiagnostics()->Error(DiagMessage(options_.res_zip.value()) << err);
return 1;
}
-
- archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
} else {
auto collection = util::make_unique<io::FileCollection>();
@@ -786,7 +787,14 @@ int CompileCommand::Action(const std::vector<std::string>& args) {
}
file_collection = std::move(collection);
+ }
+
+ std::unique_ptr<IArchiveWriter> archive_writer;
+ file::FileType output_file_type = file::GetFileType(options_.output_path);
+ if (output_file_type == file::FileType::kDirectory) {
archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options_.output_path);
+ } else {
+ archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
}
if (!archive_writer) {
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index 5f637bd8d582..fb786a31360e 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -200,7 +200,7 @@ static void AssertTranslations(CommandTestFixture *ctf, std::string file_name,
const std::string compiled_files_dir = ctf->GetTestPath("/compiled_" + file_name);
const std::string out_apk = ctf->GetTestPath("/" + file_name + ".apk");
- CHECK(ctf->WriteFile(source_file, sTranslatableXmlContent));
+ ctf->WriteFile(source_file, sTranslatableXmlContent);
CHECK(file::mkdirs(compiled_files_dir.data()));
ASSERT_EQ(CompileCommand(&diag).Execute({
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 0cf86ccdd59f..22bcd8589ce9 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -243,6 +243,12 @@ class Context : public IAaptContext {
return 0u;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
+ static std::set<std::string> empty;
+ return empty;
+ }
+
bool verbose_ = false;
std::string package_;
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 262f4fc4e394..d56994e3ae24 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -65,6 +65,12 @@ class DiffContext : public IAaptContext {
return 0;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
+ static std::set<std::string> empty;
+ return empty;
+ }
+
private:
std::string empty_;
StdErrDiagnostics diagnostics_;
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index a23a6a46cf0f..3982d12f6036 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -118,6 +118,12 @@ class DumpContext : public IAaptContext {
return 0;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
+ static std::set<std::string> empty;
+ return empty;
+ }
+
private:
StdErrDiagnostics diagnostics_;
bool verbose_ = false;
@@ -388,6 +394,17 @@ int DumpXmlTreeCommand::Dump(LoadedApk* apk) {
return 0;
}
+int DumpOverlayableCommand::Dump(LoadedApk* apk) {
+ ResourceTable* table = apk->GetResourceTable();
+ if (!table) {
+ GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table");
+ return 1;
+ }
+
+ Debug::DumpOverlayable(*table, GetPrinter());
+ return 0;
+}
+
const char DumpBadgerCommand::kBadgerData[2925] = {
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 7ded9bcf8470..cd51f7a7718c 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -240,6 +240,16 @@ class DumpXmlTreeCommand : public DumpApkCommand {
std::vector<std::string> files_;
};
+class DumpOverlayableCommand : public DumpApkCommand {
+ public:
+ explicit DumpOverlayableCommand(text::Printer* printer, IDiagnostics* diag)
+ : DumpApkCommand("overlayable", printer, diag) {
+ SetDescription("Print the <overlayable> resources of an APK.");
+ }
+
+ int Dump(LoadedApk* apk) override;
+};
+
/** The default dump command. Performs no action because a subcommand is required. */
class DumpCommand : public Command {
public:
@@ -255,8 +265,8 @@ class DumpCommand : public Command {
AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_));
+ AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_));
AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true);
- // TODO(b/120609160): Add aapt2 overlayable dump command
}
int Action(const std::vector<std::string>& args) override {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index e8970d48e404..3a3fb2826b74 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -140,6 +140,14 @@ class LinkContext : public IAaptContext {
min_sdk_version_ = minSdk;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ return split_name_dependencies_;
+ }
+
+ void SetSplitNameDependencies(const std::set<std::string>& split_name_dependencies) {
+ split_name_dependencies_ = split_name_dependencies;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(LinkContext);
@@ -151,6 +159,7 @@ class LinkContext : public IAaptContext {
SymbolTable symbols_;
bool verbose_ = false;
int min_sdk_version_ = 0;
+ std::set<std::string> split_name_dependencies_;
};
// A custom delegate that generates compatible pre-O IDs for use with feature splits.
@@ -269,6 +278,7 @@ struct ResourceFileFlattenerOptions {
bool keep_raw_values = false;
bool do_not_compress_anything = false;
bool update_proguard_spec = false;
+ bool do_not_fail_on_missing_resources = false;
OutputFormat output_format = OutputFormat::kApk;
std::unordered_set<std::string> extensions_to_not_compress;
Maybe<std::regex> regex_to_not_compress;
@@ -297,6 +307,25 @@ struct R {
};
};
+template <typename T>
+uint32_t GetCompressionFlags(const StringPiece& str, T options) {
+ if (options.do_not_compress_anything) {
+ return 0;
+ }
+
+ if (options.regex_to_not_compress
+ && std::regex_search(str.to_string(), options.regex_to_not_compress.value())) {
+ return 0;
+ }
+
+ for (const std::string& extension : options.extensions_to_not_compress) {
+ if (util::EndsWith(str, extension)) {
+ return 0;
+ }
+ }
+ return ArchiveEntry::kCompress;
+}
+
class ResourceFileFlattener {
public:
ResourceFileFlattener(const ResourceFileFlattenerOptions& options, IAaptContext* context,
@@ -321,8 +350,6 @@ class ResourceFileFlattener {
std::string dst_path;
};
- uint32_t GetCompressionFlags(const StringPiece& str);
-
std::vector<std::unique_ptr<xml::XmlResource>> LinkAndVersionXmlFile(ResourceTable* table,
FileOperation* file_op);
@@ -381,26 +408,6 @@ ResourceFileFlattener::ResourceFileFlattener(const ResourceFileFlattenerOptions&
}
}
-// TODO(rtmitchell): turn this function into a variable that points to a method that retrieves the
-// compression flag
-uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) {
- if (options_.do_not_compress_anything) {
- return 0;
- }
-
- if (options_.regex_to_not_compress
- && std::regex_search(str.to_string(), options_.regex_to_not_compress.value())) {
- return 0;
- }
-
- for (const std::string& extension : options_.extensions_to_not_compress) {
- if (util::EndsWith(str, extension)) {
- return 0;
- }
- }
- return ArchiveEntry::kCompress;
-}
-
static bool IsTransitionElement(const std::string& name) {
return name == "fade" || name == "changeBounds" || name == "slide" || name == "explode" ||
name == "changeImageTransform" || name == "changeTransform" ||
@@ -438,7 +445,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer
xml::StripAndroidStudioAttributes(doc->root.get());
XmlReferenceLinker xml_linker;
- if (!xml_linker.Consume(context_, doc)) {
+ if (!options_.do_not_fail_on_missing_resources && !xml_linker.Consume(context_, doc)) {
return {};
}
@@ -640,7 +647,8 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
}
} else {
error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path,
- GetCompressionFlags(file_op.dst_path), archive_writer);
+ GetCompressionFlags(file_op.dst_path, options_),
+ archive_writer);
}
}
}
@@ -887,7 +895,7 @@ class Linker {
// android:versionCode from the framework AndroidManifest.xml.
ExtractCompileSdkVersions(asset_source->GetAssetManager());
}
- } else if (asset_source->IsPackageDynamic(entry.first)) {
+ } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) {
final_table_.included_packages_[entry.first] = entry.second;
}
}
@@ -965,6 +973,17 @@ class Linker {
app_info.min_sdk_version = ResourceUtils::ParseSdkVersion(min_sdk->value);
}
}
+
+ for (const xml::Element* child_el : manifest_el->GetChildElements()) {
+ if (child_el->namespace_uri.empty() && child_el->name == "uses-split") {
+ if (const xml::Attribute* split_name =
+ child_el->FindAttribute(xml::kSchemaAndroid, "name")) {
+ if (!split_name->value.empty()) {
+ app_info.split_name_dependencies.insert(split_name->value);
+ }
+ }
+ }
+ }
return app_info;
}
@@ -1065,7 +1084,8 @@ class Linker {
case OutputFormat::kProto: {
pb::ResourceTable pb_table;
- SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics());
+ SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics(),
+ options_.proto_table_flattener_options);
return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
ArchiveEntry::kCompress, writer);
} break;
@@ -1277,7 +1297,8 @@ class Linker {
return false;
}
- proguard::WriteKeepSet(keep_set, &fout, options_.generate_minimal_proguard_rules);
+ proguard::WriteKeepSet(keep_set, &fout, options_.generate_minimal_proguard_rules,
+ options_.no_proguard_location_reference);
fout.Flush();
if (fout.HadError()) {
@@ -1547,16 +1568,7 @@ class Linker {
}
for (auto& entry : merged_assets) {
- uint32_t compression_flags = ArchiveEntry::kCompress;
- std::string extension = file::GetExtension(entry.first).to_string();
-
- if (options_.do_not_compress_anything
- || options_.extensions_to_not_compress.count(extension) > 0
- || (options_.regex_to_not_compress
- && std::regex_search(extension, options_.regex_to_not_compress.value()))) {
- compression_flags = 0u;
- }
-
+ uint32_t compression_flags = GetCompressionFlags(entry.first, options_);
if (!io::CopyFileToArchive(context_, entry.second.get(), entry.first, compression_flags,
writer)) {
return false;
@@ -1565,6 +1577,93 @@ class Linker {
return true;
}
+ void AliasAdaptiveIcon(xml::XmlResource* manifest, ResourceTable* table) {
+ xml::Element* application = manifest->root->FindChild("", "application");
+ if (!application) {
+ return;
+ }
+
+ xml::Attribute* icon = application->FindAttribute(xml::kSchemaAndroid, "icon");
+ xml::Attribute* round_icon = application->FindAttribute(xml::kSchemaAndroid, "roundIcon");
+ if (!icon || !round_icon) {
+ return;
+ }
+
+ // Find the icon resource defined within the application.
+ auto icon_reference = ValueCast<Reference>(icon->compiled_value.get());
+ if (!icon_reference || !icon_reference->name) {
+ return;
+ }
+ auto package = table->FindPackageById(icon_reference->id.value().package_id());
+ if (!package) {
+ return;
+ }
+ auto type = package->FindType(icon_reference->name.value().type);
+ if (!type) {
+ return;
+ }
+ auto icon_entry = type->FindEntry(icon_reference->name.value().entry);
+ if (!icon_entry) {
+ return;
+ }
+
+ int icon_max_sdk = 0;
+ for (auto& config_value : icon_entry->values) {
+ icon_max_sdk = (icon_max_sdk < config_value->config.sdkVersion)
+ ? config_value->config.sdkVersion : icon_max_sdk;
+ }
+ if (icon_max_sdk < SDK_O) {
+ // Adaptive icons must be versioned with v26 qualifiers, so this is not an adaptive icon.
+ return;
+ }
+
+ // Find the roundIcon resource defined within the application.
+ auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get());
+ if (!round_icon_reference || !round_icon_reference->name) {
+ return;
+ }
+ package = table->FindPackageById(round_icon_reference->id.value().package_id());
+ if (!package) {
+ return;
+ }
+ type = package->FindType(round_icon_reference->name.value().type);
+ if (!type) {
+ return;
+ }
+ auto round_icon_entry = type->FindEntry(round_icon_reference->name.value().entry);
+ if (!round_icon_entry) {
+ return;
+ }
+
+ int round_icon_max_sdk = 0;
+ for (auto& config_value : round_icon_entry->values) {
+ round_icon_max_sdk = (round_icon_max_sdk < config_value->config.sdkVersion)
+ ? config_value->config.sdkVersion : round_icon_max_sdk;
+ }
+ if (round_icon_max_sdk >= SDK_O) {
+ // The developer explicitly used a v26 compatible drawable as the roundIcon, meaning we should
+ // not generate an alias to the icon drawable.
+ return;
+ }
+
+ // Add an equivalent v26 entry to the roundIcon for each v26 variant of the regular icon.
+ for (auto& config_value : icon_entry->values) {
+ if (config_value->config.sdkVersion < SDK_O) {
+ continue;
+ }
+
+ context_->GetDiagnostics()->Note(DiagMessage() << "generating "
+ << round_icon_reference->name.value()
+ << " with config \"" << config_value->config
+ << "\" for round icon compatibility");
+
+ auto value = icon_reference->Clone(&table->string_pool);
+ auto round_config_value = round_icon_entry->FindOrCreateValue(
+ config_value->config, config_value->product);
+ round_config_value->value.reset(value);
+ }
+ }
+
// Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
// to the IArchiveWriter.
bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
@@ -1578,6 +1677,14 @@ class Linker {
return false;
}
+ // When a developer specifies an adaptive application icon, and a non-adaptive round application
+ // icon, create an alias from the round icon to the regular icon for v26 APIs and up. We do this
+ // because certain devices prefer android:roundIcon over android:icon regardless of the API
+ // levels of the drawables set for either. This auto-aliasing behaviour allows an app to prefer
+ // the android:roundIcon on API 25 devices, and prefer the adaptive icon on API 26 devices.
+ // See (b/34829129)
+ AliasAdaptiveIcon(manifest, table);
+
ResourceFileFlattenerOptions file_flattener_options;
file_flattener_options.keep_raw_values = keep_raw_values;
file_flattener_options.do_not_compress_anything = options_.do_not_compress_anything;
@@ -1590,9 +1697,9 @@ class Linker {
file_flattener_options.update_proguard_spec =
static_cast<bool>(options_.generate_proguard_rules_path);
file_flattener_options.output_format = options_.output_format;
+ file_flattener_options.do_not_fail_on_missing_resources = options_.merge_only;
ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set);
-
if (!file_flattener.Flatten(table, writer)) {
context_->GetDiagnostics()->Error(DiagMessage() << "failed linking file resources");
return false;
@@ -1659,12 +1766,9 @@ class Linker {
return 1;
}
- // Determine the package name under which to merge resources.
- if (options_.rename_resources_package) {
- context_->SetCompilationPackage(options_.rename_resources_package.value());
- } else if (Maybe<AppInfo> maybe_app_info =
+ // First extract the Package name without modifying it (via --rename-manifest-package).
+ if (Maybe<AppInfo> maybe_app_info =
ExtractAppInfoFromManifest(manifest_xml.get(), context_->GetDiagnostics())) {
- // Extract the package name from the manifest ignoring the value of --rename-manifest-package.
const AppInfo& app_info = maybe_app_info.value();
context_->SetCompilationPackage(app_info.package);
}
@@ -1700,6 +1804,7 @@ class Linker {
context_->SetMinSdkVersion(app_info_.min_sdk_version.value_or_default(0));
context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()});
+ context_->SetSplitNameDependencies(app_info_.split_name_dependencies);
// Override the package ID when it is "android".
if (context_->GetCompilationPackage() == "android") {
@@ -1715,6 +1820,8 @@ class Linker {
TableMergerOptions table_merger_options;
table_merger_options.auto_add_overlay = options_.auto_add_overlay;
+ table_merger_options.override_styles_instead_of_overlaying =
+ options_.override_styles_instead_of_overlaying;
table_merger_options.strict_visibility = options_.strict_visibility;
table_merger_ = util::make_unique<TableMerger>(context_, &final_table_, table_merger_options);
@@ -1829,7 +1936,7 @@ class Linker {
}
ReferenceLinker linker;
- if (!linker.Consume(context_, &final_table_)) {
+ if (!options_.merge_only && !linker.Consume(context_, &final_table_)) {
context_->GetDiagnostics()->Error(DiagMessage() << "failed linking references");
return 1;
}
@@ -1981,7 +2088,7 @@ class Linker {
manifest_xml->file.name.package = context_->GetCompilationPackage();
XmlReferenceLinker manifest_linker;
- if (manifest_linker.Consume(context_, manifest_xml.get())) {
+ if (options_.merge_only || manifest_linker.Consume(context_, manifest_xml.get())) {
if (options_.generate_proguard_rules_path &&
!proguard::CollectProguardRulesForManifest(manifest_xml.get(), &proguard_keep_set)) {
error = true;
@@ -2115,6 +2222,12 @@ int LinkCommand::Action(const std::vector<std::string>& args) {
return 1;
}
+ if (options_.merge_only && !static_lib_) {
+ context.GetDiagnostics()->Error(
+ DiagMessage() << "the --merge-only flag can be only used when building a static library");
+ return 1;
+ }
+
// The default build type.
context.SetPackageType(PackageType::kApp);
context.SetPackageId(kAppPackageId);
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index e62e0a6b9f62..852b1244cd6e 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -24,6 +24,7 @@
#include "Resource.h"
#include "split/TableSplitter.h"
#include "format/binary/TableFlattener.h"
+#include "format/proto/ProtoSerialize.h"
#include "link/ManifestFixer.h"
#include "trace/TraceBuffer.h"
@@ -42,6 +43,7 @@ struct LinkOptions {
std::vector<std::string> assets_dirs;
bool output_to_directory = false;
bool auto_add_overlay = false;
+ bool override_styles_instead_of_overlaying = false;
OutputFormat output_format = OutputFormat::kApk;
Maybe<std::string> rename_resources_package;
@@ -55,6 +57,7 @@ struct LinkOptions {
bool generate_conditional_proguard_rules = false;
bool generate_minimal_proguard_rules = false;
bool generate_non_final_ids = false;
+ bool no_proguard_location_reference = false;
std::vector<std::string> javadoc_annotations;
Maybe<std::string> private_symbols;
@@ -71,6 +74,7 @@ struct LinkOptions {
// Static lib options.
bool no_static_lib_packages = false;
+ bool merge_only = false;
// AndroidManifest.xml massaging options.
ManifestFixerOptions manifest_fixer_options;
@@ -80,6 +84,7 @@ struct LinkOptions {
// Flattening options.
TableFlattenerOptions table_flattener_options;
+ SerializeTableOptions proto_table_flattener_options;
bool keep_raw_values = false;
// Split APK options.
@@ -212,6 +217,9 @@ class LinkCommand : public Command {
"Generates R.java without the final modifier. This is implied when\n"
"--static-lib is specified.",
&options_.generate_non_final_ids);
+ AddOptionalSwitch("--no-proguard-location-reference",
+ "Keep proguard rules files from having a reference to the source file",
+ &options_.no_proguard_location_reference);
AddOptionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
&stable_id_file_path_);
AddOptionalFlag("--emit-ids",
@@ -243,6 +251,10 @@ class LinkCommand : public Command {
"Allows the addition of new resources in overlays without\n"
"<add-resource> tags.",
&options_.auto_add_overlay);
+ AddOptionalSwitch("--override-styles-instead-of-overlaying",
+ "Causes styles defined in -R resources to replace previous definitions\n"
+ "instead of merging into them\n",
+ &options_.override_styles_instead_of_overlaying);
AddOptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml.",
&options_.manifest_fixer_options.rename_manifest_package);
AddOptionalFlag("--rename-resources-package", "Renames the package in resources table",
@@ -255,7 +267,7 @@ class LinkCommand : public Command {
"Changes the name of the target package for overlay. Most useful\n"
"when used in conjunction with --rename-manifest-package.",
&options_.manifest_fixer_options.rename_overlay_target_package);
- AddOptionalFlagList("-0", "File extensions not to compress.",
+ AddOptionalFlagList("-0", "File suffix not to compress.",
&options_.extensions_to_not_compress);
AddOptionalSwitch("--no-compress", "Do not compress any resources.",
&options_.do_not_compress_anything);
@@ -263,8 +275,8 @@ class LinkCommand : public Command {
&options_.keep_raw_values);
AddOptionalFlag("--no-compress-regex",
"Do not compress extensions matching the regular expression. Remember to\n"
- " use the '$' symbol for end of line. Uses a non case-sensitive\n"
- " ECMAScript regular expression grammar.",
+ "use the '$' symbol for end of line. Uses a case-sensitive ECMAScript"
+ "regular expression grammar.",
&no_compress_regex);
AddOptionalSwitch("--warn-manifest-validation",
"Treat manifest validation errors as warnings.",
@@ -284,9 +296,18 @@ class LinkCommand : public Command {
AddOptionalSwitch("--strict-visibility",
"Do not allow overlays with different visibility levels.",
&options_.strict_visibility);
+ AddOptionalSwitch("--exclude-sources",
+ "Do not serialize source file information when generating resources in\n"
+ "Protobuf format.",
+ &options_.proto_table_flattener_options.exclude_sources);
+ AddOptionalFlag("--trace-folder",
+ "Generate systrace json trace fragment to specified folder.",
+ &trace_folder_);
+ AddOptionalSwitch("--merge-only",
+ "Only merge the resources, without verifying resource references. This flag\n"
+ "should only be used together with the --static-lib flag.",
+ &options_.merge_only);
AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
- AddOptionalFlag("--trace-folder", "Generate systrace json trace fragment to specified folder.",
- &trace_folder_);
}
int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 9ea93f638aff..062dd8eac975 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "AppInfo.h"
#include "Link.h"
#include "LoadedApk.h"
@@ -43,10 +44,8 @@ TEST_F(LinkTest, RemoveRawXmlStrings) {
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
-
std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
ASSERT_THAT(data, Ne(nullptr));
-
AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has not been assigned
@@ -71,10 +70,8 @@ TEST_F(LinkTest, KeepRawXmlStrings) {
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
-
std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
ASSERT_THAT(data, Ne(nullptr));
-
AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has been set to the correct string pool entry
@@ -83,4 +80,241 @@ TEST_F(LinkTest, KeepRawXmlStrings) {
EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007"));
}
-} // namespace aapt \ No newline at end of file
+TEST_F(LinkTest, NoCompressAssets) {
+ StdErrDiagnostics diag;
+ std::string content(500, 'a');
+ WriteFile(GetTestPath("assets/testtxt"), content);
+ WriteFile(GetTestPath("assets/testtxt2"), content);
+ WriteFile(GetTestPath("assets/test.txt"), content);
+ WriteFile(GetTestPath("assets/test.hello.txt"), content);
+ WriteFile(GetTestPath("assets/test.hello.xml"), content);
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(),
+ "-o", out_apk,
+ "-0", ".txt",
+ "-0", "txt2",
+ "-0", ".hello.txt",
+ "-0", "hello.xml",
+ "-A", GetTestPath("assets")
+ };
+
+ ASSERT_TRUE(Link(link_args, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_THAT(apk, Ne(nullptr));
+ io::IFileCollection* zip = apk->GetFileCollection();
+ ASSERT_THAT(zip, Ne(nullptr));
+
+ auto file = zip->FindFile("assets/testtxt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_TRUE(file->WasCompressed());
+
+ file = zip->FindFile("assets/testtxt2");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("assets/test.txt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("assets/test.hello.txt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("assets/test.hello.xml");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+}
+
+TEST_F(LinkTest, NoCompressResources) {
+ StdErrDiagnostics diag;
+ std::string content(500, 'a');
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/raw/testtxt"), content, compiled_files_dir, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test.txt"), content, compiled_files_dir, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test1.hello.txt"), content, compiled_files_dir,
+ &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test2.goodbye.xml"), content, compiled_files_dir,
+ &diag));
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(),
+ "-o", out_apk,
+ "-0", ".txt",
+ "-0", ".hello.txt",
+ "-0", "goodbye.xml",
+ };
+
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ ASSERT_THAT(apk, Ne(nullptr));
+ io::IFileCollection* zip = apk->GetFileCollection();
+ ASSERT_THAT(zip, Ne(nullptr));
+
+ auto file = zip->FindFile("res/raw/testtxt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_TRUE(file->WasCompressed());
+
+ file = zip->FindFile("res/raw/test.txt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("res/raw/test1.hello.hello.txt");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+
+ file = zip->FindFile("res/raw/test2.goodbye.goodbye.xml");
+ ASSERT_THAT(file, Ne(nullptr));
+ EXPECT_FALSE(file->WasCompressed());
+}
+
+TEST_F(LinkTest, OverlayStyles) {
+ StdErrDiagnostics diag;
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ const std::string override_files_dir = GetTestPath("compiled-override");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:textColor">#123</item>
+ </style>
+ </resources>)",
+ compiled_files_dir, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:background">#456</item>
+ </style>
+ </resources>)",
+ override_files_dir, &diag));
+
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(kDefaultPackageName),
+ "-o", out_apk,
+ };
+ const auto override_files = file::FindFiles(override_files_dir, &diag);
+ for (const auto &override_file : override_files.value()) {
+ link_args.push_back("-R");
+ link_args.push_back(file::BuildPath({override_files_dir, override_file}));
+ }
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ const Style* actual_style = test::GetValue<Style>(
+ apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle");
+ ASSERT_NE(actual_style, nullptr);
+ ASSERT_EQ(actual_style->entries.size(), 2);
+ EXPECT_EQ(actual_style->entries[0].key.id, 0x01010098); // android:textColor
+ EXPECT_EQ(actual_style->entries[1].key.id, 0x010100d4); // android:background
+}
+
+TEST_F(LinkTest, OverrideStylesInsteadOfOverlaying) {
+ StdErrDiagnostics diag;
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ const std::string override_files_dir = GetTestPath("compiled-override");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:textColor">#123</item>
+ </style>
+ </resources>)",
+ compiled_files_dir, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:background">#456</item>
+ </style>
+ </resources>)",
+ override_files_dir, &diag));
+
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(kDefaultPackageName),
+ "--override-styles-instead-of-overlaying",
+ "-o", out_apk,
+ };
+ const auto override_files = file::FindFiles(override_files_dir, &diag);
+ for (const auto &override_file : override_files.value()) {
+ link_args.push_back("-R");
+ link_args.push_back(file::BuildPath({override_files_dir, override_file}));
+ }
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ const Style* actual_style = test::GetValue<Style>(
+ apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle");
+ ASSERT_NE(actual_style, nullptr);
+ ASSERT_EQ(actual_style->entries.size(), 1);
+ EXPECT_EQ(actual_style->entries[0].key.id, 0x010100d4); // android:background
+}
+
+TEST_F(LinkTest, AppInfoWithUsesSplit) {
+ StdErrDiagnostics diag;
+ const std::string base_files_dir = GetTestPath("base");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <string name="bar">bar</string>
+ </resources>)",
+ base_files_dir, &diag));
+ const std::string base_apk = GetTestPath("base.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest("com.aapt2.app"),
+ "-o", base_apk,
+ };
+ ASSERT_TRUE(Link(link_args, base_files_dir, &diag));
+
+ const std::string feature_manifest = GetTestPath("feature_manifest.xml");
+ WriteFile(feature_manifest, android::base::StringPrintf(R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.aapt2.app" split="feature1">
+ </manifest>)"));
+ const std::string feature_files_dir = GetTestPath("feature");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <string name="foo">foo</string>
+ </resources>)",
+ feature_files_dir, &diag));
+ const std::string feature_apk = GetTestPath("feature.apk");
+ const std::string feature_package_id = "0x80";
+ link_args = {
+ "--manifest", feature_manifest,
+ "-I", base_apk,
+ "--package-id", feature_package_id,
+ "-o", feature_apk,
+ };
+ ASSERT_TRUE(Link(link_args, feature_files_dir, &diag));
+
+ const std::string feature2_manifest = GetTestPath("feature2_manifest.xml");
+ WriteFile(feature2_manifest, android::base::StringPrintf(R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.aapt2.app" split="feature2">
+ <uses-split android:name="feature1"/>
+ </manifest>)"));
+ const std::string feature2_files_dir = GetTestPath("feature2");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <string-array name="string_array">
+ <item>@string/bar</item>
+ <item>@string/foo</item>
+ </string-array>
+ </resources>)",
+ feature2_files_dir, &diag));
+ const std::string feature2_apk = GetTestPath("feature2.apk");
+ const std::string feature2_package_id = "0x81";
+ link_args = {
+ "--manifest", feature2_manifest,
+ "-I", base_apk,
+ "-I", feature_apk,
+ "--package-id", feature2_package_id,
+ "-o", feature2_apk,
+ };
+ ASSERT_TRUE(Link(link_args, feature2_files_dir, &diag));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 2e6af18c1948..e36668e5a043 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -53,9 +53,9 @@ using ::android::ConfigDescription;
using ::android::ResTable_config;
using ::android::StringPiece;
using ::android::base::ReadFileToString;
-using ::android::base::WriteStringToFile;
using ::android::base::StringAppendF;
using ::android::base::StringPrintf;
+using ::android::base::WriteStringToFile;
namespace aapt {
@@ -108,6 +108,12 @@ class OptimizeContext : public IAaptContext {
return sdk_version_;
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
+ static std::set<std::string> empty;
+ return empty;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
@@ -294,29 +300,7 @@ class Optimizer {
OptimizeContext* context_;
};
-bool ExtractObfuscationWhitelistFromConfig(const std::string& path, OptimizeContext* context,
- OptimizeOptions* options) {
- std::string contents;
- if (!ReadFileToString(path, &contents, true)) {
- context->GetDiagnostics()->Error(DiagMessage()
- << "failed to parse whitelist from config file: " << path);
- return false;
- }
- for (StringPiece resource_name : util::Tokenize(contents, ',')) {
- options->table_flattener_options.whitelisted_resources.insert(
- resource_name.to_string());
- }
- return true;
-}
-
-bool ExtractConfig(const std::string& path, OptimizeContext* context,
- OptimizeOptions* options) {
- std::string content;
- if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
- context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading whitelist");
- return false;
- }
-
+bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOptions* options) {
size_t line_no = 0;
for (StringPiece line : util::Tokenize(content, '\n')) {
line_no++;
@@ -345,15 +329,24 @@ bool ExtractConfig(const std::string& path, OptimizeContext* context,
for (StringPiece directive : util::Tokenize(directives, ',')) {
if (directive == "remove") {
options->resources_blacklist.insert(resource_name.ToResourceName());
- } else if (directive == "no_obfuscate") {
- options->table_flattener_options.whitelisted_resources.insert(
- resource_name.entry.to_string());
+ } else if (directive == "no_collapse" || directive == "no_obfuscate") {
+ options->table_flattener_options.name_collapse_exemptions.insert(
+ resource_name.ToResourceName());
}
}
}
return true;
}
+bool ExtractConfig(const std::string& path, IAaptContext* context, OptimizeOptions* options) {
+ std::string content;
+ if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
+ context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading config file");
+ return false;
+ }
+ return ParseConfig(content, context, options);
+}
+
bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
OptimizeOptions* out_options) {
const xml::XmlResource* manifest = apk->GetManifest();
@@ -461,15 +454,6 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) {
}
}
- if (options_.table_flattener_options.collapse_key_stringpool) {
- if (whitelist_path_) {
- std::string& path = whitelist_path_.value();
- if (!ExtractObfuscationWhitelistFromConfig(path, &context, &options_)) {
- return 1;
- }
- }
- }
-
if (resources_config_path_) {
std::string& path = resources_config_path_.value();
if (!ExtractConfig(path, &context, &options_)) {
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 7f4a3ed85364..5070ccc8afbf 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -57,7 +57,7 @@ struct OptimizeOptions {
std::unordered_set<std::string> kept_artifacts;
// Whether or not to shorten resource paths in the APK.
- bool shorten_resource_paths;
+ bool shorten_resource_paths = false;
// Path to the output map of original resource paths to shortened paths.
Maybe<std::string> shortened_paths_map_path;
@@ -78,10 +78,6 @@ class OptimizeCommand : public Command {
"All the resources that would be unused on devices of the given densities will be \n"
"removed from the APK.",
&target_densities_);
- AddOptionalFlag("--whitelist-path",
- "Path to the whitelist.cfg file containing whitelisted resources \n"
- "whose names should not be altered in final resource tables.",
- &whitelist_path_);
AddOptionalFlag("--resources-config-path",
"Path to the resources.cfg file containing the list of resources and \n"
"directives to each resource. \n"
@@ -104,11 +100,13 @@ class OptimizeCommand : public Command {
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.",
&options_.table_flattener_options.use_sparse_entries);
- AddOptionalSwitch("--enable-resource-obfuscation",
- "Enables obfuscation of key string pool to single value",
+ AddOptionalSwitch("--collapse-resource-names",
+ "Collapses resource names to a single value in the key string pool. Resources can \n"
+ "be exempted using the \"no_collapse\" directive in a file specified by "
+ "--resources-config-path.",
&options_.table_flattener_options.collapse_key_stringpool);
- AddOptionalSwitch("--enable-resource-path-shortening",
- "Enables shortening of the path of the resources inside the APK.",
+ AddOptionalSwitch("--shorten-resource-paths",
+ "Shortens the paths of resources inside the APK.",
&options_.shorten_resource_paths);
AddOptionalFlag("--resource-path-shortening-map",
"Path to output the map of old resource paths to shortened paths.",
@@ -125,7 +123,6 @@ class OptimizeCommand : public Command {
const std::string &file_path);
Maybe<std::string> config_path_;
- Maybe<std::string> whitelist_path_;
Maybe<std::string> resources_config_path_;
Maybe<std::string> target_densities_;
std::vector<std::string> configs_;
diff --git a/tools/aapt2/cmd/Optimize_test.cpp b/tools/aapt2/cmd/Optimize_test.cpp
new file mode 100644
index 000000000000..ac681e85b3d6
--- /dev/null
+++ b/tools/aapt2/cmd/Optimize_test.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Optimize.h"
+
+#include "AppInfo.h"
+#include "Diagnostics.h"
+#include "LoadedApk.h"
+#include "Resource.h"
+#include "test/Test.h"
+
+using testing::Contains;
+using testing::Eq;
+
+namespace aapt {
+
+bool ParseConfig(const std::string&, IAaptContext*, OptimizeOptions*);
+
+using OptimizeTest = CommandTestFixture;
+
+TEST_F(OptimizeTest, ParseConfigWithNoCollapseExemptions) {
+ const std::string& content = R"(
+string/foo#no_collapse
+dimen/bar#no_collapse
+)";
+ aapt::test::Context context;
+ OptimizeOptions options;
+ ParseConfig(content, &context, &options);
+
+ const std::set<ResourceName>& name_collapse_exemptions =
+ options.table_flattener_options.name_collapse_exemptions;
+
+ ASSERT_THAT(name_collapse_exemptions.size(), Eq(2));
+ EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo")));
+ EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar")));
+}
+
+TEST_F(OptimizeTest, ParseConfigWithNoObfuscateExemptions) {
+ const std::string& content = R"(
+string/foo#no_obfuscate
+dimen/bar#no_obfuscate
+)";
+ aapt::test::Context context;
+ OptimizeOptions options;
+ ParseConfig(content, &context, &options);
+
+ const std::set<ResourceName>& name_collapse_exemptions =
+ options.table_flattener_options.name_collapse_exemptions;
+
+ ASSERT_THAT(name_collapse_exemptions.size(), Eq(2));
+ EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo")));
+ EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar")));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index e2c65ba74271..7214f1a68d2c 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -436,9 +436,9 @@ void SetLongVersionCode(xml::Element* manifest, uint64_t version) {
}
std::regex GetRegularExpression(const std::string &input) {
- // Standard ECMAScript grammar plus case insensitive.
+ // Standard ECMAScript grammar.
std::regex case_insensitive(
- input, std::regex_constants::icase | std::regex_constants::ECMAScript);
+ input, std::regex_constants::ECMAScript);
return case_insensitive;
}
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 7e492610b658..ac1f981d753c 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -383,7 +383,7 @@ TEST (UtilTest, AdjustSplitConstraintsForMinSdk) {
EXPECT_NE(*adjusted_contraints[1].configs.begin(), ConfigDescription::DefaultConfig());
}
-TEST(UtilTest, RegularExperssions) {
+TEST (UtilTest, RegularExperssionsSimple) {
std::string valid(".bc$");
std::regex expression = GetRegularExpression(valid);
EXPECT_TRUE(std::regex_search("file.abc", expression));
@@ -391,4 +391,24 @@ TEST(UtilTest, RegularExperssions) {
EXPECT_FALSE(std::regex_search("abc.zip", expression));
}
+TEST (UtilTest, RegularExpressionComplex) {
+ std::string valid("\\.(d|D)(e|E)(x|X)$");
+ std::regex expression = GetRegularExpression(valid);
+ EXPECT_TRUE(std::regex_search("file.dex", expression));
+ EXPECT_TRUE(std::regex_search("file.DEX", expression));
+ EXPECT_TRUE(std::regex_search("file.dEx", expression));
+ EXPECT_FALSE(std::regex_search("file.dexx", expression));
+ EXPECT_FALSE(std::regex_search("dex.file", expression));
+ EXPECT_FALSE(std::regex_search("file.adex", expression));
+}
+
+TEST (UtilTest, RegularExpressionNonEnglish) {
+ std::string valid("\\.(k|K)(o|O)(ń|Ń)(c|C)(ó|Ó)(w|W)(k|K)(a|A)$");
+ std::regex expression = GetRegularExpression(valid);
+ EXPECT_TRUE(std::regex_search("file.końcówka", expression));
+ EXPECT_TRUE(std::regex_search("file.KOŃCÓWKA", expression));
+ EXPECT_TRUE(std::regex_search("file.kOńcÓwkA", expression));
+ EXPECT_FALSE(std::regex_search("file.koncowka", expression));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index 42a64716701d..4a6bfd031284 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -91,6 +91,7 @@ enum {
};
const std::string& kAndroidNamespace = "http://schemas.android.com/apk/res/android";
+constexpr int kCurrentDevelopmentVersion = 10000;
/** Retrieves the attribute of the element with the specified attribute resource id. */
static xml::Attribute* FindAttribute(xml::Element *el, uint32_t resd_id) {
@@ -291,7 +292,10 @@ class ManifestExtractor {
}
}
}
- return &attr->value;
+
+ if (!attr->value.empty()) {
+ return &attr->value;
+ }
}
return nullptr;
}
@@ -322,7 +326,7 @@ class ManifestExtractor {
ConfigDescription config;
config.orientation = android::ResTable_config::ORIENTATION_PORT;
config.density = android::ResTable_config::DENSITY_MEDIUM;
- config.sdkVersion = 10000; // Very high.
+ config.sdkVersion = kCurrentDevelopmentVersion; // Very high.
config.screenWidthDp = 320;
config.screenHeightDp = 480;
config.smallestScreenWidthDp = 320;
@@ -425,6 +429,8 @@ class Manifest : public ManifestExtractor::Element {
const std::string* split = nullptr;
const std::string* platformVersionName = nullptr;
const std::string* platformVersionCode = nullptr;
+ const int32_t* platformVersionNameInt = nullptr;
+ const int32_t* platformVersionCodeInt = nullptr;
const int32_t* compilesdkVersion = nullptr;
const std::string* compilesdkVersionCodename = nullptr;
const int32_t* installLocation = nullptr;
@@ -440,6 +446,10 @@ class Manifest : public ManifestExtractor::Element {
"platformBuildVersionName"));
platformVersionCode = GetAttributeString(FindAttribute(manifest, {},
"platformBuildVersionCode"));
+ platformVersionNameInt = GetAttributeInteger(FindAttribute(manifest, {},
+ "platformBuildVersionName"));
+ platformVersionCodeInt = GetAttributeInteger(FindAttribute(manifest, {},
+ "platformBuildVersionCode"));
// Extract the compile sdk info
compilesdkVersion = GetAttributeInteger(FindAttribute(manifest, COMPILE_SDK_VERSION_ATTR));
@@ -459,9 +469,13 @@ class Manifest : public ManifestExtractor::Element {
}
if (platformVersionName) {
printer->Print(StringPrintf(" platformBuildVersionName='%s'", platformVersionName->data()));
+ } else if (platformVersionNameInt) {
+ printer->Print(StringPrintf(" platformBuildVersionName='%d'", *platformVersionNameInt));
}
if (platformVersionCode) {
printer->Print(StringPrintf(" platformBuildVersionCode='%s'", platformVersionCode->data()));
+ } else if (platformVersionCodeInt) {
+ printer->Print(StringPrintf(" platformBuildVersionCode='%d'", *platformVersionCodeInt));
}
if (compilesdkVersion) {
printer->Print(StringPrintf(" compileSdkVersion='%d'", *compilesdkVersion));
@@ -609,6 +623,8 @@ class UsesSdkBadging : public ManifestExtractor::Element {
}
if (target_sdk) {
extractor()->RaiseTargetSdk(*target_sdk);
+ } else if (target_sdk_name) {
+ extractor()->RaiseTargetSdk(kCurrentDevelopmentVersion);
}
}
diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp
index d152a9cc7e62..41f01a01ed7c 100644
--- a/tools/aapt2/format/Archive.cpp
+++ b/tools/aapt2/format/Archive.cpp
@@ -103,7 +103,13 @@ class DirectoryWriter : public IArchiveWriter {
return false;
}
}
- return !in->HadError();
+
+ if (in->HadError()) {
+ error_ = in->GetError();
+ return false;
+ }
+
+ return FinishEntry();
}
bool HadError() const override {
@@ -191,6 +197,7 @@ class ZipFileWriter : public IArchiveWriter {
}
if (in->HadError()) {
+ error_ = in->GetError();
return false;
}
diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp
new file mode 100644
index 000000000000..ceed3740f37a
--- /dev/null
+++ b/tools/aapt2/format/Archive_test.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "test/Test.h"
+
+namespace aapt {
+
+using ArchiveTest = TestDirectoryFixture;
+
+constexpr size_t kTestDataLength = 100;
+
+class TestData : public io::MallocData {
+ public:
+ TestData(std::unique_ptr<uint8_t[]>& data, size_t size)
+ : MallocData(std::move(data), size) {}
+
+ bool HadError() const override { return !error_.empty(); }
+
+ std::string GetError() const override { return error_; }
+
+ std::string error_;
+};
+
+std::unique_ptr<uint8_t[]> MakeTestArray() {
+ auto array = std::make_unique<uint8_t[]>(kTestDataLength);
+ for (int index = 0; index < kTestDataLength; ++index) {
+ array[index] = static_cast<uint8_t>(rand());
+ }
+ return array;
+}
+
+std::unique_ptr<IArchiveWriter> MakeDirectoryWriter(const std::string& output_path) {
+ file::mkdirs(output_path);
+
+ StdErrDiagnostics diag;
+ return CreateDirectoryArchiveWriter(&diag, output_path);
+}
+
+std::unique_ptr<IArchiveWriter> MakeZipFileWriter(const std::string& output_path) {
+ file::mkdirs(file::GetStem(output_path).to_string());
+ std::remove(output_path.c_str());
+
+ StdErrDiagnostics diag;
+ return CreateZipFileArchiveWriter(&diag, output_path);
+}
+
+void VerifyDirectory(const std::string& path, const std::string& file, const uint8_t array[]) {
+ std::string file_path = file::BuildPath({path, file});
+ auto buffer = std::make_unique<char[]>(kTestDataLength);
+ std::ifstream stream(file_path);
+ stream.read(buffer.get(), kTestDataLength);
+
+ for (int index = 0; index < kTestDataLength; ++index) {
+ ASSERT_EQ(array[index], static_cast<uint8_t>(buffer[index]));
+ }
+}
+
+void VerifyZipFile(const std::string& output_path, const std::string& file, const uint8_t array[]) {
+ std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(output_path, nullptr);
+ std::unique_ptr<io::InputStream> stream = zip->FindFile(file)->OpenInputStream();
+
+ std::vector<uint8_t> buffer;
+ const void* data;
+ size_t size;
+
+ while (stream->Next(&data, &size)) {
+ auto pointer = static_cast<const uint8_t*>(data);
+ buffer.insert(buffer.end(), pointer, pointer + size);
+ }
+
+ for (int index = 0; index < kTestDataLength; ++index) {
+ ASSERT_EQ(array[index], buffer[index]);
+ }
+}
+
+TEST_F(ArchiveTest, DirectoryWriteEntrySuccess) {
+ std::string output_path = GetTestPath("output");
+ std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path);
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+
+ ASSERT_TRUE(writer->StartEntry("test1", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ ASSERT_TRUE(writer->StartEntry("test2", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ VerifyDirectory(output_path, "test1", data1.get());
+ VerifyDirectory(output_path, "test2", data2.get());
+}
+
+TEST_F(ArchiveTest, DirectoryWriteFileSuccess) {
+ std::string output_path = GetTestPath("output");
+ std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path);
+
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ auto data1_copy = std::make_unique<uint8_t[]>(kTestDataLength);
+ std::copy(data1.get(), data1.get() + kTestDataLength, data1_copy.get());
+
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+ auto data2_copy = std::make_unique<uint8_t[]>(kTestDataLength);
+ std::copy(data2.get(), data2.get() + kTestDataLength, data2_copy.get());
+
+ auto input1 = std::make_unique<TestData>(data1_copy, kTestDataLength);
+ auto input2 = std::make_unique<TestData>(data2_copy, kTestDataLength);
+
+ ASSERT_TRUE(writer->WriteFile("test1", 0, input1.get()));
+ ASSERT_FALSE(writer->HadError());
+ ASSERT_TRUE(writer->WriteFile("test2", 0, input2.get()));
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ VerifyDirectory(output_path, "test1", data1.get());
+ VerifyDirectory(output_path, "test2", data2.get());
+}
+
+TEST_F(ArchiveTest, DirectoryWriteFileError) {
+ std::string output_path = GetTestPath("output");
+ std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path);
+ std::unique_ptr<uint8_t[]> data = MakeTestArray();
+ auto input = std::make_unique<TestData>(data, kTestDataLength);
+ input->error_ = "DirectoryWriteFileError";
+
+ ASSERT_FALSE(writer->WriteFile("test", 0, input.get()));
+ ASSERT_TRUE(writer->HadError());
+ ASSERT_EQ("DirectoryWriteFileError", writer->GetError());
+}
+
+TEST_F(ArchiveTest, ZipFileWriteEntrySuccess) {
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+
+ ASSERT_TRUE(writer->StartEntry("test1", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ ASSERT_TRUE(writer->StartEntry("test2", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ VerifyZipFile(output_path, "test1", data1.get());
+ VerifyZipFile(output_path, "test2", data2.get());
+}
+
+TEST_F(ArchiveTest, ZipFileWriteFileSuccess) {
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ auto data1_copy = std::make_unique<uint8_t[]>(kTestDataLength);
+ std::copy(data1.get(), data1.get() + kTestDataLength, data1_copy.get());
+
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+ auto data2_copy = std::make_unique<uint8_t[]>(kTestDataLength);
+ std::copy(data2.get(), data2.get() + kTestDataLength, data2_copy.get());
+
+ auto input1 = std::make_unique<TestData>(data1_copy, kTestDataLength);
+ auto input2 = std::make_unique<TestData>(data2_copy, kTestDataLength);
+
+ ASSERT_TRUE(writer->WriteFile("test1", 0, input1.get()));
+ ASSERT_FALSE(writer->HadError());
+ ASSERT_TRUE(writer->WriteFile("test2", 0, input2.get()));
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ VerifyZipFile(output_path, "test1", data1.get());
+ VerifyZipFile(output_path, "test2", data2.get());
+}
+
+TEST_F(ArchiveTest, ZipFileWriteFileError) {
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+ std::unique_ptr<uint8_t[]> data = MakeTestArray();
+ auto input = std::make_unique<TestData>(data, kTestDataLength);
+ input->error_ = "ZipFileWriteFileError";
+
+ ASSERT_FALSE(writer->WriteFile("test", 0, input.get()));
+ ASSERT_TRUE(writer->HadError());
+ ASSERT_EQ("ZipFileWriteFileError", writer->GetError());
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index fd8e36ebf823..f362744c0942 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -455,35 +455,6 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) {
const ResTable_overlayable_policy_header* policy_header =
ConvertTo<ResTable_overlayable_policy_header>(parser.chunk());
- OverlayableItem::PolicyFlags policies = OverlayableItem::Policy::kNone;
- if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) {
- policies |= OverlayableItem::Policy::kPublic;
- }
- if (policy_header->policy_flags
- & ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) {
- policies |= OverlayableItem::Policy::kSystem;
- }
- if (policy_header->policy_flags
- & ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) {
- policies |= OverlayableItem::Policy::kVendor;
- }
- if (policy_header->policy_flags
- & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) {
- policies |= OverlayableItem::Policy::kProduct;
- }
- if (policy_header->policy_flags
- & ResTable_overlayable_policy_header::POLICY_SIGNATURE) {
- policies |= OverlayableItem::Policy::kSignature;
- }
- if (policy_header->policy_flags
- & ResTable_overlayable_policy_header::POLICY_ODM_PARTITION) {
- policies |= OverlayableItem::Policy::kOdm;
- }
- if (policy_header->policy_flags
- & ResTable_overlayable_policy_header::POLICY_OEM_PARTITION) {
- policies |= OverlayableItem::Policy::kOem;
- }
-
const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>(
((uint8_t *)policy_header) + util::DeviceToHost32(policy_header->header.headerSize));
const ResTable_ref* const ref_end = ref_begin
@@ -501,7 +472,7 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) {
}
OverlayableItem overlayable_item(overlayable);
- overlayable_item.policies = policies;
+ overlayable_item.policies = policy_header->policy_flags;
if (!table_->SetOverlayable(iter->second, overlayable_item, diag_)) {
return false;
}
@@ -614,6 +585,7 @@ std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(const ResourceNameRef
if (attr->type_mask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
Attribute::Symbol symbol;
symbol.value = util::DeviceToHost32(map_entry.value.data);
+ symbol.type = map_entry.value.dataType;
symbol.symbol = Reference(util::DeviceToHost32(map_entry.name.ident));
attr->symbols.push_back(std::move(symbol));
}
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f2e72da4056a..4784ecf3d12c 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -59,10 +59,22 @@ 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 a.key.id.value() < b.key.id.value();
+ return cmp_style_ids(a.key.id.value(), b.key.id.value());
}
return true;
} else if (!b.key.id) {
@@ -107,7 +119,7 @@ class MapFlattenVisitor : public ValueVisitor {
}
for (Attribute::Symbol& s : attr->symbols) {
- BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
+ BinaryPrimitive val(s.type, s.value);
FlattenEntry(&s.symbol, &val);
}
}
@@ -221,21 +233,22 @@ class MapFlattenVisitor : public ValueVisitor {
struct OverlayableChunk {
std::string actor;
Source source;
- std::map<OverlayableItem::PolicyFlags, std::set<ResourceId>> policy_ids;
+ std::map<PolicyFlags, std::set<ResourceId>> policy_ids;
};
class PackageFlattener {
public:
PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries,
- bool collapse_key_stringpool, const std::set<std::string>& whitelisted_resources)
+ bool collapse_key_stringpool,
+ const std::set<ResourceName>& name_collapse_exemptions)
: context_(context),
diag_(context->GetDiagnostics()),
package_(package),
shared_libs_(shared_libs),
use_sparse_entries_(use_sparse_entries),
collapse_key_stringpool_(collapse_key_stringpool),
- whitelisted_resources_(whitelisted_resources) {
+ name_collapse_exemptions_(name_collapse_exemptions) {
}
bool FlattenPackage(BigBuffer* buffer) {
@@ -480,35 +493,12 @@ class PackageFlattener {
return false;
}
- uint32_t policy_flags = 0;
- if (item.policies & OverlayableItem::Policy::kPublic) {
- policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
- }
- if (item.policies & OverlayableItem::Policy::kSystem) {
- policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION;
- }
- if (item.policies & OverlayableItem::Policy::kVendor) {
- policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION;
- }
- if (item.policies & OverlayableItem::Policy::kProduct) {
- policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION;
- }
- if (item.policies & OverlayableItem::Policy::kSignature) {
- policy_flags |= ResTable_overlayable_policy_header::POLICY_SIGNATURE;
- }
- if (item.policies & OverlayableItem::Policy::kOdm) {
- policy_flags |= ResTable_overlayable_policy_header::POLICY_ODM_PARTITION;
- }
- if (item.policies & OverlayableItem::Policy::kOem) {
- policy_flags |= ResTable_overlayable_policy_header::POLICY_OEM_PARTITION;
- }
-
- auto policy = overlayable_chunk->policy_ids.find(policy_flags);
+ auto policy = overlayable_chunk->policy_ids.find(item.policies);
if (policy != overlayable_chunk->policy_ids.end()) {
policy->second.insert(id);
} else {
overlayable_chunk->policy_ids.insert(
- std::make_pair(policy_flags, std::set<ResourceId>{id}));
+ std::make_pair(item.policies, std::set<ResourceId>{id}));
}
}
}
@@ -546,7 +536,8 @@ class PackageFlattener {
ChunkWriter policy_writer(buffer);
auto* policy_type = policy_writer.StartChunk<ResTable_overlayable_policy_header>(
RES_TABLE_OVERLAYABLE_POLICY_TYPE);
- policy_type->policy_flags = util::HostToDevice32(static_cast<uint32_t>(policy_ids.first));
+ policy_type->policy_flags =
+ static_cast<PolicyFlags>(util::HostToDevice32(static_cast<uint32_t>(policy_ids.first)));
policy_type->entry_count = util::HostToDevice32(static_cast<uint32_t>(
policy_ids.second.size()));
// Write the ids after the policy header
@@ -652,11 +643,12 @@ class PackageFlattener {
for (ResourceEntry* entry : sorted_entries) {
uint32_t local_key_index;
+ ResourceName resource_name({}, type->type, entry->name);
if (!collapse_key_stringpool_ ||
- whitelisted_resources_.find(entry->name) != whitelisted_resources_.end()) {
+ name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
} else {
- // resource isn't whitelisted, add it as obfuscated value
+ // resource isn't exempt from collapse, add it as obfuscated value
local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
}
// Group values by configuration.
@@ -712,7 +704,7 @@ class PackageFlattener {
StringPool type_pool_;
StringPool key_pool_;
bool collapse_key_stringpool_;
- const std::set<std::string>& whitelisted_resources_;
+ const std::set<ResourceName>& name_collapse_exemptions_;
};
} // namespace
@@ -760,7 +752,7 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
PackageFlattener flattener(context, package.get(), &table->included_packages_,
options_.use_sparse_entries, options_.collapse_key_stringpool,
- options_.whitelisted_resources);
+ options_.name_collapse_exemptions);
if (!flattener.FlattenPackage(&package_buffer)) {
return false;
}
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 73c17295556b..4360db190146 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -19,6 +19,7 @@
#include "android-base/macros.h"
+#include "Resource.h"
#include "ResourceTable.h"
#include "process/IResourceTableConsumer.h"
#include "util/BigBuffer.h"
@@ -41,8 +42,8 @@ struct TableFlattenerOptions {
// have name indices that point to this single value
bool collapse_key_stringpool = false;
- // Set of whitelisted resource names to avoid altering in key stringpool
- std::set<std::string> whitelisted_resources;
+ // Set of resources to avoid collapsing to a single entry in key stringpool.
+ std::set<ResourceName> name_collapse_exemptions;
// Map from original resource paths to shortened resource paths.
std::map<std::string, std::string> shortened_path_map;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index a9409235e07a..59627ce579af 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -32,6 +32,8 @@ using ::testing::Gt;
using ::testing::IsNull;
using ::testing::NotNull;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
namespace aapt {
class TableFlattenerTest : public ::testing::Test {
@@ -431,6 +433,47 @@ TEST_F(TableFlattenerTest, FlattenSharedLibrary) {
EXPECT_EQ("lib", iter->second);
}
+TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) {
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("lib", 0x00)
+ .AddValue("lib:style/Theme",
+ ResourceId(0x00030001),
+ test::StyleBuilder()
+ .AddItem("lib:attr/bar", ResourceId(0x00010002),
+ ResourceUtils::TryParseInt("2"))
+ .AddItem("lib:attr/foo", ResourceId(0x00010001),
+ ResourceUtils::TryParseInt("1"))
+ .AddItem("android:attr/bar", ResourceId(0x01010002),
+ ResourceUtils::TryParseInt("4"))
+ .AddItem("android:attr/foo", ResourceId(0x01010001),
+ ResourceUtils::TryParseInt("3"))
+ .Build())
+ .Build();
+ ResourceTable result;
+ ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
+
+ Maybe<ResourceTable::SearchResult> search_result =
+ result.FindResource(test::ParseNameOrDie("lib:style/Theme"));
+ ASSERT_TRUE(search_result);
+ EXPECT_EQ(0x00u, search_result.value().package->id.value());
+ EXPECT_EQ(0x03u, search_result.value().type->id.value());
+ EXPECT_EQ(0x01u, search_result.value().entry->id.value());
+ ASSERT_EQ(1u, search_result.value().entry->values.size());
+ Value* value = search_result.value().entry->values[0]->value.get();
+ Style* style = ValueCast<Style>(value);
+ ASSERT_TRUE(style);
+ ASSERT_EQ(4u, style->entries.size());
+ // Ensure the attributes from the shared library come after the items from
+ // android.
+ EXPECT_EQ(0x01010001, style->entries[0].key.id.value());
+ EXPECT_EQ(0x01010002, style->entries[1].key.id.value());
+ EXPECT_EQ(0x00010001, style->entries[2].key.id.value());
+ EXPECT_EQ(0x00010002, style->entries[3].key.id.value());
+}
+
TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
std::unique_ptr<IAaptContext> context =
test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
@@ -518,7 +561,7 @@ TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) {
ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result));
}
-TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) {
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId("com.app.test", 0x7f)
@@ -572,7 +615,7 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) {
ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
}
-TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) {
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId("com.app.test", 0x7f)
@@ -591,21 +634,22 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) {
TableFlattenerOptions options;
options.collapse_key_stringpool = true;
- options.whitelisted_resources.insert("test");
- options.whitelisted_resources.insert("three");
+ options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one"));
+ options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test"));
ResTable res_table;
ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
- EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one",
ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
- EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {},
- Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+ EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+ ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+ // Note that this resource is also named "one", but it's a different type, so gets obfuscated.
EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
ResTable_config::CONFIG_VERSION));
@@ -629,9 +673,9 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) {
TEST_F(TableFlattenerTest, FlattenOverlayable) {
OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
- overlayable_item.policies |= OverlayableItem::Policy::kProduct;
- overlayable_item.policies |= OverlayableItem::Policy::kSystem;
- overlayable_item.policies |= OverlayableItem::Policy::kVendor;
+ overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
+ overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
+ overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
std::string name = "com.app.test:integer/overlayable";
std::unique_ptr<ResourceTable> table =
@@ -649,27 +693,27 @@ TEST_F(TableFlattenerTest, FlattenOverlayable) {
ASSERT_THAT(search_result.value().entry, NotNull());
ASSERT_TRUE(search_result.value().entry->overlayable_item);
OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value();
- EXPECT_EQ(result_overlayable_item.policies, OverlayableItem::Policy::kSystem
- | OverlayableItem::Policy::kVendor
- | OverlayableItem::Policy::kProduct);
+ EXPECT_EQ(result_overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
+ | PolicyFlags::VENDOR_PARTITION
+ | PolicyFlags::PRODUCT_PARTITION);
}
TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
auto overlayable = std::make_shared<Overlayable>("TestName", "overlay://theme");
std::string name_zero = "com.app.test:integer/overlayable_zero_item";
OverlayableItem overlayable_item_zero(overlayable);
- overlayable_item_zero.policies |= OverlayableItem::Policy::kProduct;
- overlayable_item_zero.policies |= OverlayableItem::Policy::kSystem;
+ overlayable_item_zero.policies |= PolicyFlags::PRODUCT_PARTITION;
+ overlayable_item_zero.policies |= PolicyFlags::SYSTEM_PARTITION;
std::string name_one = "com.app.test:integer/overlayable_one_item";
OverlayableItem overlayable_item_one(overlayable);
- overlayable_item_one.policies |= OverlayableItem::Policy::kPublic;
+ overlayable_item_one.policies |= PolicyFlags::PUBLIC;
std::string name_two = "com.app.test:integer/overlayable_two_item";
OverlayableItem overlayable_item_two(overlayable);
- overlayable_item_two.policies |= OverlayableItem::Policy::kProduct;
- overlayable_item_two.policies |= OverlayableItem::Policy::kSystem;
- overlayable_item_two.policies |= OverlayableItem::Policy::kVendor;
+ overlayable_item_two.policies |= PolicyFlags::PRODUCT_PARTITION;
+ overlayable_item_two.policies |= PolicyFlags::SYSTEM_PARTITION;
+ overlayable_item_two.policies |= PolicyFlags::VENDOR_PARTITION;
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
@@ -690,47 +734,48 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
ASSERT_THAT(search_result.value().entry, NotNull());
ASSERT_TRUE(search_result.value().entry->overlayable_item);
OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value();
- EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem
- | OverlayableItem::Policy::kProduct);
+ EXPECT_EQ(overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
+ | PolicyFlags::PRODUCT_PARTITION);
search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
ASSERT_TRUE(search_result.value().entry->overlayable_item);
overlayable_item = search_result.value().entry->overlayable_item.value();
- EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic);
+ EXPECT_EQ(overlayable_item.policies, PolicyFlags::PUBLIC);
search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
ASSERT_TRUE(search_result.value().entry->overlayable_item);
overlayable_item = search_result.value().entry->overlayable_item.value();
- EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem
- | OverlayableItem::Policy::kProduct
- | OverlayableItem::Policy::kVendor);
+ EXPECT_EQ(overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
+ | PolicyFlags::PRODUCT_PARTITION
+ | PolicyFlags::VENDOR_PARTITION);
}
TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
auto group = std::make_shared<Overlayable>("TestName", "overlay://theme");
std::string name_zero = "com.app.test:integer/overlayable_zero";
OverlayableItem overlayable_item_zero(group);
- overlayable_item_zero.policies |= OverlayableItem::Policy::kProduct;
- overlayable_item_zero.policies |= OverlayableItem::Policy::kSystem;
+ overlayable_item_zero.policies |= PolicyFlags::PRODUCT_PARTITION;
+ overlayable_item_zero.policies |= PolicyFlags::SYSTEM_PARTITION;
auto group_one = std::make_shared<Overlayable>("OtherName", "overlay://customization");
std::string name_one = "com.app.test:integer/overlayable_one";
OverlayableItem overlayable_item_one(group_one);
- overlayable_item_one.policies |= OverlayableItem::Policy::kPublic;
+ overlayable_item_one.policies |= PolicyFlags::PUBLIC;
std::string name_two = "com.app.test:integer/overlayable_two";
OverlayableItem overlayable_item_two(group);
- overlayable_item_two.policies |= OverlayableItem::Policy::kOdm;
- overlayable_item_two.policies |= OverlayableItem::Policy::kOem;
- overlayable_item_two.policies |= OverlayableItem::Policy::kVendor;
+ overlayable_item_two.policies |= PolicyFlags::ODM_PARTITION;
+ overlayable_item_two.policies |= PolicyFlags::OEM_PARTITION;
+ overlayable_item_two.policies |= PolicyFlags::VENDOR_PARTITION;
std::string name_three = "com.app.test:integer/overlayable_three";
OverlayableItem overlayable_item_three(group_one);
- overlayable_item_three.policies |= OverlayableItem::Policy::kSignature;
+ overlayable_item_three.policies |= PolicyFlags::SIGNATURE;
+ overlayable_item_three.policies |= PolicyFlags::ACTOR_SIGNATURE;
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
@@ -754,8 +799,8 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
OverlayableItem& result_overlayable = search_result.value().entry->overlayable_item.value();
EXPECT_EQ(result_overlayable.overlayable->name, "TestName");
EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme");
- EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kSystem
- | OverlayableItem::Policy::kProduct);
+ EXPECT_EQ(result_overlayable.policies, PolicyFlags::SYSTEM_PARTITION
+ | PolicyFlags::PRODUCT_PARTITION);
search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
ASSERT_TRUE(search_result);
@@ -764,7 +809,7 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
result_overlayable = search_result.value().entry->overlayable_item.value();
EXPECT_EQ(result_overlayable.overlayable->name, "OtherName");
EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization");
- EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kPublic);
+ EXPECT_EQ(result_overlayable.policies, PolicyFlags::PUBLIC);
search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
ASSERT_TRUE(search_result);
@@ -773,9 +818,9 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
result_overlayable = search_result.value().entry->overlayable_item.value();
EXPECT_EQ(result_overlayable.overlayable->name, "TestName");
EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme");
- EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kOdm
- | OverlayableItem::Policy::kOem
- | OverlayableItem::Policy::kVendor);
+ EXPECT_EQ(result_overlayable.policies, PolicyFlags::ODM_PARTITION
+ | PolicyFlags::OEM_PARTITION
+ | PolicyFlags::VENDOR_PARTITION);
search_result = output_table.FindResource(test::ParseNameOrDie(name_three));
ASSERT_TRUE(search_result);
@@ -784,7 +829,8 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
result_overlayable = search_result.value().entry->overlayable_item.value();
EXPECT_EQ(result_overlayable.overlayable->name, "OtherName");
EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization");
- EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kSignature);
+ EXPECT_EQ(result_overlayable.policies, PolicyFlags::SIGNATURE
+ | PolicyFlags::ACTOR_SIGNATURE);
}
TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) {
diff --git a/tools/aapt2/format/binary/XmlFlattener_test.cpp b/tools/aapt2/format/binary/XmlFlattener_test.cpp
index 1dac493359e4..c24488b16153 100644
--- a/tools/aapt2/format/binary/XmlFlattener_test.cpp
+++ b/tools/aapt2/format/binary/XmlFlattener_test.cpp
@@ -226,10 +226,10 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
// The tree needs a custom DynamicRefTable since it is not using a standard app ID (0x7f).
- android::DynamicRefTable dynamic_ref_table;
- dynamic_ref_table.addMapping(0x80, 0x80);
+ auto dynamic_ref_table = std::make_shared<android::DynamicRefTable>();
+ dynamic_ref_table->addMapping(0x80, 0x80);
- android::ResXMLTree tree(&dynamic_ref_table);
+ auto tree = android::ResXMLTree(std::move(dynamic_ref_table));
ASSERT_TRUE(Flatten(doc.get(), &tree));
while (tree.next() != android::ResXMLTree::START_TAG) {
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index bb21c1c539fb..2fd01d7f3dee 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -30,6 +30,8 @@ using ::android::ConfigDescription;
using ::android::LocaleValue;
using ::android::ResStringPool;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
namespace aapt {
namespace {
@@ -379,25 +381,28 @@ bool DeserializeOverlayableItemFromPb(const pb::OverlayableItem& pb_overlayable,
for (const int policy : pb_overlayable.policy()) {
switch (policy) {
case pb::OverlayableItem::PUBLIC:
- out_overlayable->policies |= OverlayableItem::Policy::kPublic;
+ out_overlayable->policies |= PolicyFlags::PUBLIC;
break;
case pb::OverlayableItem::SYSTEM:
- out_overlayable->policies |= OverlayableItem::Policy::kSystem;
+ out_overlayable->policies |= PolicyFlags::SYSTEM_PARTITION;
break;
case pb::OverlayableItem::VENDOR:
- out_overlayable->policies |= OverlayableItem::Policy::kVendor;
+ out_overlayable->policies |= PolicyFlags::VENDOR_PARTITION;
break;
case pb::OverlayableItem::PRODUCT:
- out_overlayable->policies |= OverlayableItem::Policy::kProduct;
+ out_overlayable->policies |= PolicyFlags::PRODUCT_PARTITION;
break;
case pb::OverlayableItem::SIGNATURE:
- out_overlayable->policies |= OverlayableItem::Policy::kSignature;
+ out_overlayable->policies |= PolicyFlags::SIGNATURE;
break;
case pb::OverlayableItem::ODM:
- out_overlayable->policies |= OverlayableItem::Policy::kOdm;
+ out_overlayable->policies |= PolicyFlags::ODM_PARTITION;
break;
case pb::OverlayableItem::OEM:
- out_overlayable->policies |= OverlayableItem::Policy::kOem;
+ out_overlayable->policies |= PolicyFlags::OEM_PARTITION;
+ break;
+ case pb::OverlayableItem::ACTOR:
+ out_overlayable->policies |= PolicyFlags::ACTOR_SIGNATURE;
break;
default:
*out_error = "unknown overlayable policy";
@@ -634,6 +639,7 @@ static bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* o
std::string* out_error) {
out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type());
out_ref->private_reference = pb_ref.private_();
+ out_ref->is_dynamic = pb_ref.is_dynamic().value();
if (pb_ref.id() != 0) {
out_ref->id = ResourceId(pb_ref.id());
@@ -708,6 +714,8 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
return {};
}
symbol.value = pb_symbol.value();
+ symbol.type = pb_symbol.type() != 0U ? pb_symbol.type()
+ : android::Res_value::TYPE_INT_DEC;
attr->symbols.push_back(std::move(symbol));
}
value = std::move(attr);
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index a54822b20302..ba6df22af9d3 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -21,6 +21,8 @@
using android::ConfigDescription;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
namespace aapt {
void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool, IDiagnostics* diag) {
@@ -290,43 +292,51 @@ static void SerializeOverlayableItemToPb(const OverlayableItem& overlayable_item
pb::Overlayable* pb_overlayable = pb_table->add_overlayable();
pb_overlayable->set_name(overlayable_item.overlayable->name);
pb_overlayable->set_actor(overlayable_item.overlayable->actor);
- SerializeSourceToPb(overlayable_item.overlayable->source, source_pool,
- pb_overlayable->mutable_source());
+ if (source_pool != nullptr) {
+ SerializeSourceToPb(overlayable_item.overlayable->source, source_pool,
+ pb_overlayable->mutable_source());
+ }
}
pb::OverlayableItem* pb_overlayable_item = pb_entry->mutable_overlayable_item();
pb_overlayable_item->set_overlayable_idx(i);
- if (overlayable_item.policies & OverlayableItem::Policy::kPublic) {
+ if (overlayable_item.policies & PolicyFlags::PUBLIC) {
pb_overlayable_item->add_policy(pb::OverlayableItem::PUBLIC);
}
- if (overlayable_item.policies & OverlayableItem::Policy::kProduct) {
+ if (overlayable_item.policies & PolicyFlags::PRODUCT_PARTITION) {
pb_overlayable_item->add_policy(pb::OverlayableItem::PRODUCT);
}
- if (overlayable_item.policies & OverlayableItem::Policy::kSystem) {
+ if (overlayable_item.policies & PolicyFlags::SYSTEM_PARTITION) {
pb_overlayable_item->add_policy(pb::OverlayableItem::SYSTEM);
}
- if (overlayable_item.policies & OverlayableItem::Policy::kVendor) {
+ if (overlayable_item.policies & PolicyFlags::VENDOR_PARTITION) {
pb_overlayable_item->add_policy(pb::OverlayableItem::VENDOR);
}
- if (overlayable_item.policies & OverlayableItem::Policy::kSignature) {
+ if (overlayable_item.policies & PolicyFlags::SIGNATURE) {
pb_overlayable_item->add_policy(pb::OverlayableItem::SIGNATURE);
}
- if (overlayable_item.policies & OverlayableItem::Policy::kOdm) {
+ if (overlayable_item.policies & PolicyFlags::ODM_PARTITION) {
pb_overlayable_item->add_policy(pb::OverlayableItem::ODM);
}
- if (overlayable_item.policies & OverlayableItem::Policy::kOem) {
+ if (overlayable_item.policies & PolicyFlags::OEM_PARTITION) {
pb_overlayable_item->add_policy(pb::OverlayableItem::OEM);
}
+ if (overlayable_item.policies & PolicyFlags::ACTOR_SIGNATURE) {
+ pb_overlayable_item->add_policy(pb::OverlayableItem::ACTOR);
+ }
- SerializeSourceToPb(overlayable_item.source, source_pool,
- pb_overlayable_item->mutable_source());
+ if (source_pool != nullptr) {
+ SerializeSourceToPb(overlayable_item.source, source_pool,
+ pb_overlayable_item->mutable_source());
+ }
pb_overlayable_item->set_comment(overlayable_item.comment);
}
void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table,
- IDiagnostics* diag) {
- StringPool source_pool;
+ IDiagnostics* diag, SerializeTableOptions options) {
+ auto source_pool = (options.exclude_sources) ? nullptr : util::make_unique<StringPool>();
+
pb::ToolFingerprint* pb_fingerprint = out_table->add_tool_fingerprint();
pb_fingerprint->set_tool(util::GetToolName());
pb_fingerprint->set_version(util::GetToolFingerprint());
@@ -356,32 +366,40 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table
// Write the Visibility struct.
pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level));
- SerializeSourceToPb(entry->visibility.source, &source_pool,
- pb_visibility->mutable_source());
+ if (source_pool != nullptr) {
+ SerializeSourceToPb(entry->visibility.source, source_pool.get(),
+ pb_visibility->mutable_source());
+ }
pb_visibility->set_comment(entry->visibility.comment);
if (entry->allow_new) {
pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new();
- SerializeSourceToPb(entry->allow_new.value().source, &source_pool,
- pb_allow_new->mutable_source());
+ if (source_pool != nullptr) {
+ SerializeSourceToPb(entry->allow_new.value().source, source_pool.get(),
+ pb_allow_new->mutable_source());
+ }
pb_allow_new->set_comment(entry->allow_new.value().comment);
}
if (entry->overlayable_item) {
- SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables, &source_pool,
- pb_entry, out_table);
+ SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables,
+ source_pool.get(), pb_entry, out_table);
}
for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
SerializeConfig(config_value->config, pb_config_value->mutable_config());
pb_config_value->mutable_config()->set_product(config_value->product);
- SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(), &source_pool);
+ SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(),
+ source_pool.get());
}
}
}
}
- SerializeStringPoolToPb(source_pool, out_table->mutable_source_pool(), diag);
+
+ if (source_pool != nullptr) {
+ SerializeStringPoolToPb(*source_pool, out_table->mutable_source_pool(), diag);
+ }
}
static pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) {
@@ -405,6 +423,9 @@ static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref)
pb_ref->set_private_(ref.private_reference);
pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type));
+ if (ref.is_dynamic) {
+ pb_ref->mutable_is_dynamic()->set_value(ref.is_dynamic);
+ }
}
template <typename T>
@@ -552,6 +573,7 @@ class ValueSerializer : public ConstValueVisitor {
SerializeItemMetaDataToPb(symbol.symbol, pb_symbol, src_pool_);
SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name());
pb_symbol->set_value(symbol.value);
+ pb_symbol->set_type(symbol.type);
}
}
diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h
index 33ffd182435b..7a3ea9903732 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.h
+++ b/tools/aapt2/format/proto/ProtoSerialize.h
@@ -35,6 +35,11 @@ struct SerializeXmlOptions {
bool remove_empty_text_nodes = false;
};
+struct SerializeTableOptions {
+ /** Prevent serializing the source pool and source protos. */
+ bool exclude_sources = false;
+};
+
// Serializes a Value to its protobuf representation. An optional StringPool will hold the
// source path string.
void SerializeValueToPb(const Value& value, pb::Value* out_value, StringPool* src_pool = nullptr);
@@ -59,7 +64,8 @@ void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool
void SerializeConfig(const android::ConfigDescription& config, pb::Configuration* out_pb_config);
// Serializes a ResourceTable into its protobuf representation.
-void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table, IDiagnostics* diag);
+void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table,
+ IDiagnostics* diag, SerializeTableOptions options = {});
// Serializes a ResourceFile into its protobuf representation.
void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file);
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index f252f33f44fb..1a7de6dc1c48 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -28,6 +28,8 @@ using ::testing::NotNull;
using ::testing::SizeIs;
using ::testing::StrEq;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
namespace aapt {
class MockFileCollection : public io::IFileCollection {
@@ -80,7 +82,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), context->GetDiagnostics()));
ASSERT_TRUE(table->AddResource(
test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), "tablet",
- test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 321u), context->GetDiagnostics()));
+ test::BuildPrimitive(android::Res_value::TYPE_INT_HEX, 321u), context->GetDiagnostics()));
// Make a reference with both resource name and resource ID.
// The reference should point to a resource outside of this table to test that both name and id
@@ -133,11 +135,13 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
&new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), "");
ASSERT_THAT(prim, NotNull());
EXPECT_THAT(prim->value.data, Eq(123u));
+ EXPECT_THAT(prim->value.dataType, Eq(0x10));
prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
&new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet");
ASSERT_THAT(prim, NotNull());
EXPECT_THAT(prim->value.data, Eq(321u));
+ EXPECT_THAT(prim->value.dataType, Eq(0x11));
Reference* actual_ref = test::GetValue<Reference>(&new_table, "com.app.a:layout/abc");
ASSERT_THAT(actual_ref, NotNull());
@@ -169,7 +173,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme"));
EXPECT_THAT(result_overlayable_item.overlayable->source.path, Eq("res/values/overlayable.xml"));
EXPECT_THAT(result_overlayable_item.overlayable->source.line, Eq(40));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::NONE));
EXPECT_THAT(result_overlayable_item.source.path, Eq("res/values/overlayable.xml"));
EXPECT_THAT(result_overlayable_item.source.line, Eq(42));
}
@@ -514,23 +518,28 @@ TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) {
TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
OverlayableItem overlayable_item_foo(std::make_shared<Overlayable>(
"CustomizableResources", "overlay://customization"));
- overlayable_item_foo.policies |= OverlayableItem::Policy::kSystem;
- overlayable_item_foo.policies |= OverlayableItem::Policy::kProduct;
+ overlayable_item_foo.policies |= PolicyFlags::SYSTEM_PARTITION;
+ overlayable_item_foo.policies |= PolicyFlags::PRODUCT_PARTITION;
OverlayableItem overlayable_item_bar(std::make_shared<Overlayable>(
"TaskBar", "overlay://theme"));
- overlayable_item_bar.policies |= OverlayableItem::Policy::kPublic;
- overlayable_item_bar.policies |= OverlayableItem::Policy::kVendor;
+ overlayable_item_bar.policies |= PolicyFlags::PUBLIC;
+ overlayable_item_bar.policies |= PolicyFlags::VENDOR_PARTITION;
OverlayableItem overlayable_item_baz(std::make_shared<Overlayable>(
"FontPack", "overlay://theme"));
- overlayable_item_baz.policies |= OverlayableItem::Policy::kPublic;
+ overlayable_item_baz.policies |= PolicyFlags::PUBLIC;
OverlayableItem overlayable_item_boz(std::make_shared<Overlayable>(
"IconPack", "overlay://theme"));
- overlayable_item_boz.policies |= OverlayableItem::Policy::kSignature;
- overlayable_item_boz.policies |= OverlayableItem::Policy::kOdm;
- overlayable_item_boz.policies |= OverlayableItem::Policy::kOem;
+ overlayable_item_boz.policies |= PolicyFlags::SIGNATURE;
+ overlayable_item_boz.policies |= PolicyFlags::ODM_PARTITION;
+ overlayable_item_boz.policies |= PolicyFlags::OEM_PARTITION;
+
+ OverlayableItem overlayable_item_actor_config(std::make_shared<Overlayable>(
+ "ActorConfig", "overlay://theme"));
+ overlayable_item_actor_config.policies |= PolicyFlags::SIGNATURE;
+ overlayable_item_actor_config.policies |= PolicyFlags::ACTOR_SIGNATURE;
OverlayableItem overlayable_item_biz(std::make_shared<Overlayable>(
"Other", "overlay://customization"));
@@ -544,6 +553,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
.SetOverlayable("com.app.a:bool/baz", overlayable_item_baz)
.SetOverlayable("com.app.a:bool/boz", overlayable_item_boz)
.SetOverlayable("com.app.a:bool/biz", overlayable_item_biz)
+ .SetOverlayable("com.app.a:bool/actor_config", overlayable_item_actor_config)
.AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true"))
.Build();
@@ -563,8 +573,8 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(overlayable_item.overlayable->name, Eq("CustomizableResources"));
EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://customization"));
- EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kSystem
- | OverlayableItem::Policy::kProduct));
+ EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::SYSTEM_PARTITION
+ | PolicyFlags::PRODUCT_PARTITION));
search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar"));
ASSERT_TRUE(search_result);
@@ -572,8 +582,8 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(overlayable_item.overlayable->name, Eq("TaskBar"));
EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme"));
- EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic
- | OverlayableItem::Policy::kVendor));
+ EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::PUBLIC
+ | PolicyFlags::VENDOR_PARTITION));
search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz"));
ASSERT_TRUE(search_result);
@@ -581,7 +591,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(overlayable_item.overlayable->name, Eq("FontPack"));
EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme"));
- EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic));
+ EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::PUBLIC));
search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/boz"));
ASSERT_TRUE(search_result);
@@ -589,16 +599,25 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(overlayable_item.overlayable->name, Eq("IconPack"));
EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme"));
- EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature
- | OverlayableItem::Policy::kOdm
- | OverlayableItem::Policy::kOem));
+ EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::SIGNATURE
+ | PolicyFlags::ODM_PARTITION
+ | PolicyFlags::OEM_PARTITION));
+
+ search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/actor_config"));
+ ASSERT_TRUE(search_result);
+ ASSERT_TRUE(search_result.value().entry->overlayable_item);
+ overlayable_item = search_result.value().entry->overlayable_item.value();
+ EXPECT_THAT(overlayable_item.overlayable->name, Eq("ActorConfig"));
+ EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme"));
+ EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::SIGNATURE
+ | PolicyFlags::ACTOR_SIGNATURE));
search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz"));
ASSERT_TRUE(search_result);
ASSERT_TRUE(search_result.value().entry->overlayable_item);
overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(overlayable_item.overlayable->name, Eq("Other"));
- EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kNone));
+ EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::NONE));
EXPECT_THAT(overlayable_item.comment, Eq("comment"));
search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz"));
@@ -606,4 +625,41 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
ASSERT_FALSE(search_result.value().entry->overlayable_item);
}
+TEST(ProtoSerializeTest, SerializeAndDeserializeDynamicReference) {
+ Reference ref(ResourceId(0x00010001));
+ ref.is_dynamic = true;
+
+ pb::Item pb_item;
+ SerializeItemToPb(ref, &pb_item);
+
+ ASSERT_TRUE(pb_item.has_ref());
+ EXPECT_EQ(pb_item.ref().id(), ref.id.value().id);
+ EXPECT_TRUE(pb_item.ref().is_dynamic().value());
+
+ std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(),
+ android::ConfigDescription(), nullptr,
+ nullptr, nullptr);
+ Reference* actual_ref = ValueCast<Reference>(item.get());
+ EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id);
+ EXPECT_TRUE(actual_ref->is_dynamic);
+}
+
+TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) {
+ Reference ref(ResourceId(0x00010001));
+
+ pb::Item pb_item;
+ SerializeItemToPb(ref, &pb_item);
+
+ ASSERT_TRUE(pb_item.has_ref());
+ EXPECT_EQ(pb_item.ref().id(), ref.id.value().id);
+ EXPECT_FALSE(pb_item.ref().has_is_dynamic());
+
+ std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(),
+ android::ConfigDescription(), nullptr,
+ nullptr, nullptr);
+ Reference* actual_ref = ValueCast<Reference>(item.get());
+ EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id);
+ EXPECT_FALSE(actual_ref->is_dynamic);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
new file mode 100644
index 000000000000..6361f9b8ae7d
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
@@ -0,0 +1,2 @@
+LOCAL_PATH := $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
new file mode 100644
index 000000000000..6bc2064c6e63
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_AAPT_NAMESPACES := true
+LOCAL_PACKAGE_NAME := AaptTestMergeOnly_App
+LOCAL_SDK_VERSION := current
+LOCAL_EXPORT_PACKAGE_RESOURCES := true
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ AaptTestMergeOnly_LeafLib \
+ AaptTestMergeOnly_LocalLib
+include $(BUILD_PACKAGE) \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml b/tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml
new file mode 100644
index 000000000000..bc3a7e5ebd21
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.app">
+
+ <application
+ android:label="@*com.local.lib:string/lib_string"/>
+
+</manifest> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
new file mode 100644
index 000000000000..7bf8cf84426c
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_AAPT_NAMESPACES := true
+LOCAL_MODULE := AaptTestMergeOnly_LeafLib
+LOCAL_SDK_VERSION := current
+LOCAL_MODULE_TAGS := tests
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_MIN_SDK_VERSION := 21
+LOCAL_AAPT_FLAGS := --merge-only
+include $(BUILD_STATIC_JAVA_LIBRARY) \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml
new file mode 100644
index 000000000000..9907bd98790d
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest package="com.leaf.lib" />
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml
new file mode 100644
index 000000000000..07de87fa1d33
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:leaf="http://schemas.android.com/apk/res/com.leaf.lib">
+
+ <TextView android:text="@string/leaf_string"
+ leaf:leaf_attr="hello"
+ style="@style/LeafChildStyle"/>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml
new file mode 100644
index 000000000000..7f94c26de23c
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <attr format="string" name="leaf_attr"/>
+ <attr format="string" name="leaf_attr2"/>
+
+ <string name="leaf_string">I am a leaf</string>
+
+ <style name="LeafParentStyle">
+ <item type="attr" name="leaf_attr"/>
+ <item type="attr" name="leaf_attr2"/>
+ </style>
+
+ <style name="LeafChildStyle" parent="LeafParentStyle">
+ <item type="attr" name="leaf_attr2">hello</item>
+ </style>
+
+ <style name="LeafParentStyle.DottedChild"/>
+
+ <declare-styleable name="leaf_ds">
+ <attr name="leaf_attr">hello</attr>
+ </declare-styleable>
+
+ <public type="attr" name="leaf_attr"/>
+ <public type="attr" name="leaf_attr2"/>
+ <public type="style" name="LeafParentStyle"/>
+ <public type="style" name="LeafChildStyle"/>
+ <public type="style" name="LeafParentStyle.DottedChild"/>
+</resources> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
new file mode 100644
index 000000000000..ba781c56a913
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_AAPT_NAMESPACES := true
+LOCAL_MODULE := AaptTestMergeOnly_LocalLib
+LOCAL_SDK_VERSION := current
+LOCAL_MODULE_TAGS := tests
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_MIN_SDK_VERSION := 21
+LOCAL_AAPT_FLAGS := --merge-only
+include $(BUILD_STATIC_JAVA_LIBRARY) \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml
new file mode 100644
index 000000000000..aa0ff5dcb4b6
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.local.lib">
+
+ <application>
+
+ <activity
+ android:name="com.myapp.MyActivity"
+ android:theme="@com.leaf.lib:style/LeafParentStyle.DottedChild"/>
+
+ </application>
+
+</manifest>
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml
new file mode 100644
index 000000000000..80d2fd6bcd09
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:leaf="http://schemas.android.com/apk/res/com.leaf.lib">
+
+ <TextView android:text="@*com.leaf.lib:string/leaf_string"
+ leaf:leaf_attr="hello"
+ style="@com.leaf.lib:style/LeafChildStyle"/>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml
new file mode 100644
index 000000000000..f06371874a45
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:leaf="http://schemas.android.com/apk/res/com.leaf.lib">
+
+ <include layout="@layout/activity"/>
+
+ <include layout="@*com.leaf.lib:layout/activity"/>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml
new file mode 100644
index 000000000000..2f9704df0570
--- /dev/null
+++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="lib_string">@*com.leaf.lib:string/leaf_string</string>
+
+ <style name="lib_style" parent="@com.leaf.lib:style/LeafChildStyle">
+ <item name="com.leaf.lib:leaf_attr">hello</item>
+ </style>
+
+ <style name="LeafParentStyle.DottedChild.LocalLibStyle"
+ parent="@com.leaf.lib:style/LeafParentStyle.DottedChild"/>
+
+ <public type="style" name="LeafParentStyle.DottedChild.LocalLibStyle"/>
+
+</resources> \ No newline at end of file
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index ce6d9352180d..bb925c9b3f8e 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -58,7 +58,7 @@ bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file
return CopyFileToArchive(context, file, out_path, compression_flags, writer);
}
-bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg,
+bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
const std::string& out_path, uint32_t compression_flags,
IArchiveWriter* writer) {
TRACE_CALL();
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 5f978a8e2c35..5cb8206db23c 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -19,7 +19,7 @@
#include <string>
-#include "google/protobuf/message_lite.h"
+#include "google/protobuf/message.h"
#include "google/protobuf/io/coded_stream.h"
#include "format/Archive.h"
@@ -39,7 +39,7 @@ bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& ou
bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file,
const std::string& out_path, IArchiveWriter* writer);
-bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg,
+bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
const std::string& out_path, uint32_t compression_flags,
IArchiveWriter* writer);
@@ -127,13 +127,13 @@ class ProtoInputStreamReader {
public:
explicit ProtoInputStreamReader(io::InputStream* in) : in_(in) { }
- /** Deserializes a MessageLite proto from the current position in the input stream.*/
- template <typename T> bool ReadMessage(T *message_lite) {
+ /** Deserializes a Message proto from the current position in the input stream.*/
+ template <typename T> bool ReadMessage(T *message) {
ZeroCopyInputAdaptor adapter(in_);
google::protobuf::io::CodedInputStream coded_stream(&adapter);
coded_stream.SetTotalBytesLimit(std::numeric_limits<int32_t>::max(),
coded_stream.BytesUntilTotalBytesLimit());
- return message_lite->ParseFromCodedStream(&coded_stream);
+ return message->ParseFromCodedStream(&coded_stream);
}
private:
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index a4610b2575b9..cec59e75831d 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -18,6 +18,7 @@
#include <algorithm>
#include <array>
+#include <regex>
#include "text/Unicode.h"
#include "text/Utf8Iterator.h"
@@ -65,14 +66,26 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) {
// Treat deprecated specially, since we don't remove it from the source comment.
if (comment.find(sDeprecated) != std::string::npos) {
- annotation_bit_mask_ |= AnnotationRule::kDeprecated;
+ annotation_parameter_map_[AnnotationRule::kDeprecated] = "";
}
for (const AnnotationRule& rule : sAnnotationRules) {
std::string::size_type idx = comment.find(rule.doc_str.data());
if (idx != std::string::npos) {
- annotation_bit_mask_ |= rule.bit_mask;
- comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size());
+ // Captures all parameters associated with the specified annotation rule
+ // by matching the first pair of parantheses after the rule.
+ std::regex re(rule.doc_str.to_string() + "\\s*\\((.+)\\)");
+ std::smatch match_result;
+ const bool is_match = std::regex_search(comment, match_result, re);
+ // We currently only capture and preserve parameters for SystemApi.
+ if (is_match && rule.bit_mask == AnnotationRule::kSystemApi) {
+ annotation_parameter_map_[rule.bit_mask] = match_result[1].str();
+ comment.erase(comment.begin() + match_result.position(),
+ comment.begin() + match_result.position() + match_result.length());
+ } else {
+ annotation_parameter_map_[rule.bit_mask] = "";
+ comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size());
+ }
}
}
@@ -119,13 +132,19 @@ void AnnotationProcessor::Print(Printer* printer) const {
printer->Println(" */");
}
- if (annotation_bit_mask_ & AnnotationRule::kDeprecated) {
+ if (annotation_parameter_map_.find(AnnotationRule::kDeprecated) !=
+ annotation_parameter_map_.end()) {
printer->Println("@Deprecated");
}
for (const AnnotationRule& rule : sAnnotationRules) {
- if (annotation_bit_mask_ & rule.bit_mask) {
- printer->Println(rule.annotation);
+ const auto& it = annotation_parameter_map_.find(rule.bit_mask);
+ if (it != annotation_parameter_map_.end()) {
+ printer->Print(rule.annotation);
+ if (!it->second.empty()) {
+ printer->Print("(").Print(it->second).Print(")");
+ }
+ printer->Print("\n");
}
}
}
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index ae7bdb0c3ae2..fdb58468d995 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -19,6 +19,7 @@
#include <sstream>
#include <string>
+#include <unordered_map>
#include "androidfw/StringPiece.h"
@@ -70,7 +71,7 @@ class AnnotationProcessor {
std::stringstream comment_;
std::stringstream mAnnotations;
bool has_comments_ = false;
- uint32_t annotation_bit_mask_ = 0;
+ std::unordered_map<uint32_t, std::string> annotation_parameter_map_;
void AppendCommentLine(std::string line);
};
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index 69f49c8b97c3..7d0a4e9af632 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -61,6 +61,21 @@ TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) {
EXPECT_THAT(annotations, HasSubstr("This is a system API"));
}
+TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationParamsAndRemovesFromComment) {
+ AnnotationProcessor processor;
+ processor.AppendComment("@SystemApi (p1=k1,p2=k2) This is a system API");
+
+ std::string annotations;
+ StringOutputStream out(&annotations);
+ Printer printer(&out);
+ processor.Print(&printer);
+ out.Flush();
+
+ EXPECT_THAT(annotations, HasSubstr("@android.annotation.SystemApi(p1=k1,p2=k2)"));
+ EXPECT_THAT(annotations, Not(HasSubstr("@SystemApi")));
+ EXPECT_THAT(annotations, HasSubstr("This is a system API"));
+}
+
TEST(AnnotationProcessorTest, EmitsTestApiAnnotationAndRemovesFromComment) {
AnnotationProcessor processor;
processor.AppendComment("@TestApi This is a test API");
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 31d205e1b9c9..bb541fe2490b 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -304,9 +304,11 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res
auto documentation_remove_iter = std::remove_if(documentation_attrs.begin(),
documentation_attrs.end(),
[&](StyleableAttr entry) -> bool {
- StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
- return SkipSymbol(entry.symbol) || attr_comment_line.contains("@removed")
- || attr_comment_line.contains("@hide");
+ if (SkipSymbol(entry.symbol)) {
+ return true;
+ }
+ const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
+ return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide");
});
documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end());
@@ -428,7 +430,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res
out_rewrite_method->AppendStatement(
StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data()));
out_rewrite_method->AppendStatement(
- StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | (p << 24);",
+ StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | packageIdBits;",
array_field_name.data(), array_field_name.data()));
out_rewrite_method->AppendStatement(" }");
out_rewrite_method->AppendStatement("}");
@@ -487,9 +489,9 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso
if (out_rewrite_method != nullptr) {
const StringPiece& type_str = to_string(name.type);
- out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);",
- type_str.data(), field_name.data(),
- type_str.data(), field_name.data()));
+ out_rewrite_method->AppendStatement(
+ StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(),
+ field_name.data(), type_str.data(), field_name.data()));
}
}
@@ -599,6 +601,7 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
rewrite_method->AppendStatement(
StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data()));
}
+ rewrite_method->AppendStatement("final int packageIdBits = p << 24;");
}
for (const auto& package : table_->packages) {
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 4f51fc48c80e..1e1fe4740c6b 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -522,9 +522,15 @@ TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary)
ASSERT_TRUE(generator.Generate("android", &out));
out.Flush();
- EXPECT_THAT(output, HasSubstr("void onResourcesLoaded"));
- EXPECT_THAT(output, HasSubstr("com.foo.R.onResourcesLoaded"));
- EXPECT_THAT(output, HasSubstr("com.boo.R.onResourcesLoaded"));
+ EXPECT_THAT(output, HasSubstr(
+ R"( public static void onResourcesLoaded(int p) {
+ com.foo.R.onResourcesLoaded(p);
+ com.boo.R.onResourcesLoaded(p);
+ final int packageIdBits = p << 24;
+ attr.foo = (attr.foo & 0x00ffffff) | packageIdBits;
+ id.foo = (id.foo & 0x00ffffff) | packageIdBits;
+ style.foo = (style.foo & 0x00ffffff) | packageIdBits;
+ })"));
}
TEST(JavaClassGeneratorTest, OnlyGenerateRText) {
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index ff7f5c12404c..d9a4caa34e0d 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -155,13 +155,19 @@ class MenuVisitor : public BaseVisitor {
void Visit(xml::Element* node) override {
if (node->namespace_uri.empty() && node->name == "item") {
for (const auto& attr : node->attributes) {
- if (attr.namespace_uri == xml::kSchemaAndroid) {
- if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") &&
- util::IsJavaClassName(attr.value)) {
- AddClass(node->line_number, attr.value, "android.content.Context");
- } else if (attr.name == "onClick") {
- AddMethod(node->line_number, attr.value, "android.view.MenuItem");
- }
+ // AppCompat-v7 defines its own versions of Android attributes if
+ // they're defined after SDK 7 (the below are from 11 and 14,
+ // respectively), so don't bother checking the XML namespace.
+ //
+ // Given the names of the containing XML files and the attribute
+ // names, it's unlikely that keeping these classes would be wrong.
+ if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") &&
+ util::IsJavaClassName(attr.value)) {
+ AddClass(node->line_number, attr.value, "android.content.Context");
+ }
+
+ if (attr.namespace_uri == xml::kSchemaAndroid && attr.name == "onClick") {
+ AddMethod(node->line_number, attr.value, "android.view.MenuItem");
}
}
}
@@ -388,11 +394,15 @@ bool CollectProguardRules(IAaptContext* context_, xml::XmlResource* res, KeepSet
return true;
}
-void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep) {
+void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep,
+ bool no_location_reference) {
+
Printer printer(out);
for (const auto& entry : keep_set.manifest_class_set_) {
- for (const UsageLocation& location : entry.second) {
- printer.Print("# Referenced at ").Println(location.source.to_string());
+ if (!no_location_reference) {
+ for (const UsageLocation& location : entry.second) {
+ printer.Print("# Referenced at ").Println(location.source.to_string());
+ }
}
printer.Print("-keep class ").Print(entry.first).Println(" { <init>(); }");
}
@@ -409,7 +419,9 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep)
if (can_be_conditional) {
for (const UsageLocation& location : locations) {
- printer.Print("# Referenced at ").Println(location.source.to_string());
+ if (!no_location_reference) {
+ printer.Print("# Referenced at ").Println(location.source.to_string());
+ }
printer.Print("-if class **.R$layout { int ")
.Print(JavaClassGenerator::TransformToFieldName(location.name.entry))
.Println("; }");
@@ -419,8 +431,10 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep)
printer.Println("); }");
}
} else {
- for (const UsageLocation& location : entry.second) {
- printer.Print("# Referenced at ").Println(location.source.to_string());
+ if (!no_location_reference) {
+ for (const UsageLocation& location : entry.second) {
+ printer.Print("# Referenced at ").Println(location.source.to_string());
+ }
}
printer.Print("-keep class ").Print(entry.first.name).Print(" { <init>(");
@@ -431,8 +445,10 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep)
}
for (const auto& entry : keep_set.method_set_) {
- for (const UsageLocation& location : entry.second) {
- printer.Print("# Referenced at ").Println(location.source.to_string());
+ if (!no_location_reference) {
+ for (const UsageLocation& location : entry.second) {
+ printer.Print("# Referenced at ").Println(location.source.to_string());
+ }
}
printer.Print("-keepclassmembers class * { *** ").Print(entry.first.name)
.Print("(").Print(entry.first.signature).Println("); }");
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index b15df59f56a6..a01b64d024d2 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -70,7 +70,8 @@ class KeepSet {
}
private:
- friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep);
+ friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep,
+ bool no_location_reference);
friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
std::set<UsageLocation>* locations);
@@ -89,7 +90,8 @@ bool CollectProguardRules(IAaptContext* context, xml::XmlResource* res, KeepSet*
bool CollectResourceReferences(IAaptContext* context, ResourceTable* table, KeepSet* keep_set);
-void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep);
+void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep,
+ bool no_location_reference);
bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
std::set<UsageLocation>* locations);
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index 7c3cda089b40..c7ae0b6a75cc 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -30,7 +30,7 @@ namespace aapt {
std::string GetKeepSetString(const proguard::KeepSet& set, bool minimal_rules) {
std::string out;
StringOutputStream sout(&out);
- proguard::WriteKeepSet(set, &sout, minimal_rules);
+ proguard::WriteKeepSet(set, &sout, minimal_rules, false);
sout.Flush();
return out;
}
@@ -381,6 +381,25 @@ TEST(ProguardRulesTest, MenuRulesAreEmitted) {
EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat")));
}
+TEST(ProguardRulesTest, MenuRulesAreEmittedForActionClasses) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> menu = test::BuildXmlDom(R"(
+ <menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:id="@+id/my_item"
+ app:actionViewClass="com.foo.Bar"
+ app:actionProviderClass="com.foo.Baz" />
+ </menu>)");
+ menu->file.name = test::ParseNameOrDie("menu/foo");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), menu.get(), &set));
+
+ std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz"));
+}
+
TEST(ProguardRulesTest, TransitionPathMotionRulesAreEmitted) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<xml::XmlResource> transition = test::BuildXmlDom(R"(
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 06303c273261..c813a446b8db 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -214,6 +214,33 @@ static bool VerifyUsesFeature(xml::Element* el, SourcePathDiagnostics* diag) {
return true;
}
+// Ensure that 'ns_decls' contains a declaration for 'uri', using 'prefix' as
+// the xmlns prefix if possible.
+static void EnsureNamespaceIsDeclared(const std::string& prefix, const std::string& uri,
+ std::vector<xml::NamespaceDecl>* ns_decls) {
+ if (std::find_if(ns_decls->begin(), ns_decls->end(), [&](const xml::NamespaceDecl& ns_decl) {
+ return ns_decl.uri == uri;
+ }) != ns_decls->end()) {
+ return;
+ }
+
+ std::set<std::string> used_prefixes;
+ for (const auto& ns_decl : *ns_decls) {
+ used_prefixes.insert(ns_decl.prefix);
+ }
+
+ // Make multiple attempts in the unlikely event that 'prefix' is already taken.
+ std::string disambiguator;
+ for (int i = 0; i < used_prefixes.size() + 1; i++) {
+ std::string attempted_prefix = prefix + disambiguator;
+ if (used_prefixes.find(attempted_prefix) == used_prefixes.end()) {
+ ns_decls->push_back(xml::NamespaceDecl{attempted_prefix, uri});
+ return;
+ }
+ disambiguator = std::to_string(i);
+ }
+}
+
bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
IDiagnostics* diag) {
// First verify some options.
@@ -272,6 +299,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
manifest_action.Action(VerifyManifest);
manifest_action.Action(FixCoreAppAttribute);
manifest_action.Action([&](xml::Element* el) -> bool {
+ EnsureNamespaceIsDeclared("android", xml::kSchemaAndroid, &el->namespace_decls);
+
if (options_.version_name_default) {
if (options_.replace_version) {
el->RemoveAttribute(xml::kSchemaAndroid, "versionName");
@@ -330,6 +359,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
}
return true;
});
+ manifest_action["uses-sdk"]["extension-sdk"];
// Instrumentation actions.
manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName);
@@ -346,6 +376,12 @@ 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"];
manifest_action["overlay"].Action([&](xml::Element* el) -> bool {
if (!options_.rename_overlay_target_package) {
@@ -377,6 +413,10 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
manifest_action["package-verifier"];
manifest_action["meta-data"] = meta_data_action;
manifest_action["uses-split"].Action(RequiredNameIsJavaPackage);
+ manifest_action["queries"]["package"].Action(RequiredNameIsJavaPackage);
+ manifest_action["queries"]["intent"] = intent_filter_action;
+ manifest_action["queries"]["provider"].Action(RequiredAndroidAttribute("authorities"));
+ // TODO: more complicated component name tag
manifest_action["key-sets"]["key-set"]["public-key"];
manifest_action["key-sets"]["upgrade-key-set"];
@@ -413,6 +453,12 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
application_action["meta-data"] = meta_data_action;
+ application_action["processes"];
+ application_action["processes"]["deny-permission"];
+ application_action["processes"]["allow-permission"];
+ application_action["processes"]["process"]["deny-permission"];
+ application_action["processes"]["process"]["allow-permission"];
+
application_action["activity"] = component_action;
application_action["activity"]["layout"];
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 3aba4e2ec49f..0791805e2506 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -753,8 +753,7 @@ TEST_F(ManifestFixerTest, SupportKeySets) {
}
TEST_F(ManifestFixerTest, InsertCompileSdkVersions) {
- std::string input = R"(
- <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" />)";
+ std::string input = R"(<manifest package="com.pkg" />)";
ManifestFixerOptions options;
options.compile_sdk_version = {"28"};
options.compile_sdk_version_codename = {"P"};
@@ -762,6 +761,12 @@ TEST_F(ManifestFixerTest, InsertCompileSdkVersions) {
std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
ASSERT_THAT(manifest, NotNull());
+ // There should be a declaration of kSchemaAndroid, even when the input
+ // didn't have one.
+ EXPECT_EQ(manifest->root->namespace_decls.size(), 1);
+ EXPECT_EQ(manifest->root->namespace_decls[0].prefix, "android");
+ EXPECT_EQ(manifest->root->namespace_decls[0].uri, xml::kSchemaAndroid);
+
xml::Attribute* attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersion");
ASSERT_THAT(attr, NotNull());
EXPECT_THAT(attr->value, StrEq("28"));
@@ -808,6 +813,27 @@ TEST_F(ManifestFixerTest, OverrideCompileSdkVersions) {
EXPECT_THAT(attr->value, StrEq("P"));
}
+TEST_F(ManifestFixerTest, AndroidPrefixAlreadyUsed) {
+ std::string input =
+ R"(<manifest package="com.pkg"
+ xmlns:android="http://schemas.android.com/apk/prv/res/android"
+ android:private_attr="foo" />)";
+ ManifestFixerOptions options;
+ options.compile_sdk_version = {"28"};
+ options.compile_sdk_version_codename = {"P"};
+
+ std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+ ASSERT_THAT(manifest, NotNull());
+
+ // Make sure that we don't redefine "android".
+ EXPECT_EQ(manifest->root->namespace_decls.size(), 2);
+ EXPECT_EQ(manifest->root->namespace_decls[0].prefix, "android");
+ EXPECT_EQ(manifest->root->namespace_decls[0].uri,
+ "http://schemas.android.com/apk/prv/res/android");
+ EXPECT_EQ(manifest->root->namespace_decls[1].prefix, "android0");
+ EXPECT_EQ(manifest->root->namespace_decls[1].uri, xml::kSchemaAndroid);
+}
+
TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) {
std::string input = R"(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 28f09aa48365..8e49fabe6a5c 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -17,6 +17,7 @@
#include "link/ReferenceLinker.h"
#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
#include "androidfw/ResourceTypes.h"
#include "Diagnostics.h"
@@ -33,6 +34,7 @@
using ::aapt::ResourceUtils::StringBuilder;
using ::android::StringPiece;
+using ::android::base::StringPrintf;
namespace aapt {
@@ -81,7 +83,7 @@ class ReferenceLinkerVisitor : public DescendingValueVisitor {
// Find the attribute in the symbol table and check if it is visible from this callsite.
const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveAttributeCheckVisibility(
- transformed_reference, callsite_, symbols_, &err_str);
+ transformed_reference, callsite_, context_, symbols_, &err_str);
if (symbol) {
// Assign our style key the correct ID. The ID may not exist.
entry.key.id = symbol->id;
@@ -203,12 +205,35 @@ bool IsSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref,
const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols) {
if (reference.name) {
const ResourceName& name = reference.name.value();
if (name.package.empty()) {
// Use the callsite's package name if no package name was defined.
- return symbols->FindByName(ResourceName(callsite.package, name.type, name.entry));
+ const SymbolTable::Symbol* symbol = symbols->FindByName(
+ ResourceName(callsite.package, name.type, name.entry));
+ if (symbol) {
+ return symbol;
+ }
+
+ // If the callsite package is the same as the current compilation package,
+ // check the feature split dependencies as well. Feature split resources
+ // can be referenced without a namespace, just like the base package.
+ // TODO: modify the package name of included splits instead of having the
+ // symbol table look up the resource in in every package. b/136105066
+ if (callsite.package == context->GetCompilationPackage()) {
+ const auto& split_name_dependencies = context->GetSplitNameDependencies();
+ for (const std::string& split_name : split_name_dependencies) {
+ std::string split_package =
+ StringPrintf("%s.%s", callsite.package.c_str(), split_name.c_str());
+ symbol = symbols->FindByName(ResourceName(split_package, name.type, name.entry));
+ if (symbol) {
+ return symbol;
+ }
+ }
+ }
+ return nullptr;
}
return symbols->FindByName(name);
} else if (reference.id) {
@@ -220,9 +245,10 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& refer
const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols,
std::string* out_error) {
- const SymbolTable::Symbol* symbol = ResolveSymbol(reference, callsite, symbols);
+ const SymbolTable::Symbol* symbol = ResolveSymbol(reference, callsite, context, symbols);
if (!symbol) {
if (out_error) *out_error = "not found";
return nullptr;
@@ -236,10 +262,10 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const R
}
const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility(
- const Reference& reference, const CallSite& callsite, SymbolTable* symbols,
- std::string* out_error) {
+ const Reference& reference, const CallSite& callsite, IAaptContext* context,
+ SymbolTable* symbols, std::string* out_error) {
const SymbolTable::Symbol* symbol =
- ResolveSymbolCheckVisibility(reference, callsite, symbols, out_error);
+ ResolveSymbolCheckVisibility(reference, callsite, context, symbols, out_error);
if (!symbol) {
return nullptr;
}
@@ -253,10 +279,11 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility(
Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols,
std::string* out_error) {
const SymbolTable::Symbol* symbol =
- ResolveAttributeCheckVisibility(reference, callsite, symbols, out_error);
+ ResolveAttributeCheckVisibility(reference, callsite, context, symbols, out_error);
if (!symbol) {
return {};
}
@@ -335,7 +362,7 @@ bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* referen
std::string err_str;
const SymbolTable::Symbol* s =
- ResolveSymbolCheckVisibility(transformed_reference, callsite, symbols, &err_str);
+ ResolveSymbolCheckVisibility(transformed_reference, callsite, context, symbols, &err_str);
if (s) {
// The ID may not exist. This is fine because of the possibility of building
// against libraries without assigned IDs.
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
index b0b49457e5dd..1256709edbf4 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -39,13 +39,16 @@ class ReferenceLinker : public IResourceTableConsumer {
// package if the reference has no package name defined (implicit).
// Returns nullptr if the symbol was not found.
static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference,
- const CallSite& callsite, SymbolTable* symbols);
+ const CallSite& callsite,
+ IAaptContext* context,
+ SymbolTable* symbols);
// Performs name mangling and looks up the resource in the symbol table. If the symbol is not
// visible by the reference at the callsite, nullptr is returned.
// `out_error` holds the error message.
static const SymbolTable::Symbol* ResolveSymbolCheckVisibility(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols,
std::string* out_error);
@@ -53,6 +56,7 @@ class ReferenceLinker : public IResourceTableConsumer {
// That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute.
static const SymbolTable::Symbol* ResolveAttributeCheckVisibility(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols,
std::string* out_error);
@@ -60,6 +64,7 @@ class ReferenceLinker : public IResourceTableConsumer {
// If resolution fails, outError holds the error message.
static Maybe<xml::AaptAttribute> CompileXmlAttribute(const Reference& reference,
const CallSite& callsite,
+ IAaptContext* context,
SymbolTable* symbols,
std::string* out_error);
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
index be38b967c986..a31ce9496d0c 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -266,8 +266,13 @@ TEST(ReferenceLinkerTest, AppsWithSamePackageButDifferentIdAreVisibleNonPublic)
std::string error;
const CallSite call_site{"com.app.test"};
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .Build();
const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveSymbolCheckVisibility(
- *test::BuildReference("com.app.test:string/foo"), call_site, &table, &error);
+ *test::BuildReference("com.app.test:string/foo"), call_site, context.get(), &table, &error);
ASSERT_THAT(symbol, NotNull());
EXPECT_TRUE(error.empty());
}
@@ -281,17 +286,23 @@ TEST(ReferenceLinkerTest, AppsWithDifferentPackageCanNotUseEachOthersAttribute)
.AddPublicSymbol("com.app.test:attr/public_foo", ResourceId(0x7f010001),
test::AttributeBuilder().Build())
.Build());
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.ext")
+ .SetPackageId(0x7f)
+ .Build();
std::string error;
const CallSite call_site{"com.app.ext"};
EXPECT_FALSE(ReferenceLinker::CompileXmlAttribute(
- *test::BuildReference("com.app.test:attr/foo"), call_site, &table, &error));
+ *test::BuildReference("com.app.test:attr/foo"), call_site, context.get(), &table, &error));
EXPECT_FALSE(error.empty());
error = "";
ASSERT_TRUE(ReferenceLinker::CompileXmlAttribute(
- *test::BuildReference("com.app.test:attr/public_foo"), call_site, &table, &error));
+ *test::BuildReference("com.app.test:attr/public_foo"), call_site, context.get(), &table,
+ &error));
EXPECT_TRUE(error.empty());
}
@@ -302,20 +313,62 @@ TEST(ReferenceLinkerTest, ReferenceWithNoPackageUsesCallSitePackage) {
.AddSymbol("com.app.test:string/foo", ResourceId(0x7f010000))
.AddSymbol("com.app.lib:string/foo", ResourceId(0x7f010001))
.Build());
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x7f)
+ .Build();
const SymbolTable::Symbol* s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"),
- CallSite{"com.app.test"}, &table);
+ CallSite{"com.app.test"},
+ context.get(), &table);
ASSERT_THAT(s, NotNull());
EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010000)));
s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.lib"},
- &table);
+ context.get(), &table);
ASSERT_THAT(s, NotNull());
EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010001)));
EXPECT_THAT(ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"),
- CallSite{"com.app.bad"}, &table),
+ CallSite{"com.app.bad"}, context.get(), &table),
IsNull());
}
+TEST(ReferenceLinkerTest, ReferenceSymbolFromOtherSplit) {
+ NameMangler mangler(NameManglerPolicy{"com.app.test"});
+ SymbolTable table(&mangler);
+ table.AppendSource(test::StaticSymbolSourceBuilder()
+ .AddSymbol("com.app.test.feature:string/bar", ResourceId(0x80010000))
+ .Build());
+ std::set<std::string> split_name_dependencies;
+ split_name_dependencies.insert("feature");
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x81)
+ .SetSplitNameDependencies(split_name_dependencies)
+ .Build();
+
+ const SymbolTable::Symbol* s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/bar"),
+ CallSite{"com.app.test"},
+ context.get(), &table);
+ ASSERT_THAT(s, NotNull());
+ EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x80010000)));
+
+ s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.lib"},
+ context.get(), &table);
+ EXPECT_THAT(s, IsNull());
+
+ context =
+ test::ContextBuilder()
+ .SetCompilationPackage("com.app.test")
+ .SetPackageId(0x81)
+ .Build();
+ s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/bar"),CallSite{"com.app.test"},
+ context.get(), &table);
+
+ EXPECT_THAT(s, IsNull());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 3f65e868505d..c25e4503a208 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -172,28 +172,32 @@ static bool MergeEntry(IAaptContext* context, const Source& src,
//
// Styleables and Styles don't simply overlay each other, their definitions merge and accumulate.
// If both values are Styleables/Styles, we just merge them into the existing value.
-static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Value* incoming,
- StringPool* pool) {
+static ResourceTable::CollisionResult ResolveMergeCollision(
+ bool override_styles_instead_of_overlaying, Value* existing, Value* incoming,
+ StringPool* pool) {
if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) {
if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) {
// Styleables get merged.
existing_styleable->MergeWith(incoming_styleable);
return ResourceTable::CollisionResult::kKeepOriginal;
}
- } else if (Style* existing_style = ValueCast<Style>(existing)) {
- if (Style* incoming_style = ValueCast<Style>(incoming)) {
- // Styles get merged.
- existing_style->MergeWith(incoming_style, pool);
- return ResourceTable::CollisionResult::kKeepOriginal;
+ } else if (!override_styles_instead_of_overlaying) {
+ if (Style* existing_style = ValueCast<Style>(existing)) {
+ if (Style* incoming_style = ValueCast<Style>(incoming)) {
+ // Styles get merged.
+ existing_style->MergeWith(incoming_style, pool);
+ return ResourceTable::CollisionResult::kKeepOriginal;
+ }
}
}
// Delegate to the default handler.
- return ResourceTable::ResolveValueCollision(existing, incoming, true /* overlay */);
+ return ResourceTable::ResolveValueCollision(existing, incoming);
}
static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
const ResourceNameRef& res_name,
bool overlay,
+ bool override_styles_instead_of_overlaying,
ResourceConfigValue* dst_config_value,
ResourceConfigValue* src_config_value,
StringPool* pool) {
@@ -204,13 +208,18 @@ static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
CollisionResult collision_result;
if (overlay) {
- collision_result = ResolveMergeCollision(dst_value, src_value, pool);
+ collision_result =
+ ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
} else {
- collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value,
- false /* overlay */);
+ collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
}
if (collision_result == CollisionResult::kConflict) {
+ if (overlay) {
+ return CollisionResult::kTakeNew;
+ }
+
+ // Error!
context->GetDiagnostics()->Error(DiagMessage(src_value->GetSource())
<< "resource '" << res_name << "' has a conflicting value for "
<< "configuration (" << src_config_value->config << ")");
@@ -268,9 +277,9 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package,
ResourceConfigValue* dst_config_value = dst_entry->FindValue(
src_config_value->config, src_config_value->product);
if (dst_config_value) {
- CollisionResult collision_result =
- MergeConfigValue(context_, res_name, overlay, dst_config_value,
- src_config_value.get(), &master_table_->string_pool);
+ CollisionResult collision_result = MergeConfigValue(
+ context_, res_name, overlay, options_.override_styles_instead_of_overlaying,
+ dst_config_value, src_config_value.get(), &master_table_->string_pool);
if (collision_result == CollisionResult::kConflict) {
error = true;
continue;
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 51305cfcdd25..a35a134a887d 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -37,6 +37,8 @@ struct TableMergerOptions {
bool auto_add_overlay = false;
// If true, resource overlays with conflicting visibility are not allowed.
bool strict_visibility = false;
+ // If true, styles specified via "aapt2 link -R" completely replace any previously-seen resources.
+ bool override_styles_instead_of_overlaying = false;
};
// TableMerger takes resource tables and merges all packages within the tables that have the same
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 78d42a160e21..69cf5ee7002b 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -29,6 +29,8 @@ using ::testing::Pointee;
using ::testing::StrEq;
using ::testing::UnorderedElementsAreArray;
+using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
+
namespace aapt {
struct TableMergerTest : public ::testing::Test {
@@ -352,62 +354,6 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
}
-TEST_F(TableMergerTest, OverrideAttributeSameFormatsWithOverlay) {
- std::unique_ptr<ResourceTable> base =
- test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
- .AddValue("attr/foo", test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_STRING)
- .SetWeak(false)
- .Build())
- .Build();
-
- std::unique_ptr<ResourceTable> overlay =
- test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
- .AddValue("attr/foo", test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_STRING)
- .SetWeak(false)
- .Build())
- .Build();
-
- ResourceTable final_table;
- TableMergerOptions options;
- options.auto_add_overlay = false;
- TableMerger merger(context_.get(), &final_table, options);
-
- ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
- ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/));
-}
-
-TEST_F(TableMergerTest, FailToOverrideConflictingAttributeFormatsWithOverlay) {
- std::unique_ptr<ResourceTable> base =
- test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
- .AddValue("attr/foo", test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_ANY)
- .SetWeak(false)
- .Build())
- .Build();
-
- std::unique_ptr<ResourceTable> overlay =
- test::ResourceTableBuilder()
- .SetPackageId("", 0x7f)
- .AddValue("attr/foo", test::AttributeBuilder()
- .SetTypeMask(android::ResTable_map::TYPE_STRING)
- .SetWeak(false)
- .Build())
- .Build();
-
- ResourceTable final_table;
- TableMergerOptions options;
- options.auto_add_overlay = false;
- TableMerger merger(context_.get(), &final_table, options);
-
- ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
- ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
-}
-
TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
@@ -492,12 +438,59 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent")))));
}
+TEST_F(TableMergerTest, OverrideStyleInsteadOfOverlaying) {
+ std::unique_ptr<ResourceTable> table_a =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddValue(
+ "com.app.a:styleable/MyWidget",
+ test::StyleableBuilder().AddItem("com.app.a:attr/foo", ResourceId(0x1234)).Build())
+ .AddValue("com.app.a:style/Theme",
+ test::StyleBuilder()
+ .AddItem("com.app.a:attr/foo", ResourceUtils::MakeBool(false))
+ .Build())
+ .Build();
+ std::unique_ptr<ResourceTable> table_b =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddValue(
+ "com.app.a:styleable/MyWidget",
+ test::StyleableBuilder().AddItem("com.app.a:attr/bar", ResourceId(0x5678)).Build())
+ .AddValue(
+ "com.app.a:style/Theme",
+ test::StyleBuilder().AddItem("com.app.a:attr/bat", util::make_unique<Id>()).Build())
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = true;
+ options.override_styles_instead_of_overlaying = true;
+ TableMerger merger(context_.get(), &final_table, options);
+ ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+ ASSERT_TRUE(merger.Merge({}, table_b.get(), true /*overlay*/));
+
+ // Styleables are always overlaid
+ std::unique_ptr<Styleable> expected_styleable = test::StyleableBuilder()
+ // The merged Styleable has its entries ordered by name.
+ .AddItem("com.app.a:attr/bar", ResourceId(0x5678))
+ .AddItem("com.app.a:attr/foo", ResourceId(0x1234))
+ .Build();
+ const Styleable* actual_styleable =
+ test::GetValue<Styleable>(&final_table, "com.app.a:styleable/MyWidget");
+ ASSERT_NE(actual_styleable, nullptr);
+ EXPECT_TRUE(actual_styleable->Equals(expected_styleable.get()));
+ // Style should be overridden
+ const Style* actual_style = test::GetValue<Style>(&final_table, "com.app.a:style/Theme");
+ ASSERT_NE(actual_style, nullptr);
+ EXPECT_TRUE(actual_style->Equals(test::GetValue<Style>(table_b.get(), "com.app.a:style/Theme")));
+}
+
TEST_F(TableMergerTest, SetOverlayable) {
auto overlayable = std::make_shared<Overlayable>("CustomizableResources",
"overlay://customization");
OverlayableItem overlayable_item(overlayable);
- overlayable_item.policies |= OverlayableItem::Policy::kProduct;
- overlayable_item.policies |= OverlayableItem::Policy::kVendor;
+ overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
+ overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
@@ -525,8 +518,8 @@ TEST_F(TableMergerTest, SetOverlayable) {
OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("CustomizableResources"));
EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://customization"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct
- | OverlayableItem::Policy::kVendor));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION
+ | PolicyFlags::VENDOR_PARTITION));
}
TEST_F(TableMergerTest, SetOverlayableLater) {
@@ -539,8 +532,8 @@ TEST_F(TableMergerTest, SetOverlayableLater) {
.Build();
OverlayableItem overlayable_item(overlayable);
- overlayable_item.policies |= OverlayableItem::Policy::kPublic;
- overlayable_item.policies |= OverlayableItem::Policy::kSystem;
+ overlayable_item.policies |= PolicyFlags::PUBLIC;
+ overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
@@ -561,15 +554,15 @@ TEST_F(TableMergerTest, SetOverlayableLater) {
OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("CustomizableResources"));
EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://customization"));
- EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic
- | OverlayableItem::Policy::kSystem));
+ EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PUBLIC
+ | PolicyFlags::SYSTEM_PARTITION));
}
TEST_F(TableMergerTest, SameResourceDifferentNameFail) {
auto overlayable_first = std::make_shared<Overlayable>("CustomizableResources",
"overlay://customization");
OverlayableItem overlayable_item_first(overlayable_first);
- overlayable_item_first.policies |= OverlayableItem::Policy::kProduct;
+ overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
@@ -579,7 +572,7 @@ TEST_F(TableMergerTest, SameResourceDifferentNameFail) {
auto overlayable_second = std::make_shared<Overlayable>("ThemeResources",
"overlay://customization");
OverlayableItem overlayable_item_second(overlayable_second);
- overlayable_item_second.policies |= OverlayableItem::Policy::kProduct;
+ overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
@@ -598,7 +591,7 @@ TEST_F(TableMergerTest, SameResourceDifferentActorFail) {
auto overlayable_first = std::make_shared<Overlayable>("CustomizableResources",
"overlay://customization");
OverlayableItem overlayable_item_first(overlayable_first);
- overlayable_item_first.policies |= OverlayableItem::Policy::kProduct;
+ overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
@@ -608,7 +601,7 @@ TEST_F(TableMergerTest, SameResourceDifferentActorFail) {
auto overlayable_second = std::make_shared<Overlayable>("CustomizableResources",
"overlay://theme");
OverlayableItem overlayable_item_second(overlayable_second);
- overlayable_item_second.policies |= OverlayableItem::Policy::kProduct;
+ overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
@@ -627,7 +620,7 @@ TEST_F(TableMergerTest, SameResourceDifferentPoliciesFail) {
auto overlayable_first = std::make_shared<Overlayable>("CustomizableResources",
"overlay://customization");
OverlayableItem overlayable_item_first(overlayable_first);
- overlayable_item_first.policies |= OverlayableItem::Policy::kProduct;
+ overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
@@ -637,7 +630,7 @@ TEST_F(TableMergerTest, SameResourceDifferentPoliciesFail) {
auto overlayable_second = std::make_shared<Overlayable>("CustomizableResources",
"overlay://customization");
OverlayableItem overlayable_item_second(overlayable_second);
- overlayable_item_second.policies |= OverlayableItem::Policy::kSignature;
+ overlayable_item_second.policies |= PolicyFlags::SIGNATURE;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
@@ -657,7 +650,7 @@ TEST_F(TableMergerTest, SameResourceSameOverlayable) {
"overlay://customization");
OverlayableItem overlayable_item_first(overlayable);
- overlayable_item_first.policies |= OverlayableItem::Policy::kProduct;
+ overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
@@ -665,7 +658,7 @@ TEST_F(TableMergerTest, SameResourceSameOverlayable) {
.Build();
OverlayableItem overlayable_item_second(overlayable);
- overlayable_item_second.policies |= OverlayableItem::Policy::kProduct;
+ overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION;
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index d68f7dd44c9f..c3c16b92f712 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -83,6 +83,15 @@ class XmlVisitor : public xml::PackageAwareVisitor {
Attribute default_attribute(android::ResTable_map::TYPE_ANY);
default_attribute.SetWeak(true);
+ // The default orientation of gradients in android Q is different than previous android
+ // versions. Set the android:angle attribute to "0" to ensure that the default gradient
+ // orientation will remain left-to-right in android Q.
+ if (el->name == "gradient" && context_->GetMinSdkVersion() <= SDK_Q) {
+ if (!el->FindAttribute(xml::kSchemaAndroid, "angle")) {
+ el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "angle", "0"});
+ }
+ }
+
const Source source = source_.WithLine(el->line_number);
for (xml::Attribute& attr : el->attributes) {
// If the attribute has no namespace, interpret values as if
@@ -99,7 +108,7 @@ class XmlVisitor : public xml::PackageAwareVisitor {
std::string err_str;
attr.compiled_attribute =
- ReferenceLinker::CompileXmlAttribute(attr_ref, callsite_, symbols_, &err_str);
+ ReferenceLinker::CompileXmlAttribute(attr_ref, callsite_, context_, symbols_, &err_str);
if (!attr.compiled_attribute) {
DiagMessage error_msg(source);
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index ef99355e5b5f..0ce2e50d6e44 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -47,6 +47,8 @@ class XmlReferenceLinkerTest : public ::testing::Test {
test::AttributeBuilder()
.SetTypeMask(android::ResTable_map::TYPE_STRING)
.Build())
+ .AddPublicSymbol("android:attr/angle", ResourceId(0x01010004),
+ test::AttributeBuilder().Build())
// Add one real symbol that was introduces in v21
.AddPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435),
@@ -75,7 +77,7 @@ class XmlReferenceLinkerTest : public ::testing::Test {
}
protected:
- std::unique_ptr<IAaptContext> context_;
+ std::unique_ptr<test::Context> context_;
};
TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
@@ -254,4 +256,63 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
EXPECT_EQ(make_value(ResourceId(0x7f030000)), ref->id);
}
+
+TEST_F(XmlReferenceLinkerTest, AddAngleOnGradientForAndroidQ) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <gradient />)");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+ xml::Element* gradient_el = doc->root.get();
+ ASSERT_THAT(gradient_el, NotNull());
+
+ xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle");
+ ASSERT_THAT(xml_attr, NotNull());
+ ASSERT_TRUE(xml_attr->compiled_attribute);
+ EXPECT_EQ(make_value(ResourceId(0x01010004)), xml_attr->compiled_attribute.value().id);
+
+ BinaryPrimitive* value = ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get());
+ ASSERT_THAT(value, NotNull());
+ EXPECT_EQ(value->value.dataType, android::Res_value::TYPE_INT_DEC);
+ EXPECT_EQ(value->value.data, 0U);
+}
+
+TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForAndroidQ) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <gradient xmlns:android="http://schemas.android.com/apk/res/android"
+ android:angle="90"/>)");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+ xml::Element* gradient_el = doc->root.get();
+ ASSERT_THAT(gradient_el, NotNull());
+
+ xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle");
+ ASSERT_THAT(xml_attr, NotNull());
+ ASSERT_TRUE(xml_attr->compiled_attribute);
+ EXPECT_EQ(make_value(ResourceId(0x01010004)), xml_attr->compiled_attribute.value().id);
+
+ BinaryPrimitive* value = ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get());
+ ASSERT_THAT(value, NotNull());
+ EXPECT_EQ(value->value.dataType, android::Res_value::TYPE_INT_DEC);
+ EXPECT_EQ(value->value.data, 90U);
+}
+
+TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForPostAndroidQ) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <gradient xmlns:android="http://schemas.android.com/apk/res/android" />)");
+ context_->SetMinSdkVersion(30);
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+ xml::Element* gradient_el = doc->root.get();
+ ASSERT_THAT(gradient_el, NotNull());
+
+ xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle");
+ ASSERT_THAT(xml_attr, IsNull());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 8c9c43409569..c686a10a3fa9 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -101,6 +101,10 @@ class ContextWrapper : public IAaptContext {
util::make_unique<SourcePathDiagnostics>(Source{source}, context_->GetDiagnostics());
}
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ return context_->GetSplitNameDependencies();
+ }
+
private:
IAaptContext* context_;
std::unique_ptr<SourcePathDiagnostics> source_diag_;
diff --git a/tools/aapt2/optimize/ResourceDeduper.cpp b/tools/aapt2/optimize/ResourceDeduper.cpp
index 78ebcb97b811..0278b439cfae 100644
--- a/tools/aapt2/optimize/ResourceDeduper.cpp
+++ b/tools/aapt2/optimize/ResourceDeduper.cpp
@@ -63,13 +63,14 @@ class DominatedKeyValueRemover : public DominatorTree::BottomUpVisitor {
// Compare compatible configs for this entry and ensure the values are
// equivalent.
const ConfigDescription& node_configuration = node_value->config;
- for (const auto& sibling : entry_->values) {
- if (!sibling->value) {
+ for (const auto& sibling : parent->children()) {
+ ResourceConfigValue* sibling_value = sibling->value();
+ if (!sibling_value->value) {
// Sibling was already removed.
continue;
}
- if (node_configuration.IsCompatibleWith(sibling->config) &&
- !node_value->value->Equals(sibling->value.get())) {
+ if (node_configuration.IsCompatibleWith(sibling_value->config) &&
+ !node_value->value->Equals(sibling_value->value.get())) {
// The configurations are compatible, but the value is
// different, so we can't remove this value.
return;
diff --git a/tools/aapt2/optimize/ResourceDeduper_test.cpp b/tools/aapt2/optimize/ResourceDeduper_test.cpp
index 2e098aec4f8d..048e318d2802 100644
--- a/tools/aapt2/optimize/ResourceDeduper_test.cpp
+++ b/tools/aapt2/optimize/ResourceDeduper_test.cpp
@@ -80,11 +80,58 @@ TEST(ResourceDeduperTest, DifferentValuesAreKept) {
.Build();
ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/keep", default_config));
EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config));
EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_v21_config));
EXPECT_THAT(table, HasValue("android:string/keep", land_config));
}
+TEST(ResourceDeduperTest, SameValuesAreDedupedIncompatibleSiblings) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const ConfigDescription default_config = {};
+ const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl");
+ const ConfigDescription ldrtl_night_config = test::ParseConfigOrDie("ldrtl-night");
+ // Chosen because this configuration is not compatible with ldrtl-night.
+ const ConfigDescription ldrtl_notnight_config = test::ParseConfigOrDie("ldrtl-notnight");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/keep", ResourceId{}, default_config, "keep")
+ .AddString("android:string/keep", ResourceId{}, ldrtl_config, "dedupe")
+ .AddString("android:string/keep", ResourceId{}, ldrtl_night_config, "dedupe")
+ .AddString("android:string/keep", ResourceId{}, ldrtl_notnight_config, "keep2")
+ .Build();
+
+ ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/keep", default_config));
+ EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/keep", ldrtl_night_config)));
+ EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_notnight_config));
+}
+
+TEST(ResourceDeduperTest, SameValuesAreDedupedCompatibleNonSiblings) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const ConfigDescription default_config = {};
+ const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl");
+ const ConfigDescription ldrtl_night_config = test::ParseConfigOrDie("ldrtl-night");
+ // Chosen because this configuration is compatible with ldrtl.
+ const ConfigDescription land_config = test::ParseConfigOrDie("land");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/keep", ResourceId{}, default_config, "keep")
+ .AddString("android:string/keep", ResourceId{}, ldrtl_config, "dedupe")
+ .AddString("android:string/keep", ResourceId{}, ldrtl_night_config, "dedupe")
+ .AddString("android:string/keep", ResourceId{}, land_config, "keep2")
+ .Build();
+
+ ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/keep", default_config));
+ EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/keep", ldrtl_night_config)));
+ EXPECT_THAT(table, HasValue("android:string/keep", land_config));
+}
+
TEST(ResourceDeduperTest, LocalesValuesAreKept) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
const ConfigDescription default_config = {};
diff --git a/tools/aapt2/optimize/ResourcePathShortener.cpp b/tools/aapt2/optimize/ResourcePathShortener.cpp
index c5df3dd00db9..7ff9bf5aa8df 100644
--- a/tools/aapt2/optimize/ResourcePathShortener.cpp
+++ b/tools/aapt2/optimize/ResourcePathShortener.cpp
@@ -16,13 +16,14 @@
#include "optimize/ResourcePathShortener.h"
-#include <math.h>
+#include <set>
#include <unordered_set>
#include "androidfw/StringPiece.h"
#include "ResourceTable.h"
#include "ValueVisitor.h"
+#include "util/Util.h"
static const std::string base64_chars =
@@ -50,18 +51,15 @@ std::string ShortenFileName(const android::StringPiece& file_path, int output_le
}
-// Calculate the optimal hash length such that an average of 10% of resources
-// collide in their shortened path.
+// Return the optimal hash length such that at most 10% of resources collide in
+// their shortened path.
// Reference: http://matt.might.net/articles/counting-hash-collisions/
int OptimalShortenedLength(int num_resources) {
- int num_chars = 2;
- double N = 64*64; // hash space when hash is 2 chars long
- double max_collisions = num_resources * 0.1;
- while (num_resources - N + N * pow((N - 1) / N, num_resources) > max_collisions) {
- N *= 64;
- num_chars++;
+ if (num_resources > 4000) {
+ return 3;
+ } else {
+ return 2;
}
- return num_chars;
}
std::string GetShortenedPath(const android::StringPiece& shortened_filename,
@@ -74,10 +72,19 @@ std::string GetShortenedPath(const android::StringPiece& shortened_filename,
return shortened_path;
}
+// implement custom comparator of FileReference pointers so as to use the
+// underlying filepath as key rather than the integer address. This is to ensure
+// determinism of output for colliding files.
+struct PathComparator {
+ bool operator() (const FileReference* lhs, const FileReference* rhs) const {
+ return lhs->path->compare(*rhs->path);
+ }
+};
+
bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) {
// used to detect collisions
std::unordered_set<std::string> shortened_paths;
- std::unordered_set<FileReference*> file_refs;
+ std::set<FileReference*, PathComparator> file_refs;
for (auto& package : table->packages) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
@@ -95,6 +102,10 @@ bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table)
android::StringPiece res_subdir, actual_filename, extension;
util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension);
+ // Android detects ColorStateLists via pathname, skip res/color*
+ if (util::StartsWith(res_subdir, "res/color"))
+ continue;
+
std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars);
int collision_count = 0;
std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
diff --git a/tools/aapt2/optimize/ResourcePathShortener_test.cpp b/tools/aapt2/optimize/ResourcePathShortener_test.cpp
index 88cadc76c336..f5a02be0ea5e 100644
--- a/tools/aapt2/optimize/ResourcePathShortener_test.cpp
+++ b/tools/aapt2/optimize/ResourcePathShortener_test.cpp
@@ -24,6 +24,19 @@ using ::testing::Not;
using ::testing::NotNull;
using ::testing::Eq;
+android::StringPiece GetExtension(android::StringPiece path) {
+ auto iter = std::find(path.begin(), path.end(), '.');
+ return android::StringPiece(iter, path.end() - iter);
+}
+
+void FillTable(aapt::test::ResourceTableBuilder& builder, int start, int end) {
+ for (int i=start; i<end; i++) {
+ builder.AddFileReference(
+ "android:drawable/xmlfile" + std::to_string(i),
+ "res/drawable/xmlfile" + std::to_string(i) + ".xml");
+ }
+}
+
namespace aapt {
TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) {
@@ -64,4 +77,90 @@ TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) {
EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end()));
}
+TEST(ResourcePathShortenerTest, SkipColorFileRefPaths) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:color/colorlist", "res/color/colorlist.xml")
+ .AddFileReference("android:color/colorlist",
+ "res/color-mdp-v21/colorlist.xml",
+ test::ParseConfigOrDie("mdp-v21"))
+ .Build();
+
+ std::map<std::string, std::string> path_map;
+ ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+
+ // Expect that the path map to not contain the ColorStateList
+ ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
+ ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end()));
+}
+
+TEST(ResourcePathShortenerTest, KeepExtensions) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ std::string original_xml_path = "res/drawable/xmlfile.xml";
+ std::string original_png_path = "res/drawable/pngfile.png";
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:color/xmlfile", original_xml_path)
+ .AddFileReference("android:color/pngfile", original_png_path)
+ .Build();
+
+ std::map<std::string, std::string> path_map;
+ ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+
+ // Expect that the path map is populated
+ ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
+ ASSERT_THAT(path_map.find("res/drawable/pngfile.png"), Not(Eq(path_map.end())));
+
+ auto shortend_xml_path = path_map[original_xml_path];
+ auto shortend_png_path = path_map[original_png_path];
+
+ EXPECT_THAT(GetExtension(path_map[original_xml_path]), Eq(android::StringPiece(".xml")));
+ EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png")));
+}
+
+TEST(ResourcePathShortenerTest, DeterministicallyHandleCollisions) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ // 4000 resources is the limit at which the hash space is expanded to 3
+ // letters to reduce collisions, we want as many collisions as possible thus
+ // N-1.
+ const auto kNumResources = 3999;
+ const auto kNumTries = 5;
+
+ test::ResourceTableBuilder builder1;
+ FillTable(builder1, 0, kNumResources);
+ std::unique_ptr<ResourceTable> table1 = builder1.Build();
+ std::map<std::string, std::string> expected_mapping;
+ ASSERT_TRUE(ResourcePathShortener(expected_mapping).Consume(context.get(), table1.get()));
+
+ // We are trying to ensure lack of non-determinism, it is not simple to prove
+ // a negative, thus we must try the test a few times so that the test itself
+ // is non-flaky. Basically create the pathmap 5 times from the same set of
+ // resources but a different order of addition and then ensure they are always
+ // mapped to the same short path.
+ for (int i=0; i<kNumTries; i++) {
+ test::ResourceTableBuilder builder2;
+ // This loop adds resources to the resource table in the range of
+ // [0:kNumResources). Adding the file references in different order makes
+ // non-determinism more likely to surface. Thus we add resources
+ // [start_index:kNumResources) first then [0:start_index). We also use a
+ // different start_index each run.
+ int start_index = (kNumResources/kNumTries)*i;
+ FillTable(builder2, start_index, kNumResources);
+ FillTable(builder2, 0, start_index);
+ std::unique_ptr<ResourceTable> table2 = builder2.Build();
+
+ std::map<std::string, std::string> actual_mapping;
+ ASSERT_TRUE(ResourcePathShortener(actual_mapping).Consume(context.get(), table2.get()));
+
+ for (auto& item : actual_mapping) {
+ ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
+ }
+ }
+}
+
} // namespace aapt
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index 30dad8025900..9c4b323db433 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -19,6 +19,7 @@
#include <iostream>
#include <list>
+#include <set>
#include <sstream>
#include "Diagnostics.h"
@@ -50,6 +51,7 @@ struct IAaptContext {
virtual NameMangler* GetNameMangler() = 0;
virtual bool IsVerbose() = 0;
virtual int GetMinSdkVersion() = 0;
+ virtual const std::set<std::string>& GetSplitNameDependencies() = 0;
};
struct IResourceTableConsumer {
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 61a8fbbb7f52..897fa80ffedb 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -245,7 +245,8 @@ std::map<size_t, std::string> AssetManagerSymbolSource::GetAssignedPackageIds()
return package_map;
}
-bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const {
+bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId,
+ const std::string& package_name) const {
if (packageId == 0) {
return true;
}
@@ -253,7 +254,7 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const {
for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) {
for (const std::unique_ptr<const android::LoadedPackage>& loaded_package
: assets->GetLoadedArsc()->GetPackages()) {
- if (packageId == loaded_package->GetPackageId() && loaded_package->IsDynamic()) {
+ if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) {
return true;
}
}
@@ -313,6 +314,7 @@ static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable(
symbol.symbol.name = parsed_name.value();
symbol.symbol.id = ResourceId(map_entry.key);
symbol.value = map_entry.value.data;
+ symbol.type = map_entry.value.dataType;
s->attribute->symbols.push_back(std::move(symbol));
}
}
@@ -327,19 +329,19 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName(
bool found = false;
ResourceId res_id = 0;
uint32_t type_spec_flags;
+ ResourceName real_name;
// There can be mangled resources embedded within other packages. Here we will
// look into each package and look-up the mangled name until we find the resource.
asset_manager_.ForEachPackage([&](const std::string& package_name, uint8_t id) -> bool {
- ResourceName real_name(name.package, name.type, name.entry);
-
+ real_name = ResourceName(name.package, name.type, name.entry);
if (package_name != name.package) {
real_name.entry = mangled_entry;
real_name.package = package_name;
}
res_id = asset_manager_.GetResourceId(real_name.to_string());
- if (res_id.is_valid() && asset_manager_.GetResourceFlags(res_id.id, &type_spec_flags)) {
+ if (res_id.is_valid_static() && asset_manager_.GetResourceFlags(res_id.id, &type_spec_flags)) {
found = true;
return false;
}
@@ -352,12 +354,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName(
}
std::unique_ptr<SymbolTable::Symbol> s;
- if (name.type == ResourceType::kAttr) {
+ if (real_name.type == ResourceType::kAttr) {
s = LookupAttributeInTable(asset_manager_, res_id);
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = res_id;
- s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id());
+ s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package);
}
if (s) {
@@ -378,7 +380,7 @@ static Maybe<ResourceName> GetResourceName(android::AssetManager2& am,
std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById(
ResourceId id) {
- if (!id.is_valid()) {
+ if (!id.is_valid_static()) {
// Exit early and avoid the error logs from AssetManager.
return {};
}
@@ -405,7 +407,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById(
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = id;
- s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id());
+ s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package);
}
if (s) {
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 6997cd6714a8..06eaf63ad442 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -194,7 +194,7 @@ class AssetManagerSymbolSource : public ISymbolSource {
bool AddAssetPath(const android::StringPiece& path);
std::map<size_t, std::string> GetAssignedPackageIds() const;
- bool IsPackageDynamic(uint32_t packageId) const;
+ bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
std::unique_ptr<SymbolTable::Symbol> FindByName(
const ResourceName& name) override;
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 0564db063b9a..553c43e6c469 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -81,6 +81,14 @@ class Context : public IAaptContext {
return min_sdk_version_;
}
+ void SetMinSdkVersion(int min_sdk_version) {
+ min_sdk_version_ = min_sdk_version;
+ }
+
+ const std::set<std::string>& GetSplitNameDependencies() override {
+ return split_name_dependencies_;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(Context);
@@ -93,6 +101,7 @@ class Context : public IAaptContext {
NameMangler name_mangler_;
SymbolTable symbols_;
int min_sdk_version_;
+ std::set<std::string> split_name_dependencies_;
};
class ContextBuilder {
@@ -127,6 +136,11 @@ class ContextBuilder {
return *this;
}
+ ContextBuilder& SetSplitNameDependencies(const std::set<std::string>& split_name_dependencies) {
+ context_->split_name_dependencies_ = split_name_dependencies;
+ return *this;
+ }
+
std::unique_ptr<Context> Build() { return std::move(context_); }
private:
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index a51b4a4649f1..5386802dbc8e 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -80,7 +80,7 @@ void TestDirectoryFixture::TearDown() {
ClearDirectory(temp_dir_);
}
-bool TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) {
+void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) {
CHECK(util::StartsWith(path, temp_dir_))
<< "Attempting to create a file outside of test temporary directory.";
@@ -91,16 +91,31 @@ bool TestDirectoryFixture::WriteFile(const std::string& path, const std::string&
file::mkdirs(dirs);
}
- return android::base::WriteStringToFile(contents, path);
+ CHECK(android::base::WriteStringToFile(contents, path));
}
bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents,
const android::StringPiece& out_dir, IDiagnostics* diag) {
- CHECK(WriteFile(path, contents));
+ WriteFile(path, contents);
CHECK(file::mkdirs(out_dir.data()));
return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0;
}
+bool CommandTestFixture::Link(const std::vector<std::string>& args, IDiagnostics* diag) {
+ std::vector<android::StringPiece> link_args;
+ for(const std::string& arg : args) {
+ link_args.emplace_back(arg);
+ }
+
+ // Link against the android SDK
+ std::string android_sdk = file::BuildPath({android::base::GetExecutableDirectory(),
+ "integration-tests", "CommandTests",
+ "android-28.jar"});
+ link_args.insert(link_args.end(), {"-I", android_sdk});
+
+ return LinkCommand(diag).Execute(link_args, &std::cerr) == 0;
+}
+
bool CommandTestFixture::Link(const std::vector<std::string>& args,
const android::StringPiece& flat_dir, IDiagnostics* diag) {
std::vector<android::StringPiece> link_args;
@@ -128,10 +143,10 @@ bool CommandTestFixture::Link(const std::vector<std::string>& args,
std::string CommandTestFixture::GetDefaultManifest(const char* package_name) {
const std::string manifest_file = GetTestPath("AndroidManifest.xml");
- CHECK(WriteFile(manifest_file, android::base::StringPrintf(R"(
+ WriteFile(manifest_file, android::base::StringPrintf(R"(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="%s">
- </manifest>)", package_name)));
+ </manifest>)", package_name));
return manifest_file;
}
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index fce2aebfecaa..457d65e30b65 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -58,7 +58,7 @@ class TestDirectoryFixture : public ::testing::Test {
// Creates a file with the specified contents, creates any intermediate directories in the
// process. The file path must be an absolute path within the test directory.
- bool WriteFile(const std::string& path, const std::string& contents);
+ void WriteFile(const std::string& path, const std::string& contents);
private:
std::string temp_dir_;
@@ -75,6 +75,9 @@ class CommandTestFixture : public TestDirectoryFixture {
bool CompileFile(const std::string& path, const std::string& contents,
const android::StringPiece& flat_out_dir, IDiagnostics* diag);
+ // Executes the link command with the specified arguments.
+ bool Link(const std::vector<std::string>& args, IDiagnostics* diag);
+
// Executes the link command with the specified arguments. The flattened files residing in the
// flat directory will be added to the link command as file arguments.
bool Link(const std::vector<std::string>& args, const android::StringPiece& flat_dir,
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
index d80c2e742fae..fd184f50091a 100644
--- a/tools/bit/main.cpp
+++ b/tools/bit/main.cpp
@@ -708,10 +708,12 @@ run_phases(vector<Target*> targets, const Options& options)
}
}
+
// Figure out whether we need to sync the system and which apks to install
string deviceTargetPath = buildOut + "/target/product/" + buildDevice;
string systemPath = deviceTargetPath + "/system/";
string dataPath = deviceTargetPath + "/data/";
+ string testPath = deviceTargetPath + "/testcases/";
bool syncSystem = false;
bool alwaysSyncSystem = false;
vector<string> systemFiles;
@@ -734,7 +736,8 @@ run_phases(vector<Target*> targets, const Options& options)
continue;
}
// Apk in the data partition
- if (starts_with(file, dataPath) && ends_with(file, ".apk")) {
+ if (ends_with(file, ".apk")
+ && (starts_with(file, dataPath) || starts_with(file, testPath))) {
// Always install it if we didn't build it because otherwise
// it will never have changed.
installApks.push_back(InstallApk(file, !target->build));
@@ -966,8 +969,9 @@ run_phases(vector<Target*> targets, const Options& options)
for (size_t j=0; j<target->module.installed.size(); j++) {
string filename = target->module.installed[j];
- // Apk in the data partition
- if (!starts_with(filename, dataPath) || !ends_with(filename, ".apk")) {
+ // Skip of not apk in the data partition or test
+ if (!(ends_with(filename, ".apk")
+ && (starts_with(filename, dataPath) || starts_with(filename, testPath)))) {
continue;
}
diff --git a/tools/codegen/.gitignore b/tools/codegen/.gitignore
new file mode 100755
index 000000000000..9fb18b42668f
--- /dev/null
+++ b/tools/codegen/.gitignore
@@ -0,0 +1,2 @@
+.idea
+out
diff --git a/tools/codegen/Android.bp b/tools/codegen/Android.bp
new file mode 100644
index 000000000000..677bee2cce81
--- /dev/null
+++ b/tools/codegen/Android.bp
@@ -0,0 +1,18 @@
+java_binary_host {
+ name: "codegen_cli",
+ manifest: "manifest.txt",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "javaparser",
+ ],
+}
+
+java_library_host {
+ name: "codegen-version-info",
+
+ srcs: [
+ "src/**/SharedConstants.kt",
+ ],
+}
diff --git a/tools/codegen/OWNERS b/tools/codegen/OWNERS
new file mode 100644
index 000000000000..da723b3b67da
--- /dev/null
+++ b/tools/codegen/OWNERS
@@ -0,0 +1 @@
+eugenesusla@google.com \ No newline at end of file
diff --git a/tools/codegen/manifest.txt b/tools/codegen/manifest.txt
new file mode 100644
index 000000000000..6e1018ba6b55
--- /dev/null
+++ b/tools/codegen/manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.codegen.MainKt
diff --git a/tools/codegen/src/com/android/codegen/ClassInfo.kt b/tools/codegen/src/com/android/codegen/ClassInfo.kt
new file mode 100644
index 000000000000..bf95a2eb2193
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/ClassInfo.kt
@@ -0,0 +1,27 @@
+package com.android.codegen
+
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+
+open class ClassInfo(val classAst: ClassOrInterfaceDeclaration, val fileInfo: FileInfo) {
+
+ val fileAst = fileInfo.fileAst
+
+ val nestedClasses = classAst.members.filterIsInstance<ClassOrInterfaceDeclaration>()
+
+ val superInterfaces = classAst.implementedTypes.map { it.asString() }
+ val superClass = classAst.extendedTypes.getOrNull(0)
+
+ val ClassName = classAst.nameAsString
+ private val genericArgsAst = classAst.typeParameters
+ val genericArgs = if (genericArgsAst.isEmpty()) "" else {
+ genericArgsAst.map { it.nameAsString }.joinToString(", ").let { "<$it>" }
+ }
+ val ClassType = ClassName + genericArgs
+
+ val constDefs = mutableListOf<ConstDef>()
+
+ val fields = classAst.fields
+ .filterNot { it.isTransient || it.isStatic }
+ .mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
+ .apply { lastOrNull()?.isLast = true }
+} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/ClassPrinter.kt b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
new file mode 100644
index 000000000000..b90e1bb3e7e7
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
@@ -0,0 +1,234 @@
+package com.android.codegen
+
+import com.github.javaparser.ast.Modifier
+import com.github.javaparser.ast.body.CallableDeclaration
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.TypeDeclaration
+import com.github.javaparser.ast.expr.*
+import com.github.javaparser.ast.type.ClassOrInterfaceType
+
+/**
+ * [ClassInfo] + utilities for printing out new class code with proper indentation and imports
+ */
+class ClassPrinter(
+ classAst: ClassOrInterfaceDeclaration,
+ fileInfo: FileInfo
+) : ClassInfo(classAst, fileInfo), Printer<ClassPrinter>, ImportsProvider {
+
+ val GENERATED_MEMBER_HEADER by lazy { "@$GeneratedMember" }
+
+ init {
+ val fieldsWithMissingNullablity = fields.filter { field ->
+ !field.isPrimitive
+ && field.fieldAst.modifiers.none { it.keyword == Modifier.Keyword.TRANSIENT }
+ && "@$Nullable" !in field.annotations
+ && "@$NonNull" !in field.annotations
+ }
+ if (fieldsWithMissingNullablity.isNotEmpty()) {
+ abort("Non-primitive fields must have @$Nullable or @$NonNull annotation.\n" +
+ "Missing nullability annotations on: "
+ + fieldsWithMissingNullablity.joinToString(", ") { it.name })
+ }
+
+ if (!classAst.isFinal &&
+ classAst.extendedTypes.any { it.nameAsString == Parcelable }) {
+ abort("Parcelable classes must be final")
+ }
+ }
+
+ val cliArgs get() = fileInfo.cliArgs
+
+ fun print() {
+ currentIndent = fileInfo.sourceLines
+ .find { "class $ClassName" in it }!!
+ .takeWhile { it.isWhitespace() }
+ .plus(INDENT_SINGLE)
+
+ +fileInfo.generatedWarning
+
+ if (FeatureFlag.CONST_DEFS()) generateConstDefs()
+
+
+ if (FeatureFlag.CONSTRUCTOR()) {
+ generateConstructor("public")
+ } else if (FeatureFlag.BUILDER()
+ || FeatureFlag.COPY_CONSTRUCTOR()
+ || FeatureFlag.WITHERS()) {
+ generateConstructor("/* package-private */")
+ }
+ if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor()
+
+ if (FeatureFlag.GETTERS()) generateGetters()
+ if (FeatureFlag.SETTERS()) generateSetters()
+ if (FeatureFlag.TO_STRING()) generateToString()
+ if (FeatureFlag.EQUALS_HASH_CODE()) generateEqualsHashcode()
+
+ if (FeatureFlag.FOR_EACH_FIELD()) generateForEachField()
+
+ if (FeatureFlag.WITHERS()) generateWithers()
+
+ if (FeatureFlag.PARCELABLE()) generateParcelable()
+
+ if (FeatureFlag.BUILDER() && FeatureFlag.BUILD_UPON()) generateBuildUpon()
+ if (FeatureFlag.BUILDER()) generateBuilder()
+
+ if (FeatureFlag.AIDL()) fileInfo.generateAidl() //TODO guard against nested classes requesting aidl
+
+ generateMetadata(fileInfo.file)
+
+ +"""
+ //@formatter:on
+ $GENERATED_END
+
+ """
+
+ rmEmptyLine()
+ }
+
+ override var currentIndent: String
+ get() = fileInfo.currentIndent
+ set(value) { fileInfo.currentIndent = value }
+ override val stringBuilder get() = fileInfo.stringBuilder
+
+
+ val dataClassAnnotationFeatures = classAst.annotations
+ .find { it.nameAsString == DataClass }
+ ?.let { it as? NormalAnnotationExpr }
+ ?.pairs
+ ?.map { pair -> pair.nameAsString to (pair.value as BooleanLiteralExpr).value }
+ ?.toMap()
+ ?: emptyMap()
+
+ val internalAnnotations = setOf(ParcelWith, DataClassEnum, PluralOf, UnsupportedAppUsage,
+ DataClassSuppressConstDefs, MaySetToNull, Each, DataClass)
+ val knownNonValidationAnnotations = internalAnnotations + Each + Nullable
+
+ /**
+ * @return whether the given feature is enabled
+ */
+ operator fun FeatureFlag.invoke(): Boolean {
+ if (cliArgs.contains("--no-$kebabCase")) return false
+ if (cliArgs.contains("--$kebabCase")) return true
+
+ val annotationKey = "gen$upperCamelCase"
+ val annotationHiddenKey = "genHidden$upperCamelCase"
+ if (dataClassAnnotationFeatures.containsKey(annotationKey)) {
+ return dataClassAnnotationFeatures[annotationKey]!!
+ }
+ if (dataClassAnnotationFeatures.containsKey(annotationHiddenKey)) {
+ return dataClassAnnotationFeatures[annotationHiddenKey]!!
+ }
+
+ if (cliArgs.contains("--all")) return true
+ if (hidden) return true
+
+ return when (this) {
+ FeatureFlag.SETTERS ->
+ !FeatureFlag.CONSTRUCTOR() && !FeatureFlag.BUILDER() && fields.any { !it.isFinal }
+ FeatureFlag.BUILDER -> cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS)
+ || fields.any { it.hasDefault }
+ || onByDefault
+ FeatureFlag.CONSTRUCTOR -> !FeatureFlag.BUILDER()
+ FeatureFlag.PARCELABLE -> "Parcelable" in superInterfaces
+ FeatureFlag.AIDL -> fileInfo.mainClass.nameAsString == ClassName && FeatureFlag.PARCELABLE()
+ FeatureFlag.IMPLICIT_NONNULL -> fields.any { it.isNullable }
+ && fields.none { "@$NonNull" in it.annotations }
+ else -> onByDefault
+ }
+ }
+
+ val FeatureFlag.hidden: Boolean
+ get(): Boolean {
+ val annotationHiddenKey = "genHidden$upperCamelCase"
+ if (dataClassAnnotationFeatures.containsKey(annotationHiddenKey)) {
+ return dataClassAnnotationFeatures[annotationHiddenKey]!!
+ }
+ return when {
+ cliArgs.contains("--hidden-$kebabCase") -> true
+ this == FeatureFlag.BUILD_UPON -> FeatureFlag.BUILDER.hidden
+ else -> false
+ }
+ }
+
+
+
+ inline operator fun <R> invoke(f: ClassPrinter.() -> R): R = run(f)
+
+ var BuilderClass = CANONICAL_BUILDER_CLASS
+ var BuilderType = BuilderClass + genericArgs
+ val customBaseBuilderAst: ClassOrInterfaceDeclaration? by lazy {
+ nestedClasses.find { it.nameAsString == BASE_BUILDER_CLASS }
+ }
+
+ val suppressedMembers by lazy {
+ getSuppressedMembers(classAst)
+ }
+ val builderSuppressedMembers by lazy {
+ getSuppressedMembers(customBaseBuilderAst) + suppressedMembers.mapNotNull {
+ if (it.startsWith("$CANONICAL_BUILDER_CLASS.")) {
+ it.removePrefix("$CANONICAL_BUILDER_CLASS.")
+ } else {
+ null
+ }
+ }
+ }
+
+ private fun getSuppressedMembers(clazz: ClassOrInterfaceDeclaration?): List<String> {
+ return clazz
+ ?.annotations
+ ?.find { it.nameAsString == DataClassSuppress }
+ ?.as_<SingleMemberAnnotationExpr>()
+ ?.memberValue
+ ?.run {
+ when (this) {
+ is ArrayInitializerExpr -> values.map { it.asLiteralStringValueExpr().value }
+ is StringLiteralExpr -> listOf(value)
+ else -> abort("Can't parse annotation arg: $this")
+ }
+ }
+ ?: emptyList()
+ }
+
+ fun isMethodGenerationSuppressed(name: String, vararg argTypes: String): Boolean {
+ return name in suppressedMembers || hasMethod(name, *argTypes)
+ }
+
+ fun hasMethod(name: String, vararg argTypes: String): Boolean {
+ val members: List<CallableDeclaration<*>> =
+ if (name == ClassName) classAst.constructors else classAst.methods
+ return members.any {
+ it.name.asString() == name &&
+ it.parameters.map { it.type.asString() } == argTypes.toList()
+ }
+ }
+
+ val lazyTransientFields = classAst.fields
+ .filter { it.isTransient && !it.isStatic }
+ .mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
+ .filter { hasMethod("lazyInit${it.NameUpperCamel}") }
+
+ val extendsParcelableClass by lazy {
+ Parcelable !in superInterfaces && superClass != null
+ }
+
+ init {
+ val builderFactoryOverride = classAst.methods.find {
+ it.isStatic && it.nameAsString == "builder"
+ }
+ if (builderFactoryOverride != null) {
+ BuilderClass = (builderFactoryOverride.type as ClassOrInterfaceType).nameAsString
+ BuilderType = builderFactoryOverride.type.asString()
+ } else {
+ val builderExtension = classAst
+ .childNodes
+ .filterIsInstance(TypeDeclaration::class.java)
+ .find { it.nameAsString == CANONICAL_BUILDER_CLASS }
+ if (builderExtension != null) {
+ BuilderClass = BASE_BUILDER_CLASS
+ val tp = (builderExtension as ClassOrInterfaceDeclaration).typeParameters
+ BuilderType = if (tp.isEmpty()) BuilderClass
+ else "$BuilderClass<${tp.map { it.nameAsString }.joinToString(", ")}>"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/ConstDef.kt b/tools/codegen/src/com/android/codegen/ConstDef.kt
new file mode 100644
index 000000000000..f559d6f87027
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/ConstDef.kt
@@ -0,0 +1,17 @@
+package com.android.codegen
+
+import com.github.javaparser.ast.body.FieldDeclaration
+
+/**
+ * `@IntDef` or `@StringDef`
+ */
+data class ConstDef(val type: Type, val AnnotationName: String, val values: List<FieldDeclaration>) {
+
+ enum class Type {
+ INT, INT_FLAGS, STRING;
+
+ val isInt get() = this == INT || this == INT_FLAGS
+ }
+
+ val CONST_NAMES get() = values.flatMap { it.variables }.map { it.nameAsString }
+} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/FeatureFlag.kt b/tools/codegen/src/com/android/codegen/FeatureFlag.kt
new file mode 100644
index 000000000000..24150d637a7b
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/FeatureFlag.kt
@@ -0,0 +1,27 @@
+package com.android.codegen
+
+
+/**
+ * See also [ClassPrinter.invoke] for more default flag values resolution rules
+ */
+enum class FeatureFlag(val onByDefault: Boolean, val desc: String = "") {
+ PARCELABLE(false, "implement Parcelable contract"),
+ AIDL(false, "generate a 'parcelable declaration' .aidl file alongside"),
+ CONSTRUCTOR(true, "an all-argument constructor"),
+ BUILDER(false, "e.g. MyClass.builder().setFoo(..).build();"),
+ GETTERS(true, "getters, e.g. getFoo()"),
+ SETTERS(false, "chainable/fluent setters, e.g. setFoo(..).setBar(..)"),
+ WITHERS(false, "'immutable setters' returning a new instance, " +
+ "e.g. newFoo = foo.withBar(barValue)"),
+ EQUALS_HASH_CODE(false, "equals + hashCode based on fields"),
+ TO_STRING(false, "toString based on fields"),
+ BUILD_UPON(false, "builder factory from existing instance, " +
+ "e.g. instance.buildUpon().setFoo(..).build()"),
+ IMPLICIT_NONNULL(true, "treat lack of @Nullable as @NonNull for Object fields"),
+ COPY_CONSTRUCTOR(false, "a constructor for an instance identical to the given one"),
+ CONST_DEFS(true, "@Int/StringDef's based on declared static constants"),
+ FOR_EACH_FIELD(false, "forEachField((name, value) -> ...)");
+
+ val kebabCase = name.toLowerCase().replace("_", "-")
+ val upperCamelCase = name.split("_").map { it.toLowerCase().capitalize() }.joinToString("")
+}
diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt
new file mode 100644
index 000000000000..02ebaef90f0b
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -0,0 +1,230 @@
+package com.android.codegen
+
+import com.github.javaparser.JavaParser
+import com.github.javaparser.ast.body.FieldDeclaration
+import com.github.javaparser.ast.expr.ClassExpr
+import com.github.javaparser.ast.expr.Name
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
+import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.type.ArrayType
+import com.github.javaparser.ast.type.ClassOrInterfaceType
+import com.github.javaparser.javadoc.Javadoc
+
+data class FieldInfo(
+ val index: Int,
+ val fieldAst: FieldDeclaration,
+ private val classInfo: ClassInfo
+) {
+
+ val classPrinter = classInfo as ClassPrinter
+
+ // AST
+ internal val variableAst = fieldAst.variables[0]
+ val typeAst = variableAst.type
+
+ // Field type
+ val Type = typeAst.asString()
+ val FieldClass = Type.takeWhile { it != '<' }
+ val isPrimitive = Type in PRIMITIVE_TYPES
+
+ // Javadoc
+ val javadoc: Javadoc? = fieldAst.javadoc.orElse(null)
+ private val javadocText = javadoc?.toText()?.let {
+ // Workaround for a bug in Javaparser for javadocs starting with {
+ if (it.hasUnbalancedCurlyBrace()) "{$it" else it
+ }
+ val javadocTextNoAnnotationLines = javadocText
+ ?.lines()
+ ?.dropLastWhile { it.startsWith("@") || it.isBlank() }
+ ?.let { if (it.isEmpty()) null else it }
+ val javadocFull = javadocText
+ ?.trimBlankLines()
+ ?.mapLines { " * $this" }
+ ?.let { "/**\n$it\n */" }
+
+
+ // Field name
+ val name = variableAst.name.asString()!!
+ private val isNameHungarian = name[0] == 'm' && name[1].isUpperCase()
+ val NameUpperCamel = if (isNameHungarian) name.substring(1) else name.capitalize()
+ val nameLowerCamel = if (isNameHungarian) NameUpperCamel.decapitalize() else name
+ val _name = if (name != nameLowerCamel) nameLowerCamel else "_$nameLowerCamel"
+ val SingularNameOrNull by lazy {
+ classPrinter {
+ fieldAst.annotations
+ .find { it.nameAsString == PluralOf }
+ ?.let { it as? SingleMemberAnnotationExpr }
+ ?.memberValue
+ ?.let { it as? StringLiteralExpr }
+ ?.value
+ ?.toLowerCamel()
+ ?.capitalize()
+ }
+ }
+ val SingularName by lazy { SingularNameOrNull ?: NameUpperCamel }
+
+
+ // Field value
+ val mayBeNull: Boolean
+ get() = when {
+ isPrimitive -> false
+ "@${classPrinter.NonNull}" in annotations -> false
+ "@${classPrinter.NonEmpty}" in annotations -> false
+ isNullable -> true
+ lazyInitializer != null -> true
+ else -> classPrinter { !FeatureFlag.IMPLICIT_NONNULL() }
+ }
+ val lazyInitializer
+ get() = classInfo.classAst.methods.find { method ->
+ method.nameAsString == "lazyInit$NameUpperCamel" && method.parameters.isEmpty()
+ }?.nameAsString
+ val internalGetter get() = if (lazyInitializer != null) "get$NameUpperCamel()" else name
+ val defaultExpr: Any?
+ get() {
+ variableAst.initializer.orElse(null)?.let { return it }
+ classInfo.classAst.methods.find {
+ it.nameAsString == "default$NameUpperCamel" && it.parameters.isEmpty()
+ }?.run { return "$nameAsString()" }
+ return null
+ }
+ val hasDefault get() = defaultExpr != null
+
+
+ // Generic args
+ val isArray = Type.endsWith("[]")
+ val isList = FieldClass == "List" || FieldClass == "ArrayList"
+ val isMap = FieldClass == "Map" || FieldClass == "ArrayMap"
+ || FieldClass == "HashMap" || FieldClass == "LinkedHashMap"
+ val fieldBit = bitAtExpr(index)
+ var isLast = false
+ val isFinal = fieldAst.isFinal
+ val fieldTypeGenegicArgs = when (typeAst) {
+ is ArrayType -> listOf(fieldAst.elementType.asString())
+ is ClassOrInterfaceType -> {
+ typeAst.typeArguments.orElse(null)?.map { it.asString() } ?: emptyList()
+ }
+ else -> emptyList()
+ }
+ val FieldInnerType = fieldTypeGenegicArgs.firstOrNull()
+ val FieldInnerClass = FieldInnerType?.takeWhile { it != '<' }
+
+
+ // Annotations
+ var intOrStringDef = null as ConstDef?
+ val annotations by lazy {
+ if (FieldClass in BUILTIN_SPECIAL_PARCELLINGS) {
+ classPrinter {
+ fileInfo.apply {
+ fieldAst.addAnnotation(SingleMemberAnnotationExpr(
+ Name(ParcelWith),
+ ClassExpr(parseJava(JavaParser::parseClassOrInterfaceType,
+ "$Parcelling.BuiltIn.For$FieldClass"))))
+ }
+ }
+ }
+ fieldAst.annotations.map { it.removeComment().toString() }
+ }
+ val annotationsNoInternal by lazy {
+ annotations.filterNot { ann ->
+ classPrinter {
+ internalAnnotations.any {
+ it in ann
+ }
+ }
+ }
+ }
+
+ fun hasAnnotation(a: String) = annotations.any { it.startsWith(a) }
+ val isNullable by lazy { hasAnnotation("@Nullable") }
+ val isNonEmpty by lazy { hasAnnotation("@${classPrinter.NonEmpty}") }
+ val customParcellingClass by lazy {
+ fieldAst.annotations.find { it.nameAsString == classPrinter.ParcelWith }
+ ?.singleArgAs<ClassExpr>()
+ ?.type
+ ?.asString()
+ }
+ val annotationsAndType by lazy { (annotationsNoInternal + Type).joinToString(" ") }
+ val sParcelling by lazy { customParcellingClass?.let { "sParcellingFor$NameUpperCamel" } }
+
+ val SetterParamType = if (isArray) "$FieldInnerType..." else Type
+ val annotationsForSetterParam by lazy {
+ buildList<String> {
+ addAll(annotationsNoInternal)
+ classPrinter {
+ if ("@$Nullable" in annotations
+ && "@$MaySetToNull" !in annotations) {
+ remove("@$Nullable")
+ add("@$NonNull")
+ }
+ }
+ }.joinToString(" ")
+ }
+ val annotatedTypeForSetterParam by lazy { "$annotationsForSetterParam $SetterParamType" }
+
+ // Utilities
+
+ /**
+ * `mFoo.size()`
+ */
+ val ClassPrinter.sizeExpr get() = when {
+ isArray && FieldInnerClass !in PRIMITIVE_TYPES ->
+ memberRef("com.android.internal.util.ArrayUtils.size") + "($name)"
+ isArray -> "$name.length"
+ listOf("List", "Set", "Map").any { FieldClass.endsWith(it) } ->
+ memberRef("com.android.internal.util.CollectionUtils.size") + "($name)"
+ Type == "String" -> memberRef("android.text.TextUtils.length") + "($name)"
+ Type == "CharSequence" -> "$name.length()"
+ else -> "$name.size()"
+ }
+ /**
+ * `mFoo.get(0)`
+ */
+ fun elemAtIndexExpr(indexExpr: String) = when {
+ isArray -> "$name[$indexExpr]"
+ FieldClass == "ArraySet" -> "$name.valueAt($indexExpr)"
+ else -> "$name.get($indexExpr)"
+ }
+ /**
+ * `mFoo.isEmpty()`
+ */
+ val ClassPrinter.isEmptyExpr get() = when {
+ isArray || Type == "CharSequence" -> "$sizeExpr == 0"
+ else -> "$name.isEmpty()"
+ }
+
+ /**
+ * `mFoo == that` or `Objects.equals(mFoo, that)`, etc.
+ */
+ fun ClassPrinter.isEqualToExpr(that: String) = when {
+ Type in PRIMITIVE_TYPES -> "$internalGetter == $that"
+ isArray -> "${memberRef("java.util.Arrays.equals")}($internalGetter, $that)"
+ else -> "${memberRef("java.util.Objects.equals")}($internalGetter, $that)"
+ }
+
+ /**
+ * Parcel.write* and Parcel.read* method name wildcard values
+ */
+ val ParcelMethodsSuffix = when {
+ FieldClass in PRIMITIVE_TYPES - "char" - "boolean" + BOXED_PRIMITIVE_TYPES +
+ listOf("String", "CharSequence", "Exception", "Size", "SizeF", "Bundle",
+ "FileDescriptor", "SparseBooleanArray", "SparseIntArray", "SparseArray") ->
+ FieldClass
+ isMap && fieldTypeGenegicArgs[0] == "String" -> "Map"
+ isArray -> when {
+ FieldInnerType!! in (PRIMITIVE_TYPES + "String") -> FieldInnerType + "Array"
+ isBinder(FieldInnerType) -> "BinderArray"
+ else -> "TypedArray"
+ }
+ isList -> when {
+ FieldInnerType == "String" -> "StringList"
+ isBinder(FieldInnerType!!) -> "BinderList"
+ else -> "ParcelableList"
+ }
+ isIInterface(Type) -> "StrongInterface"
+ isBinder(Type) -> "StrongBinder"
+ else -> "TypedObject"
+ }.capitalize()
+
+ private fun isBinder(type: String) = type == "Binder" || type == "IBinder" || isIInterface(type)
+ private fun isIInterface(type: String) = type.length >= 2 && type[0] == 'I' && type[1].isUpperCase()
+} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/FileInfo.kt b/tools/codegen/src/com/android/codegen/FileInfo.kt
new file mode 100644
index 000000000000..909472640f29
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/FileInfo.kt
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.codegen
+
+import com.github.javaparser.JavaParser
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.TypeDeclaration
+import java.io.File
+
+/**
+ * File-level parsing & printing logic
+ *
+ * @see [main] entrypoint
+ */
+class FileInfo(
+ val sourceLines: List<String>,
+ val cliArgs: Array<String>,
+ val file: File)
+ : Printer<FileInfo>, ImportsProvider {
+
+ override val fileAst: CompilationUnit
+ = parseJava(JavaParser::parse, sourceLines.joinToString("\n"))
+
+ override val stringBuilder = StringBuilder()
+ override var currentIndent = INDENT_SINGLE
+
+
+ val generatedWarning = run {
+ val fileEscaped = file.absolutePath.replace(
+ System.getenv("ANDROID_BUILD_TOP"), "\$ANDROID_BUILD_TOP")
+
+ """
+
+
+ // $GENERATED_WARNING_PREFIX v$CODEGEN_VERSION.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ $THIS_SCRIPT_LOCATION$CODEGEN_NAME ${cliArgs.dropLast(1).joinToString("") { "$it " }}$fileEscaped
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+ """
+ }
+ private val generatedWarningNumPrecedingEmptyLines
+ = generatedWarning.lines().takeWhile { it.isBlank() }.size
+
+ val classes = fileAst.types
+ .filterIsInstance<ClassOrInterfaceDeclaration>()
+ .flatMap { it.plusNested() }
+ .filterNot { it.isInterface }
+
+ val mainClass = classes.find { it.nameAsString == file.nameWithoutExtension }!!
+
+ // Parse stage 1
+ val classBounds: List<ClassBounds> = classes.map { ast ->
+ ClassBounds(ast, fileInfo = this)
+ }.apply {
+ forEachApply {
+ if (ast.isNestedType) {
+ val parent = find {
+ it.name == (ast.parentNode.get()!! as TypeDeclaration<*>).nameAsString
+ }!!
+ parent.nested.add(this)
+ nestedIn = parent
+ }
+ }
+ }
+
+ // Parse Stage 2
+ var codeChunks = buildList<CodeChunk> {
+ val mainClassBounds = classBounds.find { it.nestedIn == null }!!
+ add(CodeChunk.FileHeader(
+ mainClassBounds.fileInfo.sourceLines.subList(0, mainClassBounds.range.start)))
+ add(CodeChunk.DataClass.parse(mainClassBounds))
+ }
+
+ // Output stage
+ fun main() {
+ codeChunks.forEach { print(it) }
+ }
+
+ fun print(chunk: CodeChunk) {
+ when(chunk) {
+ is CodeChunk.GeneratedCode -> {
+ // Re-parse class code, discarding generated code and nested dataclasses
+ val ast = chunk.owner.chunks
+ .filter {
+ it.javaClass == CodeChunk.Code::class.java
+ || it.javaClass == CodeChunk.ClosingBrace::class.java
+ }
+ .flatMap { (it as CodeChunk.Code).lines }
+ .joinToString("\n")
+ .let {
+ parseJava(JavaParser::parseTypeDeclaration, it)
+ as ClassOrInterfaceDeclaration
+ }
+
+ // Write new generated code
+ ClassPrinter(ast, fileInfo = this).print()
+ }
+ is CodeChunk.ClosingBrace -> {
+ // Special case - print closing brace with -1 indent
+ rmEmptyLine()
+ popIndent()
+ +"\n}"
+ }
+ // Print general code as-is
+ is CodeChunk.Code -> chunk.lines.forEach { stringBuilder.appendln(it) }
+ // Recursively render data classes
+ is CodeChunk.DataClass -> chunk.chunks.forEach { print(it) }
+ }
+ }
+
+ /**
+ * Output of stage 1 of parsing a file:
+ * Recursively nested ranges of code line numbers containing nested classes
+ */
+ data class ClassBounds(
+ val ast: ClassOrInterfaceDeclaration,
+ val fileInfo: FileInfo,
+ val name: String = ast.nameAsString,
+ val range: ClosedRange<Int> = ast.range.get()!!.let { rng -> rng.begin.line-1..rng.end.line-1 },
+ val nested: MutableList<ClassBounds> = mutableListOf(),
+ var nestedIn: ClassBounds? = null) {
+
+ val nestedDataClasses: List<ClassBounds> by lazy {
+ nested.filter { it.isDataclass }.sortedBy { it.range.start }
+ }
+ val isDataclass = ast.annotations.any { it.nameAsString.endsWith("DataClass") }
+
+ val baseIndentLength = fileInfo.sourceLines.find { "class $name" in it }!!.takeWhile { it == ' ' }.length
+ val baseIndent = buildString { repeat(baseIndentLength) { append(' ') } }
+
+ val sourceNoPrefix = fileInfo.sourceLines.drop(range.start)
+ val generatedCodeRange = sourceNoPrefix
+ .indexOfFirst { it.startsWith("$baseIndent$INDENT_SINGLE// $GENERATED_WARNING_PREFIX") }
+ .let { start ->
+ if (start < 0) {
+ null
+ } else {
+ var endInclusive = sourceNoPrefix.indexOfFirst {
+ it.startsWith("$baseIndent$INDENT_SINGLE$GENERATED_END")
+ }
+ if (endInclusive == -1) {
+ // Legacy generated code doesn't have end markers
+ endInclusive = sourceNoPrefix.size - 2
+ }
+ IntRange(
+ range.start + start - fileInfo.generatedWarningNumPrecedingEmptyLines,
+ range.start + endInclusive)
+ }
+ }
+
+ /** Debug info */
+ override fun toString(): String {
+ return buildString {
+ appendln("class $name $range")
+ nested.forEach {
+ appendln(it)
+ }
+ appendln("end $name")
+ }
+ }
+ }
+
+ /**
+ * Output of stage 2 of parsing a file
+ */
+ sealed class CodeChunk {
+ /** General code */
+ open class Code(val lines: List<String>): CodeChunk() {}
+
+ /** Copyright + package + imports + main javadoc */
+ class FileHeader(lines: List<String>): Code(lines)
+
+ /** Code to be discarded and refreshed */
+ open class GeneratedCode(lines: List<String>): Code(lines) {
+ lateinit var owner: DataClass
+
+ class Placeholder: GeneratedCode(emptyList())
+ }
+
+ object ClosingBrace: Code(listOf("}"))
+
+ data class DataClass(
+ val ast: ClassOrInterfaceDeclaration,
+ val chunks: List<CodeChunk>,
+ val generatedCode: GeneratedCode?): CodeChunk() {
+
+ companion object {
+ fun parse(classBounds: ClassBounds): DataClass {
+ val initial = Code(lines = classBounds.fileInfo.sourceLines.subList(
+ fromIndex = classBounds.range.start,
+ toIndex = findLowerBound(
+ thisClass = classBounds,
+ nextNestedClass = classBounds.nestedDataClasses.getOrNull(0))))
+
+ val chunks = mutableListOf<CodeChunk>(initial)
+
+ classBounds.nestedDataClasses.forEachSequentialPair {
+ nestedDataClass, nextNestedDataClass ->
+ chunks += DataClass.parse(nestedDataClass)
+ chunks += Code(lines = classBounds.fileInfo.sourceLines.subList(
+ fromIndex = nestedDataClass.range.endInclusive + 1,
+ toIndex = findLowerBound(
+ thisClass = classBounds,
+ nextNestedClass = nextNestedDataClass)))
+ }
+
+ var generatedCode = classBounds.generatedCodeRange?.let { rng ->
+ GeneratedCode(classBounds.fileInfo.sourceLines.subList(
+ rng.start, rng.endInclusive+1))
+ }
+ if (generatedCode != null) {
+ chunks += generatedCode
+ chunks += ClosingBrace
+ } else if (classBounds.isDataclass) {
+
+ // Insert placeholder for generated code to be inserted for the 1st time
+ chunks.last = (chunks.last as Code)
+ .lines
+ .dropLastWhile { it.isBlank() }
+ .run {
+ if (last().dropWhile { it.isWhitespace() }.startsWith("}")) {
+ dropLast(1)
+ } else {
+ this
+ }
+ }.let { Code(it) }
+ generatedCode = GeneratedCode.Placeholder()
+ chunks += generatedCode
+ chunks += ClosingBrace
+ } else {
+ // Outer class may be not a @DataClass but contain ones
+ // so just skip generated code for them
+ }
+
+ return DataClass(classBounds.ast, chunks, generatedCode).also {
+ generatedCode?.owner = it
+ }
+ }
+
+ private fun findLowerBound(thisClass: ClassBounds, nextNestedClass: ClassBounds?): Int {
+ return nextNestedClass?.range?.start
+ ?: thisClass.generatedCodeRange?.start
+ ?: thisClass.range.endInclusive + 1
+ }
+ }
+ }
+
+ /** 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" +
+ chunks.joinToString("\n") { it.summary() } +
+ "\n//end ${ast.nameAsString}"
+ }
+ }
+
+ private fun ClassOrInterfaceDeclaration.plusNested(): List<ClassOrInterfaceDeclaration> {
+ return mutableListOf<ClassOrInterfaceDeclaration>().apply {
+ add(this@plusNested)
+ childNodes.filterIsInstance<ClassOrInterfaceDeclaration>()
+ .flatMap { it.plusNested() }
+ .let { addAll(it) }
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
new file mode 100644
index 000000000000..5a96cf1d9bdb
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -0,0 +1,949 @@
+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 java.io.File
+
+
+/**
+ * IntDefs and StringDefs based on constants
+ */
+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")
+ } && it.annotations.none { it.nameAsString == DataClassSuppressConstDefs }
+ }.flatMap { field -> field.variables.map { it to field } }
+ val intConsts = consts.filter { it.first.type.asString() == "int" }
+ val strConsts = consts.filter { it.first.type.asString() == "String" }
+ val intGroups = intConsts.groupBy { it.first.nameAsString.split("_")[0] }.values
+ val strGroups = strConsts.groupBy { it.first.nameAsString.split("_")[0] }.values
+ intGroups.forEach {
+ generateConstDef(it)
+ }
+ strGroups.forEach {
+ generateConstDef(it)
+ }
+}
+
+fun ClassPrinter.generateConstDef(consts: List<Pair<VariableDeclarator, FieldDeclaration>>) {
+ if (consts.size <= 1) return
+
+ val names = consts.map { it.first.nameAsString!! }
+ val prefix = names
+ .reduce { a, b -> a.commonPrefixWith(b) }
+ .dropLastWhile { it != '_' }
+ .dropLast(1)
+ if (prefix.isEmpty()) {
+ println("Failed to generate const def for $names")
+ return
+ }
+ var AnnotationName = prefix.split("_")
+ .filterNot { it.isBlank() }
+ .map { it.toLowerCase().capitalize() }
+ .joinToString("")
+ val annotatedConst = consts.find { it.second.annotations.isNonEmpty }
+ if (annotatedConst != null) {
+ AnnotationName = annotatedConst.second.annotations.first().nameAsString
+ }
+ val type = consts[0].first.type.asString()
+ val flag = type == "int" && consts.all { it.first.initializer.get().toString().startsWith("0x") }
+ val constDef = ConstDef(type = when {
+ type == "String" -> ConstDef.Type.STRING
+ flag -> ConstDef.Type.INT_FLAGS
+ else -> ConstDef.Type.INT
+ },
+ AnnotationName = AnnotationName,
+ values = consts.map { it.second }
+ )
+ constDefs += constDef
+ fields.forEachApply {
+ if (fieldAst.annotations.any { it.nameAsString == AnnotationName }) {
+ this.intOrStringDef = constDef
+ }
+ }
+
+ val visibility = if (consts[0].second.isPublic) "public" else "/* package-private */"
+
+ val Retention = classRef("java.lang.annotation.Retention")
+ val RetentionPolicySource = memberRef("java.lang.annotation.RetentionPolicy.SOURCE")
+ val ConstDef = classRef("android.annotation.${type.capitalize()}Def")
+
+ if (FeatureFlag.CONST_DEFS.hidden) {
+ +"/** @hide */"
+ }
+ "@$ConstDef(${if_(flag, "flag = true, ")}prefix = \"${prefix}_\", value = {" {
+ names.forEachLastAware { name, isLast ->
+ +"$name${if_(!isLast, ",")}"
+ }
+ } + ")"
+ +"@$Retention($RetentionPolicySource)"
+ +GENERATED_MEMBER_HEADER
+ +"$visibility @interface $AnnotationName {}"
+ +""
+
+ if (type == "int") {
+ if (FeatureFlag.CONST_DEFS.hidden) {
+ +"/** @hide */"
+ }
+ +GENERATED_MEMBER_HEADER
+ val methodDefLine = "$visibility static String ${AnnotationName.decapitalize()}ToString(" +
+ "@$AnnotationName int value)"
+ if (flag) {
+ val flg2str = memberRef("com.android.internal.util.BitUtils.flagsToString")
+ methodDefLine {
+ "return $flg2str(" {
+ +"value, $ClassName::single${AnnotationName}ToString"
+ } + ";"
+ }
+ +GENERATED_MEMBER_HEADER
+ !"static String single${AnnotationName}ToString(@$AnnotationName int value)"
+ } else {
+ !methodDefLine
+ }
+ " {" {
+ "switch (value) {" {
+ names.forEach { name ->
+ "case $name:" {
+ +"return \"$name\";"
+ }
+ }
+ +"default: return Integer.toHexString(value);"
+ }
+ }
+ }
+}
+
+fun FileInfo.generateAidl() {
+ val aidl = File(file.path.substringBeforeLast(".java") + ".aidl")
+ if (aidl.exists()) return
+ aidl.writeText(buildString {
+ sourceLines.dropLastWhile { !it.startsWith("package ") }.forEach {
+ appendln(it)
+ }
+ append("\nparcelable ${mainClass.nameAsString};\n")
+ })
+}
+
+/**
+ * ```
+ * Foo newFoo = oldFoo.withBar(newBar);
+ * ```
+ */
+fun ClassPrinter.generateWithers() {
+ fields.forEachApply {
+ val metodName = "with$NameUpperCamel"
+ if (!isMethodGenerationSuppressed(metodName, Type)) {
+ generateFieldJavadoc(forceHide = FeatureFlag.WITHERS.hidden)
+ """@$NonNull
+ $GENERATED_MEMBER_HEADER
+ public $ClassType $metodName($annotatedTypeForSetterParam value)""" {
+ val changedFieldName = name
+
+ "return new $ClassType(" {
+ fields.forEachTrimmingTrailingComma {
+ if (name == changedFieldName) +"value," else +"$name,"
+ }
+ } + ";"
+ }
+ }
+ }
+}
+
+fun ClassPrinter.generateCopyConstructor() {
+ if (classAst.constructors.any {
+ it.parameters.size == 1 &&
+ it.parameters[0].type.asString() == ClassType
+ }) {
+ return
+ }
+
+ +"/** Copy constructor */"
+ +GENERATED_MEMBER_HEADER
+ "public $ClassName(@$NonNull $ClassName orig)" {
+ fields.forEachApply {
+ +"$name = orig.$name;"
+ }
+ }
+}
+
+/**
+ * ```
+ * Foo newFoo = oldFoo.buildUpon().setBar(newBar).build();
+ * ```
+ */
+fun ClassPrinter.generateBuildUpon() {
+ if (isMethodGenerationSuppressed("buildUpon")) return
+
+ +"/**"
+ +" * Provides an instance of {@link $BuilderClass} with state corresponding to this instance."
+ if (FeatureFlag.BUILD_UPON.hidden) {
+ +" * @hide"
+ }
+ +" */"
+ +GENERATED_MEMBER_HEADER
+ "public $BuilderType buildUpon()" {
+ "return new $BuilderType()" {
+ fields.forEachApply {
+ +".set$NameUpperCamel($internalGetter)"
+ } + ";"
+ }
+ }
+}
+
+fun ClassPrinter.generateBuilder() {
+ val setterVisibility = if (cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS))
+ "protected" else "public"
+ val constructorVisibility = if (BuilderClass == CANONICAL_BUILDER_CLASS)
+ "public" else "/* package-*/"
+
+ val providedSubclassAst = nestedClasses.find {
+ it.extendedTypes.any { it.nameAsString == BASE_BUILDER_CLASS }
+ }
+
+ val BuilderSupertype = if (customBaseBuilderAst != null) {
+ customBaseBuilderAst!!.nameAsString
+ } else {
+ "Object"
+ }
+
+ val maybeFinal = if_(classAst.isFinal, "final ")
+
+ +"/**"
+ +" * A builder for {@link $ClassName}"
+ if (FeatureFlag.BUILDER.hidden) +" * @hide"
+ +" */"
+ +"@SuppressWarnings(\"WeakerAccess\")"
+ +GENERATED_MEMBER_HEADER
+ !"public static ${maybeFinal}class $BuilderClass$genericArgs"
+ if (BuilderSupertype != "Object") {
+ appendSameLine(" extends $BuilderSupertype")
+ }
+ " {" {
+
+ +""
+ fields.forEachApply {
+ +"private $annotationsAndType $name;"
+ }
+ +""
+ +"private long mBuilderFieldsSet = 0L;"
+ +""
+
+ val requiredFields = fields.filter { !it.hasDefault }
+
+ generateConstructorJavadoc(
+ fields = requiredFields,
+ ClassName = BuilderClass,
+ hidden = false)
+ "$constructorVisibility $BuilderClass(" {
+ requiredFields.forEachLastAware { field, isLast ->
+ +"${field.annotationsAndType} ${field._name}${if_(!isLast, ",")}"
+ }
+ }; " {" {
+ requiredFields.forEachApply {
+ generateSetFrom(_name)
+ }
+ }
+
+ generateBuilderSetters(setterVisibility)
+
+ generateBuilderBuild()
+
+ "private void checkNotUsed() {" {
+ "if ((mBuilderFieldsSet & ${bitAtExpr(fields.size)}) != 0)" {
+ "throw new IllegalStateException(" {
+ +"\"This Builder should not be reused. Use a new Builder instance instead\""
+ }
+ +";"
+ }
+ }
+
+ rmEmptyLine()
+ }
+}
+
+private fun ClassPrinter.generateBuilderMethod(
+ defVisibility: String,
+ name: String,
+ paramAnnotations: String? = null,
+ paramTypes: List<String>,
+ paramNames: List<String> = listOf("value"),
+ genJavadoc: ClassPrinter.() -> Unit,
+ genBody: ClassPrinter.() -> Unit) {
+
+ val providedMethod = customBaseBuilderAst?.members?.find {
+ it is MethodDeclaration
+ && it.nameAsString == name
+ && it.parameters.map { it.typeAsString } == paramTypes.toTypedArray().toList()
+ } as? MethodDeclaration
+
+ if ((providedMethod == null || providedMethod.isAbstract)
+ && name !in builderSuppressedMembers) {
+ val visibility = providedMethod?.visibility?.asString() ?: defVisibility
+ val ReturnType = providedMethod?.typeAsString ?: CANONICAL_BUILDER_CLASS
+ val Annotations = providedMethod?.annotations?.joinToString("\n")
+
+ genJavadoc()
+ +GENERATED_MEMBER_HEADER
+ if (providedMethod?.isAbstract == true) +"@Override"
+ if (!Annotations.isNullOrEmpty()) +Annotations
+ val ParamAnnotations = if (!paramAnnotations.isNullOrEmpty()) "$paramAnnotations " else ""
+
+ "$visibility @$NonNull $ReturnType $name(${
+ paramTypes.zip(paramNames).joinToString(", ") { (Type, paramName) ->
+ "$ParamAnnotations$Type $paramName"
+ }
+ })" {
+ genBody()
+ }
+ }
+}
+
+private fun ClassPrinter.generateBuilderSetters(visibility: String) {
+
+ fields.forEachApply {
+ val maybeCast =
+ if_(BuilderClass != CANONICAL_BUILDER_CLASS, " ($CANONICAL_BUILDER_CLASS)")
+
+ val setterName = "set$NameUpperCamel"
+
+ generateBuilderMethod(
+ name = setterName,
+ defVisibility = visibility,
+ paramAnnotations = annotationsForSetterParam,
+ paramTypes = listOf(SetterParamType),
+ genJavadoc = { generateFieldJavadoc() }) {
+ +"checkNotUsed();"
+ +"mBuilderFieldsSet |= $fieldBit;"
+ +"$name = value;"
+ +"return$maybeCast this;"
+ }
+
+ val javadocSeeSetter = "/** @see #$setterName */"
+ val adderName = "add$SingularName"
+
+ val singularNameCustomizationHint = if (SingularNameOrNull == null) {
+ "// You can refine this method's name by providing item's singular name, e.g.:\n" +
+ "// @DataClass.PluralOf(\"item\")) mItems = ...\n\n"
+ } else ""
+
+
+ if (isList && FieldInnerType != null) {
+ generateBuilderMethod(
+ name = adderName,
+ defVisibility = visibility,
+ paramAnnotations = "@$NonNull",
+ paramTypes = listOf(FieldInnerType),
+ genJavadoc = { +javadocSeeSetter }) {
+
+ !singularNameCustomizationHint
+ +"if ($name == null) $setterName(new $ArrayList<>());"
+ +"$name.add(value);"
+ +"return$maybeCast this;"
+ }
+ }
+
+ if (isMap && FieldInnerType != null) {
+ generateBuilderMethod(
+ name = adderName,
+ defVisibility = visibility,
+ paramAnnotations = "@$NonNull",
+ paramTypes = fieldTypeGenegicArgs,
+ paramNames = listOf("key", "value"),
+ genJavadoc = { +javadocSeeSetter }) {
+ !singularNameCustomizationHint
+ +"if ($name == null) $setterName(new ${if (FieldClass == "Map") LinkedHashMap else FieldClass}());"
+ +"$name.put(key, value);"
+ +"return$maybeCast this;"
+ }
+ }
+ }
+}
+
+private fun ClassPrinter.generateBuilderBuild() {
+ +"/** Builds the instance. This builder should not be touched after calling this! */"
+ "public @$NonNull $ClassType build()" {
+ +"checkNotUsed();"
+ +"mBuilderFieldsSet |= ${bitAtExpr(fields.size)}; // Mark builder used"
+ +""
+ fields.forEachApply {
+ if (hasDefault) {
+ "if ((mBuilderFieldsSet & $fieldBit) == 0)" {
+ +"$name = $defaultExpr;"
+ }
+ }
+ }
+ "$ClassType o = new $ClassType(" {
+ fields.forEachTrimmingTrailingComma {
+ +"$name,"
+ }
+ } + ";"
+ +"return o;"
+ }
+}
+
+fun ClassPrinter.generateParcelable() {
+ val booleanFields = fields.filter { it.Type == "boolean" }
+ val objectFields = fields.filter { it.Type !in PRIMITIVE_TYPES }
+ val nullableFields = objectFields.filter { it.mayBeNull }
+ val nonBooleanFields = fields - booleanFields
+
+
+ val flagStorageType = when (fields.size) {
+ in 0..7 -> "byte"
+ in 8..15 -> "int"
+ in 16..31 -> "long"
+ else -> throw NotImplementedError("32+ field classes not yet supported")
+ }
+ val FlagStorageType = flagStorageType.capitalize()
+
+ fields.forEachApply {
+ if (sParcelling != null) {
+ +GENERATED_MEMBER_HEADER
+ "static $Parcelling<$Type> $sParcelling =" {
+ "$Parcelling.Cache.get(" {
+ +"$customParcellingClass.class"
+ } + ";"
+ }
+ "static {" {
+ "if ($sParcelling == null)" {
+ "$sParcelling = $Parcelling.Cache.put(" {
+ +"new $customParcellingClass()"
+ } + ";"
+ }
+ }
+ +""
+ }
+ }
+
+ val Parcel = classRef("android.os.Parcel")
+ if (!isMethodGenerationSuppressed("writeToParcel", Parcel, "int")) {
+ +"@Override"
+ +GENERATED_MEMBER_HEADER
+ "public void writeToParcel(@$NonNull $Parcel dest, int flags)" {
+ +"// You can override field parcelling by defining methods like:"
+ +"// void parcelFieldName(Parcel dest, int flags) { ... }"
+ +""
+
+ if (extendsParcelableClass) {
+ +"super.writeToParcel(dest, flags);\n"
+ }
+
+ if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) {
+ +"$flagStorageType flg = 0;"
+ booleanFields.forEachApply {
+ +"if ($internalGetter) flg |= $fieldBit;"
+ }
+ nullableFields.forEachApply {
+ +"if ($internalGetter != null) flg |= $fieldBit;"
+ }
+ +"dest.write$FlagStorageType(flg);"
+ }
+
+ nonBooleanFields.forEachApply {
+ val customParcellingMethod = "parcel$NameUpperCamel"
+ when {
+ hasMethod(customParcellingMethod, Parcel, "int") ->
+ +"$customParcellingMethod(dest, flags);"
+ customParcellingClass != null -> +"$sParcelling.parcel($name, dest, flags);"
+ hasAnnotation("@$DataClassEnum") ->
+ +"dest.writeInt($internalGetter == null ? -1 : $internalGetter.ordinal());"
+ else -> {
+ if (mayBeNull) !"if ($internalGetter != null) "
+ var args = internalGetter
+ if (ParcelMethodsSuffix.startsWith("Parcelable")
+ || ParcelMethodsSuffix.startsWith("TypedObject")
+ || ParcelMethodsSuffix == "TypedArray") {
+ args += ", flags"
+ }
+ +"dest.write$ParcelMethodsSuffix($args);"
+ }
+ }
+ }
+ }
+ }
+
+ if (!isMethodGenerationSuppressed("describeContents")) {
+ +"@Override"
+ +GENERATED_MEMBER_HEADER
+ +"public int describeContents() { return 0; }"
+ +""
+ }
+
+ if (!hasMethod(ClassName, Parcel)) {
+ val visibility = if (classAst.isFinal) "/* package-private */" else "protected"
+
+ +"/** @hide */"
+ +"@SuppressWarnings({\"unchecked\", \"RedundantCast\"})"
+ +GENERATED_MEMBER_HEADER
+ "$visibility $ClassName(@$NonNull $Parcel in) {" {
+ +"// You can override field unparcelling by defining methods like:"
+ +"// static FieldType unparcelFieldName(Parcel in) { ... }"
+ +""
+
+ if (extendsParcelableClass) {
+ +"super(in);\n"
+ }
+
+ if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) {
+ +"$flagStorageType flg = in.read$FlagStorageType();"
+ }
+ booleanFields.forEachApply {
+ +"$Type $_name = (flg & $fieldBit) != 0;"
+ }
+ nonBooleanFields.forEachApply {
+
+ // Handle customized parceling
+ val customParcellingMethod = "unparcel$NameUpperCamel"
+ if (hasMethod(customParcellingMethod, Parcel)) {
+ +"$Type $_name = $customParcellingMethod(in);"
+ } else if (customParcellingClass != null) {
+ +"$Type $_name = $sParcelling.unparcel(in);"
+ } else if (hasAnnotation("@$DataClassEnum")) {
+ val ordinal = "${_name}Ordinal"
+ +"int $ordinal = in.readInt();"
+ +"$Type $_name = $ordinal < 0 ? null : $FieldClass.values()[$ordinal];"
+ } else {
+ val methodArgs = mutableListOf<String>()
+
+ // Create container if any
+ val containerInitExpr = when {
+ FieldClass == "Map" -> "new $LinkedHashMap<>()"
+ isMap -> "new $FieldClass()"
+ FieldClass == "List" || FieldClass == "ArrayList" ->
+ "new ${classRef("java.util.ArrayList")}<>()"
+ else -> ""
+ }
+ val passContainer = containerInitExpr.isNotEmpty()
+
+ // nullcheck +
+ // "FieldType fieldName = (FieldType)"
+ if (passContainer) {
+ methodArgs.add(_name)
+ !"$Type $_name = "
+ if (mayBeNull) {
+ +"null;"
+ !"if ((flg & $fieldBit) != 0) {"
+ pushIndent()
+ +""
+ !"$_name = "
+ }
+ +"$containerInitExpr;"
+ } else {
+ !"$Type $_name = "
+ if (mayBeNull) !"(flg & $fieldBit) == 0 ? null : "
+ if (ParcelMethodsSuffix == "StrongInterface") {
+ !"$FieldClass.Stub.asInterface("
+ } else if (Type !in PRIMITIVE_TYPES + "String" + "Bundle" &&
+ (!isArray || FieldInnerType !in PRIMITIVE_TYPES + "String") &&
+ ParcelMethodsSuffix != "Parcelable") {
+ !"($FieldClass) "
+ }
+ }
+
+ // Determine method args
+ when {
+ ParcelMethodsSuffix == "Parcelable" ->
+ methodArgs += "$FieldClass.class.getClassLoader()"
+ ParcelMethodsSuffix == "SparseArray" ->
+ methodArgs += "$FieldInnerClass.class.getClassLoader()"
+ ParcelMethodsSuffix == "TypedObject" ->
+ methodArgs += "$FieldClass.CREATOR"
+ ParcelMethodsSuffix == "TypedArray" ->
+ methodArgs += "$FieldInnerClass.CREATOR"
+ ParcelMethodsSuffix == "Map" ->
+ methodArgs += "${fieldTypeGenegicArgs[1].substringBefore("<")}.class.getClassLoader()"
+ ParcelMethodsSuffix.startsWith("Parcelable")
+ || (isList || isArray)
+ && FieldInnerType !in PRIMITIVE_TYPES + "String" ->
+ methodArgs += "$FieldInnerClass.class.getClassLoader()"
+ }
+
+ // ...in.readFieldType(args...);
+ when {
+ ParcelMethodsSuffix == "StrongInterface" -> !"in.readStrongBinder"
+ isArray -> !"in.create$ParcelMethodsSuffix"
+ else -> !"in.read$ParcelMethodsSuffix"
+ }
+ !"(${methodArgs.joinToString(", ")})"
+ if (ParcelMethodsSuffix == "StrongInterface") !")"
+ +";"
+
+ // Cleanup if passContainer
+ if (passContainer && mayBeNull) {
+ popIndent()
+ rmEmptyLine()
+ +"\n}"
+ }
+ }
+ }
+
+ +""
+ fields.forEachApply {
+ !"this."
+ generateSetFrom(_name)
+ }
+
+ generateOnConstructedCallback()
+ }
+ }
+
+ if (classAst.fields.none { it.variables[0].nameAsString == "CREATOR" }) {
+ val Creator = classRef("android.os.Parcelable.Creator")
+
+ +GENERATED_MEMBER_HEADER
+ "public static final @$NonNull $Creator<$ClassName> CREATOR" {
+ +"= new $Creator<$ClassName>()"
+ }; " {" {
+
+ +"@Override"
+ "public $ClassName[] newArray(int size)" {
+ +"return new $ClassName[size];"
+ }
+
+ +"@Override"
+ "public $ClassName createFromParcel(@$NonNull $Parcel in)" {
+ +"return new $ClassName(in);"
+ }
+ rmEmptyLine()
+ } + ";"
+ +""
+ }
+}
+
+fun ClassPrinter.generateEqualsHashcode() {
+ if (!isMethodGenerationSuppressed("equals", "Object")) {
+ +"@Override"
+ +GENERATED_MEMBER_HEADER
+ "public boolean equals(@$Nullable Object o)" {
+ +"// You can override field equality logic by defining either of the methods like:"
+ +"// boolean fieldNameEquals($ClassName other) { ... }"
+ +"// boolean fieldNameEquals(FieldType otherValue) { ... }"
+ +""
+ """if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ $ClassType that = ($ClassType) o;
+ //noinspection PointlessBooleanExpression
+ return true""" {
+ fields.forEachApply {
+ val sfx = if (isLast) ";" else ""
+ val customEquals = "${nameLowerCamel}Equals"
+ when {
+ hasMethod(customEquals, Type) -> +"&& $customEquals(that.$internalGetter)$sfx"
+ hasMethod(customEquals, ClassType) -> +"&& $customEquals(that)$sfx"
+ else -> +"&& ${isEqualToExpr("that.$internalGetter")}$sfx"
+ }
+ }
+ }
+ }
+ }
+
+ if (!isMethodGenerationSuppressed("hashCode")) {
+ +"@Override"
+ +GENERATED_MEMBER_HEADER
+ "public int hashCode()" {
+ +"// You can override field hashCode logic by defining methods like:"
+ +"// int fieldNameHashCode() { ... }"
+ +""
+ +"int _hash = 1;"
+ fields.forEachApply {
+ !"_hash = 31 * _hash + "
+ val customHashCode = "${nameLowerCamel}HashCode"
+ when {
+ hasMethod(customHashCode) -> +"$customHashCode();"
+ Type == "int" || Type == "byte" -> +"$internalGetter;"
+ Type in PRIMITIVE_TYPES -> +"${Type.capitalize()}.hashCode($internalGetter);"
+ isArray -> +"${memberRef("java.util.Arrays.hashCode")}($internalGetter);"
+ else -> +"${memberRef("java.util.Objects.hashCode")}($internalGetter);"
+ }
+ }
+ +"return _hash;"
+ }
+ }
+}
+
+//TODO support IntDef flags?
+fun ClassPrinter.generateToString() {
+ if (!isMethodGenerationSuppressed("toString")) {
+ +"@Override"
+ +GENERATED_MEMBER_HEADER
+ "public String toString()" {
+ +"// You can override field toString logic by defining methods like:"
+ +"// String fieldNameToString() { ... }"
+ +""
+ "return \"$ClassName { \" +" {
+ fields.forEachApply {
+ val customToString = "${nameLowerCamel}ToString"
+ val expr = when {
+ hasMethod(customToString) -> "$customToString()"
+ isArray -> "${memberRef("java.util.Arrays.toString")}($internalGetter)"
+ intOrStringDef?.type?.isInt == true ->
+ "${intOrStringDef!!.AnnotationName.decapitalize()}ToString($name)"
+ else -> internalGetter
+ }
+ +"\"$nameLowerCamel = \" + $expr${if_(!isLast, " + \", \"")} +"
+ }
+ }
+ +"\" }\";"
+ }
+ }
+}
+
+fun ClassPrinter.generateSetters() {
+ fields.forEachApply {
+ if (!isMethodGenerationSuppressed("set$NameUpperCamel", Type)
+ && !fieldAst.isPublic
+ && !isFinal) {
+
+ generateFieldJavadoc(forceHide = FeatureFlag.SETTERS.hidden)
+ +GENERATED_MEMBER_HEADER
+ "public $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" {
+ generateSetFrom("value")
+ +"return this;"
+ }
+ }
+ }
+}
+
+fun ClassPrinter.generateGetters() {
+ (fields + lazyTransientFields).forEachApply {
+ val methodPrefix = if (Type == "boolean") "is" else "get"
+ val methodName = methodPrefix + NameUpperCamel
+
+ if (!isMethodGenerationSuppressed(methodName) && !fieldAst.isPublic) {
+
+ generateFieldJavadoc(forceHide = FeatureFlag.GETTERS.hidden)
+ +GENERATED_MEMBER_HEADER
+ "public $annotationsAndType $methodName()" {
+ if (lazyInitializer == null) {
+ +"return $name;"
+ } else {
+ +"$Type $_name = $name;"
+ "if ($_name == null)" {
+ if (fieldAst.isVolatile) {
+ "synchronized(this)" {
+ +"$_name = $name;"
+ "if ($_name == null)" {
+ +"$_name = $name = $lazyInitializer();"
+ }
+ }
+ } else {
+ +"// You can mark field as volatile for thread-safe double-check init"
+ +"$_name = $name = $lazyInitializer();"
+ }
+ }
+ +"return $_name;"
+ }
+ }
+ }
+ }
+}
+
+fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter {
+ if (javadocFull != null || forceHide) {
+ var hidden = false
+ (javadocFull ?: "/**\n */").lines().forEach {
+ if (it.contains("@hide")) hidden = true
+ if (it.contains("*/") && forceHide && !hidden) {
+ if (javadocFull != null) +" *"
+ +" * @hide"
+ }
+ +it
+ }
+ }
+}
+
+fun FieldInfo.generateSetFrom(source: String) = classPrinter {
+ +"$name = $source;"
+ generateFieldValidation(field = this@generateSetFrom)
+}
+
+fun ClassPrinter.generateConstructor(visibility: String = "public") {
+ if (visibility == "public") {
+ generateConstructorJavadoc()
+ }
+ +GENERATED_MEMBER_HEADER
+ "$visibility $ClassName(" {
+ fields.forEachApply {
+ +"$annotationsAndType $nameLowerCamel${if_(!isLast, ",")}"
+ }
+ }
+ " {" {
+ fields.forEachApply {
+ !"this."
+ generateSetFrom(nameLowerCamel)
+ }
+
+ generateOnConstructedCallback()
+ }
+}
+
+private fun ClassPrinter.generateConstructorJavadoc(
+ fields: List<FieldInfo> = this.fields,
+ ClassName: String = this.ClassName,
+ hidden: Boolean = FeatureFlag.CONSTRUCTOR.hidden) {
+ if (fields.all { it.javadoc == null } && !FeatureFlag.CONSTRUCTOR.hidden) return
+ +"/**"
+ +" * Creates a new $ClassName."
+ +" *"
+ fields.filter { it.javadoc != null }.forEachApply {
+ javadocTextNoAnnotationLines?.apply {
+ +" * @param $nameLowerCamel"
+ forEach {
+ +" * $it"
+ }
+ }
+ }
+ if (FeatureFlag.CONSTRUCTOR.hidden) +" * @hide"
+ +" */"
+}
+
+private fun ClassPrinter.appendLinesWithContinuationIndent(text: String) {
+ val lines = text.lines()
+ if (lines.isNotEmpty()) {
+ !lines[0]
+ }
+ if (lines.size >= 2) {
+ "" {
+ lines.drop(1).forEach {
+ +it
+ }
+ }
+ }
+}
+
+private fun ClassPrinter.generateFieldValidation(field: FieldInfo) = field.run {
+ if (isNonEmpty) {
+ "if ($isEmptyExpr)" {
+ +"throw new IllegalArgumentException(\"$nameLowerCamel cannot be empty\");"
+ }
+ }
+ if (intOrStringDef != null) {
+ if (intOrStringDef!!.type == ConstDef.Type.INT_FLAGS) {
+ +""
+ "$Preconditions.checkFlagsArgument(" {
+ +"$name, "
+ appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n| "))
+ }
+ +";"
+ } else {
+ +""
+ !"if ("
+ appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n&& ") {
+ "!(${isEqualToExpr(it)})"
+ })
+ rmEmptyLine(); ") {" {
+ "throw new ${classRef<IllegalArgumentException>()}(" {
+ "\"$nameLowerCamel was \" + $internalGetter + \" but must be one of: \"" {
+
+ intOrStringDef!!.CONST_NAMES.forEachLastAware { CONST_NAME, isLast ->
+ +"""+ "$CONST_NAME(" + $CONST_NAME + ")${if_(!isLast, ", ")}""""
+ }
+ }
+ }
+ +";"
+ }
+ }
+ }
+
+ val eachLine = fieldAst.annotations.find { it.nameAsString == Each }?.range?.orElse(null)?.end?.line
+ val perElementValidations = if (eachLine == null) emptyList() else fieldAst.annotations.filter {
+ it.nameAsString != Each &&
+ it.range.orElse(null)?.begin?.line?.let { it >= eachLine } ?: false
+ }
+
+ val Size = classRef("android.annotation.Size")
+ fieldAst.annotations.filterNot {
+ it.nameAsString == intOrStringDef?.AnnotationName
+ || it.nameAsString in knownNonValidationAnnotations
+ || it in perElementValidations
+ || it.args.any { (_, value) -> value is ArrayInitializerExpr }
+ }.forEach { annotation ->
+ appendValidateCall(annotation,
+ valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name)
+ }
+
+ if (perElementValidations.isNotEmpty()) {
+ +"int ${nameLowerCamel}Size = $sizeExpr;"
+ "for (int i = 0; i < ${nameLowerCamel}Size; i++) {" {
+ perElementValidations.forEach { annotation ->
+ appendValidateCall(annotation,
+ valueToValidate = elemAtIndexExpr("i"))
+ }
+ }
+ }
+}
+
+fun ClassPrinter.appendValidateCall(annotation: AnnotationExpr, valueToValidate: String) {
+ val validate = memberRef("com.android.internal.util.AnnotationValidations.validate")
+ "$validate(" {
+ !"${annotation.nameAsString}.class, null, $valueToValidate"
+ annotation.args.forEach { name, value ->
+ !",\n\"$name\", $value"
+ }
+ }
+ +";"
+}
+
+private fun ClassPrinter.generateOnConstructedCallback(prefix: String = "") {
+ +""
+ val call = "${prefix}onConstructed();"
+ if (hasMethod("onConstructed")) {
+ +call
+ } else {
+ +"// $call // You can define this method to get a callback"
+ }
+}
+
+fun ClassPrinter.generateForEachField() {
+ val specializations = listOf("Object", "int")
+ val usedSpecializations = fields.map { if (it.Type in specializations) it.Type else "Object" }
+ val usedSpecializationsSet = usedSpecializations.toSet()
+
+ val PerObjectFieldAction = classRef("com.android.internal.util.DataClass.PerObjectFieldAction")
+
+ +GENERATED_MEMBER_HEADER
+ "void forEachField(" {
+ usedSpecializationsSet.toList().forEachLastAware { specType, isLast ->
+ val SpecType = specType.capitalize()
+ val ActionClass = classRef("com.android.internal.util.DataClass.Per${SpecType}FieldAction")
+ +"@$NonNull $ActionClass<$ClassType> action$SpecType${if_(!isLast, ",")}"
+ }
+ }; " {" {
+ usedSpecializations.forEachIndexed { i, specType ->
+ val SpecType = specType.capitalize()
+ fields[i].apply {
+ +"action$SpecType.accept$SpecType(this, \"$nameLowerCamel\", $name);"
+ }
+ }
+ }
+
+ if (usedSpecializationsSet.size > 1) {
+ +"/** @deprecated May cause boxing allocations - use with caution! */"
+ +"@Deprecated"
+ +GENERATED_MEMBER_HEADER
+ "void forEachField(@$NonNull $PerObjectFieldAction<$ClassType> action)" {
+ fields.forEachApply {
+ +"action.acceptObject(this, \"$nameLowerCamel\", $name);"
+ }
+ }
+ }
+}
+
+fun ClassPrinter.generateMetadata(file: File) {
+ "@$DataClassGenerated(" {
+ +"time = ${System.currentTimeMillis()}L,"
+ +"codegenVersion = \"$CODEGEN_VERSION\","
+ +"sourceFile = \"${file.relativeTo(File(System.getenv("ANDROID_BUILD_TOP")))}\","
+ +"inputSignatures = \"${getInputSignatures().joinToString("\\n")}\""
+ }
+ +""
+ +"@Deprecated"
+ +"private void __metadata() {}\n"
+} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/ImportsProvider.kt b/tools/codegen/src/com/android/codegen/ImportsProvider.kt
new file mode 100644
index 000000000000..27dd9587db25
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/ImportsProvider.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.codegen
+
+import com.github.javaparser.ast.CompilationUnit
+
+/**
+ * Mixin for optionally shortening references based on existing imports
+ */
+interface ImportsProvider {
+
+ abstract val fileAst: CompilationUnit
+
+ val NonNull: String get() { return classRef("android.annotation.NonNull") }
+ val NonEmpty: String get() { return classRef("android.annotation.NonEmpty") }
+ val Nullable: String get() { return classRef("android.annotation.Nullable") }
+ val TextUtils: String get() { return classRef("android.text.TextUtils") }
+ val LinkedHashMap: String get() { return classRef("java.util.LinkedHashMap") }
+ val Collections: String get() { return classRef("java.util.Collections") }
+ val Preconditions: String get() { return classRef("com.android.internal.util.Preconditions") }
+ val ArrayList: String get() { return classRef("java.util.ArrayList") }
+ val DataClass: String get() { return classRef("com.android.internal.util.DataClass") }
+ val DataClassEnum: String get() { return classRef("com.android.internal.util.DataClass.Enum") }
+ val ParcelWith: String get() { return classRef("com.android.internal.util.DataClass.ParcelWith") }
+ val PluralOf: String get() { return classRef("com.android.internal.util.DataClass.PluralOf") }
+ val Each: String get() { return classRef("com.android.internal.util.DataClass.Each") }
+ val MaySetToNull: String get() { return classRef("com.android.internal.util.DataClass.MaySetToNull") }
+ val DataClassGenerated: String get() { return classRef("com.android.internal.util.DataClass.Generated") }
+ val DataClassSuppressConstDefs: String get() { return classRef("com.android.internal.util.DataClass.SuppressConstDefsGeneration") }
+ val DataClassSuppress: String get() { return classRef("com.android.internal.util.DataClass.Suppress") }
+ val GeneratedMember: String get() { return classRef("com.android.internal.util.DataClass.Generated.Member") }
+ val Parcelling: String get() { return classRef("com.android.internal.util.Parcelling") }
+ val Parcelable: String get() { return classRef("android.os.Parcelable") }
+ val Parcel: String get() { return classRef("android.os.Parcel") }
+ val UnsupportedAppUsage: String get() { return classRef("android.compat.annotation.UnsupportedAppUsage") }
+
+ /**
+ * Optionally shortens a class reference if there's a corresponding import present
+ */
+ fun classRef(fullName: String): String {
+
+ val pkg = fullName.substringBeforeLast(".")
+ val simpleName = fullName.substringAfterLast(".")
+ if (fileAst.imports.any { imprt ->
+ imprt.nameAsString == fullName
+ || (imprt.isAsterisk && imprt.nameAsString == pkg)
+ }) {
+ return simpleName
+ } else {
+ val outerClass = pkg.substringAfterLast(".", "")
+ if (outerClass.firstOrNull()?.isUpperCase() == true) {
+ return classRef(pkg) + "." + simpleName
+ }
+ }
+ return fullName
+ }
+
+ /** @see classRef */
+ fun memberRef(fullName: String): String {
+ val className = fullName.substringBeforeLast(".")
+ val methodName = fullName.substringAfterLast(".")
+ return if (fileAst.imports.any {
+ it.isStatic
+ && (it.nameAsString == fullName
+ || (it.isAsterisk && it.nameAsString == className))
+ }) {
+ className.substringAfterLast(".") + "." + methodName
+ } else {
+ classRef(className) + "." + methodName
+ }
+ }
+}
+
+/** @see classRef */
+inline fun <reified T : Any> ImportsProvider.classRef(): String {
+ return classRef(T::class.java.name)
+} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
new file mode 100644
index 000000000000..d6953c00fc0b
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
@@ -0,0 +1,151 @@
+package com.android.codegen
+
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.expr.*
+import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations
+import com.github.javaparser.ast.type.ClassOrInterfaceType
+import com.github.javaparser.ast.type.Type
+
+
+fun ClassPrinter.getInputSignatures(): List<String> {
+ return generateInputSignaturesForClass(classAst) +
+ annotationToString(classAst.annotations.find { it.nameAsString == DataClass }) +
+ generateInputSignaturesForClass(customBaseBuilderAst)
+}
+
+private fun ClassPrinter.generateInputSignaturesForClass(classAst: ClassOrInterfaceDeclaration?): List<String> {
+ if (classAst == null) return emptyList()
+
+ return classAst.fields.map { fieldAst ->
+ buildString {
+ append(fieldAst.modifiers.joinToString(" ") { it.keyword.asString() })
+ append(" ")
+ append(annotationsToString(fieldAst))
+ append(" ")
+ append(getFullClassName(fieldAst.commonType))
+ append(" ")
+ append(fieldAst.variables.joinToString(", ") { it.nameAsString })
+ }
+ } + classAst.methods.map { methodAst ->
+ buildString {
+ append(methodAst.modifiers.joinToString(" ") { it.keyword.asString() })
+ append(" ")
+ append(annotationsToString(methodAst))
+ append(" ")
+ append(getFullClassName(methodAst.type))
+ append(" ")
+ append(methodAst.nameAsString)
+ append("(")
+ append(methodAst.parameters.joinToString(",") { getFullClassName(it.type) })
+ append(")")
+ }
+ } + ("class ${classAst.nameAsString}" +
+ " extends ${classAst.extendedTypes.map { getFullClassName(it) }.ifEmpty { listOf("java.lang.Object") }.joinToString(", ")}" +
+ " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]")
+}
+
+private fun ClassPrinter.annotationsToString(annotatedAst: NodeWithAnnotations<*>): String {
+ return annotatedAst
+ .annotations
+ .groupBy { it.nameAsString } // dedupe annotations by name (javaparser bug?)
+ .values
+ .joinToString(" ") {
+ annotationToString(it[0])
+ }
+}
+
+private fun ClassPrinter.annotationToString(ann: AnnotationExpr?): String {
+ if (ann == null) return ""
+ return buildString {
+ append("@")
+ append(getFullClassName(ann.nameAsString))
+ if (ann is MarkerAnnotationExpr) return@buildString
+
+ append("(")
+
+ when (ann) {
+ is SingleMemberAnnotationExpr -> {
+ appendExpr(this, ann.memberValue)
+ }
+ is NormalAnnotationExpr -> {
+ ann.pairs.forEachLastAware { pair, isLast ->
+ append(pair.nameAsString)
+ append("=")
+ appendExpr(this, pair.value)
+ if (!isLast) append(", ")
+ }
+ }
+ }
+
+ append(")")
+ }.replace("\"", "\\\"")
+}
+
+private fun ClassPrinter.appendExpr(sb: StringBuilder, ex: Expression?) {
+ when (ex) {
+ is ClassExpr -> sb.append(getFullClassName(ex.typeAsString)).append(".class")
+ is IntegerLiteralExpr -> sb.append(ex.asInt()).append("L")
+ is LongLiteralExpr -> sb.append(ex.asLong()).append("L")
+ is DoubleLiteralExpr -> sb.append(ex.asDouble())
+ is ArrayInitializerExpr -> {
+ sb.append("{")
+ ex.values.forEachLastAware { arrayElem, isLast ->
+ appendExpr(sb, arrayElem)
+ if (!isLast) sb.append(", ")
+ }
+ sb.append("}")
+ }
+ else -> sb.append(ex)
+ }
+}
+
+private fun ClassPrinter.getFullClassName(type: Type): String {
+ return if (type is ClassOrInterfaceType) {
+
+ getFullClassName(buildString {
+ type.scope.ifPresent { append(it).append(".") }
+ append(type.nameAsString)
+ }) + (type.typeArguments.orElse(null)?.let { args -> args.joinToString(",") {getFullClassName(it)}}?.let { "<$it>" } ?: "")
+ } else getFullClassName(type.asString())
+}
+
+private fun ClassPrinter.getFullClassName(className: String): String {
+ if (className.endsWith("[]")) return getFullClassName(className.removeSuffix("[]")) + "[]"
+
+ if (className.matches("\\.[a-z]".toRegex())) return className //qualified name
+
+ if ("." in className) return getFullClassName(className.substringBeforeLast(".")) + "." + className.substringAfterLast(".")
+
+ fileAst.imports.find { imp ->
+ imp.nameAsString.endsWith(".$className")
+ }?.nameAsString?.let { return it }
+
+ val thisPackagePrefix = fileAst.packageDeclaration.map { it.nameAsString + "." }.orElse("")
+ val thisClassPrefix = thisPackagePrefix + classAst.nameAsString + "."
+
+ if (classAst.nameAsString == className) return thisPackagePrefix + classAst.nameAsString
+
+ nestedClasses.find {
+ it.nameAsString == className
+ }?.let { return thisClassPrefix + it.nameAsString }
+
+ if (className == CANONICAL_BUILDER_CLASS || className == BASE_BUILDER_CLASS) {
+ return thisClassPrefix + className
+ }
+
+ constDefs.find { it.AnnotationName == className }?.let { return thisClassPrefix + className }
+
+ if (tryOrNull { Class.forName("java.lang.$className") } != null) {
+ return "java.lang.$className"
+ }
+
+ if (className[0].isLowerCase()) return className //primitive
+
+ return thisPackagePrefix + className
+}
+
+private inline fun <T> tryOrNull(f: () -> T?) = try {
+ f()
+} catch (e: Exception) {
+ null
+}
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
new file mode 100755
index 000000000000..4b508d022991
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -0,0 +1,136 @@
+package com.android.codegen
+
+import com.github.javaparser.JavaParser
+import java.io.File
+
+
+const val THIS_SCRIPT_LOCATION = ""
+const val GENERATED_WARNING_PREFIX = "Code below generated by $CODEGEN_NAME"
+const val GENERATED_END = "// End of generated code"
+const val INDENT_SINGLE = " "
+
+val PRIMITIVE_TYPES = listOf("byte", "short", "int", "long", "char", "float", "double", "boolean")
+val BOXED_PRIMITIVE_TYPES = PRIMITIVE_TYPES.map { it.capitalize() } - "Int" + "Integer" - "Char" + "Character"
+
+val BUILTIN_SPECIAL_PARCELLINGS = listOf("Pattern")
+
+const val FLAG_BUILDER_PROTECTED_SETTERS = "--builder-protected-setters"
+const val FLAG_NO_FULL_QUALIFIERS = "--no-full-qualifiers"
+
+val JAVA_PARSER = JavaParser()
+
+/** @see [FeatureFlag] */
+val USAGE = """
+Usage: $CODEGEN_NAME [--[PREFIX-]FEATURE...] JAVAFILE
+
+Generates boilerplade parcelable/data class code at the bottom of JAVAFILE, based o fields' declaration in the given JAVAFILE's top-level class
+
+FEATURE represents some generatable code, and can be among:
+${FeatureFlag.values().map { feature ->
+ " ${feature.kebabCase}" to feature.desc
+}.columnize(" - ")}
+
+And PREFIX can be:
+ <empty> - request to generate the feature
+ no - suppress generation of the feature
+ hidden - request to generate the feature with @hide
+
+Extra options:
+ --help - view this help
+ --update-only - auto-detect flags from the previously auto-generated comment within the file
+ $FLAG_NO_FULL_QUALIFIERS
+ - when referring to classes don't use package name prefix; handy with IDE auto-import
+ $FLAG_BUILDER_PROTECTED_SETTERS
+ - make builder's setters protected to expose them as public in a subclass on a whitelist basis
+
+
+Special field modifiers and annotations:
+ transient - ignore the field completely
+ @Nullable - support null value when parcelling, and never throw on null input
+ @NonNull - throw on null input and don't parcel the nullness bit for the field
+ @DataClass.Enum - parcel field as an enum value by ordinal
+ @DataClass.PluralOf(..) - provide a singular version of a collection field name to be used in the builder's 'addFoo(..)'
+ @DataClass.ParcelWith(..) - provide a custom Parcelling class, specifying the custom (un)parcelling logic for this field
+ = <initializer>; - provide default value and never throw if this field was not provided e.g. when using builder
+ /** ... */ - copy given javadoc on field's getters/setters/constructor params/builder setters etc.
+ @hide (in javadoc) - force field's getters/setters/withers/builder setters to be @hide-den if generated
+
+
+Special methods/etc. you can define:
+
+ <any auto-generatable method>
+ For any method to be generated, if a method with same name and argument types is already
+ defined, than that method will not be generated.
+ This allows you to override certain details on granular basis.
+
+ void onConstructed()
+ Will be called in constructor, after all the fields have been initialized.
+ This is a good place to put any custom validation logic that you may have
+
+ static class $CANONICAL_BUILDER_CLASS extends $BASE_BUILDER_CLASS
+ If a class extending $BASE_BUILDER_CLASS is specified, generated builder's setters will
+ return the provided $CANONICAL_BUILDER_CLASS type.
+ $BASE_BUILDER_CLASS's constructor(s) will be package-private to encourage using $CANONICAL_BUILDER_CLASS instead
+ This allows you to extend the generated builder, adding or overriding any methods you may want
+
+
+In addition, for any field mMyField(or myField) of type FieldType you can define the following methods:
+
+ void parcelMyField(Parcel dest, int flags)
+ Allows you to provide custom logic for storing mMyField into a Parcel
+
+ static FieldType unparcelMyField(Parcel in)
+ Allows you to provide custom logic to deserialize the value of mMyField from a Parcel
+
+ String myFieldToString()
+ Allows you to provide a custom toString representation of mMyField's value
+
+ FieldType lazyInitMyField()
+ Requests a lazy initialization in getMyField(), with the provided method being the constructor
+ You may additionally mark the fields as volatile to cause this to generate a thread-safe
+ double-check locking lazy initialization
+
+ FieldType defaultMyField()
+ Allows you to provide a default value to initialize the field to, in case an explicit one
+ was not provided.
+ This is an alternative to providing a field initializer that, unlike the initializer,
+ you can use with final fields.
+
+Version: $CODEGEN_VERSION
+
+Questions? Feedback?
+Contact: eugenesusla@
+Bug/feature request: http://go/codegen-bug
+
+Slides: http://go/android-codegen
+In-depth example: http://go/SampleDataClass
+"""
+
+fun main(args: Array<String>) {
+ if (args.contains("--help")) {
+ println(USAGE)
+ System.exit(0)
+ }
+ if (args.contains("--version")) {
+ println(CODEGEN_VERSION)
+ System.exit(0)
+ }
+ val file = File(args.last()).absoluteFile
+ val sourceLisnesOriginal = file.readLines()
+ val sourceLinesNoClosingBrace = sourceLisnesOriginal.dropLastWhile {
+ it.startsWith("}") || it.all(Char::isWhitespace)
+ }
+ val cliArgs = handleUpdateFlag(args, sourceLinesNoClosingBrace)
+
+ val fileInfo = FileInfo(sourceLisnesOriginal, cliArgs, file)
+ fileInfo.main()
+ file.writeText(fileInfo.stringBuilder.toString().mapLines { trimEnd() })
+}
+
+private fun handleUpdateFlag(cliArgs: Array<String>, sourceLines: List<String>): Array<String> {
+ if ("--update-only" in cliArgs
+ && sourceLines.none { GENERATED_WARNING_PREFIX in it || it.startsWith("@DataClass") }) {
+ System.exit(0)
+ }
+ return cliArgs - "--update-only"
+} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/Printer.kt b/tools/codegen/src/com/android/codegen/Printer.kt
new file mode 100644
index 000000000000..b30e3f68b307
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/Printer.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.codegen
+
+/**
+ * Mixin for syntactic sugar around indent-aware printing into [stringBuilder]
+ */
+interface Printer<SELF: Printer<SELF>> {
+
+ val stringBuilder: StringBuilder
+
+ var currentIndent: String
+
+ fun pushIndent() {
+ currentIndent += INDENT_SINGLE
+ }
+
+ fun popIndent() {
+ currentIndent = if (currentIndent.length >= INDENT_SINGLE.length) {
+ currentIndent.substring(0, currentIndent.length - INDENT_SINGLE.length)
+ } else {
+ ""
+ }
+ }
+
+ fun backspace() = stringBuilder.setLength(stringBuilder.length - 1)
+ val lastChar get() = stringBuilder[stringBuilder.length - 1]
+
+ private fun appendRaw(s: String) {
+ stringBuilder.append(s)
+ }
+
+ fun append(s: String) {
+ if (s.isBlank() && s != "\n") {
+ appendRaw(s)
+ } else {
+ appendRaw(s.lines().map { line ->
+ if (line.startsWith(" *")) line else line.trimStart()
+ }.joinToString("\n$currentIndent"))
+ }
+ }
+
+ fun appendSameLine(s: String) {
+ while (lastChar.isWhitespace() || lastChar.isNewline()) {
+ backspace()
+ }
+ appendRaw(s)
+ }
+
+ fun rmEmptyLine() {
+ while (lastChar.isWhitespaceNonNewline()) backspace()
+ if (lastChar.isNewline()) backspace()
+ }
+
+ /**
+ * Syntactic sugar for:
+ * ```
+ * +"code()";
+ * ```
+ * to append the given string plus a newline
+ */
+ operator fun String.unaryPlus() = append("$this\n")
+
+ /**
+ * Syntactic sugar for:
+ * ```
+ * !"code()";
+ * ```
+ * to append the given string without a newline
+ */
+ operator fun String.not() = append(this)
+
+ /**
+ * Syntactic sugar for:
+ * ```
+ * ... {
+ * ...
+ * }+";"
+ * ```
+ * to append a ';' on same line after a block, and a newline afterwards
+ */
+ operator fun Unit.plus(s: String) {
+ appendSameLine(s)
+ +""
+ }
+
+ /**
+ * A multi-purpose syntactic sugar for appending the given string plus anything generated in
+ * the given [block], the latter with the appropriate deeper indent,
+ * and resetting the indent back to original at the end
+ *
+ * Usage examples:
+ *
+ * ```
+ * "if (...)" {
+ * ...
+ * }
+ * ```
+ * to append a corresponding if block appropriate indentation
+ *
+ * ```
+ * "void foo(...)" {
+ * ...
+ * }
+ * ```
+ * similar to the previous one, plus an extra empty line after the function body
+ *
+ * ```
+ * "void foo(" {
+ * <args code>
+ * }
+ * ```
+ * to use proper indentation for args code and close the bracket on same line at end
+ *
+ * ```
+ * "..." {
+ * ...
+ * }
+ * to use the correct indentation for inner code, resetting it at the end
+ */
+ operator fun String.invoke(block: SELF.() -> Unit) {
+ if (this == " {") {
+ appendSameLine(this)
+ } else {
+ append(this)
+ }
+ when {
+ endsWith("(") -> {
+ indentedBy(2, block)
+ appendSameLine(")")
+ }
+ endsWith("{") || endsWith(")") -> {
+ if (!endsWith("{")) appendSameLine(" {")
+ indentedBy(1, block)
+ +"}"
+ if ((endsWith(") {") || endsWith(")") || this == " {")
+ && !startsWith("synchronized")
+ && !startsWith("switch")
+ && !startsWith("if ")
+ && !contains(" else ")
+ && !contains("new ")
+ && !contains("return ")) {
+ +"" // extra line after function definitions
+ }
+ }
+ else -> indentedBy(2, block)
+ }
+ }
+
+ fun indentedBy(level: Int, block: SELF.() -> Unit) {
+ append("\n")
+ level times {
+ append(INDENT_SINGLE)
+ pushIndent()
+ }
+ (this as SELF).block()
+ level times { popIndent() }
+ rmEmptyLine()
+ +""
+ }
+
+ fun Iterable<FieldInfo>.forEachTrimmingTrailingComma(b: FieldInfo.() -> Unit) {
+ forEachApply {
+ b()
+ if (isLast) {
+ while (lastChar == ' ' || lastChar == '\n') backspace()
+ if (lastChar == ',') backspace()
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
new file mode 100644
index 000000000000..6f740cd663e3
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -0,0 +1,7 @@
+package com.android.codegen
+
+const val CODEGEN_NAME = "codegen"
+const val CODEGEN_VERSION = "1.0.15"
+
+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
new file mode 100644
index 000000000000..c19ae3b0b11f
--- /dev/null
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -0,0 +1,146 @@
+package com.android.codegen
+
+import com.github.javaparser.JavaParser
+import com.github.javaparser.ParseProblemException
+import com.github.javaparser.ParseResult
+import com.github.javaparser.ast.Node
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.TypeDeclaration
+import com.github.javaparser.ast.expr.*
+import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
+import java.time.Instant
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+import java.time.format.FormatStyle
+
+/**
+ * [Iterable.forEach] + [Any.apply]
+ */
+inline fun <T> Iterable<T>.forEachApply(block: T.() -> Unit) = forEach(block)
+
+inline fun String.mapLines(f: String.() -> String?) = lines().mapNotNull(f).joinToString("\n")
+inline fun <T> Iterable<T>.trim(f: T.() -> Boolean) = dropWhile(f).dropLastWhile(f)
+fun String.trimBlankLines() = lines().trim { isBlank() }.joinToString("\n")
+
+fun Char.isNewline() = this == '\n' || this == '\r'
+fun Char.isWhitespaceNonNewline() = isWhitespace() && !isNewline()
+
+fun if_(cond: Boolean, then: String) = if (cond) then else ""
+
+fun <T> Any?.as_(): T = this as T
+
+inline infix fun Int.times(action: () -> Unit) {
+ for (i in 1..this) action()
+}
+
+/**
+ * a bbb
+ * cccc dd
+ *
+ * ->
+ *
+ * a bbb
+ * cccc dd
+ */
+fun Iterable<Pair<String, String>>.columnize(separator: String = " | "): String {
+ val col1w = map { (a, _) -> a.length }.max()!!
+ val col2w = map { (_, b) -> b.length }.max()!!
+ return map { it.first.padEnd(col1w) + separator + it.second.padEnd(col2w) }.joinToString("\n")
+}
+
+fun String.hasUnbalancedCurlyBrace(): Boolean {
+ var braces = 0
+ forEach {
+ if (it == '{') braces++
+ if (it == '}') braces--
+ if (braces < 0) return true
+ }
+ return false
+}
+
+fun String.toLowerCamel(): String {
+ if (length >= 2 && this[0] == 'm' && this[1].isUpperCase()) return substring(1).capitalize()
+ if (all { it.isLetterOrDigit() }) return decapitalize()
+ return split("[^a-zA-Z0-9]".toRegex())
+ .map { it.toLowerCase().capitalize() }
+ .joinToString("")
+ .decapitalize()
+}
+
+inline fun <T> List<T>.forEachLastAware(f: (T, Boolean) -> Unit) {
+ forEachIndexed { index, t -> f(t, index == size - 1) }
+}
+
+@Suppress("UNCHECKED_CAST")
+fun <T : Expression> AnnotationExpr.singleArgAs()
+ = ((this as SingleMemberAnnotationExpr).memberValue as T)
+
+inline operator fun <reified T> Array<T>.minus(item: T) = toList().minus(item).toTypedArray()
+
+fun currentTimestamp() = DateTimeFormatter
+ .ofLocalizedDateTime(/* date */ FormatStyle.MEDIUM, /* time */ FormatStyle.LONG)
+ .withZone(ZoneId.systemDefault())
+ .format(Instant.now())
+
+val NodeWithModifiers<*>.visibility get() = accessSpecifier
+
+fun abort(msg: String): Nothing {
+ System.err.println("ERROR: $msg")
+ System.exit(1)
+ throw InternalError() // can't get here
+}
+
+fun bitAtExpr(bitIndex: Int) = "0x${java.lang.Long.toHexString(1L shl bitIndex)}"
+
+val AnnotationExpr.args: Map<String, Expression> get() = when (this) {
+ is MarkerAnnotationExpr -> emptyMap()
+ is SingleMemberAnnotationExpr -> mapOf("value" to memberValue)
+ is NormalAnnotationExpr -> pairs.map { it.name.asString() to it.value }.toMap()
+ else -> throw IllegalArgumentException("Unknown annotation expression: $this")
+}
+
+val TypeDeclaration<*>.nestedTypes get() = childNodes.filterIsInstance<TypeDeclaration<*>>()
+val TypeDeclaration<*>.nestedDataClasses get()
+ = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>()
+ .filter { it.annotations.any { it.nameAsString.endsWith("DataClass") } }
+val TypeDeclaration<*>.startLine get() = range.get()!!.begin.line
+
+inline fun <T> List<T>.forEachSequentialPair(action: (T, T?) -> Unit) {
+ forEachIndexed { index, t ->
+ action(t, getOrNull(index + 1))
+ }
+}
+
+fun <T: Node> parseJava(fn: JavaParser.(String) -> ParseResult<T>, source: String): T = try {
+ val parse = JAVA_PARSER.fn(source)
+ if (parse.problems.isNotEmpty()) {
+ throw parseFailed(
+ source,
+ desc = parse.problems.joinToString("\n"),
+ cause = parse.problems.mapNotNull { it.cause.orElse(null) }.firstOrNull())
+ }
+ parse.result.get()
+} catch (e: ParseProblemException) {
+ throw parseFailed(source, cause = e)
+}
+
+private fun parseFailed(source: String, cause: Throwable? = null, desc: String = ""): RuntimeException {
+ return RuntimeException("Failed to parse code:\n" +
+ source
+ .lines()
+ .mapIndexed { lnNum, ln -> "/*$lnNum*/$ln" }
+ .joinToString("\n") + "\n$desc",
+ cause)
+}
+
+var <T> MutableList<T>.last
+ get() = last()
+ set(value) {
+ if (isEmpty()) {
+ add(value)
+ } else {
+ this[size - 1] = value
+ }
+ }
+
+inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init) \ No newline at end of file
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index ba1267b28fbb..dd266ffe0f16 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -418,7 +418,7 @@ static bool generateSectionListCpp(Descriptor const* descriptor) {
}
const SectionFlags s = getSectionFlags(field);
- if (s.userdebug_and_eng_only()) {
+ if (s.userdebug_and_eng_only() || s.type() == SECTION_TEXT_DUMPSYS) {
printf("#if ALLOW_RESTRICTED_SECTIONS\n");
}
@@ -439,7 +439,9 @@ static bool generateSectionListCpp(Descriptor const* descriptor) {
printf(" NULL),\n");
break;
case SECTION_LOG:
- printf(" new LogSection(%d, %s),\n", field->number(), s.args().c_str());
+ printf(" new LogSection(%d, ", field->number());
+ splitAndPrint(s.args());
+ printf(" NULL),\n");
break;
case SECTION_GZIP:
printf(" new GZipSection(%d,", field->number());
@@ -450,8 +452,13 @@ static bool generateSectionListCpp(Descriptor const* descriptor) {
printf(" new TombstoneSection(%d, \"%s\"),\n", field->number(),
s.args().c_str());
break;
+ case SECTION_TEXT_DUMPSYS:
+ printf(" new TextDumpsysSection(%d, ", field->number());
+ splitAndPrint(s.args());
+ printf(" NULL),\n");
+ break;
}
- if (s.userdebug_and_eng_only()) {
+ if (s.userdebug_and_eng_only() || s.type() == SECTION_TEXT_DUMPSYS) {
printf("#endif\n");
}
}
diff --git a/tools/processors/staledataclass/Android.bp b/tools/processors/staledataclass/Android.bp
new file mode 100644
index 000000000000..58a7d346ce1f
--- /dev/null
+++ b/tools/processors/staledataclass/Android.bp
@@ -0,0 +1,29 @@
+
+java_plugin {
+ name: "staledataclass-annotation-processor",
+ processor_class: "android.processor.staledataclass.StaleDataclassProcessor",
+
+ java_resources: [
+ "META-INF/**/*",
+ ],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "codegen-version-info",
+ ],
+ // The --add-modules/exports flags below don't work for kotlinc yet, so pin this module to Java language level 8 (see b/139342589):
+ java_version: "1.8",
+ openjdk9: {
+ javacflags: [
+ "--add-modules=jdk.compiler",
+ "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+ "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ ],
+ },
+
+ use_tools_jar: true,
+}
diff --git a/tools/processors/staledataclass/META-INF/services/javax.annotation.processing.Processor b/tools/processors/staledataclass/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 000000000000..15ee6230c023
--- /dev/null
+++ b/tools/processors/staledataclass/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+android.processor.staledataclass.StaleDataclassProcessorOld
diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
new file mode 100644
index 000000000000..51faa49a86cc
--- /dev/null
+++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.processor.staledataclass
+
+import com.android.codegen.BASE_BUILDER_CLASS
+import com.android.codegen.CANONICAL_BUILDER_CLASS
+import com.android.codegen.CODEGEN_NAME
+import com.android.codegen.CODEGEN_VERSION
+import com.sun.tools.javac.code.Symbol
+import com.sun.tools.javac.code.Type
+import java.io.File
+import java.io.FileNotFoundException
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
+import javax.annotation.processing.SupportedAnnotationTypes
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.TypeElement
+import javax.tools.Diagnostic
+
+private const val STALE_FILE_THRESHOLD_MS = 1000
+private val WORKING_DIR = File(".").absoluteFile
+
+private const val DATACLASS_ANNOTATION_NAME = "com.android.internal.util.DataClass"
+private const val GENERATED_ANNOTATION_NAME = "com.android.internal.util.DataClass.Generated"
+private const val GENERATED_MEMBER_ANNOTATION_NAME
+ = "com.android.internal.util.DataClass.Generated.Member"
+
+
+@SupportedAnnotationTypes(DATACLASS_ANNOTATION_NAME, GENERATED_ANNOTATION_NAME)
+class StaleDataclassProcessor: AbstractProcessor() {
+
+ private var dataClassAnnotation: TypeElement? = null
+ private var generatedAnnotation: TypeElement? = null
+ private var repoRoot: File? = null
+
+ private val stale = mutableListOf<Stale>()
+
+ /**
+ * This is the main entry point in the processor, called by the compiler.
+ */
+ override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
+
+ if (generatedAnnotation == null) {
+ generatedAnnotation = annotations.find {
+ it.qualifiedName.toString() == GENERATED_ANNOTATION_NAME
+ }
+ }
+ if (dataClassAnnotation == null) {
+ dataClassAnnotation = annotations.find {
+ it.qualifiedName.toString() == DATACLASS_ANNOTATION_NAME
+ } ?: return true
+ }
+
+ val generatedAnnotatedElements = if (generatedAnnotation != null) {
+ roundEnv.getElementsAnnotatedWith(generatedAnnotation)
+ } else {
+ emptySet()
+ }
+ generatedAnnotatedElements.forEach {
+ processSingleFile(it)
+ }
+
+
+ val dataClassesWithoutGeneratedPart =
+ roundEnv.getElementsAnnotatedWith(dataClassAnnotation) -
+ generatedAnnotatedElements.map { it.enclosingElement }
+
+ dataClassesWithoutGeneratedPart.forEach { dataClass ->
+ stale += Stale(dataClass.toString(), file = null, lastGenerated = 0L)
+ }
+
+
+ if (!stale.isEmpty()) {
+ error("Stale generated dataclass(es) detected. " +
+ "Run the following command(s) to update them:" +
+ stale.joinToString("") { "\n" + it.refreshCmd })
+ }
+ return true
+ }
+
+ private fun elemToString(elem: Element): String {
+ return buildString {
+ append(elem.modifiers.joinToString(" ") { it.name.toLowerCase() }).append(" ")
+ append(elem.annotationMirrors.joinToString(" ")).append(" ")
+ if (elem is Symbol) {
+ if (elem.type is Type.MethodType) {
+ append((elem.type as Type.MethodType).returnType)
+ } else {
+ append(elem.type)
+ }
+ append(" ")
+ }
+ append(elem)
+ }
+ }
+
+ private fun processSingleFile(elementAnnotatedWithGenerated: Element) {
+
+ val classElement = elementAnnotatedWithGenerated.enclosingElement
+
+ val inputSignatures = computeSignaturesForClass(classElement)
+ .plus(computeSignaturesForClass(classElement.enclosedElements.find {
+ it.kind == ElementKind.CLASS
+ && !isGenerated(it)
+ && it.simpleName.toString() == BASE_BUILDER_CLASS
+ }))
+ .plus(computeSignaturesForClass(classElement.enclosedElements.find {
+ it.kind == ElementKind.CLASS
+ && !isGenerated(it)
+ && it.simpleName.toString() == CANONICAL_BUILDER_CLASS
+ }))
+ .plus(classElement
+ .annotationMirrors
+ .find { it.annotationType.toString() == DATACLASS_ANNOTATION_NAME }
+ .toString())
+ .toSet()
+
+ val annotationParams = elementAnnotatedWithGenerated
+ .annotationMirrors
+ .find { ann -> isGeneratedAnnotation(ann) }!!
+ .elementValues
+ .map { (k, v) -> k.simpleName.toString() to v.value }
+ .toMap()
+
+ val lastGenerated = annotationParams["time"] as Long
+ val codegenVersion = annotationParams["codegenVersion"] as String
+ val codegenMajorVersion = codegenVersion.substringBefore(".")
+ val sourceRelative = File(annotationParams["sourceFile"] as String)
+
+ val lastGenInputSignatures = (annotationParams["inputSignatures"] as String).lines().toSet()
+
+ if (repoRoot == null) {
+ repoRoot = generateSequence(WORKING_DIR) { it.parentFile }
+ .find { it.resolve(sourceRelative).isFile }
+ ?.canonicalFile
+ ?: throw FileNotFoundException(
+ "Failed to detect repository root: " +
+ "no parent of $WORKING_DIR contains $sourceRelative")
+ }
+
+ val source = repoRoot!!.resolve(sourceRelative)
+ val clazz = classElement.toString()
+
+ if (inputSignatures != lastGenInputSignatures) {
+ error(buildString {
+ append(sourceRelative).append(":\n")
+ append(" Added:\n").append((inputSignatures-lastGenInputSignatures).joinToString("\n"))
+ append("\n")
+ append(" Removed:\n").append((lastGenInputSignatures-inputSignatures).joinToString("\n"))
+ })
+ stale += Stale(clazz, source, lastGenerated)
+ }
+
+ if (codegenMajorVersion != CODEGEN_VERSION.substringBefore(".")) {
+ stale += Stale(clazz, source, lastGenerated)
+ }
+ }
+
+ private fun computeSignaturesForClass(classElement: Element?): List<String> {
+ if (classElement == null) return emptyList()
+ val type = classElement as TypeElement
+ return classElement
+ .enclosedElements
+ .filterNot {
+ it.kind == ElementKind.CLASS
+ || it.kind == ElementKind.CONSTRUCTOR
+ || it.kind == ElementKind.INTERFACE
+ || it.kind == ElementKind.ENUM
+ || it.kind == ElementKind.ANNOTATION_TYPE
+ || it.kind == ElementKind.INSTANCE_INIT
+ || it.kind == ElementKind.STATIC_INIT
+ || isGenerated(it)
+ }.map {
+ elemToString(it)
+ } + "class ${classElement.simpleName} extends ${type.superclass} implements [${type.interfaces.joinToString(", ")}]"
+ }
+
+ private fun isGenerated(it: Element) =
+ it.annotationMirrors.any { "Generated" in it.annotationType.toString() }
+
+ private fun error(msg: String) {
+ processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, msg)
+ }
+
+ private fun isGeneratedAnnotation(ann: AnnotationMirror): Boolean {
+ return generatedAnnotation!!.qualifiedName.toString() == ann.annotationType.toString()
+ }
+
+ data class Stale(val clazz: String, val file: File?, val lastGenerated: Long) {
+ val refreshCmd = if (file != null) {
+ "$CODEGEN_NAME $file"
+ } else {
+ var gotTopLevelCalssName = false
+ val filePath = clazz.split(".")
+ .takeWhile { word ->
+ if (!gotTopLevelCalssName && word[0].isUpperCase()) {
+ gotTopLevelCalssName = true
+ return@takeWhile true
+ }
+ !gotTopLevelCalssName
+ }.joinToString("/")
+ "find \$ANDROID_BUILD_TOP -path */$filePath.java -exec $CODEGEN_NAME {} \\;"
+ }
+ }
+
+ override fun getSupportedSourceVersion(): SourceVersion {
+ return SourceVersion.latest()
+ }
+} \ No newline at end of file
diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp
new file mode 100644
index 000000000000..ce551bd0cc10
--- /dev/null
+++ b/tools/protologtool/Android.bp
@@ -0,0 +1,33 @@
+java_library_host {
+ name: "protologtool-lib",
+ srcs: [
+ "src/com/android/protolog/tool/**/*.kt",
+ ],
+ static_libs: [
+ "protolog-common",
+ "javaparser",
+ "platformprotos",
+ "jsonlib",
+ ],
+}
+
+java_binary_host {
+ name: "protologtool",
+ manifest: "manifest.txt",
+ static_libs: [
+ "protologtool-lib",
+ ],
+}
+
+java_test_host {
+ name: "protologtool-tests",
+ test_suites: ["general-tests"],
+ srcs: [
+ "tests/**/*.kt",
+ ],
+ static_libs: [
+ "protologtool-lib",
+ "junit",
+ "mockito",
+ ],
+}
diff --git a/tools/protologtool/README.md b/tools/protologtool/README.md
new file mode 100644
index 000000000000..ba639570f88c
--- /dev/null
+++ b/tools/protologtool/README.md
@@ -0,0 +1,119 @@
+# ProtoLogTool
+
+Code transformation tool and viewer for ProtoLog.
+
+## What does it do?
+
+ProtoLogTool incorporates three different modes of operation:
+
+### Code transformation
+
+Command: `protologtool transform-protolog-calls
+ --protolog-class <protolog class name>
+ --protolog-impl-class <protolog implementation class name>
+ --loggroups-class <protolog groups class name>
+ --loggroups-jar <config jar path>
+ --output-srcjar <output.srcjar>
+ [<input.java>]`
+
+In this mode ProtoLogTool transforms every ProtoLog logging call in form of:
+```java
+ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
+```
+into:
+```java
+if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
+ int protoLogParam0 = value1;
+ String protoLogParam1 = String.valueOf(value2);
+ ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, 0b0100, "Format string %d %s or null", protoLogParam0, protoLogParam1);
+}
+```
+where `ProtoLog`, `ProtoLogImpl` and `ProtoLogGroup` are the classes provided as arguments
+ (can be imported, static imported or full path, wildcard imports are not allowed) and, `x` is the
+ logging method. The transformation is done on the source level. A hash is generated from the format
+ string, log level and log group name and inserted after the `ProtoLogGroup` argument. After the hash
+ we insert a bitmask specifying the types of logged parameters. The format string is replaced
+ by `null` if `ProtoLogGroup.GROUP_NAME.isLogToLogcat()` returns false. If `ProtoLogGroup.GROUP_NAME.isEnabled()`
+ returns false the log statement is removed entirely from the resultant code. The real generated code is inlined
+ and a number of new line characters is added as to preserve line numbering in file.
+
+Input is provided as a list of java source file names. Transformed source is saved to a single
+source jar file. The ProtoLogGroup class with all dependencies should be provided as a compiled
+jar file (config.jar).
+
+### Viewer config generation
+
+Command: `generate-viewer-config
+ --protolog-class <protolog class name>
+ --loggroups-class <protolog groups class name>
+ --loggroups-jar <config jar path>
+ --viewer-conf <viewer.json>
+ [<input.java>]`
+
+This command is similar in it's syntax to the previous one, only instead of creating a processed source jar
+it writes a viewer configuration file with following schema:
+```json
+{
+ "version": "1.0.0",
+ "messages": {
+ "123456": {
+ "message": "Format string %d %s",
+ "level": "ERROR",
+ "group": "GROUP_NAME",
+ "at": "com\/android\/server\/example\/Class.java"
+ }
+ },
+ "groups": {
+ "GROUP_NAME": {
+ "tag": "TestLog"
+ }
+ }
+}
+
+```
+
+### Binary log viewing
+
+Command: `read-log --viewer-conf <viewer.json> <wm_log.pb>`
+
+Reads the binary ProtoLog log file and outputs a human-readable LogCat-like text log.
+
+## What is ProtoLog?
+
+ProtoLog is a generic logging system created for the WindowManager project. It allows both binary and text logging
+and is tunable in runtime. It consists of 3 different submodules:
+* logging system built-in the Android app,
+* log viewer for reading binary logs,
+* a code processing tool.
+
+ProtoLog is designed to reduce both application size (and by that memory usage) and amount of resources needed
+for logging. This is achieved by replacing log message strings with their hashes and only loading to memory/writing
+full log messages when necessary.
+
+### Text logging
+
+For text-based logs Android LogCat is used as a backend. Message strings are loaded from a viewer config
+located on the device when needed.
+
+### Binary logging
+
+Binary logs are saved as Protocol Buffers file. They can be read using the ProtoLog tool or specialised
+viewer like Winscope.
+
+## How to use ProtoLog?
+
+### Adding a new logging group or log statement
+
+To add a new ProtoLogGroup simple create a new enum ProtoLogGroup member with desired parameters.
+
+To add a new logging statement just add a new call to ProtoLog.x where x is a log level.
+
+After doing any changes to logging groups or statements you should build the project and follow instructions printed by the tool.
+
+## How to change settings on device in runtime?
+Use the `adb shell su root cmd window logging` command. To get help just type
+`adb shell su root cmd window logging help`.
+
+
+
+
diff --git a/tools/protologtool/TEST_MAPPING b/tools/protologtool/TEST_MAPPING
new file mode 100644
index 000000000000..52b12dc26be9
--- /dev/null
+++ b/tools/protologtool/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "protologtool-tests"
+ }
+ ]
+}
diff --git a/tools/protologtool/manifest.txt b/tools/protologtool/manifest.txt
new file mode 100644
index 000000000000..cabebd51a2fa
--- /dev/null
+++ b/tools/protologtool/manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.protolog.tool.ProtoLogTool
diff --git a/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
new file mode 100644
index 000000000000..a52c8042582b
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.ImportDeclaration
+import com.github.javaparser.ast.expr.BinaryExpr
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.StringLiteralExpr
+
+object CodeUtils {
+ /**
+ * Returns a stable hash of a string.
+ * We reimplement String::hashCode() for readability reasons.
+ */
+ fun hash(position: String, messageString: String, logLevel: LogLevel, logGroup: LogGroup): Int {
+ return (position + messageString + logLevel.name + logGroup.name)
+ .map { c -> c.toInt() }.reduce { h, c -> h * 31 + c }
+ }
+
+ fun checkWildcardStaticImported(code: CompilationUnit, className: String, fileName: String) {
+ code.findAll(ImportDeclaration::class.java)
+ .forEach { im ->
+ if (im.isStatic && im.isAsterisk && im.name.toString() == className) {
+ throw IllegalImportException("Wildcard static imports of $className " +
+ "methods are not supported.", ParsingContext(fileName, im))
+ }
+ }
+ }
+
+ fun isClassImportedOrSamePackage(code: CompilationUnit, className: String): Boolean {
+ val packageName = className.substringBeforeLast('.')
+ return code.packageDeclaration.isPresent &&
+ code.packageDeclaration.get().nameAsString == packageName ||
+ code.findAll(ImportDeclaration::class.java)
+ .any { im ->
+ !im.isStatic &&
+ ((!im.isAsterisk && im.name.toString() == className) ||
+ (im.isAsterisk && im.name.toString() == packageName))
+ }
+ }
+
+ fun staticallyImportedMethods(code: CompilationUnit, className: String): Set<String> {
+ return code.findAll(ImportDeclaration::class.java)
+ .filter { im ->
+ im.isStatic &&
+ im.name.toString().substringBeforeLast('.') == className
+ }
+ .map { im -> im.name.toString().substringAfterLast('.') }.toSet()
+ }
+
+ fun concatMultilineString(expr: Expression, context: ParsingContext): String {
+ return when (expr) {
+ is StringLiteralExpr -> expr.asString()
+ is BinaryExpr -> when {
+ expr.operator == BinaryExpr.Operator.PLUS ->
+ concatMultilineString(expr.left, context) +
+ concatMultilineString(expr.right, context)
+ else -> throw InvalidProtoLogCallException(
+ "expected a string literal " +
+ "or concatenation of string literals, got: $expr", context)
+ }
+ else -> throw InvalidProtoLogCallException("expected a string literal " +
+ "or concatenation of string literals, got: $expr", context)
+ }
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
new file mode 100644
index 000000000000..bfbbf7a32c22
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import java.util.regex.Pattern
+
+class CommandOptions(args: Array<String>) {
+ companion object {
+ const val TRANSFORM_CALLS_CMD = "transform-protolog-calls"
+ const val GENERATE_CONFIG_CMD = "generate-viewer-config"
+ const val READ_LOG_CMD = "read-log"
+ private val commands = setOf(TRANSFORM_CALLS_CMD, GENERATE_CONFIG_CMD, READ_LOG_CMD)
+
+ private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
+ private const val PROTOLOGIMPL_CLASS_PARAM = "--protolog-impl-class"
+ private const val PROTOLOGCACHE_CLASS_PARAM = "--protolog-cache-class"
+ private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
+ private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
+ private const val VIEWER_CONFIG_JSON_PARAM = "--viewer-conf"
+ private const val OUTPUT_SOURCE_JAR_PARAM = "--output-srcjar"
+ private val parameters = setOf(PROTOLOG_CLASS_PARAM, PROTOLOGIMPL_CLASS_PARAM,
+ PROTOLOGCACHE_CLASS_PARAM, PROTOLOGGROUP_CLASS_PARAM, PROTOLOGGROUP_JAR_PARAM,
+ VIEWER_CONFIG_JSON_PARAM, OUTPUT_SOURCE_JAR_PARAM)
+
+ val USAGE = """
+ Usage: ${Constants.NAME} <command> [<args>]
+ Available commands:
+
+ $TRANSFORM_CALLS_CMD $PROTOLOG_CLASS_PARAM <class name> $PROTOLOGIMPL_CLASS_PARAM
+ <class name> $PROTOLOGCACHE_CLASS_PARAM
+ <class name> $PROTOLOGGROUP_CLASS_PARAM <class name> $PROTOLOGGROUP_JAR_PARAM
+ <config.jar> $OUTPUT_SOURCE_JAR_PARAM <output.srcjar> [<input.java>]
+ - processes java files replacing stub calls with logging code.
+
+ $GENERATE_CONFIG_CMD $PROTOLOG_CLASS_PARAM <class name> $PROTOLOGGROUP_CLASS_PARAM
+ <class name> $PROTOLOGGROUP_JAR_PARAM <config.jar> $VIEWER_CONFIG_JSON_PARAM
+ <viewer.json> [<input.java>]
+ - creates viewer config file from given java files.
+
+ $READ_LOG_CMD $VIEWER_CONFIG_JSON_PARAM <viewer.json> <wm_log.pb>
+ - translates a binary log to a readable format.
+ """.trimIndent()
+
+ private fun validateClassName(name: String): String {
+ if (!Pattern.matches("^([a-z]+[A-Za-z0-9]*\\.)+([A-Za-z0-9$]+)$", name)) {
+ throw InvalidCommandException("Invalid class name $name")
+ }
+ return name
+ }
+
+ private fun getParam(paramName: String, params: Map<String, String>): String {
+ if (!params.containsKey(paramName)) {
+ throw InvalidCommandException("Param $paramName required")
+ }
+ return params.getValue(paramName)
+ }
+
+ private fun validateNotSpecified(paramName: String, params: Map<String, String>): String {
+ if (params.containsKey(paramName)) {
+ throw InvalidCommandException("Unsupported param $paramName")
+ }
+ return ""
+ }
+
+ private fun validateJarName(name: String): String {
+ if (!name.endsWith(".jar")) {
+ throw InvalidCommandException("Jar file required, got $name instead")
+ }
+ return name
+ }
+
+ private fun validateSrcJarName(name: String): String {
+ if (!name.endsWith(".srcjar")) {
+ throw InvalidCommandException("Source jar file required, got $name instead")
+ }
+ return name
+ }
+
+ private fun validateJSONName(name: String): String {
+ if (!name.endsWith(".json")) {
+ throw InvalidCommandException("Json file required, got $name instead")
+ }
+ return name
+ }
+
+ private fun validateJavaInputList(list: List<String>): List<String> {
+ if (list.isEmpty()) {
+ throw InvalidCommandException("No java source input files")
+ }
+ list.forEach { name ->
+ if (!name.endsWith(".java")) {
+ throw InvalidCommandException("Not a java source file $name")
+ }
+ }
+ return list
+ }
+
+ private fun validateLogInputList(list: List<String>): String {
+ if (list.isEmpty()) {
+ throw InvalidCommandException("No log input file")
+ }
+ if (list.size > 1) {
+ throw InvalidCommandException("Only one log input file allowed")
+ }
+ return list[0]
+ }
+ }
+
+ val protoLogClassNameArg: String
+ val protoLogGroupsClassNameArg: String
+ val protoLogImplClassNameArg: String
+ val protoLogCacheClassNameArg: String
+ val protoLogGroupsJarArg: String
+ val viewerConfigJsonArg: String
+ val outputSourceJarArg: String
+ val logProtofileArg: String
+ val javaSourceArgs: List<String>
+ val command: String
+
+ init {
+ if (args.isEmpty()) {
+ throw InvalidCommandException("No command specified.")
+ }
+ command = args[0]
+ if (command !in commands) {
+ throw InvalidCommandException("Unknown command.")
+ }
+
+ val params: MutableMap<String, String> = mutableMapOf()
+ val inputFiles: MutableList<String> = mutableListOf()
+
+ var idx = 1
+ while (idx < args.size) {
+ if (args[idx].startsWith("--")) {
+ if (idx + 1 >= args.size) {
+ throw InvalidCommandException("No value for ${args[idx]}")
+ }
+ if (args[idx] !in parameters) {
+ throw InvalidCommandException("Unknown parameter ${args[idx]}")
+ }
+ if (args[idx + 1].startsWith("--")) {
+ throw InvalidCommandException("No value for ${args[idx]}")
+ }
+ if (params.containsKey(args[idx])) {
+ throw InvalidCommandException("Duplicated parameter ${args[idx]}")
+ }
+ params[args[idx]] = args[idx + 1]
+ idx += 2
+ } else {
+ inputFiles.add(args[idx])
+ idx += 1
+ }
+ }
+
+ when (command) {
+ TRANSFORM_CALLS_CMD -> {
+ protoLogClassNameArg = validateClassName(getParam(PROTOLOG_CLASS_PARAM, params))
+ protoLogGroupsClassNameArg = validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM,
+ params))
+ protoLogImplClassNameArg = validateClassName(getParam(PROTOLOGIMPL_CLASS_PARAM,
+ params))
+ protoLogCacheClassNameArg = validateClassName(getParam(PROTOLOGCACHE_CLASS_PARAM,
+ params))
+ protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
+ viewerConfigJsonArg = validateNotSpecified(VIEWER_CONFIG_JSON_PARAM, params)
+ outputSourceJarArg = validateSrcJarName(getParam(OUTPUT_SOURCE_JAR_PARAM, params))
+ javaSourceArgs = validateJavaInputList(inputFiles)
+ logProtofileArg = ""
+ }
+ GENERATE_CONFIG_CMD -> {
+ protoLogClassNameArg = validateClassName(getParam(PROTOLOG_CLASS_PARAM, params))
+ protoLogGroupsClassNameArg = validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM,
+ params))
+ protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
+ protoLogCacheClassNameArg = validateNotSpecified(PROTOLOGCACHE_CLASS_PARAM, params)
+ protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
+ viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
+ outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
+ javaSourceArgs = validateJavaInputList(inputFiles)
+ logProtofileArg = ""
+ }
+ READ_LOG_CMD -> {
+ protoLogClassNameArg = validateNotSpecified(PROTOLOG_CLASS_PARAM, params)
+ protoLogGroupsClassNameArg = validateNotSpecified(PROTOLOGGROUP_CLASS_PARAM, params)
+ protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
+ protoLogCacheClassNameArg = validateNotSpecified(PROTOLOGCACHE_CLASS_PARAM, params)
+ protoLogGroupsJarArg = validateNotSpecified(PROTOLOGGROUP_JAR_PARAM, params)
+ viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
+ outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
+ javaSourceArgs = listOf()
+ logProtofileArg = validateLogInputList(inputFiles)
+ }
+ else -> {
+ throw InvalidCommandException("Unknown command.")
+ }
+ }
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/Constants.kt b/tools/protologtool/src/com/android/protolog/tool/Constants.kt
new file mode 100644
index 000000000000..aa3e00f2f4db
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/Constants.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+object Constants {
+ const val NAME = "protologtool"
+ const val VERSION = "1.0.0"
+ const val IS_ENABLED_METHOD = "isEnabled"
+ const val ENUM_VALUES_METHOD = "values"
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogGroup.kt b/tools/protologtool/src/com/android/protolog/tool/LogGroup.kt
new file mode 100644
index 000000000000..587f7b9db016
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/LogGroup.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+data class LogGroup(
+ val name: String,
+ val enabled: Boolean,
+ val textEnabled: Boolean,
+ val tag: String
+)
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt b/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt
new file mode 100644
index 000000000000..e88f0f8231bd
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.github.javaparser.ast.Node
+
+enum class LogLevel {
+ DEBUG, VERBOSE, INFO, WARN, ERROR, WTF;
+
+ companion object {
+ fun getLevelForMethodName(name: String, node: Node, context: ParsingContext): LogLevel {
+ return when (name) {
+ "d" -> DEBUG
+ "v" -> VERBOSE
+ "i" -> INFO
+ "w" -> WARN
+ "e" -> ERROR
+ "wtf" -> WTF
+ else ->
+ throw InvalidProtoLogCallException("Unknown log level $name in $node", context)
+ }
+ }
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
new file mode 100644
index 000000000000..a59038fc99a0
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+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
+
+/**
+ * Implements a simple parser/viewer for binary ProtoLog logs.
+ * A binary log is translated into Android "LogCat"-like text log.
+ */
+class LogParser(private val configParser: ViewerConfigParser) {
+ companion object {
+ private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+ private val magicNumber =
+ ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+ ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+ }
+
+ private fun printTime(time: Long, offset: Long, ps: PrintStream) {
+ ps.print(dateFormat.format(Date(time / 1000000 + offset)) + " ")
+ }
+
+ private fun printFormatted(
+ protoLogMessage: ProtoLogMessage,
+ configEntry: ViewerConfigParser.ConfigEntry,
+ ps: PrintStream
+ ) {
+ val strParmIt = protoLogMessage.strParamsList.iterator()
+ val longParamsIt = protoLogMessage.sint64ParamsList.iterator()
+ val doubleParamsIt = protoLogMessage.doubleParamsList.iterator()
+ val boolParamsIt = protoLogMessage.booleanParamsList.iterator()
+ val args = mutableListOf<Any>()
+ val format = configEntry.messageString
+ val argTypes = LogDataType.parseFormatString(format)
+ try {
+ argTypes.forEach {
+ when (it) {
+ LogDataType.BOOLEAN -> args.add(boolParamsIt.next())
+ LogDataType.LONG -> args.add(longParamsIt.next())
+ LogDataType.DOUBLE -> args.add(doubleParamsIt.next())
+ LogDataType.STRING -> args.add(strParmIt.next())
+ null -> throw NullPointerException()
+ }
+ }
+ } catch (ex: NoSuchElementException) {
+ throw InvalidFormatStringException("Invalid format string in config", ex)
+ }
+ if (strParmIt.hasNext() || longParamsIt.hasNext() ||
+ doubleParamsIt.hasNext() || boolParamsIt.hasNext()) {
+ throw RuntimeException("Invalid format string in config - no enough matchers")
+ }
+ val formatted = format.format(*(args.toTypedArray()))
+ ps.print("${configEntry.level} ${configEntry.tag}: $formatted\n")
+ }
+
+ private fun printUnformatted(protoLogMessage: ProtoLogMessage, ps: PrintStream, tag: String) {
+ ps.println("$tag: ${protoLogMessage.messageHash} - ${protoLogMessage.strParamsList}" +
+ " ${protoLogMessage.sint64ParamsList} ${protoLogMessage.doubleParamsList}" +
+ " ${protoLogMessage.booleanParamsList}")
+ }
+
+ fun parse(protoLogInput: InputStream, jsonConfigInput: InputStream, ps: PrintStream) {
+ val jsonReader = JsonReader(BufferedReader(InputStreamReader(jsonConfigInput)))
+ val config = configParser.parseConfig(jsonReader)
+ val protoLog = ProtoLogFileProto.parseFrom(protoLogInput)
+
+ if (protoLog.magicNumber != magicNumber) {
+ throw InvalidInputException("ProtoLog file magic number is invalid.")
+ }
+ if (protoLog.version != Constants.VERSION) {
+ throw InvalidInputException("ProtoLog file version not supported by this tool," +
+ " log version ${protoLog.version}, viewer version ${Constants.VERSION}")
+ }
+
+ protoLog.logList.forEach { log ->
+ printTime(log.elapsedRealtimeNanos, protoLog.realTimeToElapsedTimeOffsetMillis, ps)
+ if (log.messageHash !in config) {
+ printUnformatted(log, ps, "UNKNOWN")
+ } else {
+ val conf = config.getValue(log.messageHash)
+ try {
+ printFormatted(log, conf, ps)
+ } catch (ex: Exception) {
+ printUnformatted(log, ps, "INVALID")
+ }
+ }
+ }
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt b/tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt
new file mode 100644
index 000000000000..c6aedfc3093e
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.github.javaparser.ast.Node
+
+data class ParsingContext(val filePath: String, val lineNumber: Int) {
+ constructor(filePath: String, node: Node)
+ : this(filePath, if (node.range.isPresent) node.range.get().begin.line else -1)
+
+ constructor() : this("", -1)
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
new file mode 100644
index 000000000000..2181cf680f6c
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.FieldAccessExpr
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
+
+/**
+ * Helper class for visiting all ProtoLog calls.
+ * For every valid call in the given {@code CompilationUnit} a {@code ProtoLogCallVisitor} callback
+ * is executed.
+ */
+open class ProtoLogCallProcessor(
+ private val protoLogClassName: String,
+ private val protoLogGroupClassName: String,
+ private val groupMap: Map<String, LogGroup>
+) {
+ private val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
+ private val protoLogGroupSimpleClassName = protoLogGroupClassName.substringAfterLast('.')
+
+ private fun getLogGroupName(
+ expr: Expression,
+ isClassImported: Boolean,
+ staticImports: Set<String>,
+ fileName: String
+ ): String {
+ val context = ParsingContext(fileName, expr)
+ return when (expr) {
+ is NameExpr -> when {
+ expr.nameAsString in staticImports -> expr.nameAsString
+ else ->
+ throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
+ context)
+ }
+ is FieldAccessExpr -> when {
+ expr.scope.toString() == protoLogGroupClassName
+ || isClassImported &&
+ expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
+ else ->
+ throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
+ context)
+ }
+ else -> throw InvalidProtoLogCallException("Invalid group argument " +
+ "- must be ProtoLogGroup enum member reference: $expr", context)
+ }
+ }
+
+ private fun isProtoCall(
+ call: MethodCallExpr,
+ isLogClassImported: Boolean,
+ staticLogImports: Collection<String>
+ ): Boolean {
+ return call.scope.isPresent && call.scope.get().toString() == protoLogClassName ||
+ isLogClassImported && call.scope.isPresent &&
+ call.scope.get().toString() == protoLogSimpleClassName ||
+ !call.scope.isPresent && staticLogImports.contains(call.name.toString())
+ }
+
+ open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?, fileName: String):
+ CompilationUnit {
+ CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName)
+ CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName)
+
+ val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName)
+ val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName)
+ val isGroupClassImported = CodeUtils.isClassImportedOrSamePackage(code,
+ protoLogGroupClassName)
+ val staticGroupImports = CodeUtils.staticallyImportedMethods(code, protoLogGroupClassName)
+
+ code.findAll(MethodCallExpr::class.java)
+ .filter { call ->
+ isProtoCall(call, isLogClassImported, staticLogImports)
+ }.forEach { call ->
+ val context = ParsingContext(fileName, call)
+ if (call.arguments.size < 2) {
+ throw InvalidProtoLogCallException("Method signature does not match " +
+ "any ProtoLog method: $call", context)
+ }
+
+ val messageString = CodeUtils.concatMultilineString(call.getArgument(1),
+ context)
+ val groupNameArg = call.getArgument(0)
+ val groupName =
+ getLogGroupName(groupNameArg, isGroupClassImported,
+ staticGroupImports, fileName)
+ if (groupName !in groupMap) {
+ throw InvalidProtoLogCallException("Unknown group argument " +
+ "- not a ProtoLogGroup enum member: $call", context)
+ }
+
+ callVisitor?.processCall(call, messageString, LogLevel.getLevelForMethodName(
+ call.name.toString(), call, context), groupMap.getValue(groupName))
+ }
+ return code
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt
new file mode 100644
index 000000000000..aa58b69d61cb
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.github.javaparser.ast.expr.MethodCallExpr
+
+interface ProtoLogCallVisitor {
+ fun processCall(call: MethodCallExpr, messageString: String, level: LogLevel, group: LogGroup)
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
new file mode 100644
index 000000000000..75493b6427cb
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.android.protolog.tool.Constants.ENUM_VALUES_METHOD
+import com.android.server.protolog.common.IProtoLogGroup
+import java.io.File
+import java.net.URLClassLoader
+
+class ProtoLogGroupReader {
+ private fun getClassloaderForJar(jarPath: String): ClassLoader {
+ val jarFile = File(jarPath)
+ val url = jarFile.toURI().toURL()
+ return URLClassLoader(arrayOf(url), ProtoLogGroupReader::class.java.classLoader)
+ }
+
+ private fun getEnumValues(clazz: Class<*>): List<IProtoLogGroup> {
+ val valuesMethod = clazz.getMethod(ENUM_VALUES_METHOD)
+ @Suppress("UNCHECKED_CAST")
+ return (valuesMethod.invoke(null) as Array<IProtoLogGroup>).toList()
+ }
+
+ fun loadFromJar(jarPath: String, className: String): Map<String, LogGroup> {
+ try {
+ val classLoader = getClassloaderForJar(jarPath)
+ val clazz = classLoader.loadClass(className)
+ val values = getEnumValues(clazz)
+ return values.map { group ->
+ group.name() to
+ LogGroup(group.name(), group.isEnabled, group.isLogToLogcat, group.tag)
+ }.toMap()
+ } catch (ex: ReflectiveOperationException) {
+ throw RuntimeException("Unable to load ProtoLogGroup enum class", ex)
+ }
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
new file mode 100644
index 000000000000..3c55237ce443
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.android.protolog.tool.CommandOptions.Companion.USAGE
+import com.github.javaparser.ParseProblemException
+import com.github.javaparser.ParserConfiguration
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.CompilationUnit
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.OutputStream
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.jar.JarOutputStream
+import java.util.zip.ZipEntry
+import kotlin.system.exitProcess
+
+object ProtoLogTool {
+ private fun showHelpAndExit() {
+ println(USAGE)
+ exitProcess(-1)
+ }
+
+ private fun containsProtoLogText(source: String, protoLogClassName: String): Boolean {
+ val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
+ return source.contains(protoLogSimpleClassName)
+ }
+
+ private fun processClasses(command: CommandOptions) {
+ val groups = injector.readLogGroups(
+ command.protoLogGroupsJarArg,
+ command.protoLogGroupsClassNameArg)
+ val out = injector.fileOutputStream(command.outputSourceJarArg)
+ val outJar = JarOutputStream(out)
+ val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
+ command.protoLogGroupsClassNameArg, groups)
+
+ val executor = newThreadPool()
+
+ try {
+ command.javaSourceArgs.map { path ->
+ executor.submitCallable {
+ val transformer = SourceTransformer(command.protoLogImplClassNameArg,
+ command.protoLogCacheClassNameArg, processor)
+ val file = File(path)
+ val text = injector.readText(file)
+ val outSrc = try {
+ val code = tryParse(text, path)
+ if (containsProtoLogText(text, command.protoLogClassNameArg)) {
+ transformer.processClass(text, path, packagePath(file, code), code)
+ } else {
+ text
+ }
+ } catch (ex: ParsingException) {
+ // If we cannot parse this file, skip it (and log why). Compilation will
+ // fail in a subsequent build step.
+ injector.reportParseError(ex)
+ text
+ }
+ path to outSrc
+ }
+ }.map { future ->
+ val (path, outSrc) = future.get()
+ outJar.putNextEntry(ZipEntry(path))
+ outJar.write(outSrc.toByteArray())
+ outJar.closeEntry()
+ }
+ } finally {
+ executor.shutdown()
+ }
+
+ val cacheSplit = command.protoLogCacheClassNameArg.split(".")
+ val cacheName = cacheSplit.last()
+ val cachePackage = cacheSplit.dropLast(1).joinToString(".")
+ val cachePath = "gen/${cacheSplit.joinToString("/")}.java"
+
+ outJar.putNextEntry(ZipEntry(cachePath))
+ outJar.write(generateLogGroupCache(cachePackage, cacheName, groups,
+ command.protoLogImplClassNameArg, command.protoLogGroupsClassNameArg).toByteArray())
+
+ outJar.close()
+ out.close()
+ }
+
+ fun generateLogGroupCache(
+ cachePackage: String,
+ cacheName: String,
+ groups: Map<String, LogGroup>,
+ protoLogImplClassName: String,
+ protoLogGroupsClassName: String
+ ): String {
+ val fields = groups.values.map {
+ "public static boolean ${it.name}_enabled = false;"
+ }.joinToString("\n")
+
+ val updates = groups.values.map {
+ "${it.name}_enabled = " +
+ "$protoLogImplClassName.isEnabled($protoLogGroupsClassName.${it.name});"
+ }.joinToString("\n")
+
+ return """
+ package $cachePackage;
+
+ public class $cacheName {
+${fields.replaceIndent(" ")}
+
+ static {
+ $protoLogImplClassName.sCacheUpdater = $cacheName::update;
+ update();
+ }
+
+ static void update() {
+${updates.replaceIndent(" ")}
+ }
+ }
+ """.trimIndent()
+ }
+
+ private fun tryParse(code: String, fileName: String): CompilationUnit {
+ try {
+ return StaticJavaParser.parse(code)
+ } catch (ex: ParseProblemException) {
+ val problem = ex.problems.first()
+ throw ParsingException("Java parsing erro" +
+ "r: ${problem.verboseMessage}",
+ ParsingContext(fileName, problem.location.orElse(null)
+ ?.begin?.range?.orElse(null)?.begin?.line
+ ?: 0))
+ }
+ }
+
+ private fun viewerConf(command: CommandOptions) {
+ val groups = injector.readLogGroups(
+ command.protoLogGroupsJarArg,
+ command.protoLogGroupsClassNameArg)
+ val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
+ command.protoLogGroupsClassNameArg, groups)
+ val builder = ViewerConfigBuilder(processor)
+
+ val executor = newThreadPool()
+
+ try {
+ command.javaSourceArgs.map { path ->
+ executor.submitCallable {
+ val file = File(path)
+ val text = injector.readText(file)
+ if (containsProtoLogText(text, command.protoLogClassNameArg)) {
+ try {
+ val code = tryParse(text, path)
+ builder.findLogCalls(code, path, packagePath(file, code))
+ } catch (ex: ParsingException) {
+ // If we cannot parse this file, skip it (and log why). Compilation will
+ // fail in a subsequent build step.
+ injector.reportParseError(ex)
+ null
+ }
+ } else {
+ null
+ }
+ }
+ }.forEach { future ->
+ builder.addLogCalls(future.get() ?: return@forEach)
+ }
+ } finally {
+ executor.shutdown()
+ }
+
+ val out = injector.fileOutputStream(command.viewerConfigJsonArg)
+ out.write(builder.build().toByteArray())
+ out.close()
+ }
+
+ private fun packagePath(file: File, code: CompilationUnit): String {
+ val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
+ .get().nameAsString else ""
+ val packagePath = pack.replace('.', '/') + '/' + file.name
+ return packagePath
+ }
+
+ private fun read(command: CommandOptions) {
+ LogParser(ViewerConfigParser())
+ .parse(FileInputStream(command.logProtofileArg),
+ FileInputStream(command.viewerConfigJsonArg), System.out)
+ }
+
+ @JvmStatic
+ fun main(args: Array<String>) {
+ try {
+ val command = CommandOptions(args)
+ invoke(command)
+ } catch (ex: InvalidCommandException) {
+ println("\n${ex.message}\n")
+ showHelpAndExit()
+ } catch (ex: CodeProcessingException) {
+ println("\n${ex.message}\n")
+ exitProcess(1)
+ }
+ }
+
+ fun invoke(command: CommandOptions) {
+ StaticJavaParser.setConfiguration(ParserConfiguration().apply {
+ setLanguageLevel(ParserConfiguration.LanguageLevel.RAW)
+ setAttributeComments(false)
+ })
+
+ when (command.command) {
+ CommandOptions.TRANSFORM_CALLS_CMD -> processClasses(command)
+ CommandOptions.GENERATE_CONFIG_CMD -> viewerConf(command)
+ CommandOptions.READ_LOG_CMD -> read(command)
+ }
+ }
+
+ var injector = object : Injector {
+ override fun fileOutputStream(file: String) = FileOutputStream(file)
+ override fun readText(file: File) = file.readText()
+ override fun readLogGroups(jarPath: String, className: String) =
+ ProtoLogGroupReader().loadFromJar(jarPath, className)
+ override fun reportParseError(ex: ParsingException) {
+ println("\n${ex.message}\n")
+ }
+ }
+
+ interface Injector {
+ fun fileOutputStream(file: String): OutputStream
+ fun readText(file: File): String
+ fun readLogGroups(jarPath: String, className: String): Map<String, LogGroup>
+ fun reportParseError(ex: ParsingException)
+ }
+}
+
+private fun <T> ExecutorService.submitCallable(f: () -> T) = submit(f)
+
+private fun newThreadPool() = Executors.newFixedThreadPool(
+ Runtime.getRuntime().availableProcessors())
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
new file mode 100644
index 000000000000..36ea41129450
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.android.server.protolog.common.LogDataType
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.NodeList
+import com.github.javaparser.ast.body.VariableDeclarator
+import com.github.javaparser.ast.expr.BooleanLiteralExpr
+import com.github.javaparser.ast.expr.CastExpr
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.FieldAccessExpr
+import com.github.javaparser.ast.expr.IntegerLiteralExpr
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
+import com.github.javaparser.ast.expr.NullLiteralExpr
+import com.github.javaparser.ast.expr.SimpleName
+import com.github.javaparser.ast.expr.TypeExpr
+import com.github.javaparser.ast.expr.VariableDeclarationExpr
+import com.github.javaparser.ast.stmt.BlockStmt
+import com.github.javaparser.ast.stmt.ExpressionStmt
+import com.github.javaparser.ast.stmt.IfStmt
+import com.github.javaparser.ast.type.ArrayType
+import com.github.javaparser.ast.type.ClassOrInterfaceType
+import com.github.javaparser.ast.type.PrimitiveType
+import com.github.javaparser.ast.type.Type
+import com.github.javaparser.printer.PrettyPrinter
+import com.github.javaparser.printer.PrettyPrinterConfiguration
+
+class SourceTransformer(
+ protoLogImplClassName: String,
+ protoLogCacheClassName: String,
+ private val protoLogCallProcessor: ProtoLogCallProcessor
+) : ProtoLogCallVisitor {
+ override fun processCall(
+ call: MethodCallExpr,
+ messageString: String,
+ level: LogLevel,
+ group: LogGroup
+ ) {
+ // Input format: ProtoLog.e(GROUP, "msg %d", arg)
+ if (!call.parentNode.isPresent) {
+ // Should never happen
+ throw RuntimeException("Unable to process log call $call " +
+ "- no parent node in AST")
+ }
+ if (call.parentNode.get() !is ExpressionStmt) {
+ // Should never happen
+ throw RuntimeException("Unable to process log call $call " +
+ "- parent node in AST is not an ExpressionStmt")
+ }
+ val parentStmt = call.parentNode.get() as ExpressionStmt
+ if (!parentStmt.parentNode.isPresent) {
+ // Should never happen
+ throw RuntimeException("Unable to process log call $call " +
+ "- no grandparent node in AST")
+ }
+ val ifStmt: IfStmt
+ if (group.enabled) {
+ val hash = CodeUtils.hash(packagePath, messageString, level, group)
+ val newCall = call.clone()
+ if (!group.textEnabled) {
+ // Remove message string if text logging is not enabled by default.
+ // Out: ProtoLog.e(GROUP, null, arg)
+ newCall.arguments[1].replace(NameExpr("null"))
+ }
+ // Insert message string hash as a second argument.
+ // Out: ProtoLog.e(GROUP, 1234, null, arg)
+ newCall.arguments.add(1, IntegerLiteralExpr(hash))
+ val argTypes = LogDataType.parseFormatString(messageString)
+ val typeMask = LogDataType.logDataTypesToBitMask(argTypes)
+ // Insert bitmap representing which Number parameters are to be considered as
+ // floating point numbers.
+ // 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)
+ newCall.setScope(protoLogImplClassNode)
+ // Create a call to ProtoLog$Cache.GROUP_enabled
+ // Out: com.android.server.protolog.ProtoLog$Cache.GROUP_enabled
+ val isLogEnabled = FieldAccessExpr(protoLogCacheClassNode, "${group.name}_enabled")
+ if (argTypes.size != call.arguments.size - 2) {
+ throw InvalidProtoLogCallException(
+ "Number of arguments (${argTypes.size} does not mach format" +
+ " string in: $call", ParsingContext(path, call))
+ }
+ val blockStmt = BlockStmt()
+ if (argTypes.isNotEmpty()) {
+ // Assign every argument to a variable to check its type in compile time
+ // (this is assignment is optimized-out by dex tool, there is no runtime impact)/
+ // Out: long protoLogParam0 = arg
+ argTypes.forEachIndexed { idx, type ->
+ val varName = "protoLogParam$idx"
+ val declaration = VariableDeclarator(getASTTypeForDataType(type), varName,
+ getConversionForType(type)(newCall.arguments[idx + 4].clone()))
+ blockStmt.addStatement(ExpressionStmt(VariableDeclarationExpr(declaration)))
+ newCall.setArgument(idx + 4, NameExpr(SimpleName(varName)))
+ }
+ } else {
+ // Assign (Object[])null as the vararg parameter to prevent allocating an empty
+ // object array.
+ val nullArray = CastExpr(ArrayType(objectType), NullLiteralExpr())
+ newCall.addArgument(nullArray)
+ }
+ blockStmt.addStatement(ExpressionStmt(newCall))
+ // Create an IF-statement with the previously created condition.
+ // Out: if (com.android.server.protolog.ProtoLogImpl.isEnabled(GROUP)) {
+ // long protoLogParam0 = arg;
+ // com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
+ // }
+ ifStmt = IfStmt(isLogEnabled, blockStmt, null)
+ } else {
+ // Surround with if (false).
+ val newCall = parentStmt.clone()
+ ifStmt = IfStmt(BooleanLiteralExpr(false), BlockStmt(NodeList(newCall)), null)
+ newCall.setBlockComment(" ${group.name} is disabled ")
+ }
+ // Inline the new statement.
+ val printedIfStmt = inlinePrinter.print(ifStmt)
+ // Append blank lines to preserve line numbering in file (to allow debugging)
+ val parentRange = parentStmt.range.get()
+ val newLines = parentRange.end.line - parentRange.begin.line
+ val newStmt = printedIfStmt.substringBeforeLast('}') + ("\n".repeat(newLines)) + '}'
+ // pre-workaround code, see explanation below
+ /*
+ val inlinedIfStmt = StaticJavaParser.parseStatement(newStmt)
+ LexicalPreservingPrinter.setup(inlinedIfStmt)
+ // Replace the original call.
+ if (!parentStmt.replace(inlinedIfStmt)) {
+ // Should never happen
+ throw RuntimeException("Unable to process log call $call " +
+ "- unable to replace the call.")
+ }
+ */
+ /** Workaround for a bug in JavaParser (AST tree invalid after replacing a node when using
+ * LexicalPreservingPrinter (https://github.com/javaparser/javaparser/issues/2290).
+ * Replace the code below with the one commended-out above one the issue is resolved. */
+ if (!parentStmt.range.isPresent) {
+ // Should never happen
+ throw RuntimeException("Unable to process log call $call " +
+ "- unable to replace the call.")
+ }
+ val range = parentStmt.range.get()
+ val begin = range.begin.line - 1
+ val oldLines = processedCode.subList(begin, range.end.line)
+ val oldCode = oldLines.joinToString("\n")
+ val newCode = oldCode.replaceRange(
+ offsets[begin] + range.begin.column - 1,
+ oldCode.length - oldLines.lastOrNull()!!.length +
+ range.end.column + offsets[range.end.line - 1], newStmt)
+ newCode.split("\n").forEachIndexed { idx, line ->
+ offsets[begin + idx] += line.length - processedCode[begin + idx].length
+ processedCode[begin + idx] = line
+ }
+ }
+
+ private val inlinePrinter: PrettyPrinter
+ private val objectType = StaticJavaParser.parseClassOrInterfaceType("Object")
+
+ init {
+ val config = PrettyPrinterConfiguration()
+ config.endOfLineCharacter = " "
+ config.indentSize = 0
+ config.tabWidth = 1
+ inlinePrinter = PrettyPrinter(config)
+ }
+
+ companion object {
+ private val stringType: ClassOrInterfaceType =
+ StaticJavaParser.parseClassOrInterfaceType("String")
+
+ fun getASTTypeForDataType(type: Int): Type {
+ return when (type) {
+ LogDataType.STRING -> stringType.clone()
+ LogDataType.LONG -> PrimitiveType.longType()
+ LogDataType.DOUBLE -> PrimitiveType.doubleType()
+ LogDataType.BOOLEAN -> PrimitiveType.booleanType()
+ else -> {
+ // Should never happen.
+ throw RuntimeException("Invalid LogDataType")
+ }
+ }
+ }
+
+ fun getConversionForType(type: Int): (Expression) -> Expression {
+ return when (type) {
+ LogDataType.STRING -> { expr ->
+ MethodCallExpr(TypeExpr(StaticJavaParser.parseClassOrInterfaceType("String")),
+ SimpleName("valueOf"), NodeList(expr))
+ }
+ else -> { expr -> expr }
+ }
+ }
+ }
+
+ private val protoLogImplClassNode =
+ StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
+ private val protoLogCacheClassNode =
+ StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogCacheClassName)
+ private var processedCode: MutableList<String> = mutableListOf()
+ private var offsets: IntArray = IntArray(0)
+ /** The path of the file being processed, relative to $ANDROID_BUILD_TOP */
+ private var path: String = ""
+ /** The path of the file being processed, relative to the root package */
+ private var packagePath: String = ""
+
+ fun processClass(
+ code: String,
+ path: String,
+ packagePath: String,
+ compilationUnit: CompilationUnit =
+ StaticJavaParser.parse(code)
+ ): String {
+ this.path = path
+ this.packagePath = packagePath
+ processedCode = code.split('\n').toMutableList()
+ offsets = IntArray(processedCode.size)
+ protoLogCallProcessor.process(compilationUnit, this, path)
+ return processedCode.joinToString("\n")
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
new file mode 100644
index 000000000000..175c71ff810b
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.android.json.stream.JsonWriter
+import com.github.javaparser.ast.CompilationUnit
+import com.android.protolog.tool.Constants.VERSION
+import com.github.javaparser.ast.expr.MethodCallExpr
+import java.io.StringWriter
+
+class ViewerConfigBuilder(
+ private val processor: ProtoLogCallProcessor
+) {
+ private fun addLogCall(logCall: LogCall, context: ParsingContext) {
+ val group = logCall.logGroup
+ val messageString = logCall.messageString
+ if (group.enabled) {
+ val key = logCall.key()
+ if (statements.containsKey(key)) {
+ if (statements[key] != logCall) {
+ throw HashCollisionException(
+ "Please modify the log message \"$messageString\" " +
+ "or \"${statements[key]}\" - their hashes are equal.", context)
+ }
+ } else {
+ groups.add(group)
+ statements[key] = logCall
+ }
+ }
+ }
+
+ private val statements: MutableMap<Int, LogCall> = mutableMapOf()
+ private val groups: MutableSet<LogGroup> = mutableSetOf()
+
+ fun findLogCalls(
+ unit: CompilationUnit,
+ path: String,
+ packagePath: String
+ ): List<Pair<LogCall, ParsingContext>> {
+ val calls = mutableListOf<Pair<LogCall, ParsingContext>>()
+ val visitor = object : ProtoLogCallVisitor {
+ override fun processCall(
+ call: MethodCallExpr,
+ messageString: String,
+ level: LogLevel,
+ group: LogGroup
+ ) {
+ val logCall = LogCall(messageString, level, group, packagePath)
+ val context = ParsingContext(path, call)
+ calls.add(logCall to context)
+ }
+ }
+ processor.process(unit, visitor, path)
+
+ return calls
+ }
+
+ fun addLogCalls(calls: List<Pair<LogCall, ParsingContext>>) {
+ calls.forEach { (logCall, context) ->
+ addLogCall(logCall, context)
+ }
+ }
+
+ fun build(): String {
+ val stringWriter = StringWriter()
+ val writer = JsonWriter(stringWriter)
+ writer.setIndent(" ")
+ writer.beginObject()
+ writer.name("version")
+ writer.value(VERSION)
+ writer.name("messages")
+ writer.beginObject()
+ statements.toSortedMap().forEach { (key, value) ->
+ writer.name(key.toString())
+ writer.beginObject()
+ writer.name("message")
+ writer.value(value.messageString)
+ writer.name("level")
+ writer.value(value.logLevel.name)
+ writer.name("group")
+ writer.value(value.logGroup.name)
+ writer.name("at")
+ writer.value(value.position)
+ writer.endObject()
+ }
+ writer.endObject()
+ writer.name("groups")
+ writer.beginObject()
+ groups.toSortedSet(Comparator { o1, o2 -> o1.name.compareTo(o2.name) }).forEach { group ->
+ writer.name(group.name)
+ writer.beginObject()
+ writer.name("tag")
+ writer.value(group.tag)
+ writer.endObject()
+ }
+ writer.endObject()
+ writer.endObject()
+ stringWriter.buffer.append('\n')
+ return stringWriter.toString()
+ }
+
+ data class LogCall(
+ val messageString: String,
+ val logLevel: LogLevel,
+ val logGroup: LogGroup,
+ val position: String
+ ) {
+ fun key() = CodeUtils.hash(position, messageString, logLevel, logGroup)
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt
new file mode 100644
index 000000000000..7278db0094e6
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.android.json.stream.JsonReader
+
+open class ViewerConfigParser {
+ data class MessageEntry(
+ val messageString: String,
+ val level: String,
+ val groupName: String
+ )
+
+ fun parseMessage(jsonReader: JsonReader): MessageEntry {
+ jsonReader.beginObject()
+ var message: String? = null
+ var level: String? = null
+ var groupName: String? = null
+ while (jsonReader.hasNext()) {
+ when (jsonReader.nextName()) {
+ "message" -> message = jsonReader.nextString()
+ "level" -> level = jsonReader.nextString()
+ "group" -> groupName = jsonReader.nextString()
+ else -> jsonReader.skipValue()
+ }
+ }
+ jsonReader.endObject()
+ if (message.isNullOrBlank() || level.isNullOrBlank() || groupName.isNullOrBlank()) {
+ throw InvalidViewerConfigException("Invalid message entry in viewer config")
+ }
+ return MessageEntry(message, level, groupName)
+ }
+
+ data class GroupEntry(val tag: String)
+
+ fun parseGroup(jsonReader: JsonReader): GroupEntry {
+ jsonReader.beginObject()
+ var tag: String? = null
+ while (jsonReader.hasNext()) {
+ when (jsonReader.nextName()) {
+ "tag" -> tag = jsonReader.nextString()
+ else -> jsonReader.skipValue()
+ }
+ }
+ jsonReader.endObject()
+ if (tag.isNullOrBlank()) {
+ throw InvalidViewerConfigException("Invalid group entry in viewer config")
+ }
+ return GroupEntry(tag)
+ }
+
+ fun parseMessages(jsonReader: JsonReader): Map<Int, MessageEntry> {
+ val config: MutableMap<Int, MessageEntry> = mutableMapOf()
+ jsonReader.beginObject()
+ while (jsonReader.hasNext()) {
+ val key = jsonReader.nextName()
+ val hash = key.toIntOrNull()
+ ?: throw InvalidViewerConfigException("Invalid key in messages viewer config")
+ config[hash] = parseMessage(jsonReader)
+ }
+ jsonReader.endObject()
+ return config
+ }
+
+ fun parseGroups(jsonReader: JsonReader): Map<String, GroupEntry> {
+ val config: MutableMap<String, GroupEntry> = mutableMapOf()
+ jsonReader.beginObject()
+ while (jsonReader.hasNext()) {
+ val key = jsonReader.nextName()
+ config[key] = parseGroup(jsonReader)
+ }
+ jsonReader.endObject()
+ return config
+ }
+
+ data class ConfigEntry(val messageString: String, val level: String, val tag: String)
+
+ open fun parseConfig(jsonReader: JsonReader): Map<Int, ConfigEntry> {
+ var messages: Map<Int, MessageEntry>? = null
+ var groups: Map<String, GroupEntry>? = null
+ var version: String? = null
+
+ jsonReader.beginObject()
+ while (jsonReader.hasNext()) {
+ when (jsonReader.nextName()) {
+ "messages" -> messages = parseMessages(jsonReader)
+ "groups" -> groups = parseGroups(jsonReader)
+ "version" -> version = jsonReader.nextString()
+
+ else -> jsonReader.skipValue()
+ }
+ }
+ jsonReader.endObject()
+ if (messages == null || groups == null || version == null) {
+ throw InvalidViewerConfigException("Invalid config - definitions missing")
+ }
+ if (version != Constants.VERSION) {
+ throw InvalidViewerConfigException("Viewer config version not supported by this tool," +
+ " config version $version, viewer version ${Constants.VERSION}")
+ }
+ return messages.map { msg ->
+ msg.key to ConfigEntry(
+ msg.value.messageString, msg.value.level, groups[msg.value.groupName]?.tag
+ ?: throw InvalidViewerConfigException(
+ "Group definition missing for ${msg.value.groupName}"))
+ }.toMap()
+ }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/exceptions.kt b/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
new file mode 100644
index 000000000000..ae00df123353
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import java.lang.Exception
+
+open class CodeProcessingException(message: String, context: ParsingContext)
+ : Exception("Code processing error in ${context.filePath}:${context.lineNumber}:\n" +
+ " $message")
+
+class HashCollisionException(message: String, context: ParsingContext) :
+ CodeProcessingException(message, context)
+
+class IllegalImportException(message: String, context: ParsingContext) :
+ CodeProcessingException("Illegal import: $message", context)
+
+class InvalidProtoLogCallException(message: String, context: ParsingContext)
+ : CodeProcessingException("InvalidProtoLogCall: $message", context)
+
+class ParsingException(message: String, context: ParsingContext)
+ : CodeProcessingException(message, context)
+
+class InvalidViewerConfigException(message: String) : Exception(message)
+
+class InvalidInputException(message: String) : Exception(message)
+
+class InvalidCommandException(message: String) : Exception(message)
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
new file mode 100644
index 000000000000..b916f8f00a68
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.expr.BinaryExpr
+import com.github.javaparser.ast.expr.StringLiteralExpr
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class CodeUtilsTest {
+ @Test
+ fun hash() {
+ assertEquals(-1259556708, CodeUtils.hash("Test.java:50", "test",
+ LogLevel.DEBUG, LogGroup("test", true, true, "TAG")))
+ }
+
+ @Test
+ fun hash_changeLocation() {
+ assertEquals(15793504, CodeUtils.hash("Test.java:10", "test2",
+ LogLevel.DEBUG, LogGroup("test", true, true, "TAG")))
+ }
+
+ @Test
+ fun hash_changeLevel() {
+ assertEquals(-731772463, CodeUtils.hash("Test.java:50", "test",
+ LogLevel.ERROR, LogGroup("test", true, true, "TAG")))
+ }
+
+ @Test
+ fun hash_changeMessage() {
+ assertEquals(-2026343204, CodeUtils.hash("Test.java:50", "test2",
+ LogLevel.DEBUG, LogGroup("test", true, true, "TAG")))
+ }
+
+ @Test
+ fun hash_changeGroup() {
+ assertEquals(1607870166, CodeUtils.hash("Test.java:50", "test2",
+ LogLevel.DEBUG, LogGroup("test2", true, true, "TAG")))
+ }
+
+ @Test(expected = IllegalImportException::class)
+ fun checkWildcardStaticImported_true() {
+ val code = """package org.example.test;
+ import static org.example.Test.*;
+ """
+ CodeUtils.checkWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test", "")
+ }
+
+ @Test
+ fun checkWildcardStaticImported_notStatic() {
+ val code = """package org.example.test;
+ import org.example.Test.*;
+ """
+ CodeUtils.checkWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test", "")
+ }
+
+ @Test
+ fun checkWildcardStaticImported_differentClass() {
+ val code = """package org.example.test;
+ import static org.example.Test2.*;
+ """
+ CodeUtils.checkWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test", "")
+ }
+
+ @Test
+ fun checkWildcardStaticImported_notWildcard() {
+ val code = """package org.example.test;
+ import org.example.Test.test;
+ """
+ CodeUtils.checkWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test", "")
+ }
+
+ @Test
+ fun isClassImportedOrSamePackage_imported() {
+ val code = """package org.example.test;
+ import org.example.Test;
+ """
+ assertTrue(CodeUtils.isClassImportedOrSamePackage(
+ StaticJavaParser.parse(code), "org.example.Test"))
+ }
+
+ @Test
+ fun isClassImportedOrSamePackage_samePackage() {
+ val code = """package org.example.test;
+ """
+ assertTrue(CodeUtils.isClassImportedOrSamePackage(
+ StaticJavaParser.parse(code), "org.example.test.Test"))
+ }
+
+ @Test
+ fun isClassImportedOrSamePackage_false() {
+ val code = """package org.example.test;
+ import org.example.Test;
+ """
+ assertFalse(CodeUtils.isClassImportedOrSamePackage(
+ StaticJavaParser.parse(code), "org.example.Test2"))
+ }
+
+ @Test
+ fun staticallyImportedMethods_ab() {
+ val code = """
+ import static org.example.Test.a;
+ import static org.example.Test.b;
+ """
+ val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
+ "org.example.Test")
+ assertTrue(imported.containsAll(listOf("a", "b")))
+ assertEquals(2, imported.size)
+ }
+
+ @Test
+ fun staticallyImportedMethods_differentClass() {
+ val code = """
+ import static org.example.Test.a;
+ import static org.example.Test2.b;
+ """
+ val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
+ "org.example.Test")
+ assertTrue(imported.containsAll(listOf("a")))
+ assertEquals(1, imported.size)
+ }
+
+ @Test
+ fun staticallyImportedMethods_notStatic() {
+ val code = """
+ import static org.example.Test.a;
+ import org.example.Test.b;
+ """
+ val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
+ "org.example.Test")
+ assertTrue(imported.containsAll(listOf("a")))
+ assertEquals(1, imported.size)
+ }
+
+ @Test
+ fun concatMultilineString_single() {
+ val str = StringLiteralExpr("test")
+ val out = CodeUtils.concatMultilineString(str, ParsingContext())
+ assertEquals("test", out)
+ }
+
+ @Test
+ fun concatMultilineString_double() {
+ val str = """
+ "test" + "abc"
+ """
+ val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
+ val out = CodeUtils.concatMultilineString(code, ParsingContext())
+ assertEquals("testabc", out)
+ }
+
+ @Test
+ fun concatMultilineString_multiple() {
+ val str = """
+ "test" + "abc" + "1234" + "test"
+ """
+ val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
+ val out = CodeUtils.concatMultilineString(code, ParsingContext())
+ assertEquals("testabc1234test", out)
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
new file mode 100644
index 000000000000..cf36651c3e39
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class CommandOptionsTest {
+ companion object {
+ val TEST_JAVA_SRC = listOf(
+ "frameworks/base/services/core/java/com/android/server/wm/" +
+ "AccessibilityController.java",
+ "frameworks/base/services/core/java/com/android/server/wm/ActivityDisplay.java",
+ "frameworks/base/services/core/java/com/android/server/wm/" +
+ "ActivityMetricsLaunchObserver.java"
+ )
+ 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_JAR = "out/soong/.intermediates/frameworks/base/" +
+ "services/core/services.core.wm.protologgroups/android_common/javac/" +
+ "services.core.wm.protologgroups.jar"
+ private const val TEST_SRC_JAR = "out/soong/.temp/sbox175955373/" +
+ "services.core.wm.protolog.srcjar"
+ private const val TEST_VIEWER_JSON = "out/soong/.temp/sbox175955373/" +
+ "services.core.wm.protolog.json"
+ private const val TEST_LOG = "./test_log.pb"
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun noCommand() {
+ CommandOptions(arrayOf())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun invalidCommand() {
+ val testLine = "invalid"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test
+ fun transformClasses() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ val cmd = CommandOptions(testLine.split(' ').toTypedArray())
+ assertEquals(CommandOptions.TRANSFORM_CALLS_CMD, cmd.command)
+ assertEquals(TEST_PROTOLOG_CLASS, cmd.protoLogClassNameArg)
+ assertEquals(TEST_PROTOLOGIMPL_CLASS, cmd.protoLogImplClassNameArg)
+ assertEquals(TEST_PROTOLOGGROUP_CLASS, cmd.protoLogGroupsClassNameArg)
+ assertEquals(TEST_PROTOLOGGROUP_JAR, cmd.protoLogGroupsJarArg)
+ assertEquals(TEST_SRC_JAR, cmd.outputSourceJarArg)
+ assertEquals(TEST_JAVA_SRC, cmd.javaSourceArgs)
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noProtoLogClass() {
+ val testLine = "transform-protolog-calls " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noProtoLogImplClass() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noProtoLogCacheClass() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noProtoLogGroupClass() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noProtoLogGroupJar() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noOutJar() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ TEST_JAVA_SRC.joinToString(" ")
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noJavaInput() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidProtoLogClass() {
+ val testLine = "transform-protolog-calls --protolog-class invalid " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidProtoLogImplClass() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class invalid " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidProtoLogCacheClass() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class invalid " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidProtoLogGroupClass() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class invalid " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidProtoLogGroupJar() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar invalid.txt " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidOutJar() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar invalid.db ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_invalidJavaInput() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR invalid.py"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_unknownParam() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--unknown test --protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun transformClasses_noValue() {
+ val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--protolog-impl-class " +
+ "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test
+ fun generateConfig() {
+ val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--viewer-conf $TEST_VIEWER_JSON ${TEST_JAVA_SRC.joinToString(" ")}"
+ val cmd = CommandOptions(testLine.split(' ').toTypedArray())
+ assertEquals(CommandOptions.GENERATE_CONFIG_CMD, cmd.command)
+ assertEquals(TEST_PROTOLOG_CLASS, cmd.protoLogClassNameArg)
+ assertEquals(TEST_PROTOLOGGROUP_CLASS, cmd.protoLogGroupsClassNameArg)
+ assertEquals(TEST_PROTOLOGGROUP_JAR, cmd.protoLogGroupsJarArg)
+ assertEquals(TEST_VIEWER_JSON, cmd.viewerConfigJsonArg)
+ assertEquals(TEST_JAVA_SRC, cmd.javaSourceArgs)
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun generateConfig_noViewerConfig() {
+ val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ TEST_JAVA_SRC.joinToString(" ")
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test(expected = InvalidCommandException::class)
+ fun generateConfig_invalidViewerConfig() {
+ val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
+ "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+ "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+ "--viewer-conf invalid.yaml ${TEST_JAVA_SRC.joinToString(" ")}"
+ CommandOptions(testLine.split(' ').toTypedArray())
+ }
+
+ @Test
+ fun readLog() {
+ val testLine = "read-log --viewer-conf $TEST_VIEWER_JSON $TEST_LOG"
+ val cmd = CommandOptions(testLine.split(' ').toTypedArray())
+ assertEquals(CommandOptions.READ_LOG_CMD, cmd.command)
+ assertEquals(TEST_VIEWER_JSON, cmd.viewerConfigJsonArg)
+ assertEquals(TEST_LOG, cmd.logProtofileArg)
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
new file mode 100644
index 000000000000..dd8a0b1c50b4
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import org.junit.Assert
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.OutputStream
+import java.util.jar.JarInputStream
+
+class EndToEndTest {
+
+ @Test
+ fun e2e_transform() {
+ 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;
+
+ class Example {
+ void method() {
+ String argString = "hello";
+ int argInt = 123;
+ ProtoLog.d(GROUP, "Example: %s %d", argString, argInt);
+ }
+ }
+ """.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-cache-class",
+ "com.android.server.protolog.ProtoLog${"\$\$"}Cache",
+ "--loggroups-class", "com.android.server.wm.ProtoLogGroup",
+ "--loggroups-jar", "not_required.jar",
+ "--output-srcjar", "out.srcjar",
+ "frameworks/base/org/example/Example.java"))
+ )
+ val outSrcJar = assertLoadSrcJar(output, "out.srcjar")
+ assertTrue(" 2066303299," in outSrcJar["frameworks/base/org/example/Example.java"]!!)
+ }
+
+ @Test
+ fun e2e_viewerConfig() {
+ 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;
+
+ class Example {
+ void method() {
+ String argString = "hello";
+ int argInt = 123;
+ ProtoLog.d(GROUP, "Example: %s %d", argString, argInt);
+ }
+ }
+ """.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",
+ "--loggroups-jar", "not_required.jar",
+ "--viewer-conf", "out.json",
+ "frameworks/base/org/example/Example.java"))
+ )
+ val viewerConfigJson = assertLoadText(output, "out.json")
+ assertTrue("\"2066303299\"" in viewerConfigJson)
+ }
+
+ private fun assertLoadSrcJar(
+ outputs: Map<String, ByteArray>,
+ path: String
+ ): Map<String, String> {
+ val out = outputs[path] ?: fail("$path not in outputs (${outputs.keys})")
+
+ val sources = mutableMapOf<String, String>()
+ JarInputStream(ByteArrayInputStream(out)).use { jarStream ->
+ var entry = jarStream.nextJarEntry
+ while (entry != null) {
+ if (entry.name.endsWith(".java")) {
+ sources[entry.name] = jarStream.reader().readText()
+ }
+ entry = jarStream.nextJarEntry
+ }
+ }
+ return sources
+ }
+
+ private fun assertLoadText(outputs: Map<String, ByteArray>, path: String): String {
+ val out = outputs[path] ?: fail("$path not in outputs (${outputs.keys})")
+ return out.toString(Charsets.UTF_8)
+ }
+
+ fun run(
+ src: Pair<String, String>,
+ logGroup: LogGroup,
+ commandOptions: CommandOptions
+ ): Map<String, ByteArray> {
+ val outputs = mutableMapOf<String, ByteArrayOutputStream>()
+
+ ProtoLogTool.injector = object : ProtoLogTool.Injector {
+ override fun fileOutputStream(file: String): OutputStream =
+ ByteArrayOutputStream().also { outputs[file] = it }
+
+ override fun readText(file: File): String {
+ if (file.path == src.first) {
+ return src.second
+ }
+ throw FileNotFoundException("expected: ${src.first}, but was $file")
+ }
+
+ override fun readLogGroups(jarPath: String, className: String) = mapOf(
+ logGroup.name to logGroup)
+
+ override fun reportParseError(ex: ParsingException) = throw AssertionError(ex)
+ }
+
+ ProtoLogTool.invoke(commandOptions)
+
+ return outputs.mapValues { it.value.toByteArray() }
+ }
+
+ fun fail(message: String): Nothing = Assert.fail(message) as Nothing
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
new file mode 100644
index 000000000000..04a3bfa499d8
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.android.json.stream.JsonReader
+import com.android.server.protolog.ProtoLogMessage
+import com.android.server.protolog.ProtoLogFileProto
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.io.PrintStream
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+class LogParserTest {
+ private val configParser: ViewerConfigParser = mock(ViewerConfigParser::class.java)
+ private val parser = LogParser(configParser)
+ private var config: MutableMap<Int, ViewerConfigParser.ConfigEntry> = mutableMapOf()
+ private var outStream: OutputStream = ByteArrayOutputStream()
+ private var printStream: PrintStream = PrintStream(outStream)
+ private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+
+ @Before
+ fun init() {
+ Mockito.`when`(configParser.parseConfig(any(JsonReader::class.java))).thenReturn(config)
+ }
+
+ private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+ private fun getConfigDummyStream(): InputStream {
+ return "".byteInputStream()
+ }
+
+ private fun buildProtoInput(logBuilder: ProtoLogFileProto.Builder): InputStream {
+ logBuilder.setVersion(Constants.VERSION)
+ logBuilder.magicNumber =
+ ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+ ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+ return logBuilder.build().toByteArray().inputStream()
+ }
+
+ private fun testDate(timeMS: Long): String {
+ return dateFormat.format(Date(timeMS))
+ }
+
+ @Test
+ fun parse() {
+ config[70933285] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b",
+ "ERROR", "WindowManager")
+
+ val logBuilder = ProtoLogFileProto.newBuilder()
+ val logMessageBuilder = ProtoLogMessage.newBuilder()
+ logMessageBuilder
+ .setMessageHash(70933285)
+ .setElapsedRealtimeNanos(0)
+ .addBooleanParams(true)
+ logBuilder.addLog(logMessageBuilder.build())
+
+ parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+ assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: true\n",
+ outStream.toString())
+ }
+
+ @Test
+ fun parse_formatting() {
+ config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
+ " %x %e %g %s %f", "ERROR", "WindowManager")
+
+ val logBuilder = ProtoLogFileProto.newBuilder()
+ val logMessageBuilder = ProtoLogMessage.newBuilder()
+ logMessageBuilder
+ .setMessageHash(123)
+ .setElapsedRealtimeNanos(0)
+ .addBooleanParams(true)
+ .addAllSint64Params(listOf(1000, 20000, 300000))
+ .addAllDoubleParams(listOf(0.1, 0.00001, 1000.1))
+ .addStrParams("test")
+ logBuilder.addLog(logMessageBuilder.build())
+
+ parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+ assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: " +
+ "true 1000 % 47040 493e0 1.000000e-01 1.00000e-05 test 1000.100000\n",
+ outStream.toString())
+ }
+
+ @Test
+ fun parse_invalidParamsTooMany() {
+ config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o",
+ "ERROR", "WindowManager")
+
+ val logBuilder = ProtoLogFileProto.newBuilder()
+ val logMessageBuilder = ProtoLogMessage.newBuilder()
+ logMessageBuilder
+ .setMessageHash(123)
+ .setElapsedRealtimeNanos(0)
+ .addBooleanParams(true)
+ .addAllSint64Params(listOf(1000, 20000, 300000))
+ .addAllDoubleParams(listOf(0.1, 0.00001, 1000.1))
+ .addStrParams("test")
+ logBuilder.addLog(logMessageBuilder.build())
+
+ parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+ assertEquals("${testDate(0)} INVALID: 123 - [test] [1000, 20000, 300000] " +
+ "[0.1, 1.0E-5, 1000.1] [true]\n", outStream.toString())
+ }
+
+ @Test
+ fun parse_invalidParamsNotEnough() {
+ config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
+ " %x %e %g %s %f", "ERROR", "WindowManager")
+
+ val logBuilder = ProtoLogFileProto.newBuilder()
+ val logMessageBuilder = ProtoLogMessage.newBuilder()
+ logMessageBuilder
+ .setMessageHash(123)
+ .setElapsedRealtimeNanos(0)
+ .addBooleanParams(true)
+ .addStrParams("test")
+ logBuilder.addLog(logMessageBuilder.build())
+
+ parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+ assertEquals("${testDate(0)} INVALID: 123 - [test] [] [] [true]\n",
+ outStream.toString())
+ }
+
+ @Test(expected = InvalidInputException::class)
+ fun parse_invalidMagicNumber() {
+ val logBuilder = ProtoLogFileProto.newBuilder()
+ logBuilder.setVersion(Constants.VERSION)
+ logBuilder.magicNumber = 0
+ val stream = logBuilder.build().toByteArray().inputStream()
+
+ parser.parse(stream, getConfigDummyStream(), printStream)
+ }
+
+ @Test(expected = InvalidInputException::class)
+ fun parse_invalidVersion() {
+ val logBuilder = ProtoLogFileProto.newBuilder()
+ logBuilder.setVersion("invalid")
+ logBuilder.magicNumber =
+ ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+ ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+ val stream = logBuilder.build().toByteArray().inputStream()
+
+ parser.parse(stream, getConfigDummyStream(), printStream)
+ }
+
+ @Test
+ fun parse_noConfig() {
+ val logBuilder = ProtoLogFileProto.newBuilder()
+ val logMessageBuilder = ProtoLogMessage.newBuilder()
+ logMessageBuilder
+ .setMessageHash(70933285)
+ .setElapsedRealtimeNanos(0)
+ .addBooleanParams(true)
+ logBuilder.addLog(logMessageBuilder.build())
+
+ parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+ assertEquals("${testDate(0)} UNKNOWN: 70933285 - [] [] [] [true]\n",
+ outStream.toString())
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
new file mode 100644
index 000000000000..97f67a0a3fdb
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.expr.MethodCallExpr
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ProtoLogCallProcessorTest {
+ private data class LogCall(
+ val call: MethodCallExpr,
+ val messageString: String,
+ val level: LogLevel,
+ val group: LogGroup
+ )
+
+ private val groupMap: MutableMap<String, LogGroup> = mutableMapOf()
+ private val calls: MutableList<LogCall> = mutableListOf()
+ private val visitor = ProtoLogCallProcessor("org.example.ProtoLog", "org.example.ProtoLogGroup",
+ groupMap)
+ private val processor = object : ProtoLogCallVisitor {
+ override fun processCall(
+ call: MethodCallExpr,
+ messageString: String,
+ level: LogLevel,
+ group: LogGroup
+ ) {
+ calls.add(LogCall(call, messageString, level, group))
+ }
+ }
+
+ private fun checkCalls() {
+ assertEquals(1, calls.size)
+ val c = calls[0]
+ assertEquals("test %b", c.messageString)
+ assertEquals(groupMap["TEST"], c.group)
+ assertEquals(LogLevel.DEBUG, c.level)
+ }
+
+ @Test
+ fun process_samePackage() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ ProtoLog.e(ProtoLogGroup.ERROR, "error %d", 1);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", true, false, "WindowManager")
+ groupMap["ERROR"] = LogGroup("ERROR", true, true, "WindowManagerERROR")
+ visitor.process(StaticJavaParser.parse(code), processor, "")
+ assertEquals(2, calls.size)
+ var c = calls[0]
+ assertEquals("test %b", c.messageString)
+ assertEquals(groupMap["TEST"], c.group)
+ assertEquals(LogLevel.DEBUG, c.level)
+ c = calls[1]
+ assertEquals("error %d", c.messageString)
+ assertEquals(groupMap["ERROR"], c.group)
+ assertEquals(LogLevel.ERROR, c.level)
+ }
+
+ @Test
+ fun process_imported() {
+ val code = """
+ package org.example2;
+
+ import org.example.ProtoLog;
+ import org.example.ProtoLogGroup;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+ visitor.process(StaticJavaParser.parse(code), processor, "")
+ checkCalls()
+ }
+
+ @Test
+ fun process_importedStatic() {
+ val code = """
+ package org.example2;
+
+ import static org.example.ProtoLog.d;
+ import static org.example.ProtoLogGroup.TEST;
+
+ class Test {
+ void test() {
+ d(TEST, "test %b", true);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+ visitor.process(StaticJavaParser.parse(code), processor, "")
+ checkCalls()
+ }
+
+ @Test(expected = InvalidProtoLogCallException::class)
+ fun process_groupNotImported() {
+ val code = """
+ package org.example2;
+
+ import org.example.ProtoLog;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+ visitor.process(StaticJavaParser.parse(code), processor, "")
+ }
+
+ @Test
+ fun process_protoLogNotImported() {
+ val code = """
+ package org.example2;
+
+ import org.example.ProtoLogGroup;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+ visitor.process(StaticJavaParser.parse(code), processor, "")
+ assertEquals(0, calls.size)
+ }
+
+ @Test(expected = InvalidProtoLogCallException::class)
+ fun process_unknownGroup() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ }
+ }
+ """
+ visitor.process(StaticJavaParser.parse(code), processor, "")
+ }
+
+ @Test(expected = InvalidProtoLogCallException::class)
+ fun process_staticGroup() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(TEST, "test %b", true);
+ }
+ }
+ """
+ visitor.process(StaticJavaParser.parse(code), processor, "")
+ }
+
+ @Test(expected = InvalidProtoLogCallException::class)
+ fun process_badGroup() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(0, "test %b", true);
+ }
+ }
+ """
+ visitor.process(StaticJavaParser.parse(code), processor, "")
+ }
+
+ @Test(expected = InvalidProtoLogCallException::class)
+ fun process_invalidSignature() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d("test");
+ }
+ }
+ """
+ visitor.process(StaticJavaParser.parse(code), processor, "")
+ }
+
+ @Test
+ fun process_disabled() {
+ // Disabled groups are also processed.
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", false, true, "WindowManager")
+ visitor.process(StaticJavaParser.parse(code), processor, "")
+ checkCalls()
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt
new file mode 100644
index 000000000000..ea9a58d859af
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ProtoLogToolTest {
+
+ @Test
+ fun generateLogGroupCache() {
+ val groups = mapOf(
+ "GROUP1" to LogGroup("GROUP1", true, true, "TAG1"),
+ "GROUP2" to LogGroup("GROUP2", true, true, "TAG2")
+ )
+ val code = ProtoLogTool.generateLogGroupCache("org.example", "ProtoLog\$Cache",
+ groups, "org.example.ProtoLogImpl", "org.example.ProtoLogGroups")
+
+ assertEquals("""
+ package org.example;
+
+ public class ProtoLog${'$'}Cache {
+ public static boolean GROUP1_enabled = false;
+ public static boolean GROUP2_enabled = false;
+
+ static {
+ org.example.ProtoLogImpl.sCacheUpdater = ProtoLog${'$'}Cache::update;
+ update();
+ }
+
+ static void update() {
+ GROUP1_enabled = org.example.ProtoLogImpl.isEnabled(org.example.ProtoLogGroups.GROUP1);
+ GROUP2_enabled = org.example.ProtoLogImpl.isEnabled(org.example.ProtoLogGroups.GROUP2);
+ }
+ }
+ """.trimIndent(), code)
+ }
+} \ No newline at end of file
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
new file mode 100644
index 000000000000..4f2be328fc8a
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.stmt.IfStmt
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Test
+import org.mockito.Mockito
+
+class SourceTransformerTest {
+ companion object {
+ private const val PROTO_LOG_IMPL_PATH = "org.example.ProtoLogImpl"
+
+ /* ktlint-disable max-line-length */
+ private val TEST_CODE = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
+ }
+ }
+ """.trimIndent()
+
+ private val TEST_CODE_MULTILINE = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.w(TEST_GROUP, "test %d %f " +
+ "abc %s\n test", 100,
+ 0.1, "test");
+ }
+ }
+ """.trimIndent()
+
+ private val TEST_CODE_MULTICALLS = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
+ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
+ }
+ }
+ """.trimIndent()
+
+ private val TEST_CODE_NO_PARAMS = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.w(TEST_GROUP, "test");
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_TEXT_ENABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
+
+ }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_MULTICALL_TEXT_ENABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_NO_PARAMS = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { org.example.ProtoLogImpl.w(TEST_GROUP, -1741986185, 0, "test", (Object[]) null); }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_TEXT_DISABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, null, protoLogParam0, protoLogParam1); }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
+
+ }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_DISABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (false) { /* TEST_GROUP is disabled */ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); }
+ }
+ }
+ """.trimIndent()
+
+ private val TRANSFORMED_CODE_MULTILINE_DISABLED = """
+ package org.example;
+
+ class Test {
+ void test() {
+ if (false) { /* TEST_GROUP is disabled */ ProtoLog.w(TEST_GROUP, "test %d %f " + "abc %s\n test", 100, 0.1, "test");
+
+ }
+ }
+ }
+ """.trimIndent()
+ /* ktlint-enable max-line-length */
+
+ private const val PATH = "com.example.Test.java"
+ }
+
+ private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
+ private val implName = "org.example.ProtoLogImpl"
+ private val cacheName = "org.example.ProtoLogCache"
+ private val sourceJarWriter = SourceTransformer(implName, cacheName, processor)
+
+ private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+ @Test
+ fun processClass_textEnabled() {
+ var code = StaticJavaParser.parse(TEST_CODE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code)
+ code = StaticJavaParser.parse(out)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(3, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(6, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("1698911065", methodCall.arguments[1].toString())
+ assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+ assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals(TRANSFORMED_CODE_TEXT_ENABLED, out)
+ }
+
+ @Test
+ fun processClass_textEnabledMulticalls() {
+ var code = StaticJavaParser.parse(TEST_CODE_MULTICALLS)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ val calls = code.findAll(MethodCallExpr::class.java)
+ visitor.processCall(calls[0], "test %d %f",
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+ visitor.processCall(calls[1], "test %d %f",
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+ visitor.processCall(calls[2], "test %d %f",
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(TEST_CODE_MULTICALLS, PATH, PATH, code)
+ code = StaticJavaParser.parse(out)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(3, ifStmts.size)
+ val ifStmt = ifStmts[1]
+ assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(3, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(6, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("1698911065", methodCall.arguments[1].toString())
+ assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+ assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals(TRANSFORMED_CODE_MULTICALL_TEXT_ENABLED, out)
+ }
+
+ @Test
+ fun processClass_textEnabledMultiline() {
+ var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+ "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+ true, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code)
+ code = StaticJavaParser.parse(out)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(4, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(7, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("1780316587", methodCall.arguments[1].toString())
+ assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam2", methodCall.arguments[6].toString())
+ assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED, out)
+ }
+
+ @Test
+ fun processClass_noParams() {
+ var code = StaticJavaParser.parse(TEST_CODE_NO_PARAMS)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test",
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(TEST_CODE_NO_PARAMS, PATH, PATH, code)
+ code = StaticJavaParser.parse(out)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(1, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(5, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("-1741986185", methodCall.arguments[1].toString())
+ assertEquals(0.toString(), methodCall.arguments[2].toString())
+ assertEquals(TRANSFORMED_CODE_NO_PARAMS, out)
+ }
+
+ @Test
+ fun processClass_textDisabled() {
+ var code = StaticJavaParser.parse(TEST_CODE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+ LogLevel.WARN, LogGroup("TEST_GROUP", true, false, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code)
+ code = StaticJavaParser.parse(out)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(3, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(6, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("1698911065", methodCall.arguments[1].toString())
+ assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+ assertEquals("null", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals(TRANSFORMED_CODE_TEXT_DISABLED, out)
+ }
+
+ @Test
+ fun processClass_textDisabledMultiline() {
+ var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+ "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+ true, false, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code)
+ code = StaticJavaParser.parse(out)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
+ assertFalse(ifStmt.elseStmt.isPresent)
+ assertEquals(4, ifStmt.thenStmt.childNodes.size)
+ val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
+ assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+ assertEquals("w", methodCall.name.asString())
+ assertEquals(7, methodCall.arguments.size)
+ assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+ assertEquals("1780316587", methodCall.arguments[1].toString())
+ assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
+ assertEquals("null", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam2", methodCall.arguments[6].toString())
+ assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED, out)
+ }
+
+ @Test
+ fun processClass_disabled() {
+ var code = StaticJavaParser.parse(TEST_CODE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+ LogLevel.WARN, LogGroup("TEST_GROUP", false, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(TEST_CODE, PATH, PATH, code)
+ code = StaticJavaParser.parse(out)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("false", ifStmt.condition.toString())
+ assertEquals(TRANSFORMED_CODE_DISABLED, out)
+ }
+
+ @Test
+ fun processClass_disabledMultiline() {
+ var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+ Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
+ val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+ visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+ "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+ false, true, "WM_TEST"))
+
+ invocation.arguments[0] as CompilationUnit
+ }
+
+ val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, PATH, PATH, code)
+ code = StaticJavaParser.parse(out)
+
+ val ifStmts = code.findAll(IfStmt::class.java)
+ assertEquals(1, ifStmts.size)
+ val ifStmt = ifStmts[0]
+ assertEquals("false", ifStmt.condition.toString())
+ assertEquals(TRANSFORMED_CODE_MULTILINE_DISABLED, out)
+ }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
new file mode 100644
index 000000000000..a24761aed9db
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.android.json.stream.JsonReader
+import com.android.protolog.tool.ViewerConfigBuilder.LogCall
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.mockito.Mockito
+import java.io.StringReader
+
+class ViewerConfigBuilderTest {
+ companion object {
+ private val TAG1 = "WM_TEST"
+ private val TAG2 = "WM_DEBUG"
+ private val TEST1 = ViewerConfigParser.ConfigEntry("test1", LogLevel.INFO.name, TAG1)
+ private val TEST2 = ViewerConfigParser.ConfigEntry("test2", LogLevel.DEBUG.name, TAG2)
+ private val TEST3 = ViewerConfigParser.ConfigEntry("test3", LogLevel.ERROR.name, TAG2)
+ private val GROUP1 = LogGroup("TEST_GROUP", true, true, TAG1)
+ private val GROUP2 = LogGroup("DEBUG_GROUP", true, true, TAG2)
+ private val GROUP3 = LogGroup("DEBUG_GROUP", true, true, TAG2)
+ private val GROUP_DISABLED = LogGroup("DEBUG_GROUP", false, true, TAG2)
+ private val GROUP_TEXT_DISABLED = LogGroup("DEBUG_GROUP", true, false, TAG2)
+ private const val PATH = "/tmp/test.java"
+ }
+
+ private val configBuilder = ViewerConfigBuilder(Mockito.mock(ProtoLogCallProcessor::class.java))
+
+ private fun parseConfig(json: String): Map<Int, ViewerConfigParser.ConfigEntry> {
+ return ViewerConfigParser().parseConfig(JsonReader(StringReader(json)))
+ }
+
+ @Test
+ fun processClass() {
+ configBuilder.addLogCalls(listOf(
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
+ LogCall(TEST2.messageString, LogLevel.DEBUG, GROUP2, PATH),
+ LogCall(TEST3.messageString, LogLevel.ERROR, GROUP3, PATH)).withContext())
+
+ val parsedConfig = parseConfig(configBuilder.build())
+ assertEquals(3, parsedConfig.size)
+ assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH,
+ TEST1.messageString, LogLevel.INFO, GROUP1)])
+ assertEquals(TEST2, parsedConfig[CodeUtils.hash(PATH, TEST2.messageString,
+ LogLevel.DEBUG, GROUP2)])
+ assertEquals(TEST3, parsedConfig[CodeUtils.hash(PATH, TEST3.messageString,
+ LogLevel.ERROR, GROUP3)])
+ }
+
+ @Test
+ fun processClass_nonUnique() {
+ configBuilder.addLogCalls(listOf(
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH)).withContext())
+
+ val parsedConfig = parseConfig(configBuilder.build())
+ assertEquals(1, parsedConfig.size)
+ assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH, TEST1.messageString,
+ LogLevel.INFO, GROUP1)])
+ }
+
+ @Test
+ fun processClass_disabled() {
+ configBuilder.addLogCalls(listOf(
+ LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
+ LogCall(TEST2.messageString, LogLevel.DEBUG, GROUP_DISABLED, PATH),
+ LogCall(TEST3.messageString, LogLevel.ERROR, GROUP_TEXT_DISABLED, PATH))
+ .withContext())
+
+ val parsedConfig = parseConfig(configBuilder.build())
+ assertEquals(2, parsedConfig.size)
+ assertEquals(TEST1, parsedConfig[CodeUtils.hash(
+ PATH, TEST1.messageString, LogLevel.INFO, GROUP1)])
+ assertEquals(TEST3, parsedConfig[CodeUtils.hash(
+ PATH, TEST3.messageString, LogLevel.ERROR, GROUP_TEXT_DISABLED)])
+ }
+
+ private fun List<LogCall>.withContext() = map { it to ParsingContext() }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigParserTest.kt
new file mode 100644
index 000000000000..dc3ef7c57b35
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigParserTest.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.android.json.stream.JsonReader
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import java.io.StringReader
+
+class ViewerConfigParserTest {
+ private val parser = ViewerConfigParser()
+
+ private fun getJSONReader(str: String): JsonReader {
+ return JsonReader(StringReader(str))
+ }
+
+ @Test
+ fun parseMessage() {
+ val json = """
+ {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ """
+ val msg = parser.parseMessage(getJSONReader(json))
+ assertEquals("Test completed successfully: %b", msg.messageString)
+ assertEquals("ERROR", msg.level)
+ assertEquals("GENERIC_WM", msg.groupName)
+ }
+
+ @Test
+ fun parseMessage_reorder() {
+ val json = """
+ {
+ "group": "GENERIC_WM",
+ "level": "ERROR",
+ "message": "Test completed successfully: %b"
+ }
+ """
+ val msg = parser.parseMessage(getJSONReader(json))
+ assertEquals("Test completed successfully: %b", msg.messageString)
+ assertEquals("ERROR", msg.level)
+ assertEquals("GENERIC_WM", msg.groupName)
+ }
+
+ @Test
+ fun parseMessage_unknownEntry() {
+ val json = """
+ {
+ "unknown": "unknown entries should not block parsing",
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ """
+ val msg = parser.parseMessage(getJSONReader(json))
+ assertEquals("Test completed successfully: %b", msg.messageString)
+ assertEquals("ERROR", msg.level)
+ assertEquals("GENERIC_WM", msg.groupName)
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseMessage_noMessage() {
+ val json = """
+ {
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ """
+ parser.parseMessage(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseMessage_noLevel() {
+ val json = """
+ {
+ "message": "Test completed successfully: %b",
+ "group": "GENERIC_WM"
+ }
+ """
+ parser.parseMessage(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseMessage_noGroup() {
+ val json = """
+ {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR"
+ }
+ """
+ parser.parseMessage(getJSONReader(json))
+ }
+
+ @Test
+ fun parseGroup() {
+ val json = """
+ {
+ "tag": "WindowManager"
+ }
+ """
+ val group = parser.parseGroup(getJSONReader(json))
+ assertEquals("WindowManager", group.tag)
+ }
+
+ @Test
+ fun parseGroup_unknownEntry() {
+ val json = """
+ {
+ "unknown": "unknown entries should not block parsing",
+ "tag": "WindowManager"
+ }
+ """
+ val group = parser.parseGroup(getJSONReader(json))
+ assertEquals("WindowManager", group.tag)
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseGroup_noTag() {
+ val json = """
+ {
+ }
+ """
+ parser.parseGroup(getJSONReader(json))
+ }
+
+ @Test
+ fun parseMessages() {
+ val json = """
+ {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ },
+ "1792430067": {
+ "message": "Attempted to add window to a display that does not exist: %d. Aborting.",
+ "level": "WARN",
+ "group": "ERROR_WM"
+ }
+ }
+ """
+ val messages = parser.parseMessages(getJSONReader(json))
+ assertEquals(2, messages.size)
+ val msg1 =
+ ViewerConfigParser.MessageEntry("Test completed successfully: %b",
+ "ERROR", "GENERIC_WM")
+ val msg2 =
+ ViewerConfigParser.MessageEntry("Attempted to add window to a display that " +
+ "does not exist: %d. Aborting.", "WARN", "ERROR_WM")
+
+ assertEquals(msg1, messages[70933285])
+ assertEquals(msg2, messages[1792430067])
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseMessages_invalidHash() {
+ val json = """
+ {
+ "invalid": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ }
+ """
+ parser.parseMessages(getJSONReader(json))
+ }
+
+ @Test
+ fun parseGroups() {
+ val json = """
+ {
+ "GENERIC_WM": {
+ "tag": "WindowManager"
+ },
+ "ERROR_WM": {
+ "tag": "WindowManagerError"
+ }
+ }
+ """
+ val groups = parser.parseGroups(getJSONReader(json))
+ assertEquals(2, groups.size)
+ val grp1 = ViewerConfigParser.GroupEntry("WindowManager")
+ val grp2 = ViewerConfigParser.GroupEntry("WindowManagerError")
+ assertEquals(grp1, groups["GENERIC_WM"])
+ assertEquals(grp2, groups["ERROR_WM"])
+ }
+
+ @Test
+ fun parseConfig() {
+ val json = """
+ {
+ "version": "${Constants.VERSION}",
+ "messages": {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ },
+ "groups": {
+ "GENERIC_WM": {
+ "tag": "WindowManager"
+ }
+ }
+ }
+ """
+ val config = parser.parseConfig(getJSONReader(json))
+ assertEquals(1, config.size)
+ val cfg1 = ViewerConfigParser.ConfigEntry("Test completed successfully: %b",
+ "ERROR", "WindowManager")
+ assertEquals(cfg1, config[70933285])
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseConfig_invalidVersion() {
+ val json = """
+ {
+ "version": "invalid",
+ "messages": {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ },
+ "groups": {
+ "GENERIC_WM": {
+ "tag": "WindowManager"
+ }
+ }
+ }
+ """
+ parser.parseConfig(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseConfig_noVersion() {
+ val json = """
+ {
+ "messages": {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ },
+ "groups": {
+ "GENERIC_WM": {
+ "tag": "WindowManager"
+ }
+ }
+ }
+ """
+ parser.parseConfig(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseConfig_noMessages() {
+ val json = """
+ {
+ "version": "${Constants.VERSION}",
+ "groups": {
+ "GENERIC_WM": {
+ "tag": "WindowManager"
+ }
+ }
+ }
+ """
+ parser.parseConfig(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseConfig_noGroups() {
+ val json = """
+ {
+ "version": "${Constants.VERSION}",
+ "messages": {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ }
+ }
+ """
+ parser.parseConfig(getJSONReader(json))
+ }
+
+ @Test(expected = InvalidViewerConfigException::class)
+ fun parseConfig_missingGroup() {
+ val json = """
+ {
+ "version": "${Constants.VERSION}",
+ "messages": {
+ "70933285": {
+ "message": "Test completed successfully: %b",
+ "level": "ERROR",
+ "group": "GENERIC_WM"
+ }
+ },
+ "groups": {
+ "ERROR_WM": {
+ "tag": "WindowManager"
+ }
+ }
+ }
+ """
+ parser.parseConfig(getJSONReader(json))
+ }
+}
diff --git a/tools/stats_log_api_gen/.clang-format b/tools/stats_log_api_gen/.clang-format
new file mode 100644
index 000000000000..cead3a079435
--- /dev/null
+++ b/tools/stats_log_api_gen/.clang-format
@@ -0,0 +1,17 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowShortLoopsOnASingleLine: true
+BinPackArguments: true
+BinPackParameters: true
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ContinuationIndentWidth: 8
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+AccessModifierOffset: -4
+IncludeCategories:
+ - Regex: '^"Log\.h"'
+ Priority: -1
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index d3958a65c704..b1e2487c54fe 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -26,11 +26,9 @@ cc_binary_host {
"java_writer_q.cpp",
"main.cpp",
"native_writer.cpp",
- "native_writer_q.cpp",
"utils.cpp",
],
cflags: [
- "-DSTATS_SCHEMA_LEGACY",
"-Wall",
"-Werror",
],
@@ -122,6 +120,13 @@ cc_library {
"liblog",
"libcutils",
],
- static_libs: ["libstatssocket"],
+ target: {
+ android: {
+ shared_libs: ["libstatssocket"],
+ },
+ host: {
+ static_libs: ["libstatssocket"],
+ },
+ },
}
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 75deb017e41b..958e94efcf9c 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -15,11 +15,13 @@
*/
#include "Collation.h"
-#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
#include <stdio.h>
+
#include <map>
+#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
+
namespace android {
namespace stats_log_api_gen {
@@ -27,17 +29,16 @@ using google::protobuf::EnumDescriptor;
using google::protobuf::FieldDescriptor;
using google::protobuf::FileDescriptor;
using google::protobuf::SourceLocation;
+using std::make_shared;
using std::map;
+const bool dbg = false;
//
// AtomDecl class
//
-AtomDecl::AtomDecl()
- :code(0),
- name()
-{
+AtomDecl::AtomDecl() : code(0), name() {
}
AtomDecl::AtomDecl(const AtomDecl& that)
@@ -45,38 +46,35 @@ AtomDecl::AtomDecl(const AtomDecl& that)
name(that.name),
message(that.message),
fields(that.fields),
+ fieldNumberToAnnotations(that.fieldNumberToAnnotations),
primaryFields(that.primaryFields),
exclusiveField(that.exclusiveField),
+ defaultState(that.defaultState),
+ triggerStateReset(that.triggerStateReset),
+ nested(that.nested),
uidField(that.uidField),
whitelisted(that.whitelisted),
- binaryFields(that.binaryFields),
- hasModule(that.hasModule),
- moduleName(that.moduleName) {}
-
-AtomDecl::AtomDecl(int c, const string& n, const string& m)
- :code(c),
- name(n),
- message(m)
-{
+ truncateTimestamp(that.truncateTimestamp) {
}
-AtomDecl::~AtomDecl()
-{
+AtomDecl::AtomDecl(int c, const string& n, const string& m) : code(c), name(n), message(m) {
}
+AtomDecl::~AtomDecl() {
+}
/**
- * Print an error message for a FieldDescriptor, including the file name and line number.
+ * Print an error message for a FieldDescriptor, including the file name and
+ * line number.
*/
-static void
-print_error(const FieldDescriptor* field, const char* format, ...)
-{
+static void print_error(const FieldDescriptor* field, const char* format, ...) {
const Descriptor* message = field->containing_type();
const FileDescriptor* file = message->file();
SourceLocation loc;
if (field->GetSourceLocation(&loc)) {
- // TODO: this will work if we can figure out how to pass --include_source_info to protoc
+ // TODO: 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 {
fprintf(stderr, "%s: ", file->name().c_str());
@@ -84,15 +82,13 @@ print_error(const FieldDescriptor* field, const char* format, ...)
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
- va_end (args);
+ va_end(args);
}
/**
* Convert a protobuf type into a java type.
*/
-static java_type_t
-java_type(const FieldDescriptor* field)
-{
+static java_type_t java_type(const FieldDescriptor* field) {
int protoType = field->type();
switch (protoType) {
case FieldDescriptor::TYPE_DOUBLE:
@@ -117,12 +113,10 @@ java_type(const FieldDescriptor* field)
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") {
- return JAVA_TYPE_KEY_VALUE_PAIR;
+ 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") {
+ return JAVA_TYPE_KEY_VALUE_PAIR;
} else if (field->options().GetExtension(os::statsd::log_mode) ==
os::statsd::LogMode::MODE_BYTES) {
return JAVA_TYPE_BYTE_ARRAY;
@@ -151,210 +145,307 @@ java_type(const FieldDescriptor* field)
/**
* Gather the enums info.
*/
-void collate_enums(const EnumDescriptor &enumDescriptor, AtomField *atomField) {
+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().c_str();
}
}
-/**
- * Gather the info about an atom proto.
- */
-int collate_atom(const Descriptor *atom, AtomDecl *atomDecl,
- vector<java_type_t> *signature) {
-
- int errorCount = 0;
-
- // Build a sorted list of the fields. Descriptor has them in source file
- // order.
- map<int, const FieldDescriptor *> fields;
- for (int j = 0; j < atom->field_count(); j++) {
- const FieldDescriptor *field = atom->field(j);
- fields[field->number()] = field;
- }
-
- // Check that the parameters start at 1 and go up sequentially.
- int expectedNumber = 1;
- for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
- it != fields.end(); it++) {
- const int number = it->first;
- const FieldDescriptor *field = it->second;
- if (number != expectedNumber) {
- print_error(field,
- "Fields must be numbered consecutively starting at 1:"
- " '%s' is %d but should be %d\n",
- field->name().c_str(), number, expectedNumber);
- errorCount++;
- expectedNumber = number;
- continue;
- }
- expectedNumber++;
- }
-
- // Check that only allowed types are present. Remove any invalid ones.
- for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
- it != fields.end(); it++) {
- const FieldDescriptor *field = it->second;
- bool isBinaryField = field->options().GetExtension(os::statsd::log_mode) ==
- os::statsd::LogMode::MODE_BYTES;
-
- java_type_t javaType = java_type(field);
-
- if (javaType == JAVA_TYPE_UNKNOWN) {
- print_error(field, "Unkown type for field: %s\n", field->name().c_str());
- errorCount++;
- continue;
- } else if (javaType == JAVA_TYPE_OBJECT &&
- atomDecl->code < PULL_ATOM_START_ID) {
- // Allow attribution chain, but only at position 1.
- print_error(field,
- "Message type not allowed for field in pushed atoms: %s\n",
- field->name().c_str());
- errorCount++;
- continue;
- } else if (javaType == JAVA_TYPE_BYTE_ARRAY && !isBinaryField) {
- print_error(field, "Raw bytes type not allowed for field: %s\n",
- field->name().c_str());
- errorCount++;
- continue;
+static void addAnnotationToAtomDecl(AtomDecl* atomDecl, const int fieldNumber,
+ const AnnotationId annotationId,
+ const AnnotationType annotationType,
+ const AnnotationValue annotationValue) {
+ if (dbg) {
+ printf(" Adding annotation to %s: [%d] = {id: %d, type: %d}\n", atomDecl->name.c_str(),
+ fieldNumber, annotationId, annotationType);
}
+ atomDecl->fieldNumberToAnnotations[fieldNumber].insert(
+ make_shared<Annotation>(annotationId, atomDecl->code, annotationType, annotationValue));
+}
- if (isBinaryField && javaType != JAVA_TYPE_BYTE_ARRAY) {
- print_error(field, "Cannot mark field %s as bytes.\n",
- field->name().c_str());
- errorCount++;
- continue;
- }
+static int collate_field_annotations(AtomDecl* atomDecl, const FieldDescriptor* field,
+ const int fieldNumber, const java_type_t& javaType) {
+ int errorCount = 0;
+
+ if (field->options().HasExtension(os::statsd::state_field_option)) {
+ const os::statsd::StateAtomFieldOption& stateFieldOption =
+ field->options().GetExtension(os::statsd::state_field_option);
+ const bool primaryField = stateFieldOption.primary_field();
+ const bool exclusiveState = stateFieldOption.exclusive_state();
+ const bool primaryFieldFirstUid = stateFieldOption.primary_field_first_uid();
+
+ // Check the field is only one of primaryField, exclusiveState, or primaryFieldFirstUid.
+ if (primaryField + primaryFieldFirstUid + exclusiveState > 1) {
+ print_error(field,
+ "Field can be max 1 of primary_field, exclusive_state, "
+ "or primary_field_first_uid: '%s'\n",
+ atomDecl->message.c_str());
+ errorCount++;
+ }
- // Doubles are not supported yet.
- if (javaType == JAVA_TYPE_DOUBLE) {
- print_error(field, "Doubles are not supported in atoms. Please change field %s to float\n",
- field->name().c_str());
- errorCount++;
- continue;
- }
- }
-
- // Check that if there's an attribution chain, it's at position 1.
- for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
- it != fields.end(); it++) {
- int number = it->first;
- if (number != 1) {
- const FieldDescriptor *field = it->second;
- java_type_t javaType = java_type(field);
- if (javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- print_error(
- field,
- "AttributionChain fields must have field id 1, in message: '%s'\n",
- atom->name().c_str());
- errorCount++;
- }
+ if (primaryField) {
+ if (javaType == JAVA_TYPE_UNKNOWN || javaType == JAVA_TYPE_ATTRIBUTION_CHAIN ||
+ javaType == JAVA_TYPE_OBJECT || javaType == JAVA_TYPE_BYTE_ARRAY) {
+ print_error(field, "Invalid primary state field: '%s'\n",
+ atomDecl->message.c_str());
+ errorCount++;
+ } else {
+ atomDecl->primaryFields.push_back(fieldNumber);
+ addAnnotationToAtomDecl(atomDecl, fieldNumber, ANNOTATION_ID_PRIMARY_FIELD,
+ ANNOTATION_TYPE_BOOL, AnnotationValue(true));
+ }
+ }
+
+ if (primaryFieldFirstUid) {
+ if (javaType != JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ print_error(field,
+ "PRIMARY_FIELD_FIRST_UID annotation is only for AttributionChains: "
+ "'%s'\n",
+ atomDecl->message.c_str());
+ errorCount++;
+ } else {
+ atomDecl->primaryFields.push_back(FIRST_UID_IN_CHAIN_ID);
+ addAnnotationToAtomDecl(atomDecl, fieldNumber,
+ ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, ANNOTATION_TYPE_BOOL,
+ AnnotationValue(true));
+ }
+ }
+
+ if (exclusiveState) {
+ if (javaType == JAVA_TYPE_UNKNOWN || javaType == JAVA_TYPE_ATTRIBUTION_CHAIN ||
+ javaType == JAVA_TYPE_OBJECT || javaType == JAVA_TYPE_BYTE_ARRAY) {
+ print_error(field, "Invalid exclusive state field: '%s'\n",
+ atomDecl->message.c_str());
+ errorCount++;
+ }
+
+ if (atomDecl->exclusiveField != 0) {
+ print_error(field,
+ "Cannot have more than one exclusive state field in an "
+ "atom: '%s'\n",
+ atomDecl->message.c_str());
+ errorCount++;
+ } else {
+ atomDecl->exclusiveField = fieldNumber;
+ addAnnotationToAtomDecl(atomDecl, fieldNumber, ANNOTATION_ID_EXCLUSIVE_STATE,
+ ANNOTATION_TYPE_BOOL, AnnotationValue(true));
+ }
+
+ if (stateFieldOption.has_default_state_value()) {
+ const int defaultState = stateFieldOption.default_state_value();
+ atomDecl->defaultState = defaultState;
+
+ addAnnotationToAtomDecl(atomDecl, fieldNumber, ANNOTATION_ID_DEFAULT_STATE,
+ ANNOTATION_TYPE_INT, AnnotationValue(defaultState));
+ }
+
+ if (stateFieldOption.has_trigger_state_reset_value()) {
+ const int triggerStateReset = stateFieldOption.trigger_state_reset_value();
+
+ atomDecl->triggerStateReset = triggerStateReset;
+ addAnnotationToAtomDecl(atomDecl, fieldNumber, ANNOTATION_ID_TRIGGER_STATE_RESET,
+ ANNOTATION_TYPE_INT, AnnotationValue(triggerStateReset));
+ }
+
+ if (stateFieldOption.has_nested()) {
+ const bool nested = stateFieldOption.nested();
+ atomDecl->nested = nested;
+
+ addAnnotationToAtomDecl(atomDecl, fieldNumber, ANNOTATION_ID_STATE_NESTED,
+ ANNOTATION_TYPE_BOOL, AnnotationValue(nested));
+ }
+ }
}
- }
-
- // Build the type signature and the atom data.
- for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
- it != fields.end(); it++) {
- const FieldDescriptor *field = it->second;
- java_type_t javaType = java_type(field);
- bool isBinaryField = field->options().GetExtension(os::statsd::log_mode) ==
- os::statsd::LogMode::MODE_BYTES;
-
- AtomField atField(field->name(), javaType);
- // 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);
- collate_enums(*field->enum_type(), &atField);
- } else if (javaType == JAVA_TYPE_OBJECT && isBinaryField) {
- signature->push_back(JAVA_TYPE_BYTE_ARRAY);
- } else {
- signature->push_back(javaType);
- }
+
+ if (field->options().GetExtension(os::statsd::is_uid) == true) {
+ if (javaType != JAVA_TYPE_INT) {
+ print_error(field, "is_uid annotation can only be applied to int32 fields: '%s'\n",
+ atomDecl->message.c_str());
+ errorCount++;
+ }
+
+ if (atomDecl->uidField == 0) {
+ atomDecl->uidField = fieldNumber;
+
+ addAnnotationToAtomDecl(atomDecl, fieldNumber, ANNOTATION_ID_IS_UID,
+ ANNOTATION_TYPE_BOOL, AnnotationValue(true));
+ } else {
+ print_error(field,
+ "Cannot have more than one field in an atom with is_uid "
+ "annotation: '%s'\n",
+ atomDecl->message.c_str());
+ errorCount++;
+ }
}
- if (javaType == JAVA_TYPE_ENUM) {
- // All enums are treated as ints when it comes to function signatures.
- collate_enums(*field->enum_type(), &atField);
+
+ return errorCount;
+}
+
+/**
+ * Gather the info about an atom proto.
+ */
+int collate_atom(const Descriptor* atom, AtomDecl* atomDecl, vector<java_type_t>* signature) {
+ int errorCount = 0;
+
+ // Build a sorted list of the fields. Descriptor has them in source file
+ // order.
+ map<int, const FieldDescriptor*> fields;
+ for (int j = 0; j < atom->field_count(); j++) {
+ const FieldDescriptor* field = atom->field(j);
+ fields[field->number()] = field;
}
- atomDecl->fields.push_back(atField);
- if (field->options().GetExtension(os::statsd::state_field_option).option() ==
- os::statsd::StateField::PRIMARY) {
- if (javaType == JAVA_TYPE_UNKNOWN ||
- javaType == JAVA_TYPE_ATTRIBUTION_CHAIN ||
- javaType == JAVA_TYPE_OBJECT || javaType == JAVA_TYPE_BYTE_ARRAY) {
+ // Check that the parameters start at 1 and go up sequentially.
+ int expectedNumber = 1;
+ for (map<int, const FieldDescriptor*>::const_iterator it = fields.begin(); it != fields.end();
+ it++) {
+ const int number = it->first;
+ const FieldDescriptor* field = it->second;
+ if (number != expectedNumber) {
+ print_error(field,
+ "Fields must be numbered consecutively starting at 1:"
+ " '%s' is %d but should be %d\n",
+ field->name().c_str(), number, expectedNumber);
errorCount++;
+ expectedNumber = number;
+ continue;
}
- atomDecl->primaryFields.push_back(it->first);
+ expectedNumber++;
}
- if (field->options().GetExtension(os::statsd::state_field_option).option() ==
- os::statsd::StateField::EXCLUSIVE) {
- if (javaType == JAVA_TYPE_UNKNOWN ||
- javaType == JAVA_TYPE_ATTRIBUTION_CHAIN ||
- javaType == JAVA_TYPE_OBJECT || javaType == JAVA_TYPE_BYTE_ARRAY) {
+ // Check that only allowed types are present. Remove any invalid ones.
+ for (map<int, const FieldDescriptor*>::const_iterator it = fields.begin(); it != fields.end();
+ it++) {
+ const FieldDescriptor* field = it->second;
+ bool isBinaryField = field->options().GetExtension(os::statsd::log_mode) ==
+ os::statsd::LogMode::MODE_BYTES;
+
+ java_type_t javaType = java_type(field);
+
+ if (javaType == JAVA_TYPE_UNKNOWN) {
+ print_error(field, "Unknown type for field: %s\n", field->name().c_str());
errorCount++;
+ continue;
+ } else if (javaType == JAVA_TYPE_OBJECT && atomDecl->code < PULL_ATOM_START_ID) {
+ // Allow attribution chain, but only at position 1.
+ print_error(field, "Message type not allowed for field in pushed atoms: %s\n",
+ field->name().c_str());
+ errorCount++;
+ continue;
+ } else if (javaType == JAVA_TYPE_BYTE_ARRAY && !isBinaryField) {
+ print_error(field, "Raw bytes type not allowed for field: %s\n", field->name().c_str());
+ errorCount++;
+ continue;
}
- if (atomDecl->exclusiveField == 0) {
- atomDecl->exclusiveField = it->first;
- } else {
+ if (isBinaryField && javaType != JAVA_TYPE_BYTE_ARRAY) {
+ print_error(field, "Cannot mark field %s as bytes.\n", field->name().c_str());
errorCount++;
+ continue;
}
- }
- if (field->options().GetExtension(os::statsd::is_uid) == true) {
- if (javaType != JAVA_TYPE_INT) {
+ // Doubles are not supported yet.
+ if (javaType == JAVA_TYPE_DOUBLE) {
+ print_error(field,
+ "Doubles are not supported in atoms. Please change field %s "
+ "to float\n",
+ field->name().c_str());
errorCount++;
+ continue;
}
- if (atomDecl->uidField == 0) {
- atomDecl->uidField = it->first;
- } else {
+ if (field->is_repeated() &&
+ !(javaType == JAVA_TYPE_ATTRIBUTION_CHAIN || javaType == JAVA_TYPE_KEY_VALUE_PAIR)) {
+ print_error(field,
+ "Repeated fields are not supported in atoms. Please make "
+ "field %s not "
+ "repeated.\n",
+ field->name().c_str());
errorCount++;
+ continue;
}
}
- // Binary field validity is already checked above.
- if (isBinaryField) {
- atomDecl->binaryFields.push_back(it->first);
+
+ // Check that if there's an attribution chain, it's at position 1.
+ for (map<int, const FieldDescriptor*>::const_iterator it = fields.begin(); it != fields.end();
+ it++) {
+ int number = it->first;
+ if (number != 1) {
+ const FieldDescriptor* field = it->second;
+ java_type_t javaType = java_type(field);
+ if (javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ print_error(field,
+ "AttributionChain fields must have field id 1, in message: '%s'\n",
+ atom->name().c_str());
+ errorCount++;
+ }
+ }
}
- }
- return errorCount;
+ // Build the type signature and the atom data.
+ for (map<int, const FieldDescriptor*>::const_iterator it = fields.begin(); it != fields.end();
+ it++) {
+ const FieldDescriptor* field = it->second;
+ java_type_t javaType = java_type(field);
+ bool isBinaryField = field->options().GetExtension(os::statsd::log_mode) ==
+ os::statsd::LogMode::MODE_BYTES;
+
+ AtomField atField(field->name(), javaType);
+
+ if (javaType == JAVA_TYPE_ENUM) {
+ // All enums are treated as ints when it comes to function signatures.
+ 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);
+ }
+ }
+
+ atomDecl->fields.push_back(atField);
+
+ errorCount += collate_field_annotations(atomDecl, field, it->first, javaType);
+ }
+
+ return errorCount;
}
-// This function flattens the fields of the AttributionNode proto in an Atom proto and generates
-// the corresponding atom decl and signature.
-bool get_non_chained_node(const Descriptor *atom, AtomDecl *atomDecl,
- vector<java_type_t> *signature) {
+// This function flattens the fields of the AttributionNode proto in an Atom
+// proto and generates the corresponding atom decl and signature.
+bool get_non_chained_node(const Descriptor* atom, AtomDecl* atomDecl,
+ vector<java_type_t>* signature) {
// Build a sorted list of the fields. Descriptor has them in source file
// order.
- map<int, const FieldDescriptor *> fields;
+ map<int, const FieldDescriptor*> fields;
for (int j = 0; j < atom->field_count(); j++) {
- const FieldDescriptor *field = atom->field(j);
+ const FieldDescriptor* field = atom->field(j);
fields[field->number()] = field;
}
AtomDecl attributionDecl;
vector<java_type_t> attributionSignature;
- collate_atom(android::os::statsd::AttributionNode::descriptor(),
- &attributionDecl, &attributionSignature);
+ collate_atom(android::os::statsd::AttributionNode::descriptor(), &attributionDecl,
+ &attributionSignature);
// Build the type signature and the atom data.
bool has_attribution_node = false;
- for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
- it != fields.end(); it++) {
- const FieldDescriptor *field = it->second;
+ for (map<int, const FieldDescriptor*>::const_iterator it = fields.begin(); it != fields.end();
+ it++) {
+ const FieldDescriptor* field = it->second;
java_type_t javaType = java_type(field);
if (javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- atomDecl->fields.insert(
- atomDecl->fields.end(),
- attributionDecl.fields.begin(), attributionDecl.fields.end());
- signature->insert(
- signature->end(),
- attributionSignature.begin(), attributionSignature.end());
+ atomDecl->fields.insert(atomDecl->fields.end(), attributionDecl.fields.begin(),
+ attributionDecl.fields.end());
+ signature->insert(signature->end(), attributionSignature.begin(),
+ attributionSignature.end());
has_attribution_node = true;
} else {
@@ -372,106 +463,122 @@ bool get_non_chained_node(const Descriptor *atom, AtomDecl *atomDecl,
return has_attribution_node;
}
+static void populateFieldNumberToAtomDeclSet(const shared_ptr<AtomDecl>& atomDecl,
+ FieldNumberToAtomDeclSet* fieldNumberToAtomDeclSet) {
+ for (FieldNumberToAnnotations::const_iterator it = atomDecl->fieldNumberToAnnotations.begin();
+ it != atomDecl->fieldNumberToAnnotations.end(); it++) {
+ const int fieldNumber = it->first;
+ (*fieldNumberToAtomDeclSet)[fieldNumber].insert(atomDecl);
+ }
+}
+
/**
* Gather the info about the atoms.
*/
-int collate_atoms(const Descriptor *descriptor, Atoms *atoms) {
- int errorCount = 0;
- const bool dbg = false;
-
- int maxPushedAtomId = 2;
- for (int i = 0; i < descriptor->field_count(); i++) {
- const FieldDescriptor *atomField = descriptor->field(i);
+int collate_atoms(const Descriptor* descriptor, const string& moduleName, Atoms* atoms) {
+ int errorCount = 0;
+
+ for (int i = 0; i < descriptor->field_count(); i++) {
+ const FieldDescriptor* atomField = descriptor->field(i);
+
+ if (moduleName != DEFAULT_MODULE_NAME) {
+ const int moduleCount = atomField->options().ExtensionSize(os::statsd::module);
+ int j;
+ for (j = 0; j < moduleCount; ++j) {
+ const string atomModuleName =
+ atomField->options().GetExtension(os::statsd::module, j);
+ if (atomModuleName == moduleName) {
+ break;
+ }
+ }
- if (dbg) {
- printf(" %s (%d)\n", atomField->name().c_str(), atomField->number());
- }
+ // This atom is not in the module we're interested in; skip it.
+ if (moduleCount == j) {
+ if (dbg) {
+ printf(" Skipping %s (%d)\n", atomField->name().c_str(), atomField->number());
+ }
+ continue;
+ }
+ }
- // StatsEvent only has one oneof, which contains only messages. Don't allow
- // other types.
- if (atomField->type() != FieldDescriptor::TYPE_MESSAGE) {
- print_error(atomField,
- "Bad type for atom. StatsEvent can only have message type "
- "fields: %s\n",
- atomField->name().c_str());
- errorCount++;
- continue;
- }
+ if (dbg) {
+ printf(" %s (%d)\n", atomField->name().c_str(), atomField->number());
+ }
- const Descriptor *atom = atomField->message_type();
- AtomDecl atomDecl(atomField->number(), atomField->name(), atom->name());
+ // StatsEvent only has one oneof, which contains only messages. Don't allow
+ // other types.
+ if (atomField->type() != FieldDescriptor::TYPE_MESSAGE) {
+ print_error(atomField,
+ "Bad type for atom. StatsEvent can only have message type "
+ "fields: %s\n",
+ atomField->name().c_str());
+ errorCount++;
+ continue;
+ }
- if (atomField->options().GetExtension(os::statsd::allow_from_any_uid) == true) {
- atomDecl.whitelisted = true;
- }
+ const Descriptor* atom = atomField->message_type();
+ shared_ptr<AtomDecl> atomDecl =
+ make_shared<AtomDecl>(atomField->number(), atomField->name(), atom->name());
- if (atomField->options().HasExtension(os::statsd::log_from_module)) {
- atomDecl.hasModule = true;
- atomDecl.moduleName = atomField->options().GetExtension(os::statsd::log_from_module);
- }
+ if (atomField->options().GetExtension(os::statsd::allow_from_any_uid) == true) {
+ atomDecl->whitelisted = true;
+ if (dbg) {
+ printf("%s is whitelisted\n", atomField->name().c_str());
+ }
+ }
- vector<java_type_t> signature;
- errorCount += collate_atom(atom, &atomDecl, &signature);
- if (atomDecl.primaryFields.size() != 0 && atomDecl.exclusiveField == 0) {
- errorCount++;
- }
+ if (atomDecl->code < PULL_ATOM_START_ID &&
+ atomField->options().GetExtension(os::statsd::truncate_timestamp)) {
+ addAnnotationToAtomDecl(atomDecl.get(), ATOM_ID_FIELD_NUMBER,
+ ANNOTATION_ID_TRUNCATE_TIMESTAMP, ANNOTATION_TYPE_BOOL,
+ AnnotationValue(true));
+ if (dbg) {
+ printf("%s can have timestamp truncated\n", atomField->name().c_str());
+ }
+ }
- // Add the signature if does not already exist.
- auto signature_to_modules_it = atoms->signatures_to_modules.find(signature);
- if (signature_to_modules_it == atoms->signatures_to_modules.end()) {
- set<string> modules;
- if (atomDecl.hasModule) {
- modules.insert(atomDecl.moduleName);
+ vector<java_type_t> signature;
+ errorCount += collate_atom(atom, atomDecl.get(), &signature);
+ if (atomDecl->primaryFields.size() != 0 && atomDecl->exclusiveField == 0) {
+ print_error(atomField, "Cannot have a primary field without an exclusive field: %s\n",
+ atomField->name().c_str());
+ errorCount++;
+ continue;
}
- atoms->signatures_to_modules[signature] = modules;
- } else {
- if (atomDecl.hasModule) {
- signature_to_modules_it->second.insert(atomDecl.moduleName);
+
+ FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet = atoms->signatureInfoMap[signature];
+ populateFieldNumberToAtomDeclSet(atomDecl, &fieldNumberToAtomDeclSet);
+
+ atoms->decls.insert(atomDecl);
+
+ shared_ptr<AtomDecl> nonChainedAtomDecl =
+ make_shared<AtomDecl>(atomField->number(), atomField->name(), atom->name());
+ vector<java_type_t> nonChainedSignature;
+ if (get_non_chained_node(atom, nonChainedAtomDecl.get(), &nonChainedSignature)) {
+ FieldNumberToAtomDeclSet& nonChainedFieldNumberToAtomDeclSet =
+ atoms->nonChainedSignatureInfoMap[nonChainedSignature];
+ populateFieldNumberToAtomDeclSet(nonChainedAtomDecl,
+ &nonChainedFieldNumberToAtomDeclSet);
+
+ atoms->non_chained_decls.insert(nonChainedAtomDecl);
}
}
- atoms->decls.insert(atomDecl);
-
- AtomDecl nonChainedAtomDecl(atomField->number(), atomField->name(), atom->name());
- vector<java_type_t> nonChainedSignature;
- if (get_non_chained_node(atom, &nonChainedAtomDecl, &nonChainedSignature)) {
- auto it = atoms->non_chained_signatures_to_modules.find(nonChainedSignature);
- if (it == atoms->non_chained_signatures_to_modules.end()) {
- set<string> modules_non_chained;
- if (atomDecl.hasModule) {
- modules_non_chained.insert(atomDecl.moduleName);
- }
- atoms->non_chained_signatures_to_modules[nonChainedSignature] = modules_non_chained;
- } else {
- if (atomDecl.hasModule) {
- it->second.insert(atomDecl.moduleName);
+
+ if (dbg) {
+ 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("\n");
}
- atoms->non_chained_decls.insert(nonChainedAtomDecl);
- }
-
- if (atomDecl.code < PULL_ATOM_START_ID && atomDecl.code > maxPushedAtomId) {
- maxPushedAtomId = atomDecl.code;
- }
- }
-
- atoms->maxPushedAtomId = maxPushedAtomId;
-
- if (dbg) {
- printf("signatures = [\n");
- for (map<vector<java_type_t>, set<string>>::const_iterator it =
- atoms->signatures_to_modules.begin();
- it != atoms->signatures_to_modules.end(); it++) {
- printf(" ");
- for (vector<java_type_t>::const_iterator jt = it->first.begin();
- jt != it->first.end(); jt++) {
- printf(" %d", (int)*jt);
- }
- printf("\n");
+ printf("]\n");
}
- printf("]\n");
- }
- return errorCount;
+ return errorCount;
}
} // namespace stats_log_api_gen
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 3efdd520d7f5..043f8b1e74d8 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -17,45 +17,110 @@
#ifndef ANDROID_STATS_LOG_API_GEN_COLLATION_H
#define ANDROID_STATS_LOG_API_GEN_COLLATION_H
-
#include <google/protobuf/descriptor.h>
+#include <stdint.h>
+#include <map>
#include <set>
#include <vector>
-#include <map>
+
+#include "frameworks/base/cmds/statsd/src/atom_field_options.pb.h"
namespace android {
namespace stats_log_api_gen {
+using google::protobuf::Descriptor;
+using google::protobuf::FieldDescriptor;
using std::map;
using std::set;
+using std::shared_ptr;
using std::string;
using std::vector;
-using google::protobuf::Descriptor;
-using google::protobuf::FieldDescriptor;
const int PULL_ATOM_START_ID = 10000;
+const int FIRST_UID_IN_CHAIN_ID = 0;
+
+enum AnnotationId : uint8_t {
+ ANNOTATION_ID_IS_UID = 1,
+ ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2,
+ ANNOTATION_ID_PRIMARY_FIELD = 3,
+ ANNOTATION_ID_EXCLUSIVE_STATE = 4,
+ ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5,
+ ANNOTATION_ID_DEFAULT_STATE = 6,
+ ANNOTATION_ID_TRIGGER_STATE_RESET = 7,
+ ANNOTATION_ID_STATE_NESTED = 8,
+};
+
+const int ATOM_ID_FIELD_NUMBER = -1;
+
+const string DEFAULT_MODULE_NAME = "DEFAULT";
+
/**
* The types for atom parameters.
*/
typedef enum {
- JAVA_TYPE_UNKNOWN = 0,
-
- JAVA_TYPE_ATTRIBUTION_CHAIN = 1,
- JAVA_TYPE_BOOLEAN = 2,
- JAVA_TYPE_INT = 3,
- JAVA_TYPE_LONG = 4,
- JAVA_TYPE_FLOAT = 5,
- JAVA_TYPE_DOUBLE = 6,
- JAVA_TYPE_STRING = 7,
- JAVA_TYPE_ENUM = 8,
- JAVA_TYPE_KEY_VALUE_PAIR = 9,
-
- JAVA_TYPE_OBJECT = -1,
- JAVA_TYPE_BYTE_ARRAY = -2,
+ JAVA_TYPE_UNKNOWN = 0,
+
+ JAVA_TYPE_ATTRIBUTION_CHAIN = 1,
+ JAVA_TYPE_BOOLEAN = 2,
+ JAVA_TYPE_INT = 3,
+ JAVA_TYPE_LONG = 4,
+ JAVA_TYPE_FLOAT = 5,
+ JAVA_TYPE_DOUBLE = 6,
+ JAVA_TYPE_STRING = 7,
+ JAVA_TYPE_ENUM = 8,
+ JAVA_TYPE_KEY_VALUE_PAIR = 9,
+
+ JAVA_TYPE_OBJECT = -1,
+ JAVA_TYPE_BYTE_ARRAY = -2,
} java_type_t;
+enum AnnotationType {
+ ANNOTATION_TYPE_UNKNOWN = 0,
+ ANNOTATION_TYPE_INT = 1,
+ ANNOTATION_TYPE_BOOL = 2,
+};
+
+union AnnotationValue {
+ int intValue;
+ bool boolValue;
+
+ AnnotationValue(const int value) : intValue(value) {
+ }
+ AnnotationValue(const bool value) : boolValue(value) {
+ }
+};
+
+struct Annotation {
+ const AnnotationId annotationId;
+ const int atomId;
+ AnnotationType type;
+ AnnotationValue value;
+
+ inline Annotation(AnnotationId annotationId, int atomId, AnnotationType type,
+ AnnotationValue value)
+ : annotationId(annotationId), atomId(atomId), type(type), value(value) {
+ }
+ inline ~Annotation() {
+ }
+
+ inline bool operator<(const Annotation& that) const {
+ return atomId == that.atomId ? annotationId < that.annotationId : atomId < that.atomId;
+ }
+};
+
+struct SharedComparator {
+ template <typename T>
+ inline bool operator()(const shared_ptr<T>& lhs, const shared_ptr<T>& rhs) const {
+ return (*lhs) < (*rhs);
+ }
+};
+
+using AnnotationSet = set<shared_ptr<Annotation>, SharedComparator>;
+
+using FieldNumberToAnnotations = map<int, AnnotationSet>;
+
/**
* The name and type for an atom field.
*/
@@ -63,15 +128,20 @@ struct AtomField {
string name;
java_type_t javaType;
- // If the field is of type enum, the following map contains the list of enum values.
+ // If the field is of type enum, the following map contains the list of enum
+ // values.
map<int /* numeric value */, string /* value name */> enumValues;
- inline AtomField() :name(), javaType(JAVA_TYPE_UNKNOWN) {}
- inline AtomField(const AtomField& that) :name(that.name),
- javaType(that.javaType),
- enumValues(that.enumValues) {}
- inline AtomField(string n, java_type_t jt) :name(n), javaType(jt) {}
- inline ~AtomField() {}
+ inline AtomField() : name(), javaType(JAVA_TYPE_UNKNOWN) {
+ }
+ inline AtomField(const AtomField& that)
+ : name(that.name), javaType(that.javaType), enumValues(that.enumValues) {
+ }
+
+ inline AtomField(string n, java_type_t jt) : name(n), javaType(jt) {
+ }
+ inline ~AtomField() {
+ }
};
/**
@@ -84,17 +154,19 @@ struct AtomDecl {
string message;
vector<AtomField> fields;
+ FieldNumberToAnnotations fieldNumberToAnnotations;
+
vector<int> primaryFields;
int exclusiveField = 0;
+ int defaultState = INT_MAX;
+ int triggerStateReset = INT_MAX;
+ bool nested = true;
int uidField = 0;
bool whitelisted = false;
- vector<int> binaryFields;
-
- bool hasModule = false;
- string moduleName;
+ bool truncateTimestamp = false;
AtomDecl();
AtomDecl(const AtomDecl& that);
@@ -106,22 +178,28 @@ struct AtomDecl {
}
};
+using AtomDeclSet = set<shared_ptr<AtomDecl>, SharedComparator>;
+
+// Maps a field number to a set of atoms that have annotation(s) for their field with that field
+// number.
+using FieldNumberToAtomDeclSet = map<int, AtomDeclSet>;
+
+using SignatureInfoMap = map<vector<java_type_t>, FieldNumberToAtomDeclSet>;
+
struct Atoms {
- map<vector<java_type_t>, set<string>> signatures_to_modules;
- set<AtomDecl> decls;
- set<AtomDecl> non_chained_decls;
- map<vector<java_type_t>, set<string>> non_chained_signatures_to_modules;
- int maxPushedAtomId;
+ SignatureInfoMap signatureInfoMap;
+ AtomDeclSet decls;
+ AtomDeclSet non_chained_decls;
+ SignatureInfoMap nonChainedSignatureInfoMap;
};
/**
* Gather the information about the atoms. Returns the number of errors.
*/
-int collate_atoms(const Descriptor* descriptor, Atoms* atoms);
-int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, vector<java_type_t> *signature);
+int collate_atoms(const Descriptor* descriptor, const string& moduleName, Atoms* atoms);
+int collate_atom(const Descriptor* atom, AtomDecl* atomDecl, vector<java_type_t>* signature);
} // namespace stats_log_api_gen
} // namespace android
-
-#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
+#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp
index 54a9982bb5c2..292cb21bac30 100644
--- a/tools/stats_log_api_gen/atoms_info_writer.cpp
+++ b/tools/stats_log_api_gen/atoms_info_writer.cpp
@@ -15,174 +15,39 @@
*/
#include "atoms_info_writer.h"
-#include "utils.h"
#include <map>
#include <set>
#include <vector>
+#include "utils.h"
+
namespace android {
namespace stats_log_api_gen {
-static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) {
- fprintf(out, "struct StateAtomFieldOptions {\n");
- fprintf(out, " std::vector<int> primaryFields;\n");
- fprintf(out, " int exclusiveField;\n");
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
+static void write_atoms_info_header_body(FILE* out) {
fprintf(out, "struct AtomsInfo {\n");
- fprintf(out,
- " const static std::set<int> "
- "kTruncatingTimestampAtomBlackList;\n");
- fprintf(out, " const static std::map<int, int> kAtomsWithUidField;\n");
- fprintf(out,
- " const static std::set<int> kAtomsWithAttributionChain;\n");
- fprintf(out,
- " const static std::map<int, StateAtomFieldOptions> "
- "kStateAtomsFieldOptions;\n");
- fprintf(out,
- " const static std::map<int, std::vector<int>> "
- "kBytesFieldAtoms;\n");
- fprintf(out,
- " const static std::set<int> kWhitelistedAtoms;\n");
+ fprintf(out, " const static std::set<int> kWhitelistedAtoms;\n");
fprintf(out, "};\n");
- fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", atoms.maxPushedAtomId);
-
}
static void write_atoms_info_cpp_body(FILE* out, const Atoms& atoms) {
- std::set<string> kTruncatingAtomNames = {"mobile_radio_power_state_changed",
- "audio_state_changed",
- "call_state_changed",
- "phone_signal_strength_changed",
- "mobile_bytes_transfer_by_fg_bg",
- "mobile_bytes_transfer"};
- fprintf(out,
- "const std::set<int> "
- "AtomsInfo::kTruncatingTimestampAtomBlackList = {\n");
- for (set<string>::const_iterator blacklistedAtom = kTruncatingAtomNames.begin();
- blacklistedAtom != kTruncatingAtomNames.end(); blacklistedAtom++) {
- fprintf(out, " %s,\n", make_constant_name(*blacklistedAtom).c_str());
- }
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out,
- "const std::set<int> AtomsInfo::kAtomsWithAttributionChain = {\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- for (vector<AtomField>::const_iterator field = atom->fields.begin();
- field != atom->fields.end(); field++) {
- if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- string constant = make_constant_name(atom->name);
- fprintf(out, " %s,\n", constant.c_str());
- break;
- }
- }
- }
-
- fprintf(out, "};\n");
- fprintf(out, "\n");
- fprintf(out,
- "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->whitelisted) {
- string constant = make_constant_name(atom->name);
- fprintf(out, " %s,\n", constant.c_str());
+ fprintf(out, "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n");
+ for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end();
+ atomIt++) {
+ if ((*atomIt)->whitelisted) {
+ const string constant = make_constant_name((*atomIt)->name);
+ fprintf(out, " %d, // %s\n", (*atomIt)->code, constant.c_str());
}
}
fprintf(out, "};\n");
fprintf(out, "\n");
- fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
- fprintf(out, " std::map<int, int> uidField;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->uidField == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding uid field for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
- fprintf(out, " uidField[static_cast<int>(%s)] = %d;\n",
- make_constant_name(atom->name).c_str(), atom->uidField);
- }
-
- fprintf(out, " return uidField;\n");
- fprintf(out, "};\n");
-
- fprintf(out,
- "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
- "getAtomUidField();\n");
-
- fprintf(out,
- "static std::map<int, StateAtomFieldOptions> "
- "getStateAtomFieldOptions() {\n");
- fprintf(out, " std::map<int, StateAtomFieldOptions> options;\n");
- fprintf(out, " StateAtomFieldOptions opt;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->primaryFields.size() == 0 && atom->exclusiveField == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding primary and exclusive fields for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
- fprintf(out, " opt.primaryFields.clear();\n");
- for (const auto& field : atom->primaryFields) {
- fprintf(out, " opt.primaryFields.push_back(%d);\n", field);
- }
-
- fprintf(out, " opt.exclusiveField = %d;\n", atom->exclusiveField);
- fprintf(out, " options[static_cast<int>(%s)] = opt;\n",
- make_constant_name(atom->name).c_str());
- }
-
- fprintf(out, " return options;\n");
- fprintf(out, "}\n");
-
- fprintf(out,
- "const std::map<int, StateAtomFieldOptions> "
- "AtomsInfo::kStateAtomsFieldOptions = "
- "getStateAtomFieldOptions();\n");
-
- fprintf(out,
- "static std::map<int, std::vector<int>> "
- "getBinaryFieldAtoms() {\n");
- fprintf(out, " std::map<int, std::vector<int>> options;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->binaryFields.size() == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding binary fields for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
-
- for (const auto& field : atom->binaryFields) {
- fprintf(out, " options[static_cast<int>(%s)].push_back(%d);\n",
- make_constant_name(atom->name).c_str(), field);
- }
- }
-
- fprintf(out, " return options;\n");
- fprintf(out, "}\n");
-
- fprintf(out,
- "const std::map<int, std::vector<int>> "
- "AtomsInfo::kBytesFieldAtoms = "
- "getBinaryFieldAtoms();\n");
-
}
-int write_atoms_info_header(FILE* out, const Atoms &atoms, const string& namespaceStr) {
+int write_atoms_info_header(FILE* out, const string& namespaceStr) {
// Print prelude
fprintf(out, "// This file is autogenerated\n");
fprintf(out, "\n");
@@ -195,7 +60,7 @@ int write_atoms_info_header(FILE* out, const Atoms &atoms, const string& namespa
write_namespace(out, namespaceStr);
- write_atoms_info_header_body(out, atoms);
+ write_atoms_info_header_body(out);
fprintf(out, "\n");
write_closing_namespace(out, namespaceStr);
@@ -203,13 +68,12 @@ int write_atoms_info_header(FILE* out, const Atoms &atoms, const string& namespa
return 0;
}
-int write_atoms_info_cpp(FILE *out, const Atoms &atoms, const string& namespaceStr,
- const string& importHeader, const string& statslogHeader) {
+int write_atoms_info_cpp(FILE* out, const Atoms& atoms, const string& namespaceStr,
+ const string& importHeader) {
// Print prelude
fprintf(out, "// This file is autogenerated\n");
fprintf(out, "\n");
fprintf(out, "#include <%s>\n", importHeader.c_str());
- fprintf(out, "#include <%s>\n", statslogHeader.c_str());
fprintf(out, "\n");
write_namespace(out, namespaceStr);
diff --git a/tools/stats_log_api_gen/atoms_info_writer.h b/tools/stats_log_api_gen/atoms_info_writer.h
index 12ac862871ef..09a4303eaee6 100644
--- a/tools/stats_log_api_gen/atoms_info_writer.h
+++ b/tools/stats_log_api_gen/atoms_info_writer.h
@@ -16,20 +16,20 @@
#pragma once
-#include "Collation.h"
-
#include <stdio.h>
#include <string.h>
+#include "Collation.h"
+
namespace android {
namespace stats_log_api_gen {
using namespace std;
int write_atoms_info_cpp(FILE* out, const Atoms& atoms, const string& namespaceStr,
- const string& importHeader, const string& statslogHeader);
+ const string& importHeader);
-int write_atoms_info_header(FILE* out, const Atoms& atoms, const string& namespaceStr);
+int write_atoms_info_header(FILE* out, const string& namespaceStr);
} // namespace stats_log_api_gen
} // namespace android
diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp
index c29936b96c14..f4c937c3f599 100644
--- a/tools/stats_log_api_gen/java_writer.cpp
+++ b/tools/stats_log_api_gen/java_writer.cpp
@@ -15,18 +15,15 @@
*/
#include "java_writer.h"
+
#include "java_writer_q.h"
#include "utils.h"
namespace android {
namespace stats_log_api_gen {
-static int write_java_q_logger_class(
- FILE* out,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const AtomDecl &attributionDecl,
- const string& moduleName
- ) {
+static int write_java_q_logger_class(FILE* out, const SignatureInfoMap& signatureInfoMap,
+ const AtomDecl& attributionDecl) {
fprintf(out, "\n");
fprintf(out, " // Write logging helper methods for statsd in Q and earlier.\n");
fprintf(out, " private static class QLogger {\n");
@@ -36,41 +33,84 @@ static int write_java_q_logger_class(
// Print Q write methods.
fprintf(out, "\n");
fprintf(out, " // Write methods.\n");
- write_java_methods_q_schema(
- out, signatures_to_modules, attributionDecl, moduleName, " ");
+ write_java_methods_q_schema(out, signatureInfoMap, attributionDecl, " ");
fprintf(out, " }\n");
return 0;
}
+static void write_java_annotation_constants(FILE* out) {
+ fprintf(out, " // Annotation constants.\n");
+
+ for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) {
+ fprintf(out, " public static final byte %s = %hhu;\n", name.c_str(), id);
+ }
+ fprintf(out, "\n");
+}
-static int write_java_methods(
- FILE* out,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const AtomDecl &attributionDecl,
- const string& moduleName,
- const bool supportQ
- ) {
- for (auto signature_to_modules_it = signatures_to_modules.begin();
- signature_to_modules_it != signatures_to_modules.end(); signature_to_modules_it++) {
- // Skip if this signature is not needed for the module.
- if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) {
- continue;
+static void write_annotations(FILE* out, int argIndex,
+ const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet) {
+ FieldNumberToAtomDeclSet::const_iterator fieldNumberToAtomDeclSetIt =
+ fieldNumberToAtomDeclSet.find(argIndex);
+ if (fieldNumberToAtomDeclSet.end() == fieldNumberToAtomDeclSetIt) {
+ return;
+ }
+ const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second;
+ for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) {
+ const string atomConstant = make_constant_name(atomDecl->name);
+ fprintf(out, " if (%s == code) {\n", atomConstant.c_str());
+ const AnnotationSet& annotations = atomDecl->fieldNumberToAnnotations.at(argIndex);
+ int resetState = -1;
+ int defaultState = -1;
+ for (const shared_ptr<Annotation>& annotation : annotations) {
+ const string& annotationConstant = ANNOTATION_ID_CONSTANTS.at(annotation->annotationId);
+ switch (annotation->type) {
+ case ANNOTATION_TYPE_INT:
+ if (ANNOTATION_ID_TRIGGER_STATE_RESET == annotation->annotationId) {
+ resetState = annotation->value.intValue;
+ } else if (ANNOTATION_ID_DEFAULT_STATE == annotation->annotationId) {
+ defaultState = annotation->value.intValue;
+ } else {
+ fprintf(out, " builder.addIntAnnotation(%s, %d);\n",
+ annotationConstant.c_str(), annotation->value.intValue);
+ }
+ break;
+ case ANNOTATION_TYPE_BOOL:
+ fprintf(out, " builder.addBooleanAnnotation(%s, %s);\n",
+ annotationConstant.c_str(),
+ annotation->value.boolValue ? "true" : "false");
+ break;
+ default:
+ break;
+ }
}
+ if (defaultState != -1 && resetState != -1) {
+ const string& annotationConstant =
+ ANNOTATION_ID_CONSTANTS.at(ANNOTATION_ID_TRIGGER_STATE_RESET);
+ fprintf(out, " if (arg%d == %d) {\n", argIndex, resetState);
+ fprintf(out, " builder.addIntAnnotation(%s, %d);\n",
+ annotationConstant.c_str(), defaultState);
+ fprintf(out, " }\n");
+ }
+ fprintf(out, " }\n");
+ }
+}
+static int write_java_methods(FILE* out, const SignatureInfoMap& signatureInfoMap,
+ const AtomDecl& attributionDecl, const bool supportQ) {
+ for (auto signatureInfoMapIt = signatureInfoMap.begin();
+ signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) {
// Print method signature.
- if (DEFAULT_MODULE_NAME == moduleName) {
- fprintf(out, " /** @hide */\n");
- }
fprintf(out, " public static void write(int code");
- vector<java_type_t> signature = signature_to_modules_it->first;
+ 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++) {
+ 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());
+ 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");
@@ -84,135 +124,130 @@ static int write_java_methods(
// Print method body.
string indent("");
if (supportQ) {
- // TODO(b/146235828): Use just SDK_INT check once it is incremented from Q.
- fprintf(out, " if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q\n");
- fprintf(out, " || (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q\n");
- fprintf(out, " && Build.VERSION.PREVIEW_SDK_INT > 0)) {\n");
+ fprintf(out, " if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {\n");
indent = " ";
}
// Start StatsEvent.Builder.
- fprintf(out, "%s final StatsEvent.Builder builder = StatsEvent.newBuilder();\n",
+ 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++) {
+ 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();
+ 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;
+ 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++;
}
@@ -226,7 +261,7 @@ static int write_java_methods(
fprintf(out, " QLogger.write(code");
argIndex = 1;
for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
+ arg != signature.end(); arg++) {
if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
const char* uidName = attributionDecl.fields.front().name.c_str();
const char* tagName = attributionDecl.fields.back().name.c_str();
@@ -241,20 +276,18 @@ static int write_java_methods(
argIndex++;
}
fprintf(out, ");\n");
- fprintf(out, " }\n"); // if
+ fprintf(out, " }\n"); // if
}
- fprintf(out, " }\n"); // method
+ fprintf(out, " }\n"); // method
fprintf(out, "\n");
}
return 0;
-
}
-int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl,
- const string& moduleName, const string& javaClass,
- const string& javaPackage, const bool supportQ,
- const bool supportWorkSource) {
+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) {
// Print prelude
fprintf(out, "// This file is autogenerated\n");
fprintf(out, "\n");
@@ -273,30 +306,25 @@ int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attribut
fprintf(out, "\n");
fprintf(out, "/**\n");
fprintf(out, " * Utility class for logging statistics events.\n");
- if (DEFAULT_MODULE_NAME == moduleName) {
- fprintf(out, " * @hide\n");
- }
fprintf(out, " */\n");
fprintf(out, "public class %s {\n", javaClass.c_str());
- write_java_atom_codes(out, atoms, moduleName);
- write_java_enum_values(out, atoms, moduleName);
+ write_java_atom_codes(out, atoms);
+ write_java_enum_values(out, atoms);
+ write_java_annotation_constants(out);
int errors = 0;
// Print write methods.
fprintf(out, " // Write methods\n");
- errors += write_java_methods(
- out, atoms.signatures_to_modules, attributionDecl, moduleName, supportQ);
- errors += write_java_non_chained_methods(
- out, atoms.non_chained_signatures_to_modules, moduleName);
+ errors += write_java_methods(out, atoms.signatureInfoMap, attributionDecl, supportQ);
+ errors += write_java_non_chained_methods(out, atoms.nonChainedSignatureInfoMap);
if (supportWorkSource) {
- errors += write_java_work_source_methods(out, atoms.signatures_to_modules, moduleName);
+ errors += write_java_work_source_methods(out, atoms.signatureInfoMap);
}
if (supportQ) {
- errors += write_java_q_logger_class(
- out, atoms.signatures_to_modules, attributionDecl, moduleName);
+ errors += write_java_q_logger_class(out, atoms.signatureInfoMap, attributionDecl);
}
fprintf(out, "}\n");
diff --git a/tools/stats_log_api_gen/java_writer.h b/tools/stats_log_api_gen/java_writer.h
index 5b78f059c5b9..8b3b50588efc 100644
--- a/tools/stats_log_api_gen/java_writer.h
+++ b/tools/stats_log_api_gen/java_writer.h
@@ -16,25 +16,23 @@
#pragma once
-#include "Collation.h"
+#include <stdio.h>
+#include <string.h>
#include <map>
#include <set>
#include <vector>
-#include <stdio.h>
-#include <string.h>
+#include "Collation.h"
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& moduleName, const string& javaClass,
- const string& javaPackage, const bool supportQ,
+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
-
diff --git a/tools/stats_log_api_gen/java_writer_q.cpp b/tools/stats_log_api_gen/java_writer_q.cpp
index db766b2ade71..d21e2708b724 100644
--- a/tools/stats_log_api_gen/java_writer_q.cpp
+++ b/tools/stats_log_api_gen/java_writer_q.cpp
@@ -15,6 +15,7 @@
*/
#include "java_writer_q.h"
+
#include "utils.h"
namespace android {
@@ -24,7 +25,8 @@ void write_java_q_logging_constants(FILE* out, const string& indent) {
fprintf(out, "%s// Payload limits.\n", indent.c_str());
fprintf(out, "%sprivate static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068;\n", indent.c_str());
fprintf(out,
- "%sprivate static final int MAX_EVENT_PAYLOAD = LOGGER_ENTRY_MAX_PAYLOAD - 4;\n",
+ "%sprivate static final int MAX_EVENT_PAYLOAD = "
+ "LOGGER_ENTRY_MAX_PAYLOAD - 4;\n",
indent.c_str());
// Value types. Must match with EventLog.java and log.h.
@@ -37,47 +39,38 @@ void write_java_q_logging_constants(FILE* out, const string& indent) {
fprintf(out, "%sprivate static final byte FLOAT_TYPE = 4;\n", indent.c_str());
// Size of each value type.
- // Booleans, ints, floats, and enums take 5 bytes, 1 for the type and 4 for the value.
+ // Booleans, ints, floats, and enums take 5 bytes, 1 for the type and 4 for
+ // the value.
fprintf(out, "\n");
fprintf(out, "%s// Size of each value type.\n", indent.c_str());
fprintf(out, "%sprivate static final int INT_TYPE_SIZE = 5;\n", indent.c_str());
fprintf(out, "%sprivate static final int FLOAT_TYPE_SIZE = 5;\n", indent.c_str());
// Longs take 9 bytes, 1 for the type and 8 for the value.
fprintf(out, "%sprivate static final int LONG_TYPE_SIZE = 9;\n", indent.c_str());
- // Strings take 5 metadata bytes: 1 byte is for the type, 4 are for the length.
+ // Strings take 5 metadata bytes: 1 byte is for the type, 4 are for the
+ // length.
fprintf(out, "%sprivate static final int STRING_TYPE_OVERHEAD = 5;\n", indent.c_str());
fprintf(out, "%sprivate static final int LIST_TYPE_OVERHEAD = 2;\n", indent.c_str());
}
-int write_java_methods_q_schema(
- FILE* out,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const AtomDecl &attributionDecl,
- const string& moduleName,
- const string& indent) {
+int write_java_methods_q_schema(FILE* out, const SignatureInfoMap& signatureInfoMap,
+ const AtomDecl& attributionDecl, const string& indent) {
int requiredHelpers = 0;
- for (auto signature_to_modules_it = signatures_to_modules.begin();
- signature_to_modules_it != signatures_to_modules.end(); signature_to_modules_it++) {
- // Skip if this signature is not needed for the module.
- if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) {
- continue;
- }
-
+ for (auto signatureInfoMapIt = signatureInfoMap.begin();
+ signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) {
// Print method signature.
- vector<java_type_t> signature = signature_to_modules_it->first;
+ vector<java_type_t> signature = signatureInfoMapIt->first;
fprintf(out, "%spublic static void write(int code", indent.c_str());
int argIndex = 1;
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
+ 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());
+ fprintf(out, ", %s[] %s", java_type_name(chainField.javaType),
+ chainField.name.c_str());
}
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
- // Module logging does not yet support key value pair.
- fprintf(stderr, "Module logging does not yet support key value pair.\n");
- continue;
+ fprintf(out, ", android.util.SparseArray<Object> valueMap");
} else {
fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
}
@@ -89,87 +82,174 @@ int write_java_methods_q_schema(
fprintf(out, "%s // Initial overhead of the list, timestamp, and atom tag.\n",
indent.c_str());
fprintf(out,
- "%s int needed = LIST_TYPE_OVERHEAD + LONG_TYPE_SIZE + INT_TYPE_SIZE;\n",
+ "%s int needed = LIST_TYPE_OVERHEAD + LONG_TYPE_SIZE + "
+ "INT_TYPE_SIZE;\n",
indent.c_str());
argIndex = 1;
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
+ for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end();
+ arg++) {
switch (*arg) {
- case JAVA_TYPE_BOOLEAN:
- case JAVA_TYPE_INT:
- case JAVA_TYPE_FLOAT:
- case JAVA_TYPE_ENUM:
- fprintf(out, "%s needed += INT_TYPE_SIZE;\n", indent.c_str());
- break;
- case JAVA_TYPE_LONG:
- // Longs take 9 bytes, 1 for the type and 8 for the value.
- fprintf(out, "%s needed += LONG_TYPE_SIZE;\n", indent.c_str());
- break;
- case JAVA_TYPE_STRING:
- // Strings take 5 metadata bytes + length of byte encoded string.
- fprintf(out, "%s if (arg%d == null) {\n", indent.c_str(), argIndex);
- fprintf(out, "%s arg%d = \"\";\n", indent.c_str(), argIndex);
- fprintf(out, "%s }\n", indent.c_str());
- fprintf(out,
- "%s byte[] arg%dBytes = "
- "arg%d.getBytes(java.nio.charset.StandardCharsets.UTF_8);\n",
- indent.c_str(), argIndex, argIndex);
- fprintf(out, "%s needed += STRING_TYPE_OVERHEAD + arg%dBytes.length;\n",
- indent.c_str(), argIndex);
- break;
- case JAVA_TYPE_BYTE_ARRAY:
- // Byte arrays take 5 metadata bytes + length of byte array.
- fprintf(out, "%s if (arg%d == null) {\n", indent.c_str(), argIndex);
- fprintf(out, "%s arg%d = new byte[0];\n", indent.c_str(), argIndex);
- fprintf(out, "%s }\n", indent.c_str());
- fprintf(out, "%s needed += STRING_TYPE_OVERHEAD + arg%d.length;\n",
- indent.c_str(), 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();
- // Null checks on the params.
- fprintf(out, "%s if (%s == null) {\n", indent.c_str(), uidName);
- fprintf(out, "%s %s = new %s[0];\n", indent.c_str(), uidName,
- java_type_name(attributionDecl.fields.front().javaType));
- fprintf(out, "%s }\n", indent.c_str());
- fprintf(out, "%s if (%s == null) {\n", indent.c_str(), tagName);
- fprintf(out, "%s %s = new %s[0];\n", indent.c_str(), tagName,
- java_type_name(attributionDecl.fields.back().javaType));
- fprintf(out, "%s }\n", indent.c_str());
-
- // First check that the lengths of the uid and tag arrays are the same.
- fprintf(out, "%s if (%s.length != %s.length) {\n",
- indent.c_str(), uidName, tagName);
- fprintf(out, "%s return;\n", indent.c_str());
- fprintf(out, "%s }\n", indent.c_str());
- fprintf(out, "%s int attrSize = LIST_TYPE_OVERHEAD;\n", indent.c_str());
- fprintf(out, "%s for (int i = 0; i < %s.length; i++) {\n",
- indent.c_str(), tagName);
- fprintf(out, "%s String str%d = (%s[i] == null) ? \"\" : %s[i];\n",
- indent.c_str(), argIndex, tagName, tagName);
- fprintf(out,
- "%s int str%dlen = "
- "str%d.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;\n",
- indent.c_str(), argIndex, argIndex);
- fprintf(out,
- "%s attrSize += "
- "LIST_TYPE_OVERHEAD + INT_TYPE_SIZE + STRING_TYPE_OVERHEAD + str%dlen;\n",
- indent.c_str(), argIndex);
- fprintf(out, "%s }\n", indent.c_str());
- fprintf(out, "%s needed += attrSize;\n", indent.c_str());
- break;
- }
- default:
- // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIR.
- fprintf(stderr, "Module logging does not yet support key value pair.\n");
- return 1;
+ case JAVA_TYPE_BOOLEAN:
+ case JAVA_TYPE_INT:
+ case JAVA_TYPE_FLOAT:
+ case JAVA_TYPE_ENUM:
+ fprintf(out, "%s needed += INT_TYPE_SIZE;\n", indent.c_str());
+ break;
+ case JAVA_TYPE_LONG:
+ // Longs take 9 bytes, 1 for the type and 8 for the value.
+ fprintf(out, "%s needed += LONG_TYPE_SIZE;\n", indent.c_str());
+ break;
+ case JAVA_TYPE_STRING:
+ // Strings take 5 metadata bytes + length of byte encoded string.
+ fprintf(out, "%s if (arg%d == null) {\n", indent.c_str(), argIndex);
+ fprintf(out, "%s arg%d = \"\";\n", indent.c_str(), argIndex);
+ fprintf(out, "%s }\n", indent.c_str());
+ fprintf(out,
+ "%s byte[] arg%dBytes = "
+ "arg%d.getBytes(java.nio.charset.StandardCharsets.UTF_8);\n",
+ indent.c_str(), argIndex, argIndex);
+ fprintf(out, "%s needed += STRING_TYPE_OVERHEAD + arg%dBytes.length;\n",
+ indent.c_str(), argIndex);
+ break;
+ case JAVA_TYPE_BYTE_ARRAY:
+ // Byte arrays take 5 metadata bytes + length of byte array.
+ fprintf(out, "%s if (arg%d == null) {\n", indent.c_str(), argIndex);
+ fprintf(out, "%s arg%d = new byte[0];\n", indent.c_str(), argIndex);
+ fprintf(out, "%s }\n", indent.c_str());
+ fprintf(out, "%s needed += STRING_TYPE_OVERHEAD + arg%d.length;\n",
+ indent.c_str(), 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();
+ // Null checks on the params.
+ fprintf(out, "%s if (%s == null) {\n", indent.c_str(), uidName);
+ fprintf(out, "%s %s = new %s[0];\n", indent.c_str(), uidName,
+ java_type_name(attributionDecl.fields.front().javaType));
+ fprintf(out, "%s }\n", indent.c_str());
+ fprintf(out, "%s if (%s == null) {\n", indent.c_str(), tagName);
+ fprintf(out, "%s %s = new %s[0];\n", indent.c_str(), tagName,
+ java_type_name(attributionDecl.fields.back().javaType));
+ fprintf(out, "%s }\n", indent.c_str());
+
+ // First check that the lengths of the uid and tag arrays are the
+ // same.
+ fprintf(out, "%s if (%s.length != %s.length) {\n", indent.c_str(), uidName,
+ tagName);
+ fprintf(out, "%s return;\n", indent.c_str());
+ fprintf(out, "%s }\n", indent.c_str());
+ fprintf(out, "%s int attrSize = LIST_TYPE_OVERHEAD;\n", indent.c_str());
+ fprintf(out, "%s for (int i = 0; i < %s.length; i++) {\n", indent.c_str(),
+ tagName);
+ fprintf(out, "%s String str%d = (%s[i] == null) ? \"\" : %s[i];\n",
+ indent.c_str(), argIndex, tagName, tagName);
+ fprintf(out,
+ "%s int str%dlen = "
+ "str%d.getBytes(java.nio.charset.StandardCharsets.UTF_8)."
+ "length;\n",
+ indent.c_str(), argIndex, argIndex);
+ fprintf(out,
+ "%s attrSize += "
+ "LIST_TYPE_OVERHEAD + INT_TYPE_SIZE + STRING_TYPE_OVERHEAD + "
+ "str%dlen;\n",
+ indent.c_str(), argIndex);
+ fprintf(out, "%s }\n", indent.c_str());
+ fprintf(out, "%s needed += attrSize;\n", indent.c_str());
+ break;
+ }
+ case JAVA_TYPE_KEY_VALUE_PAIR: {
+ fprintf(out, "%s // Calculate bytes needed by Key Value Pairs.\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 int keyValuePairSize = LIST_TYPE_OVERHEAD;\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 keyValuePairSize += LIST_TYPE_OVERHEAD\n",
+ indent.c_str());
+ fprintf(out, "%s + INT_TYPE_SIZE + INT_TYPE_SIZE;\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 keyValuePairSize += LIST_TYPE_OVERHEAD\n",
+ indent.c_str());
+ fprintf(out, "%s + INT_TYPE_SIZE + LONG_TYPE_SIZE;\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 final String str = (value == null) ? \"\" : "
+ "(String) value;\n",
+ indent.c_str());
+ fprintf(out,
+ "%s final int len = "
+ "str.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;\n",
+ indent.c_str());
+ fprintf(out,
+ "%s keyValuePairSize += LIST_TYPE_OVERHEAD + "
+ "INT_TYPE_SIZE\n",
+ indent.c_str());
+ fprintf(out, "%s + STRING_TYPE_OVERHEAD + len;\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, str);\n", indent.c_str());
+ fprintf(out, "%s } else if (value instanceof Float) {\n",
+ indent.c_str());
+ fprintf(out, "%s keyValuePairSize += LIST_TYPE_OVERHEAD\n",
+ indent.c_str());
+ fprintf(out, "%s + INT_TYPE_SIZE + FLOAT_TYPE_SIZE;\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 needed += keyValuePairSize;\n", indent.c_str());
+ break;
+ }
+ default:
+ // Unsupported types: OBJECT, DOUBLE.
+ fprintf(stderr, "Module logging does not yet support Object and Double.\n");
+ return 1;
}
argIndex++;
}
- // Now we have the size that is needed. Check for overflow and return if needed.
+ // Now we have the size that is needed. Check for overflow and return if
+ // needed.
fprintf(out, "%s if (needed > MAX_EVENT_PAYLOAD) {\n", indent.c_str());
fprintf(out, "%s return;\n", indent.c_str());
fprintf(out, "%s }\n", indent.c_str());
@@ -184,7 +264,8 @@ int write_java_methods_q_schema(
fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
// Write timestamp.
- fprintf(out, "%s long elapsedRealtime = SystemClock.elapsedRealtimeNanos();\n", indent.c_str());
+ fprintf(out, "%s long elapsedRealtime = SystemClock.elapsedRealtimeNanos();\n",
+ indent.c_str());
fprintf(out, "%s buff[pos] = LONG_TYPE;\n", indent.c_str());
fprintf(out, "%s copyLong(buff, pos + 1, elapsedRealtime);\n", indent.c_str());
fprintf(out, "%s pos += LONG_TYPE_SIZE;\n", indent.c_str());
@@ -196,68 +277,82 @@ int write_java_methods_q_schema(
// Write the args.
argIndex = 1;
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
+ for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end();
+ arg++) {
switch (*arg) {
- case JAVA_TYPE_BOOLEAN:
- fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str());
- fprintf(out, "%s copyInt(buff, pos + 1, arg%d? 1 : 0);\n",
- indent.c_str(), argIndex);
- fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str());
- break;
- case JAVA_TYPE_INT:
- case JAVA_TYPE_ENUM:
- fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str());
- fprintf(out, "%s copyInt(buff, pos + 1, arg%d);\n", indent.c_str(), argIndex);
- fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str());
- break;
- case JAVA_TYPE_FLOAT:
- requiredHelpers |= JAVA_MODULE_REQUIRES_FLOAT;
- fprintf(out, "%s buff[pos] = FLOAT_TYPE;\n", indent.c_str());
- fprintf(out, "%s copyFloat(buff, pos + 1, arg%d);\n", indent.c_str(), argIndex);
- fprintf(out, "%s pos += FLOAT_TYPE_SIZE;\n", indent.c_str());
- break;
- case JAVA_TYPE_LONG:
- fprintf(out, "%s buff[pos] = LONG_TYPE;\n", indent.c_str());
- fprintf(out, "%s copyLong(buff, pos + 1, arg%d);\n", indent.c_str(), argIndex);
- fprintf(out, "%s pos += LONG_TYPE_SIZE;\n", indent.c_str());
- break;
- case JAVA_TYPE_STRING:
- fprintf(out, "%s buff[pos] = STRING_TYPE;\n", indent.c_str());
- fprintf(out, "%s copyInt(buff, pos + 1, arg%dBytes.length);\n",
- indent.c_str(), argIndex);
- fprintf(out, "%s System.arraycopy("
- "arg%dBytes, 0, buff, pos + STRING_TYPE_OVERHEAD, arg%dBytes.length);\n",
- indent.c_str(), argIndex, argIndex);
- fprintf(out, "%s pos += STRING_TYPE_OVERHEAD + arg%dBytes.length;\n",
- indent.c_str(), argIndex);
- break;
- case JAVA_TYPE_BYTE_ARRAY:
- fprintf(out, "%s buff[pos] = STRING_TYPE;\n", indent.c_str());
- fprintf(out, "%s copyInt(buff, pos + 1, arg%d.length);\n",
- indent.c_str(), argIndex);
- fprintf(out, "%s System.arraycopy("
- "arg%d, 0, buff, pos + STRING_TYPE_OVERHEAD, arg%d.length);\n",
- indent.c_str(), argIndex, argIndex);
- fprintf(out, "%s pos += STRING_TYPE_OVERHEAD + arg%d.length;\n",
- indent.c_str(), argIndex);
- break;
- case JAVA_TYPE_ATTRIBUTION_CHAIN:
- {
- requiredHelpers |= JAVA_MODULE_REQUIRES_ATTRIBUTION;
- const char* uidName = attributionDecl.fields.front().name.c_str();
- const char* tagName = attributionDecl.fields.back().name.c_str();
-
- fprintf(out, "%s writeAttributionChain(buff, pos, %s, %s);\n", indent.c_str(),
- uidName, tagName);
- fprintf(out, "%s pos += attrSize;\n", indent.c_str());
- break;
- }
- default:
- // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIR.
- fprintf(stderr,
- "Object, Double, and KeyValuePairs are not supported in module logging");
- return 1;
+ case JAVA_TYPE_BOOLEAN:
+ fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyInt(buff, pos + 1, arg%d? 1 : 0);\n", indent.c_str(),
+ argIndex);
+ fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str());
+ break;
+ case JAVA_TYPE_INT:
+ case JAVA_TYPE_ENUM:
+ fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyInt(buff, pos + 1, arg%d);\n", indent.c_str(),
+ argIndex);
+ fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str());
+ break;
+ case JAVA_TYPE_FLOAT:
+ requiredHelpers |= JAVA_MODULE_REQUIRES_FLOAT;
+ fprintf(out, "%s buff[pos] = FLOAT_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyFloat(buff, pos + 1, arg%d);\n", indent.c_str(),
+ argIndex);
+ fprintf(out, "%s pos += FLOAT_TYPE_SIZE;\n", indent.c_str());
+ break;
+ case JAVA_TYPE_LONG:
+ fprintf(out, "%s buff[pos] = LONG_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyLong(buff, pos + 1, arg%d);\n", indent.c_str(),
+ argIndex);
+ fprintf(out, "%s pos += LONG_TYPE_SIZE;\n", indent.c_str());
+ break;
+ case JAVA_TYPE_STRING:
+ fprintf(out, "%s buff[pos] = STRING_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyInt(buff, pos + 1, arg%dBytes.length);\n",
+ indent.c_str(), argIndex);
+ fprintf(out,
+ "%s System.arraycopy("
+ "arg%dBytes, 0, buff, pos + STRING_TYPE_OVERHEAD, "
+ "arg%dBytes.length);\n",
+ indent.c_str(), argIndex, argIndex);
+ fprintf(out, "%s pos += STRING_TYPE_OVERHEAD + arg%dBytes.length;\n",
+ indent.c_str(), argIndex);
+ break;
+ case JAVA_TYPE_BYTE_ARRAY:
+ fprintf(out, "%s buff[pos] = STRING_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyInt(buff, pos + 1, arg%d.length);\n", indent.c_str(),
+ argIndex);
+ fprintf(out,
+ "%s System.arraycopy("
+ "arg%d, 0, buff, pos + STRING_TYPE_OVERHEAD, arg%d.length);\n",
+ indent.c_str(), argIndex, argIndex);
+ fprintf(out, "%s pos += STRING_TYPE_OVERHEAD + arg%d.length;\n",
+ indent.c_str(), argIndex);
+ break;
+ case JAVA_TYPE_ATTRIBUTION_CHAIN: {
+ requiredHelpers |= JAVA_MODULE_REQUIRES_ATTRIBUTION;
+ const char* uidName = attributionDecl.fields.front().name.c_str();
+ const char* tagName = attributionDecl.fields.back().name.c_str();
+
+ fprintf(out, "%s writeAttributionChain(buff, pos, %s, %s);\n",
+ indent.c_str(), uidName, tagName);
+ fprintf(out, "%s pos += attrSize;\n", indent.c_str());
+ break;
+ }
+ case JAVA_TYPE_KEY_VALUE_PAIR:
+ requiredHelpers |= JAVA_MODULE_REQUIRES_FLOAT;
+ requiredHelpers |= JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS;
+ fprintf(out,
+ "%s writeKeyValuePairs(buff, pos, (byte) count, intMap, "
+ "longMap, "
+ "stringMap, floatMap);\n",
+ indent.c_str());
+ fprintf(out, "%s pos += keyValuePairSize;\n", indent.c_str());
+ break;
+ default:
+ // Unsupported types: OBJECT, DOUBLE.
+ fprintf(stderr, "Object and Double are not supported in module logging");
+ return 1;
}
argIndex++;
}
@@ -272,11 +367,8 @@ int write_java_methods_q_schema(
return 0;
}
-void write_java_helpers_for_q_schema_methods(
- FILE* out,
- const AtomDecl &attributionDecl,
- const int requiredHelpers,
- const string& indent) {
+void write_java_helpers_for_q_schema_methods(FILE* out, const AtomDecl& attributionDecl,
+ const int requiredHelpers, const string& indent) {
fprintf(out, "\n");
fprintf(out, "%s// Helper methods for copying primitives\n", indent.c_str());
fprintf(out, "%sprivate static void copyInt(byte[] buff, int pos, int val) {\n",
@@ -316,8 +408,7 @@ void write_java_helpers_for_q_schema_methods(
fprintf(out, "%sprivate static void writeAttributionChain(byte[] buff, int pos",
indent.c_str());
for (auto chainField : attributionDecl.fields) {
- fprintf(out, ", %s[] %s",
- java_type_name(chainField.javaType), chainField.name.c_str());
+ fprintf(out, ", %s[] %s", java_type_name(chainField.javaType), chainField.name.c_str());
}
fprintf(out, ") {\n");
@@ -333,8 +424,8 @@ void write_java_helpers_for_q_schema_methods(
fprintf(out, "%s for (int i = 0; i < %s.length; i++) {\n", indent.c_str(), tagName);
// Write the list begin.
fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str());
- fprintf(out, "%s buff[pos + 1] = %lu;\n",
- indent.c_str(), attributionDecl.fields.size());
+ fprintf(out, "%s buff[pos + 1] = %lu;\n", indent.c_str(),
+ attributionDecl.fields.size());
fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
// Write the uid.
@@ -343,30 +434,133 @@ void write_java_helpers_for_q_schema_methods(
fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str());
// Write the tag.
- fprintf(out, "%s String %sStr = (%s[i] == null) ? \"\" : %s[i];\n",
- indent.c_str(), tagName, tagName, tagName);
- fprintf(out, "%s byte[] %sByte = "
+ fprintf(out, "%s String %sStr = (%s[i] == null) ? \"\" : %s[i];\n", indent.c_str(),
+ tagName, tagName, tagName);
+ fprintf(out,
+ "%s byte[] %sByte = "
"%sStr.getBytes(java.nio.charset.StandardCharsets.UTF_8);\n",
indent.c_str(), tagName, tagName);
fprintf(out, "%s buff[pos] = STRING_TYPE;\n", indent.c_str());
fprintf(out, "%s copyInt(buff, pos + 1, %sByte.length);\n", indent.c_str(), tagName);
- fprintf(out, "%s System.arraycopy("
+ fprintf(out,
+ "%s System.arraycopy("
"%sByte, 0, buff, pos + STRING_TYPE_OVERHEAD, %sByte.length);\n",
indent.c_str(), tagName, tagName);
- fprintf(out, "%s pos += STRING_TYPE_OVERHEAD + %sByte.length;\n",
- indent.c_str(), tagName);
+ fprintf(out, "%s pos += STRING_TYPE_OVERHEAD + %sByte.length;\n", indent.c_str(),
+ tagName);
+ fprintf(out, "%s }\n", indent.c_str());
+ fprintf(out, "%s}\n", indent.c_str());
+ fprintf(out, "\n");
+ }
+
+ if (requiredHelpers & JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS) {
+ fprintf(out,
+ "%sprivate static void writeKeyValuePairs(byte[] buff, int pos, "
+ "byte numPairs,\n",
+ indent.c_str());
+ fprintf(out, "%s final android.util.SparseIntArray intMap,\n", indent.c_str());
+ fprintf(out, "%s final android.util.SparseLongArray longMap,\n", indent.c_str());
+ fprintf(out, "%s final android.util.SparseArray<String> stringMap,\n",
+ indent.c_str());
+ fprintf(out, "%s final android.util.SparseArray<Float> floatMap) {\n",
+ indent.c_str());
+
+ // Start list of lists.
+ fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str());
+ fprintf(out, "%s buff[pos + 1] = (byte) numPairs;\n", indent.c_str());
+ fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
+
+ // Write integers.
+ fprintf(out, "%s final int intMapSize = null == intMap ? 0 : intMap.size();\n",
+ indent.c_str());
+ fprintf(out, "%s for (int i = 0; i < intMapSize; i++) {\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str());
+ fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str());
+ fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
+ fprintf(out, "%s final int key = intMap.keyAt(i);\n", indent.c_str());
+ fprintf(out, "%s final int value = intMap.valueAt(i);\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyInt(buff, pos + 1, key);\n", indent.c_str());
+ fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyInt(buff, pos + 1, value);\n", indent.c_str());
+ fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str());
+ fprintf(out, "%s }\n", indent.c_str());
+
+ // Write longs.
+ fprintf(out, "%s final int longMapSize = null == longMap ? 0 : longMap.size();\n",
+ indent.c_str());
+ fprintf(out, "%s for (int i = 0; i < longMapSize; i++) {\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str());
+ fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str());
+ fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
+ fprintf(out, "%s final int key = longMap.keyAt(i);\n", indent.c_str());
+ fprintf(out, "%s final long value = longMap.valueAt(i);\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyInt(buff, pos + 1, key);\n", indent.c_str());
+ fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = LONG_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyLong(buff, pos + 1, value);\n", indent.c_str());
+ fprintf(out, "%s pos += LONG_TYPE_SIZE;\n", indent.c_str());
+ fprintf(out, "%s }\n", indent.c_str());
+
+ // Write Strings.
+ fprintf(out,
+ "%s final int stringMapSize = null == stringMap ? 0 : "
+ "stringMap.size();\n",
+ indent.c_str());
+ fprintf(out, "%s for (int i = 0; i < stringMapSize; i++) {\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str());
+ fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str());
+ fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
+ fprintf(out, "%s final int key = stringMap.keyAt(i);\n", indent.c_str());
+ fprintf(out, "%s final String value = stringMap.valueAt(i);\n", indent.c_str());
+ fprintf(out,
+ "%s final byte[] valueBytes = "
+ "value.getBytes(java.nio.charset.StandardCharsets.UTF_8);\n",
+ indent.c_str());
+ fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyInt(buff, pos + 1, key);\n", indent.c_str());
+ fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = STRING_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyInt(buff, pos + 1, valueBytes.length);\n", indent.c_str());
+ fprintf(out,
+ "%s System.arraycopy("
+ "valueBytes, 0, buff, pos + STRING_TYPE_OVERHEAD, "
+ "valueBytes.length);\n",
+ indent.c_str());
+ fprintf(out, "%s pos += STRING_TYPE_OVERHEAD + valueBytes.length;\n",
+ indent.c_str());
+ fprintf(out, "%s }\n", indent.c_str());
+
+ // Write floats.
+ fprintf(out,
+ "%s final int floatMapSize = null == floatMap ? 0 : "
+ "floatMap.size();\n",
+ indent.c_str());
+ fprintf(out, "%s for (int i = 0; i < floatMapSize; i++) {\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = LIST_TYPE;\n", indent.c_str());
+ fprintf(out, "%s buff[pos + 1] = (byte) 2;\n", indent.c_str());
+ fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str());
+ fprintf(out, "%s final int key = floatMap.keyAt(i);\n", indent.c_str());
+ fprintf(out, "%s final float value = floatMap.valueAt(i);\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyInt(buff, pos + 1, key);\n", indent.c_str());
+ fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str());
+ fprintf(out, "%s buff[pos] = FLOAT_TYPE;\n", indent.c_str());
+ fprintf(out, "%s copyFloat(buff, pos + 1, value);\n", indent.c_str());
+ fprintf(out, "%s pos += FLOAT_TYPE_SIZE;\n", indent.c_str());
fprintf(out, "%s }\n", indent.c_str());
fprintf(out, "%s}\n", indent.c_str());
fprintf(out, "\n");
}
}
-// This method is called in main.cpp to generate StatsLog for modules that's compatible with
-// Q at compile-time.
+// This method is called in main.cpp to generate StatsLog for modules that's
+// compatible with Q at compile-time.
int write_stats_log_java_q_for_module(FILE* out, const Atoms& atoms,
- const AtomDecl &attributionDecl, const string& moduleName,
- const string& javaClass, const string& javaPackage,
- const bool supportWorkSource) {
+ const AtomDecl& attributionDecl, const string& javaClass,
+ const string& javaPackage, const bool supportWorkSource) {
// Print prelude
fprintf(out, "// This file is autogenerated\n");
fprintf(out, "\n");
@@ -385,19 +579,17 @@ int write_stats_log_java_q_for_module(FILE* out, const Atoms& atoms,
write_java_q_logging_constants(out, " ");
- write_java_atom_codes(out, atoms, moduleName);
+ write_java_atom_codes(out, atoms);
- write_java_enum_values(out, atoms, moduleName);
+ write_java_enum_values(out, atoms);
int errors = 0;
// Print write methods
fprintf(out, " // Write methods\n");
- errors += write_java_methods_q_schema(out, atoms.signatures_to_modules, attributionDecl,
- moduleName, " ");
- errors += write_java_non_chained_methods(out, atoms.non_chained_signatures_to_modules,
- moduleName);
+ errors += write_java_methods_q_schema(out, atoms.signatureInfoMap, attributionDecl, " ");
+ errors += write_java_non_chained_methods(out, atoms.nonChainedSignatureInfoMap);
if (supportWorkSource) {
- errors += write_java_work_source_methods(out, atoms.signatures_to_modules, moduleName);
+ errors += write_java_work_source_methods(out, atoms.signatureInfoMap);
}
fprintf(out, "}\n");
@@ -405,69 +597,5 @@ int write_stats_log_java_q_for_module(FILE* out, const Atoms& atoms,
return errors;
}
-#if defined(STATS_SCHEMA_LEGACY)
-static void write_java_method(
- FILE* out,
- const string& method_name,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const AtomDecl &attributionDecl) {
-
- for (auto signature_to_modules_it = signatures_to_modules.begin();
- signature_to_modules_it != signatures_to_modules.end(); signature_to_modules_it++) {
- vector<java_type_t> signature = signature_to_modules_it->first;
- fprintf(out, " /** @hide */\n");
- fprintf(out, " public static native int %s(int code", method_name.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) {
- 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> value_map");
- } else {
- fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
- }
- argIndex++;
- }
- fprintf(out, ");\n");
- fprintf(out, "\n");
- }
-}
-
-int write_stats_log_java_q(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl,
- const bool supportWorkSource) {
- // Print prelude
- fprintf(out, "// This file is autogenerated\n");
- fprintf(out, "\n");
- fprintf(out, "package android.util;\n");
- fprintf(out, "\n");
- fprintf(out, "\n");
- fprintf(out, "/**\n");
- fprintf(out, " * API For logging statistics events.\n");
- fprintf(out, " * @hide\n");
- fprintf(out, " */\n");
- fprintf(out, "public class StatsLogInternal {\n");
- write_java_atom_codes(out, atoms, DEFAULT_MODULE_NAME);
-
- write_java_enum_values(out, atoms, DEFAULT_MODULE_NAME);
-
- // Print write methods
- fprintf(out, " // Write methods\n");
- write_java_method(out, "write", atoms.signatures_to_modules, attributionDecl);
- write_java_method(out, "write_non_chained", atoms.non_chained_signatures_to_modules,
- attributionDecl);
- if (supportWorkSource) {
- write_java_work_source_methods(out, atoms.signatures_to_modules, DEFAULT_MODULE_NAME);
- }
-
- fprintf(out, "}\n");
-
- return 0;
-}
-#endif
-
} // namespace stats_log_api_gen
} // namespace android
diff --git a/tools/stats_log_api_gen/java_writer_q.h b/tools/stats_log_api_gen/java_writer_q.h
index 7d734df1e118..c511a8436416 100644
--- a/tools/stats_log_api_gen/java_writer_q.h
+++ b/tools/stats_log_api_gen/java_writer_q.h
@@ -16,14 +16,14 @@
#pragma once
-#include "Collation.h"
+#include <stdio.h>
+#include <string.h>
#include <map>
#include <set>
#include <vector>
-#include <stdio.h>
-#include <string.h>
+#include "Collation.h"
namespace android {
namespace stats_log_api_gen {
@@ -32,26 +32,15 @@ using namespace std;
void write_java_q_logging_constants(FILE* out, const string& indent);
-int write_java_methods_q_schema(
- FILE* out,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const AtomDecl &attributionDecl,
- const string& moduleName,
- const string& indent);
+int write_java_methods_q_schema(FILE* out, const SignatureInfoMap& signatureInfoMap,
+ const AtomDecl& attributionDecl, const string& indent);
-void write_java_helpers_for_q_schema_methods(
- FILE * out,
- const AtomDecl &attributionDecl,
- const int requiredHelpers,
- const string& indent);
+void write_java_helpers_for_q_schema_methods(FILE* out, const AtomDecl& attributionDecl,
+ const int requiredHelpers, const string& indent);
int write_stats_log_java_q_for_module(FILE* out, const Atoms& atoms,
- const AtomDecl &attributionDecl, const string& moduleName, const string& javaClass,
- const string& javaPackage, const bool supportWorkSource);
+ const AtomDecl& attributionDecl, const string& javaClass,
+ const string& javaPackage, const bool supportWorkSource);
-#if defined(STATS_SCHEMA_LEGACY)
-int write_stats_log_java_q(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl,
- const bool supportWorkSource);
-#endif
} // namespace stats_log_api_gen
} // namespace android
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index ddbf22c7f12a..136933b8cfb2 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -1,25 +1,21 @@
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <map>
+#include <set>
+#include <vector>
#include "Collation.h"
#include "atoms_info_writer.h"
-#if !defined(STATS_SCHEMA_LEGACY)
+#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
#include "java_writer.h"
-#endif
#include "java_writer_q.h"
#include "native_writer.h"
#include "utils.h"
-#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
-
-#include <map>
-#include <set>
-#include <vector>
-
-#include <getopt.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
using namespace google::protobuf;
using namespace std;
@@ -28,483 +24,34 @@ namespace stats_log_api_gen {
using android::os::statsd::Atom;
-// Hide the JNI write helpers that are not used in the new schema.
-// TODO(b/145100015): Remove this and other JNI related functionality once StatsEvent migration is
-// complete.
-#if defined(STATS_SCHEMA_LEGACY)
-// JNI helpers.
-static const char*
-jni_type_name(java_type_t type)
-{
- switch (type) {
- case JAVA_TYPE_BOOLEAN:
- return "jboolean";
- case JAVA_TYPE_INT:
- case JAVA_TYPE_ENUM:
- return "jint";
- case JAVA_TYPE_LONG:
- return "jlong";
- case JAVA_TYPE_FLOAT:
- return "jfloat";
- case JAVA_TYPE_DOUBLE:
- return "jdouble";
- case JAVA_TYPE_STRING:
- return "jstring";
- case JAVA_TYPE_BYTE_ARRAY:
- return "jbyteArray";
- default:
- return "UNKNOWN";
- }
-}
-
-static const char*
-jni_array_type_name(java_type_t type)
-{
- switch (type) {
- case JAVA_TYPE_INT:
- return "jintArray";
- case JAVA_TYPE_FLOAT:
- return "jfloatArray";
- case JAVA_TYPE_STRING:
- return "jobjectArray";
- default:
- return "UNKNOWN";
- }
-}
-
-static string
-jni_function_name(const string& method_name, const vector<java_type_t>& signature)
-{
- string result("StatsLog_" + method_name);
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
- switch (*arg) {
- case JAVA_TYPE_BOOLEAN:
- result += "_boolean";
- break;
- case JAVA_TYPE_INT:
- case JAVA_TYPE_ENUM:
- result += "_int";
- break;
- case JAVA_TYPE_LONG:
- result += "_long";
- break;
- case JAVA_TYPE_FLOAT:
- result += "_float";
- break;
- case JAVA_TYPE_DOUBLE:
- result += "_double";
- break;
- case JAVA_TYPE_STRING:
- result += "_String";
- break;
- case JAVA_TYPE_ATTRIBUTION_CHAIN:
- result += "_AttributionChain";
- break;
- case JAVA_TYPE_KEY_VALUE_PAIR:
- result += "_KeyValuePairs";
- break;
- case JAVA_TYPE_BYTE_ARRAY:
- result += "_bytes";
- break;
- default:
- result += "_UNKNOWN";
- break;
- }
- }
- return result;
-}
-
-static const char*
-java_type_signature(java_type_t type)
-{
- switch (type) {
- case JAVA_TYPE_BOOLEAN:
- return "Z";
- case JAVA_TYPE_INT:
- case JAVA_TYPE_ENUM:
- return "I";
- case JAVA_TYPE_LONG:
- return "J";
- case JAVA_TYPE_FLOAT:
- return "F";
- case JAVA_TYPE_DOUBLE:
- return "D";
- case JAVA_TYPE_STRING:
- return "Ljava/lang/String;";
- case JAVA_TYPE_BYTE_ARRAY:
- return "[B";
- default:
- return "UNKNOWN";
- }
-}
-
-static string
-jni_function_signature(const vector<java_type_t>& signature, const AtomDecl &attributionDecl)
-{
- string result("(I");
- 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) {
- result += "[";
- result += java_type_signature(chainField.javaType);
- }
- } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
- result += "Landroid/util/SparseArray;";
- } else {
- result += java_type_signature(*arg);
- }
- }
- result += ")I";
- return result;
-}
-
-static void write_key_value_map_jni(FILE* out) {
- fprintf(out, " std::map<int, int32_t> int32_t_map;\n");
- fprintf(out, " std::map<int, int64_t> int64_t_map;\n");
- fprintf(out, " std::map<int, float> float_map;\n");
- fprintf(out, " std::map<int, char const*> string_map;\n\n");
-
- fprintf(out, " jclass jmap_class = env->FindClass(\"android/util/SparseArray\");\n");
-
- fprintf(out, " jmethodID jget_size_method = env->GetMethodID(jmap_class, \"size\", \"()I\");\n");
- fprintf(out, " jmethodID jget_key_method = env->GetMethodID(jmap_class, \"keyAt\", \"(I)I\");\n");
- fprintf(out, " jmethodID jget_value_method = env->GetMethodID(jmap_class, \"valueAt\", \"(I)Ljava/lang/Object;\");\n\n");
-
-
- fprintf(out, " std::vector<std::unique_ptr<ScopedUtfChars>> scoped_ufs;\n\n");
-
- fprintf(out, " jclass jint_class = env->FindClass(\"java/lang/Integer\");\n");
- fprintf(out, " jclass jlong_class = env->FindClass(\"java/lang/Long\");\n");
- fprintf(out, " jclass jfloat_class = env->FindClass(\"java/lang/Float\");\n");
- fprintf(out, " jclass jstring_class = env->FindClass(\"java/lang/String\");\n");
- fprintf(out, " jmethodID jget_int_method = env->GetMethodID(jint_class, \"intValue\", \"()I\");\n");
- fprintf(out, " jmethodID jget_long_method = env->GetMethodID(jlong_class, \"longValue\", \"()J\");\n");
- fprintf(out, " jmethodID jget_float_method = env->GetMethodID(jfloat_class, \"floatValue\", \"()F\");\n\n");
-
- fprintf(out, " jint jsize = env->CallIntMethod(value_map, jget_size_method);\n");
- fprintf(out, " for(int i = 0; i < jsize; i++) {\n");
- fprintf(out, " jint key = env->CallIntMethod(value_map, jget_key_method, i);\n");
- fprintf(out, " jobject jvalue_obj = env->CallObjectMethod(value_map, jget_value_method, i);\n");
- fprintf(out, " if (jvalue_obj == NULL) { continue; }\n");
- fprintf(out, " if (env->IsInstanceOf(jvalue_obj, jint_class)) {\n");
- fprintf(out, " int32_t_map[key] = env->CallIntMethod(jvalue_obj, jget_int_method);\n");
- fprintf(out, " } else if (env->IsInstanceOf(jvalue_obj, jlong_class)) {\n");
- fprintf(out, " int64_t_map[key] = env->CallLongMethod(jvalue_obj, jget_long_method);\n");
- fprintf(out, " } else if (env->IsInstanceOf(jvalue_obj, jfloat_class)) {\n");
- fprintf(out, " float_map[key] = env->CallFloatMethod(jvalue_obj, jget_float_method);\n");
- fprintf(out, " } else if (env->IsInstanceOf(jvalue_obj, jstring_class)) {\n");
- fprintf(out, " std::unique_ptr<ScopedUtfChars> utf(new ScopedUtfChars(env, (jstring)jvalue_obj));\n");
- fprintf(out, " if (utf->c_str() != NULL) { string_map[key] = utf->c_str(); }\n");
- fprintf(out, " scoped_ufs.push_back(std::move(utf));\n");
- fprintf(out, " }\n");
- fprintf(out, " }\n");
-}
-
-static int
-write_stats_log_jni_method(FILE* out, const string& java_method_name, const string& cpp_method_name,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const AtomDecl &attributionDecl) {
- // Print write methods
- for (auto signature_to_modules_it = signatures_to_modules.begin();
- signature_to_modules_it != signatures_to_modules.end(); signature_to_modules_it++) {
- vector<java_type_t> signature = signature_to_modules_it->first;
- int argIndex;
-
- fprintf(out, "static int\n");
- fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code",
- jni_function_name(java_method_name, signature).c_str());
- 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", jni_array_type_name(chainField.javaType),
- chainField.name.c_str());
- }
- } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
- fprintf(out, ", jobject value_map");
- } else {
- fprintf(out, ", %s arg%d", jni_type_name(*arg), argIndex);
- }
- argIndex++;
- }
- fprintf(out, ")\n");
-
- fprintf(out, "{\n");
-
- // Prepare strings
- argIndex = 1;
- bool hadStringOrChain = false;
- bool isKeyValuePairAtom = false;
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
- if (*arg == JAVA_TYPE_STRING) {
- hadStringOrChain = true;
- fprintf(out, " const char* str%d;\n", argIndex);
- fprintf(out, " if (arg%d != NULL) {\n", argIndex);
- fprintf(out, " str%d = env->GetStringUTFChars(arg%d, NULL);\n",
- argIndex, argIndex);
- fprintf(out, " } else {\n");
- fprintf(out, " str%d = NULL;\n", argIndex);
- fprintf(out, " }\n");
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- hadStringOrChain = true;
- fprintf(out, " jbyte* jbyte_array%d;\n", argIndex);
- fprintf(out, " const char* str%d;\n", argIndex);
- fprintf(out, " int str%d_length = 0;\n", argIndex);
- fprintf(out,
- " if (arg%d != NULL && env->GetArrayLength(arg%d) > "
- "0) {\n",
- argIndex, argIndex);
- fprintf(out,
- " jbyte_array%d = "
- "env->GetByteArrayElements(arg%d, NULL);\n",
- argIndex, argIndex);
- fprintf(out,
- " str%d_length = env->GetArrayLength(arg%d);\n",
- argIndex, argIndex);
- fprintf(out,
- " str%d = "
- "reinterpret_cast<char*>(env->GetByteArrayElements(arg%"
- "d, NULL));\n",
- argIndex, argIndex);
- fprintf(out, " } else {\n");
- fprintf(out, " jbyte_array%d = NULL;\n", argIndex);
- fprintf(out, " str%d = NULL;\n", argIndex);
- fprintf(out, " }\n");
-
- fprintf(out,
- " android::util::BytesField bytesField%d(str%d, "
- "str%d_length);",
- argIndex, argIndex, argIndex);
-
- } else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- hadStringOrChain = true;
- for (auto chainField : attributionDecl.fields) {
- fprintf(out, " size_t %s_length = env->GetArrayLength(%s);\n",
- chainField.name.c_str(), chainField.name.c_str());
- if (chainField.name != attributionDecl.fields.front().name) {
- fprintf(out, " if (%s_length != %s_length) {\n",
- chainField.name.c_str(),
- attributionDecl.fields.front().name.c_str());
- fprintf(out, " return -EINVAL;\n");
- fprintf(out, " }\n");
- }
- if (chainField.javaType == JAVA_TYPE_INT) {
- fprintf(out, " jint* %s_array = env->GetIntArrayElements(%s, NULL);\n",
- chainField.name.c_str(), chainField.name.c_str());
- } else if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, " std::vector<%s> %s_vec;\n",
- cpp_type_name(chainField.javaType), chainField.name.c_str());
- fprintf(out, " std::vector<ScopedUtfChars*> scoped_%s_vec;\n",
- chainField.name.c_str());
- fprintf(out, " for (size_t i = 0; i < %s_length; ++i) {\n",
- chainField.name.c_str());
- fprintf(out, " jstring jstr = "
- "(jstring)env->GetObjectArrayElement(%s, i);\n",
- chainField.name.c_str());
- fprintf(out, " if (jstr == NULL) {\n");
- fprintf(out, " %s_vec.push_back(NULL);\n",
- chainField.name.c_str());
- fprintf(out, " } else {\n");
- fprintf(out, " ScopedUtfChars* scoped_%s = "
- "new ScopedUtfChars(env, jstr);\n",
- chainField.name.c_str());
- fprintf(out, " %s_vec.push_back(scoped_%s->c_str());\n",
- chainField.name.c_str(), chainField.name.c_str());
- fprintf(out, " scoped_%s_vec.push_back(scoped_%s);\n",
- chainField.name.c_str(), chainField.name.c_str());
- fprintf(out, " }\n");
- fprintf(out, " }\n");
- }
- fprintf(out, "\n");
- }
- } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
- isKeyValuePairAtom = true;
- }
- argIndex++;
- }
- // Emit this to quiet the unused parameter warning if there were no strings or attribution
- // chains.
- if (!hadStringOrChain && !isKeyValuePairAtom) {
- fprintf(out, " (void)env;\n");
- }
- if (isKeyValuePairAtom) {
- write_key_value_map_jni(out);
- }
-
- // stats_write call
- argIndex = 1;
- fprintf(out, "\n int ret = android::util::%s(code",
- cpp_method_name.c_str());
- 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) {
- if (chainField.javaType == JAVA_TYPE_INT) {
- fprintf(out, ", (const %s*)%s_array, %s_length",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str(), chainField.name.c_str());
- } else if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, ", %s_vec", chainField.name.c_str());
- }
- }
- } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
- fprintf(out, ", int32_t_map, int64_t_map, string_map, float_map");
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, ", bytesField%d", argIndex);
- } else {
- const char* argName =
- (*arg == JAVA_TYPE_STRING) ? "str" : "arg";
- fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex);
- }
- argIndex++;
- }
- fprintf(out, ");\n");
- fprintf(out, "\n");
-
- // Clean up strings
- argIndex = 1;
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
- if (*arg == JAVA_TYPE_STRING) {
- fprintf(out, " if (str%d != NULL) {\n", argIndex);
- fprintf(out, " env->ReleaseStringUTFChars(arg%d, str%d);\n",
- argIndex, argIndex);
- fprintf(out, " }\n");
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out, " if (str%d != NULL) { \n", argIndex);
- fprintf(out,
- " env->ReleaseByteArrayElements(arg%d, "
- "jbyte_array%d, 0);\n",
- argIndex, argIndex);
- fprintf(out, " }\n");
- } else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- for (auto chainField : attributionDecl.fields) {
- if (chainField.javaType == JAVA_TYPE_INT) {
- fprintf(out, " env->ReleaseIntArrayElements(%s, %s_array, 0);\n",
- chainField.name.c_str(), chainField.name.c_str());
- } else if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, " for (size_t i = 0; i < scoped_%s_vec.size(); ++i) {\n",
- chainField.name.c_str());
- fprintf(out, " delete scoped_%s_vec[i];\n", chainField.name.c_str());
- fprintf(out, " }\n");
- }
- }
- }
- argIndex++;
- }
-
- fprintf(out, " return ret;\n");
-
- fprintf(out, "}\n");
- fprintf(out, "\n");
- }
-
-
- return 0;
-}
-
-void write_jni_registration(FILE* out, const string& java_method_name,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const AtomDecl &attributionDecl) {
- for (auto signature_to_modules_it = signatures_to_modules.begin();
- signature_to_modules_it != signatures_to_modules.end(); signature_to_modules_it++) {
- vector<java_type_t> signature = signature_to_modules_it->first;
- fprintf(out, " { \"%s\", \"%s\", (void*)%s },\n",
- java_method_name.c_str(),
- jni_function_signature(signature, attributionDecl).c_str(),
- jni_function_name(java_method_name, signature).c_str());
- }
-}
-#endif // JNI helpers.
-
-static int
-#if defined(STATS_SCHEMA_LEGACY)
-write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
-#else
-// Write empty JNI file that doesn't contain any JNI methods.
-// TODO(b/145100015): remove this function and all JNI autogen code once StatsEvent migration is
-// complete.
-write_stats_log_jni(FILE* out)
-#endif
-{
- // Print prelude
- fprintf(out, "// This file is autogenerated\n");
- fprintf(out, "\n");
-
-#if defined(STATS_SCHEMA_LEGACY)
- fprintf(out, "#include <statslog.h>\n");
- fprintf(out, "\n");
- fprintf(out, "#include <nativehelper/JNIHelp.h>\n");
- fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n");
- fprintf(out, "#include <utils/Vector.h>\n");
-#endif
- fprintf(out, "#include \"core_jni_helpers.h\"\n");
- fprintf(out, "#include \"jni.h\"\n");
- fprintf(out, "\n");
-#if defined(STATS_SCHEMA_LEGACY)
- fprintf(out, "#define UNUSED __attribute__((__unused__))\n");
- fprintf(out, "\n");
-#endif
-
- fprintf(out, "namespace android {\n");
- fprintf(out, "\n");
-
-#if defined(STATS_SCHEMA_LEGACY)
- write_stats_log_jni_method(out, "write", "stats_write", atoms.signatures_to_modules, attributionDecl);
- write_stats_log_jni_method(out, "write_non_chained", "stats_write_non_chained",
- atoms.non_chained_signatures_to_modules, attributionDecl);
-#endif
-
- // Print registration function table
- fprintf(out, "/*\n");
- fprintf(out, " * JNI registration.\n");
- fprintf(out, " */\n");
- fprintf(out, "static const JNINativeMethod gRegisterMethods[] = {\n");
-#if defined(STATS_SCHEMA_LEGACY)
- write_jni_registration(out, "write", atoms.signatures_to_modules, attributionDecl);
- write_jni_registration(out, "write_non_chained", atoms.non_chained_signatures_to_modules,
- attributionDecl);
-#endif
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- // Print registration function
- fprintf(out, "int register_android_util_StatsLogInternal(JNIEnv* env) {\n");
- fprintf(out, " return RegisterMethodsOrDie(\n");
- fprintf(out, " env,\n");
- fprintf(out, " \"android/util/StatsLogInternal\",\n");
- fprintf(out, " gRegisterMethods, NELEM(gRegisterMethods));\n");
- fprintf(out, "}\n");
-
- fprintf(out, "\n");
- fprintf(out, "} // namespace android\n");
- return 0;
-}
-
-static void
-print_usage()
-{
+static void print_usage() {
fprintf(stderr, "usage: stats-log-api-gen OPTIONS\n");
fprintf(stderr, "\n");
fprintf(stderr, "OPTIONS\n");
fprintf(stderr, " --cpp FILENAME the header file to output for write helpers\n");
fprintf(stderr, " --header FILENAME the cpp file to output for write helpers\n");
fprintf(stderr,
- " --atomsInfoCpp FILENAME the header file to output for statsd metadata\n");
- fprintf(stderr, " --atomsInfoHeader FILENAME the cpp file to output for statsd metadata\n");
+ " --atomsInfoCpp FILENAME the header file to output for "
+ "statsd metadata\n");
+ fprintf(stderr,
+ " --atomsInfoHeader FILENAME the cpp file to output for statsd "
+ "metadata\n");
fprintf(stderr, " --help this message\n");
fprintf(stderr, " --java FILENAME the java file to output\n");
- fprintf(stderr, " --jni FILENAME the jni file to output\n");
fprintf(stderr, " --module NAME optional, module name to generate outputs for\n");
- fprintf(stderr, " --namespace COMMA,SEP,NAMESPACE required for cpp/header with module\n");
- fprintf(stderr, " comma separated namespace of the files\n");
- fprintf(stderr," --importHeader NAME required for cpp/jni to say which header to import "
+ fprintf(stderr,
+ " --namespace COMMA,SEP,NAMESPACE required for cpp/header with "
+ "module\n");
+ fprintf(stderr,
+ " comma separated namespace of "
+ "the files\n");
+ fprintf(stderr,
+ " --importHeader NAME required for cpp/jni to say which header to "
+ "import "
"for write helpers\n");
- fprintf(stderr," --atomsInfoImportHeader NAME required for cpp to say which header to import "
+ fprintf(stderr,
+ " --atomsInfoImportHeader NAME required for cpp to say which "
+ "header to import "
"for statsd metadata\n");
fprintf(stderr, " --javaPackage PACKAGE the package for the java file.\n");
fprintf(stderr, " required for java with module\n");
@@ -512,30 +59,30 @@ print_usage()
fprintf(stderr, " Optional for Java with module.\n");
fprintf(stderr, " Default is \"StatsLogInternal\"\n");
fprintf(stderr, " --supportQ Include runtime support for Android Q.\n");
- fprintf(stderr, " --worksource Include support for logging WorkSource objects.\n");
- fprintf(stderr, " --compileQ Include compile-time support for Android Q "
+ fprintf(stderr,
+ " --worksource Include support for logging WorkSource "
+ "objects.\n");
+ fprintf(stderr,
+ " --compileQ Include compile-time support for Android Q "
"(Java only).\n");
}
/**
* Do the argument parsing and execute the tasks.
*/
-static int
-run(int argc, char const*const* argv)
-{
+static int run(int argc, char const* const* argv) {
string cppFilename;
string headerFilename;
string javaFilename;
- string jniFilename;
string atomsInfoCppFilename;
string atomsInfoHeaderFilename;
+ string javaPackage;
+ string javaClass;
string moduleName = DEFAULT_MODULE_NAME;
string cppNamespace = DEFAULT_CPP_NAMESPACE;
string cppHeaderImport = DEFAULT_CPP_HEADER_IMPORT;
string atomsInfoCppHeaderImport = DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT;
- string javaPackage = DEFAULT_JAVA_PACKAGE;
- string javaClass = DEFAULT_JAVA_CLASS;
bool supportQ = false;
bool supportWorkSource = false;
bool compileQ = false;
@@ -566,13 +113,6 @@ run(int argc, char const*const* argv)
return 1;
}
javaFilename = argv[index];
- } else if (0 == strcmp("--jni", argv[index])) {
- index++;
- if (index >= argc) {
- print_usage();
- return 1;
- }
- jniFilename = argv[index];
} else if (0 == strcmp("--module", argv[index])) {
index++;
if (index >= argc) {
@@ -640,12 +180,8 @@ run(int argc, char const*const* argv)
index++;
}
- if (cppFilename.size() == 0
- && headerFilename.size() == 0
- && javaFilename.size() == 0
- && jniFilename.size() == 0
- && atomsInfoHeaderFilename.size() == 0
- && atomsInfoCppFilename.size() == 0) {
+ if (cppFilename.size() == 0 && headerFilename.size() == 0 && javaFilename.size() == 0 &&
+ atomsInfoHeaderFilename.size() == 0 && atomsInfoCppFilename.size() == 0) {
print_usage();
return 1;
}
@@ -664,15 +200,15 @@ run(int argc, char const*const* argv)
// Collate the parameters
Atoms atoms;
- int errorCount = collate_atoms(Atom::descriptor(), &atoms);
+ int errorCount = collate_atoms(Atom::descriptor(), moduleName, &atoms);
if (errorCount != 0) {
return 1;
}
AtomDecl attributionDecl;
vector<java_type_t> attributionSignature;
- collate_atom(android::os::statsd::AttributionNode::descriptor(),
- &attributionDecl, &attributionSignature);
+ collate_atom(android::os::statsd::AttributionNode::descriptor(), &attributionDecl,
+ &attributionSignature);
// Write the atoms info .cpp file
if (atomsInfoCppFilename.size() != 0) {
@@ -681,8 +217,8 @@ run(int argc, char const*const* argv)
fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoCppFilename.c_str());
return 1;
}
- errorCount = android::stats_log_api_gen::write_atoms_info_cpp(
- out, atoms, cppNamespace, atomsInfoCppHeaderImport, cppHeaderImport);
+ errorCount = android::stats_log_api_gen::write_atoms_info_cpp(out, atoms, cppNamespace,
+ atomsInfoCppHeaderImport);
fclose(out);
}
@@ -693,11 +229,10 @@ run(int argc, char const*const* argv)
fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoHeaderFilename.c_str());
return 1;
}
- errorCount = android::stats_log_api_gen::write_atoms_info_header(out, atoms, cppNamespace);
+ errorCount = android::stats_log_api_gen::write_atoms_info_header(out, cppNamespace);
fclose(out);
}
-
// Write the .cpp file
if (cppFilename.size() != 0) {
FILE* out = fopen(cppFilename.c_str(), "w");
@@ -710,13 +245,14 @@ run(int argc, char const*const* argv)
fprintf(stderr, "Must supply --namespace if supplying a specific module\n");
return 1;
}
- // If this is for a specific module, the header file to import must also be provided.
+ // If this is for a specific module, the header file to import must also be
+ // provided.
if (moduleName != DEFAULT_MODULE_NAME && cppHeaderImport == DEFAULT_CPP_HEADER_IMPORT) {
fprintf(stderr, "Must supply --headerImport if supplying a specific module\n");
return 1;
}
errorCount = android::stats_log_api_gen::write_stats_log_cpp(
- out, atoms, attributionDecl, moduleName, cppNamespace, cppHeaderImport, supportQ);
+ out, atoms, attributionDecl, cppNamespace, cppHeaderImport, supportQ);
fclose(out);
}
@@ -731,62 +267,42 @@ run(int argc, char const*const* argv)
if (moduleName != DEFAULT_MODULE_NAME && cppNamespace == DEFAULT_CPP_NAMESPACE) {
fprintf(stderr, "Must supply --namespace if supplying a specific module\n");
}
- errorCount = android::stats_log_api_gen::write_stats_log_header(
- out, atoms, attributionDecl, moduleName, cppNamespace);
+ errorCount = android::stats_log_api_gen::write_stats_log_header(out, atoms, attributionDecl,
+ cppNamespace);
fclose(out);
}
// Write the .java file
if (javaFilename.size() != 0) {
- FILE* out = fopen(javaFilename.c_str(), "w");
- if (out == NULL) {
- fprintf(stderr, "Unable to open file for write: %s\n", javaFilename.c_str());
+ if (javaClass.size() == 0) {
+ fprintf(stderr, "Must supply --javaClass if supplying a Java filename");
return 1;
}
-#if defined(STATS_SCHEMA_LEGACY)
- if (moduleName == DEFAULT_MODULE_NAME) {
- errorCount = android::stats_log_api_gen::write_stats_log_java_q(
- out, atoms, attributionDecl, supportWorkSource);
- } else {
- errorCount = android::stats_log_api_gen::write_stats_log_java_q_for_module(
- out, atoms, attributionDecl, moduleName, javaClass, javaPackage,
- supportWorkSource);
+ if (javaPackage.size() == 0) {
+ fprintf(stderr, "Must supply --javaPackage if supplying a Java filename");
+ return 1;
+ }
+ if (moduleName.size() == 0) {
+ fprintf(stderr, "Must supply --module if supplying a Java filename");
+ return 1;
}
-#else
- if (moduleName == DEFAULT_MODULE_NAME) {
- javaClass = "StatsLogInternal";
- javaPackage = "android.util";
+
+ FILE* out = fopen(javaFilename.c_str(), "w");
+ if (out == NULL) {
+ fprintf(stderr, "Unable to open file for write: %s\n", javaFilename.c_str());
+ return 1;
}
+
if (compileQ) {
errorCount = android::stats_log_api_gen::write_stats_log_java_q_for_module(
- out, atoms, attributionDecl, moduleName, javaClass, javaPackage,
- supportWorkSource);
+ out, atoms, attributionDecl, javaClass, javaPackage, supportWorkSource);
} else {
errorCount = android::stats_log_api_gen::write_stats_log_java(
- out, atoms, attributionDecl, moduleName, javaClass, javaPackage, supportQ,
+ out, atoms, attributionDecl, javaClass, javaPackage, supportQ,
supportWorkSource);
}
-#endif
-
- fclose(out);
- }
-
- // Write the jni file
- if (jniFilename.size() != 0) {
- FILE* out = fopen(jniFilename.c_str(), "w");
- if (out == NULL) {
- fprintf(stderr, "Unable to open file for write: %s\n", jniFilename.c_str());
- return 1;
- }
-
-#if defined(STATS_SCHEMA_LEGACY)
- errorCount = android::stats_log_api_gen::write_stats_log_jni(
- out, atoms, attributionDecl);
-#else
- errorCount = android::stats_log_api_gen::write_stats_log_jni(out);
-#endif
fclose(out);
}
@@ -800,9 +316,7 @@ run(int argc, char const*const* argv)
/**
* Main.
*/
-int
-main(int argc, char const*const* argv)
-{
+int main(int argc, char const* const* argv) {
GOOGLE_PROTOBUF_VERIFY_VERSION;
return android::stats_log_api_gen::run(argc, argv);
diff --git a/tools/stats_log_api_gen/native_writer.cpp b/tools/stats_log_api_gen/native_writer.cpp
index c7a34feff94b..0c6c0099e459 100644
--- a/tools/stats_log_api_gen/native_writer.cpp
+++ b/tools/stats_log_api_gen/native_writer.cpp
@@ -15,42 +15,93 @@
*/
#include "native_writer.h"
-#include "native_writer_q.h"
+
#include "utils.h"
namespace android {
namespace stats_log_api_gen {
-#if !defined(STATS_SCHEMA_LEGACY)
-static void write_native_key_value_pairs_for_type(FILE* out, const int argIndex,
- const int typeIndex, const string& type, const string& valueFieldName) {
- fprintf(out, " for (const auto& it : arg%d_%d) {\n", argIndex, typeIndex);
- fprintf(out, " pairs.push_back("
- "{ .key = it.first, .valueType = %s, .%s = it.second });\n",
- type.c_str(), valueFieldName.c_str());
- fprintf(out, " }\n");
+static void write_native_annotation_constants(FILE* out) {
+ fprintf(out, "// Annotation constants.\n");
+ for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) {
+ fprintf(out, "const uint8_t %s = %hhu;\n", name.c_str(), id);
+ }
+ fprintf(out, "\n");
+}
+
+static void write_annotations(FILE* out, int argIndex,
+ const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet,
+ const string& methodPrefix, const string& methodSuffix) {
+ FieldNumberToAtomDeclSet::const_iterator fieldNumberToAtomDeclSetIt =
+ fieldNumberToAtomDeclSet.find(argIndex);
+ if (fieldNumberToAtomDeclSet.end() == fieldNumberToAtomDeclSetIt) {
+ return;
+ }
+ const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second;
+ for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) {
+ const string atomConstant = make_constant_name(atomDecl->name);
+ fprintf(out, " if (%s == code) {\n", atomConstant.c_str());
+ const AnnotationSet& annotations = atomDecl->fieldNumberToAnnotations.at(argIndex);
+ int resetState = -1;
+ int defaultState = -1;
+ for (const shared_ptr<Annotation>& annotation : annotations) {
+ const string& annotationConstant = ANNOTATION_ID_CONSTANTS.at(annotation->annotationId);
+ switch (annotation->type) {
+ case ANNOTATION_TYPE_INT:
+ if (ANNOTATION_ID_TRIGGER_STATE_RESET == annotation->annotationId) {
+ resetState = annotation->value.intValue;
+ } else if (ANNOTATION_ID_DEFAULT_STATE == annotation->annotationId) {
+ defaultState = annotation->value.intValue;
+ } else {
+ fprintf(out, " %saddInt32Annotation(%s%s, %d);\n",
+ methodPrefix.c_str(), methodSuffix.c_str(),
+ annotationConstant.c_str(), annotation->value.intValue);
+ }
+ 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");
+ break;
+ default:
+ break;
+ }
+ }
+ if (defaultState != -1 && resetState != -1) {
+ const string& annotationConstant =
+ ANNOTATION_ID_CONSTANTS.at(ANNOTATION_ID_TRIGGER_STATE_RESET);
+ fprintf(out, " if (arg%d == %d) {\n", argIndex, resetState);
+ fprintf(out, " %saddInt32Annotation(%s%s, %d);\n", methodPrefix.c_str(),
+ methodSuffix.c_str(), annotationConstant.c_str(), defaultState);
+ fprintf(out, " }\n");
+ }
+ fprintf(out, " }\n");
+ }
}
static int write_native_stats_write_methods(FILE* out, const Atoms& atoms,
- const AtomDecl& attributionDecl, const string& moduleName, const bool supportQ) {
+ const AtomDecl& attributionDecl, const bool supportQ) {
fprintf(out, "\n");
- for (auto signature_to_modules_it = atoms.signatures_to_modules.begin();
- signature_to_modules_it != atoms.signatures_to_modules.end(); signature_to_modules_it++) {
- if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) {
+ for (auto signatureInfoMapIt = atoms.signatureInfoMap.begin();
+ signatureInfoMapIt != atoms.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()) {
continue;
}
- vector<java_type_t> signature = signature_to_modules_it->first;
-
- write_native_method_signature(out, "int stats_write", signature,
- attributionDecl, " {");
+ write_native_method_signature(out, "int stats_write", signature, attributionDecl, " {");
int argIndex = 1;
if (supportQ) {
fprintf(out, " StatsEventCompat event;\n");
fprintf(out, " event.setAtomId(code);\n");
+ write_annotations(out, ATOM_ID_FIELD_NUMBER, fieldNumberToAtomDeclSet, "event.", "");
for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
+ arg != signature.end(); arg++) {
switch (*arg) {
case JAVA_TYPE_ATTRIBUTION_CHAIN: {
const char* uidName = attributionDecl.fields.front().name.c_str();
@@ -59,11 +110,6 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms,
uidName, uidName, tagName);
break;
}
- case JAVA_TYPE_KEY_VALUE_PAIR:
- fprintf(out, " event.writeKeyValuePairs("
- "arg%d_1, arg%d_2, arg%d_3, arg%d_4);\n",
- argIndex, argIndex, argIndex, argIndex);
- break;
case JAVA_TYPE_BYTE_ARRAY:
fprintf(out, " event.writeByteArray(arg%d.arg, arg%d.arg_length);\n",
argIndex, argIndex);
@@ -71,7 +117,7 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms,
case JAVA_TYPE_BOOLEAN:
fprintf(out, " event.writeBool(arg%d);\n", argIndex);
break;
- case JAVA_TYPE_INT: // Fall through.
+ case JAVA_TYPE_INT: // Fall through.
case JAVA_TYPE_ENUM:
fprintf(out, " event.writeInt32(arg%d);\n", argIndex);
break;
@@ -85,74 +131,66 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms,
fprintf(out, " event.writeString(arg%d);\n", argIndex);
break;
default:
- // Unsupported types: OBJECT, DOUBLE.
+ // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS.
fprintf(stderr, "Encountered unsupported type.");
return 1;
}
+ write_annotations(out, argIndex, fieldNumberToAtomDeclSet, "event.", "");
argIndex++;
}
fprintf(out, " return event.writeToSocket();\n");
} else {
- fprintf(out, " struct stats_event* event = stats_event_obtain();\n");
- fprintf(out, " stats_event_set_atom_id(event, code);\n");
+ 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++) {
+ 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,
- " stats_event_write_attribution_chain(event, "
+ " 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_KEY_VALUE_PAIR:
- fprintf(out, " std::vector<key_value_pair> pairs;\n");
- write_native_key_value_pairs_for_type(
- out, argIndex, 1, "INT32_TYPE", "int32Value");
- write_native_key_value_pairs_for_type(
- out, argIndex, 2, "INT64_TYPE", "int64Value");
- write_native_key_value_pairs_for_type(
- out, argIndex, 3, "STRING_TYPE", "stringValue");
- write_native_key_value_pairs_for_type(
- out, argIndex, 4, "FLOAT_TYPE", "floatValue");
- fprintf(out,
- " stats_event_write_key_value_pairs(event, pairs.data(), "
- "static_cast<uint8_t>(pairs.size()));\n");
- break;
case JAVA_TYPE_BYTE_ARRAY:
fprintf(out,
- " stats_event_write_byte_array(event, "
- "reinterpret_cast<const uint8_t*>(arg%d.arg), arg%d.arg_length);\n",
+ " 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, " stats_event_write_bool(event, arg%d);\n", argIndex);
+ fprintf(out, " AStatsEvent_writeBool(event, arg%d);\n", argIndex);
break;
- case JAVA_TYPE_INT: // Fall through.
+ case JAVA_TYPE_INT: // Fall through.
case JAVA_TYPE_ENUM:
- fprintf(out, " stats_event_write_int32(event, arg%d);\n", argIndex);
+ fprintf(out, " AStatsEvent_writeInt32(event, arg%d);\n", argIndex);
break;
case JAVA_TYPE_FLOAT:
- fprintf(out, " stats_event_write_float(event, arg%d);\n", argIndex);
+ fprintf(out, " AStatsEvent_writeFloat(event, arg%d);\n", argIndex);
break;
case JAVA_TYPE_LONG:
- fprintf(out, " stats_event_write_int64(event, arg%d);\n", argIndex);
+ fprintf(out, " AStatsEvent_writeInt64(event, arg%d);\n", argIndex);
break;
case JAVA_TYPE_STRING:
- fprintf(out, " stats_event_write_string8(event, arg%d);\n", argIndex);
+ fprintf(out, " AStatsEvent_writeString(event, arg%d);\n", argIndex);
break;
default:
- // Unsupported types: OBJECT, DOUBLE.
+ // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS
fprintf(stderr, "Encountered unsupported type.");
return 1;
}
+ write_annotations(out, argIndex, fieldNumberToAtomDeclSet, "AStatsEvent_",
+ "event, ");
argIndex++;
}
- fprintf(out, " const int ret = stats_event_write(event);\n");
- fprintf(out, " stats_event_release(event);\n");
+ fprintf(out, " const int ret = AStatsEvent_write(event);\n");
+ fprintf(out, " AStatsEvent_release(event);\n");
fprintf(out, " return ret;\n");
}
fprintf(out, "}\n\n");
@@ -161,17 +199,18 @@ static int write_native_stats_write_methods(FILE* out, const Atoms& atoms,
}
static void write_native_stats_write_non_chained_methods(FILE* out, const Atoms& atoms,
- const AtomDecl& attributionDecl, const string& moduleName) {
+ const AtomDecl& attributionDecl) {
fprintf(out, "\n");
- for (auto signature_it = atoms.non_chained_signatures_to_modules.begin();
- signature_it != atoms.non_chained_signatures_to_modules.end(); signature_it++) {
- if (!signature_needed_for_module(signature_it->second, moduleName)) {
+ for (auto signature_it = atoms.nonChainedSignatureInfoMap.begin();
+ signature_it != atoms.nonChainedSignatureInfoMap.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()) {
continue;
}
- vector<java_type_t> signature = signature_it->first;
write_native_method_signature(out, "int stats_write_non_chained", signature,
- attributionDecl, " {");
+ attributionDecl, " {");
vector<java_type_t> newSignature;
@@ -194,63 +233,42 @@ static void write_native_stats_write_non_chained_methods(FILE* out, const Atoms&
fprintf(out, "}\n\n");
}
-
}
-#endif
-
-static void write_native_method_header(
- FILE* out,
- const string& methodName,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const AtomDecl &attributionDecl, const string& moduleName) {
-
- for (auto signature_to_modules_it = signatures_to_modules.begin();
- signature_to_modules_it != signatures_to_modules.end(); signature_to_modules_it++) {
- // Skip if this signature is not needed for the module.
- if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) {
+
+static void write_native_method_header(FILE* out, const string& methodName,
+ const SignatureInfoMap& signatureInfoMap,
+ const AtomDecl& attributionDecl) {
+ for (auto signatureInfoMapIt = signatureInfoMap.begin();
+ signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) {
+ 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()) {
continue;
}
-
- vector<java_type_t> signature = signature_to_modules_it->first;
write_native_method_signature(out, methodName, signature, attributionDecl, ";");
}
}
-int write_stats_log_cpp(FILE *out, const Atoms &atoms, const AtomDecl &attributionDecl,
- const string& moduleName, const string& cppNamespace,
- const string& importHeader, const bool supportQ) {
+int write_stats_log_cpp(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const string& cppNamespace, const string& importHeader,
+ const bool supportQ) {
// Print prelude
fprintf(out, "// This file is autogenerated\n");
fprintf(out, "\n");
fprintf(out, "#include <%s>\n", importHeader.c_str());
-#if defined(STATS_SCHEMA_LEGACY)
- (void)supportQ; // Workaround for unused parameter error.
- write_native_cpp_includes_q(out);
-#else
if (supportQ) {
fprintf(out, "#include <StatsEventCompat.h>\n");
} else {
fprintf(out, "#include <stats_event.h>\n");
}
-#endif
fprintf(out, "\n");
write_namespace(out, cppNamespace);
-#if defined(STATS_SCHEMA_LEGACY)
- write_native_stats_log_cpp_globals_q(out);
- write_native_get_timestamp_ns_q(out);
- write_native_try_stats_write_methods_q(out, atoms, attributionDecl, moduleName);
- write_native_stats_write_methods_q(out, "int stats_write", atoms, attributionDecl, moduleName,
- "try_stats_write");
- write_native_try_stats_write_non_chained_methods_q(out, atoms, attributionDecl, moduleName);
- write_native_stats_write_non_chained_methods_q(out, "int stats_write_non_chained", atoms,
- attributionDecl, moduleName, "try_stats_write_non_chained");
-#else
- write_native_stats_write_methods(out, atoms, attributionDecl, moduleName, supportQ);
- write_native_stats_write_non_chained_methods(out, atoms, attributionDecl, moduleName);
-#endif
+ write_native_stats_write_methods(out, atoms, attributionDecl, supportQ);
+ write_native_stats_write_non_chained_methods(out, atoms, attributionDecl);
// Print footer
fprintf(out, "\n");
@@ -259,8 +277,8 @@ int write_stats_log_cpp(FILE *out, const Atoms &atoms, const AtomDecl &attributi
return 0;
}
-int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl,
- const string& moduleName, const string& cppNamespace) {
+int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const string& cppNamespace) {
// Print prelude
fprintf(out, "// This file is autogenerated\n");
fprintf(out, "\n");
@@ -279,37 +297,33 @@ int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attrib
fprintf(out, " */\n");
fprintf(out, "\n");
- write_native_atom_constants(out, atoms, attributionDecl, moduleName);
+ write_native_atom_constants(out, atoms, attributionDecl);
// Print constants for the enum values.
fprintf(out, "//\n");
fprintf(out, "// Constants for enum values\n");
fprintf(out, "//\n\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- // Skip if the atom is not needed for the module.
- if (!atom_needed_for_module(*atom, moduleName)) {
- continue;
- }
-
- for (vector<AtomField>::const_iterator field = atom->fields.begin();
- field != atom->fields.end(); field++) {
+ for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end();
+ atomIt++) {
+ for (vector<AtomField>::const_iterator field = (*atomIt)->fields.begin();
+ field != (*atomIt)->fields.end(); field++) {
if (field->javaType == JAVA_TYPE_ENUM) {
- fprintf(out, "// Values for %s.%s\n", atom->message.c_str(),
- field->name.c_str());
+ fprintf(out, "// Values for %s.%s\n", (*atomIt)->message.c_str(),
+ field->name.c_str());
for (map<int, string>::const_iterator value = field->enumValues.begin();
- value != field->enumValues.end(); value++) {
+ value != field->enumValues.end(); value++) {
fprintf(out, "const int32_t %s__%s__%s = %d;\n",
- make_constant_name(atom->message).c_str(),
- make_constant_name(field->name).c_str(),
- make_constant_name(value->second).c_str(),
- value->first);
+ make_constant_name((*atomIt)->message).c_str(),
+ make_constant_name(field->name).c_str(),
+ make_constant_name(value->second).c_str(), value->first);
}
fprintf(out, "\n");
}
}
}
+ write_native_annotation_constants(out);
+
fprintf(out, "struct BytesField {\n");
fprintf(out,
" BytesField(char const* array, size_t len) : arg(array), "
@@ -323,14 +337,13 @@ 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.signatures_to_modules, attributionDecl,
- moduleName);
+ write_native_method_header(out, "int stats_write", atoms.signatureInfoMap, attributionDecl);
fprintf(out, "//\n");
fprintf(out, "// Write flattened methods\n");
fprintf(out, "//\n");
- write_native_method_header(out, "int stats_write_non_chained",
- atoms.non_chained_signatures_to_modules, attributionDecl, moduleName);
+ write_native_method_header(out, "int stats_write_non_chained", atoms.nonChainedSignatureInfoMap,
+ attributionDecl);
fprintf(out, "\n");
write_closing_namespace(out, cppNamespace);
diff --git a/tools/stats_log_api_gen/native_writer.h b/tools/stats_log_api_gen/native_writer.h
index aafa96ece2d0..264d4db29fc9 100644
--- a/tools/stats_log_api_gen/native_writer.h
+++ b/tools/stats_log_api_gen/native_writer.h
@@ -16,22 +16,22 @@
#pragma once
-#include "Collation.h"
-
#include <stdio.h>
#include <string.h>
+#include "Collation.h"
+
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& moduleName, const string& cppNamespace, const string& importHeader,
- const bool supportQ);
+int write_stats_log_cpp(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const string& cppNamespace, const string& importHeader,
+ const bool supportQ);
-int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl,
- const string& moduleName, const string& cppNamespace);
+int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const string& cppNamespace);
} // namespace stats_log_api_gen
} // namespace android
diff --git a/tools/stats_log_api_gen/native_writer_q.cpp b/tools/stats_log_api_gen/native_writer_q.cpp
deleted file mode 100644
index 299873dad975..000000000000
--- a/tools/stats_log_api_gen/native_writer_q.cpp
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "native_writer_q.h"
-#include "utils.h"
-
-namespace android {
-namespace stats_log_api_gen {
-
-static void write_native_stats_write_body_q(FILE* out, const vector<java_type_t>& signature,
- const AtomDecl& attributionDecl, const string& indent, const string& tryMethodName) {
- fprintf(out, "%sint ret = 0;\n", indent.c_str());
-
- fprintf(out, "%sfor(int retry = 0; retry < 2; ++retry) {\n", indent.c_str());
- fprintf(out, "%s ret = ", indent.c_str());
- write_native_method_call(out, tryMethodName, signature, attributionDecl);
- fprintf(out, "%s if (ret >= 0) { break; }\n", indent.c_str());
-
- fprintf(out, "%s {\n", indent.c_str());
- fprintf(out, "%s std::lock_guard<std::mutex> lock(mLogdRetryMutex);\n", indent.c_str());
- fprintf(out, "%s if ((get_elapsed_realtime_ns() - lastRetryTimestampNs) <= "
- "kMinRetryIntervalNs) break;\n", indent.c_str());
- fprintf(out, "%s lastRetryTimestampNs = get_elapsed_realtime_ns();\n",
- indent.c_str());
- fprintf(out, "%s }\n", indent.c_str());
- fprintf(out, "%s std::this_thread::sleep_for(std::chrono::milliseconds(10));\n",
- indent.c_str());
- fprintf(out, "%s}\n", indent.c_str());
- fprintf(out, "%sif (ret < 0) {\n", indent.c_str());
- fprintf(out, "%s note_log_drop(ret, code);\n", indent.c_str());
- fprintf(out, "%s}\n", indent.c_str());
- fprintf(out, "%sreturn ret;\n", indent.c_str());
-}
-
-void write_native_cpp_includes_q(FILE* out) {
- fprintf(out, "#include <mutex>\n");
- fprintf(out, "#include <chrono>\n");
- fprintf(out, "#include <thread>\n");
- fprintf(out, "#ifdef __ANDROID__\n");
- fprintf(out, "#include <cutils/properties.h>\n");
- fprintf(out, "#endif\n");
- fprintf(out, "#include <stats_event_list.h>\n");
- fprintf(out, "#include <log/log.h>\n");
- fprintf(out, "#include <time.h>\n");
-}
-
-void write_native_get_timestamp_ns_q(FILE* out) {
- fprintf(out, "\n");
- fprintf(out, "static int64_t get_elapsed_realtime_ns() {\n");
- fprintf(out, " struct timespec t;\n");
- fprintf(out, " t.tv_sec = t.tv_nsec = 0;\n");
- fprintf(out, " clock_gettime(CLOCK_BOOTTIME, &t);\n");
- fprintf(out, " return (int64_t)t.tv_sec * 1000000000LL + t.tv_nsec;\n");
- fprintf(out, "}\n");
-}
-
-void write_native_stats_log_cpp_globals_q(FILE* out) {
- fprintf(out, "// the single event tag id for all stats logs\n");
- fprintf(out, "const static int kStatsEventTag = 1937006964;\n");
- fprintf(out, "#ifdef __ANDROID__\n");
- fprintf(out,
- "const static bool kStatsdEnabled = property_get_bool(\"ro.statsd.enable\", true);\n");
- fprintf(out, "#else\n");
- fprintf(out, "const static bool kStatsdEnabled = false;\n");
- fprintf(out, "#endif\n");
-
- fprintf(out, "int64_t lastRetryTimestampNs = -1;\n");
- fprintf(out, "const int64_t kMinRetryIntervalNs = NS_PER_SEC * 60 * 20; // 20 minutes\n");
- fprintf(out, "static std::mutex mLogdRetryMutex;\n");
-}
-
-void write_native_try_stats_write_methods_q(FILE* out, const Atoms& atoms,
- const AtomDecl& attributionDecl, const string& moduleName) {
- fprintf(out, "\n");
- for (auto signature_to_modules_it = atoms.signatures_to_modules.begin();
- signature_to_modules_it != atoms.signatures_to_modules.end(); signature_to_modules_it++) {
- if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) {
- continue;
- }
- vector<java_type_t> signature = signature_to_modules_it->first;
-
- write_native_method_signature(out, "static int try_stats_write", signature,
- attributionDecl, " {");
-
- int argIndex = 1;
- fprintf(out, " if (kStatsdEnabled) {\n");
- fprintf(out, " stats_event_list event(kStatsEventTag);\n");
- fprintf(out, " event << get_elapsed_realtime_ns();\n\n");
- fprintf(out, " event << code;\n\n");
- 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) {
- if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, " if (%s_length != %s.size()) {\n",
- attributionDecl.fields.front().name.c_str(), chainField.name.c_str());
- fprintf(out, " return -EINVAL;\n");
- fprintf(out, " }\n");
- }
- }
- fprintf(out, "\n event.begin();\n");
- fprintf(out, " for (size_t i = 0; i < %s_length; ++i) {\n",
- attributionDecl.fields.front().name.c_str());
- fprintf(out, " event.begin();\n");
- for (const auto &chainField : attributionDecl.fields) {
- if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, " if (%s[i] != NULL) {\n", chainField.name.c_str());
- fprintf(out, " event << %s[i];\n", chainField.name.c_str());
- fprintf(out, " } else {\n");
- fprintf(out, " event << \"\";\n");
- fprintf(out, " }\n");
- } else {
- fprintf(out, " event << %s[i];\n", chainField.name.c_str());
- }
- }
- fprintf(out, " event.end();\n");
- fprintf(out, " }\n");
- fprintf(out, " event.end();\n\n");
- } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
- fprintf(out, " event.begin();\n\n");
- fprintf(out, " for (const auto& it : arg%d_1) {\n", argIndex);
- fprintf(out, " event.begin();\n");
- fprintf(out, " event << it.first;\n");
- fprintf(out, " event << it.second;\n");
- fprintf(out, " event.end();\n");
- fprintf(out, " }\n");
-
- fprintf(out, " for (const auto& it : arg%d_2) {\n", argIndex);
- fprintf(out, " event.begin();\n");
- fprintf(out, " event << it.first;\n");
- fprintf(out, " event << it.second;\n");
- fprintf(out, " event.end();\n");
- fprintf(out, " }\n");
-
- fprintf(out, " for (const auto& it : arg%d_3) {\n", argIndex);
- fprintf(out, " event.begin();\n");
- fprintf(out, " event << it.first;\n");
- fprintf(out, " event << it.second;\n");
- fprintf(out, " event.end();\n");
- fprintf(out, " }\n");
-
- fprintf(out, " for (const auto& it : arg%d_4) {\n", argIndex);
- fprintf(out, " event.begin();\n");
- fprintf(out, " event << it.first;\n");
- fprintf(out, " event << it.second;\n");
- fprintf(out, " event.end();\n");
- fprintf(out, " }\n");
-
- fprintf(out, " event.end();\n\n");
- } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out,
- " event.AppendCharArray(arg%d.arg, "
- "arg%d.arg_length);\n",
- argIndex, argIndex);
- } else {
- if (*arg == JAVA_TYPE_STRING) {
- fprintf(out, " if (arg%d == NULL) {\n", argIndex);
- fprintf(out, " arg%d = \"\";\n", argIndex);
- fprintf(out, " }\n");
- }
- fprintf(out, " event << arg%d;\n", argIndex);
- }
- argIndex++;
- }
-
- fprintf(out, " return event.write(LOG_ID_STATS);\n");
- fprintf(out, " } else {\n");
- fprintf(out, " return 1;\n");
- fprintf(out, " }\n");
- fprintf(out, "}\n");
- fprintf(out, "\n");
- }
-
-}
-
-void write_native_stats_write_methods_q(FILE* out, const string& methodName, const Atoms& atoms,
- const AtomDecl& attributionDecl, const string& moduleName, const string& tryMethodName) {
- for (auto signature_to_modules_it = atoms.signatures_to_modules.begin();
- signature_to_modules_it != atoms.signatures_to_modules.end();
- signature_to_modules_it++) {
- if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) {
- continue;
- }
- vector<java_type_t> signature = signature_to_modules_it->first;
-
- write_native_method_signature(out, methodName, signature, attributionDecl, " {");
-
- write_native_stats_write_body_q(out, signature, attributionDecl, " ", tryMethodName);
- fprintf(out, "}\n\n");
- }
-}
-
-void write_native_stats_write_non_chained_methods_q(FILE* out, const string& methodName,
- const Atoms& atoms, const AtomDecl& attributionDecl, const string& moduleName,
- const string& tryMethodName) {
- for (auto signature_it = atoms.non_chained_signatures_to_modules.begin();
- signature_it != atoms.non_chained_signatures_to_modules.end(); signature_it++) {
- if (!signature_needed_for_module(signature_it->second, moduleName)) {
- continue;
- }
- vector<java_type_t> signature = signature_it->first;
-
- write_native_method_signature(out, methodName, signature, attributionDecl, " {");
-
- write_native_stats_write_body_q(out, signature, attributionDecl, " ", tryMethodName);
- fprintf(out, "}\n\n");
- }
-}
-
-void write_native_try_stats_write_non_chained_methods_q(FILE* out, const Atoms& atoms,
- const AtomDecl& attributionDecl, const string& moduleName) {
- for (auto signature_it = atoms.non_chained_signatures_to_modules.begin();
- signature_it != atoms.non_chained_signatures_to_modules.end(); signature_it++) {
- if (!signature_needed_for_module(signature_it->second, moduleName)) {
- continue;
- }
- vector<java_type_t> signature = signature_it->first;
-
- write_native_method_signature(out, "static int try_stats_write_non_chained", signature,
- attributionDecl, " {");
-
- int argIndex = 1;
- fprintf(out, " if (kStatsdEnabled) {\n");
- fprintf(out, " stats_event_list event(kStatsEventTag);\n");
- fprintf(out, " event << get_elapsed_realtime_ns();\n\n");
- fprintf(out, " event << code;\n\n");
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
- if (argIndex == 1) {
- fprintf(out, " event.begin();\n\n");
- fprintf(out, " event.begin();\n");
- }
- if (*arg == JAVA_TYPE_STRING) {
- fprintf(out, " if (arg%d == NULL) {\n", argIndex);
- fprintf(out, " arg%d = \"\";\n", argIndex);
- fprintf(out, " }\n");
- }
- if (*arg == JAVA_TYPE_BYTE_ARRAY) {
- fprintf(out,
- " event.AppendCharArray(arg%d.arg, "
- "arg%d.arg_length);\n",
- argIndex, argIndex);
- } else {
- fprintf(out, " event << arg%d;\n", argIndex);
- }
- if (argIndex == 2) {
- fprintf(out, " event.end();\n\n");
- fprintf(out, " event.end();\n\n");
- }
- argIndex++;
- }
-
- fprintf(out, " return event.write(LOG_ID_STATS);\n");
- fprintf(out, " } else {\n");
- fprintf(out, " return 1;\n");
- fprintf(out, " }\n");
- fprintf(out, "}\n");
- fprintf(out, "\n");
- }
-}
-
-} // namespace stats_log_api_gen
-} // namespace android
diff --git a/tools/stats_log_api_gen/native_writer_q.h b/tools/stats_log_api_gen/native_writer_q.h
deleted file mode 100644
index a2ab1ae5d5e2..000000000000
--- a/tools/stats_log_api_gen/native_writer_q.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "Collation.h"
-
-#include <stdio.h>
-#include <string.h>
-
-namespace android {
-namespace stats_log_api_gen {
-
-using namespace std;
-
-void write_native_cpp_includes_q(FILE* out);
-
-void write_native_stats_log_cpp_globals_q(FILE* out);
-
-void write_native_try_stats_write_methods_q(FILE* out, const Atoms& atoms,
- const AtomDecl& attributionDecl, const string& moduleName);
-
-void write_native_stats_write_methods_q(FILE* out, const string& methodName, const Atoms& atoms,
- const AtomDecl& attributionDecl, const string& moduleName, const string& tryMethodName);
-
-void write_native_try_stats_write_non_chained_methods_q(FILE* out, const Atoms& atoms,
- const AtomDecl& attributionDecl, const string& moduleName);
-
-void write_native_stats_write_non_chained_methods_q(FILE* out, const string& methodName,
- const Atoms& atoms, const AtomDecl& attributionDecl, const string& moduleName,
- const string& tryMethodName);
-
-void write_native_get_timestamp_ns_q(FILE* out);
-
-} // namespace stats_log_api_gen
-} // namespace android
diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto
index c3e703826be5..d22acc6e6598 100644
--- a/tools/stats_log_api_gen/test.proto
+++ b/tools/stats_log_api_gen/test.proto
@@ -41,21 +41,20 @@ enum AnEnum {
message AllTypesAtom {
repeated android.os.statsd.AttributionNode attribution_chain = 1;
- optional double double_field = 2;
- optional float float_field = 3;
- optional int64 int64_field = 4;
- optional uint64 uint64_field = 5;
- optional int32 int32_field = 6;
- optional fixed64 fixed64_field = 7;
- optional fixed32 fixed32_field = 8;
- optional bool bool_field = 9;
- optional string string_field = 10;
- optional uint32 uint32_field = 11;
- optional AnEnum enum_field = 12;
- optional sfixed32 sfixed32_field = 13;
- optional sfixed64 sfixed64_field = 14;
- optional sint32 sint32_field = 15;
- optional sint64 sint64_field = 16;
+ optional float float_field = 2;
+ optional int64 int64_field = 3;
+ optional uint64 uint64_field = 4;
+ optional int32 int32_field = 5;
+ optional fixed64 fixed64_field = 6;
+ optional fixed32 fixed32_field = 7;
+ optional bool bool_field = 8;
+ optional string string_field = 9;
+ optional uint32 uint32_field = 10;
+ optional AnEnum enum_field = 11;
+ optional sfixed32 sfixed32_field = 12;
+ optional sfixed64 sfixed64_field = 13;
+ optional sint32 sint32_field = 14;
+ optional sint64 sint64_field = 15;
}
message Event {
@@ -70,6 +69,8 @@ message Event {
message BadTypesAtom {
optional IntAtom bad_int_atom = 1;
optional bytes bad_bytes = 2;
+ repeated int32 repeated_field = 3;
+ optional double double_field = 4;
}
message BadTypesEvent {
@@ -148,53 +149,42 @@ message GoodStateAtoms {
// The atom has only primary field but no exclusive state field.
message BadStateAtom1 {
- optional int32 uid = 1
- [(android.os.statsd.state_field_option).option = PRIMARY];
+ optional int32 uid = 1 [(android.os.statsd.state_field_option).primary_field = true];
}
// Only primative types can be annotated.
message BadStateAtom2 {
repeated android.os.statsd.AttributionNode attribution = 1
- [(android.os.statsd.state_field_option).option = PRIMARY];
- optional int32 state = 2
- [(android.os.statsd.state_field_option).option = EXCLUSIVE];
+ [(android.os.statsd.state_field_option).primary_field = true];
+ optional int32 state = 2 [(android.os.statsd.state_field_option).exclusive_state = true];
}
// Having 2 exclusive state field in the atom means the atom is badly designed.
// E.g., putting bluetooth state and wifi state in the same atom.
message BadStateAtom3 {
- optional int32 uid = 1
- [(android.os.statsd.state_field_option).option = PRIMARY];
- optional int32 state = 2
- [(android.os.statsd.state_field_option).option = EXCLUSIVE];
- optional int32 state2 = 3
- [(android.os.statsd.state_field_option).option = EXCLUSIVE];
+ optional int32 uid = 1 [(android.os.statsd.state_field_option).primary_field = true];
+ optional int32 state = 2 [(android.os.statsd.state_field_option).exclusive_state = true];
+ optional int32 state2 = 3 [(android.os.statsd.state_field_option).exclusive_state = true];
}
message GoodStateAtom1 {
- optional int32 uid = 1
- [(android.os.statsd.state_field_option).option = PRIMARY];
- optional int32 state = 2
- [(android.os.statsd.state_field_option).option = EXCLUSIVE];
+ optional int32 uid = 1 [(android.os.statsd.state_field_option).primary_field = true];
+ optional int32 state = 2 [(android.os.statsd.state_field_option).exclusive_state = true];
}
// Atoms can have exclusive state field, but no primary field. That means
// the state is globally exclusive (e.g., DisplayState).
message GoodStateAtom2 {
optional int32 uid = 1;
- optional int32 state = 2
- [(android.os.statsd.state_field_option).option = EXCLUSIVE];
+ optional int32 state = 2 [(android.os.statsd.state_field_option).exclusive_state = true];
}
// We can have more than one primary fields. That means their combination is a
// primary key.
message GoodStateAtom3 {
- optional int32 uid = 1
- [(android.os.statsd.state_field_option).option = PRIMARY];
- optional int32 tid = 2
- [(android.os.statsd.state_field_option).option = PRIMARY];
- optional int32 state = 3
- [(android.os.statsd.state_field_option).option = EXCLUSIVE];
+ optional int32 uid = 1 [(android.os.statsd.state_field_option).primary_field = true];
+ optional int32 tid = 2 [(android.os.statsd.state_field_option).primary_field = true];
+ optional int32 state = 3 [(android.os.statsd.state_field_option).exclusive_state = true];
}
message WhitelistedAtom {
@@ -216,21 +206,28 @@ message ListedAtoms {
}
message ModuleOneAtom {
- optional int32 field = 1;
+ optional int32 field = 1 [(android.os.statsd.is_uid) = true];
}
message ModuleTwoAtom {
optional int32 field = 1;
}
+message ModuleOneAndTwoAtom {
+ optional int32 field = 1 [(android.os.statsd.state_field_option).exclusive_state = true];
+}
+
message NoModuleAtom {
optional string field = 1;
}
message ModuleAtoms {
oneof event {
- ModuleOneAtom module_one_atom = 1 [(android.os.statsd.log_from_module) = "module1"];
- ModuleTwoAtom module_two_atom = 2 [(android.os.statsd.log_from_module) = "module2"];
- NoModuleAtom no_module_atom = 3;
+ 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 [
+ (android.os.statsd.module) = "module1", (android.os.statsd.module) = "module2"
+ ];
+ NoModuleAtom no_module_atom = 4;
}
-} \ No newline at end of file
+}
diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp
index bcf18ae8bf19..150475223bfa 100644
--- a/tools/stats_log_api_gen/test_collation.cpp
+++ b/tools/stats_log_api_gen/test_collation.cpp
@@ -15,11 +15,10 @@
*/
#include <gtest/gtest.h>
+#include <stdio.h>
-#include "frameworks/base/tools/stats_log_api_gen/test.pb.h"
#include "Collation.h"
-
-#include <stdio.h>
+#include "frameworks/base/tools/stats_log_api_gen/test.pb.h"
namespace android {
namespace stats_log_api_gen {
@@ -29,16 +28,14 @@ using std::set;
using std::vector;
/**
- * Return whether the set contains a vector of the elements provided.
+ * Return whether the map contains a vector of the elements provided.
*/
-static bool
-set_contains_vector(const map<vector<java_type_t>, set<string>>& s, int count, ...)
-{
+static bool map_contains_vector(const SignatureInfoMap& s, int count, ...) {
va_list args;
vector<java_type_t> v;
va_start(args, count);
- for (int i=0; i<count; i++) {
+ for (int i = 0; i < count; i++) {
v.push_back((java_type_t)va_arg(args, int));
}
va_end(args);
@@ -47,103 +44,102 @@ set_contains_vector(const map<vector<java_type_t>, set<string>>& s, int count, .
}
/**
- * Expect that the provided set contains the elements provided.
+ * Expect that the provided map contains the elements provided.
*/
-#define EXPECT_SET_CONTAINS_SIGNATURE(s, ...) \
- do { \
- int count = sizeof((int[]){__VA_ARGS__})/sizeof(int); \
- EXPECT_TRUE(set_contains_vector(s, count, __VA_ARGS__)); \
- } while(0)
+#define EXPECT_MAP_CONTAINS_SIGNATURE(s, ...) \
+ do { \
+ int count = sizeof((int[]){__VA_ARGS__}) / sizeof(int); \
+ EXPECT_TRUE(map_contains_vector(s, count, __VA_ARGS__)); \
+ } while (0)
/** Expects that the provided atom has no enum values for any field. */
-#define EXPECT_NO_ENUM_FIELD(atom) \
- do { \
+#define EXPECT_NO_ENUM_FIELD(atom) \
+ do { \
for (vector<AtomField>::const_iterator field = atom->fields.begin(); \
- field != atom->fields.end(); field++) { \
- EXPECT_TRUE(field->enumValues.empty()); \
- } \
- } while(0)
+ field != atom->fields.end(); field++) { \
+ EXPECT_TRUE(field->enumValues.empty()); \
+ } \
+ } while (0)
/** Expects that exactly one specific field has expected enum values. */
-#define EXPECT_HAS_ENUM_FIELD(atom, field_name, values) \
- do { \
+#define EXPECT_HAS_ENUM_FIELD(atom, field_name, values) \
+ do { \
for (vector<AtomField>::const_iterator field = atom->fields.begin(); \
- field != atom->fields.end(); field++) { \
- if (field->name == field_name) { \
- EXPECT_EQ(field->enumValues, values); \
- } else { \
- EXPECT_TRUE(field->enumValues.empty()); \
- } \
- } \
- } while(0)
-
+ field != atom->fields.end(); field++) { \
+ if (field->name == field_name) { \
+ EXPECT_EQ(field->enumValues, values); \
+ } else { \
+ EXPECT_TRUE(field->enumValues.empty()); \
+ } \
+ } \
+ } while (0)
/**
* Test a correct collation, with all the types.
*/
TEST(CollationTest, CollateStats) {
Atoms atoms;
- int errorCount = collate_atoms(Event::descriptor(), &atoms);
+ int errorCount = collate_atoms(Event::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_EQ(0, errorCount);
- EXPECT_EQ(3ul, atoms.signatures_to_modules.size());
+ EXPECT_EQ(3ul, atoms.signatureInfoMap.size());
// IntAtom, AnotherIntAtom
- EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures_to_modules, JAVA_TYPE_INT);
+ EXPECT_MAP_CONTAINS_SIGNATURE(atoms.signatureInfoMap, JAVA_TYPE_INT);
// OutOfOrderAtom
- EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures_to_modules, JAVA_TYPE_INT, JAVA_TYPE_INT);
+ EXPECT_MAP_CONTAINS_SIGNATURE(atoms.signatureInfoMap, JAVA_TYPE_INT, JAVA_TYPE_INT);
// AllTypesAtom
- EXPECT_SET_CONTAINS_SIGNATURE(
- atoms.signatures_to_modules,
- JAVA_TYPE_ATTRIBUTION_CHAIN, // AttributionChain
- JAVA_TYPE_DOUBLE, // double
- JAVA_TYPE_FLOAT, // float
- JAVA_TYPE_LONG, // int64
- JAVA_TYPE_LONG, // uint64
- JAVA_TYPE_INT, // int32
- JAVA_TYPE_LONG, // fixed64
- JAVA_TYPE_INT, // fixed32
- JAVA_TYPE_BOOLEAN, // bool
- JAVA_TYPE_STRING, // string
- JAVA_TYPE_INT, // uint32
- JAVA_TYPE_INT, // AnEnum
- JAVA_TYPE_INT, // sfixed32
- JAVA_TYPE_LONG, // sfixed64
- JAVA_TYPE_INT, // sint32
- JAVA_TYPE_LONG // sint64
+ EXPECT_MAP_CONTAINS_SIGNATURE(atoms.signatureInfoMap,
+ JAVA_TYPE_ATTRIBUTION_CHAIN, // AttributionChain
+ JAVA_TYPE_FLOAT, // float
+ JAVA_TYPE_LONG, // int64
+ JAVA_TYPE_LONG, // uint64
+ JAVA_TYPE_INT, // int32
+ JAVA_TYPE_LONG, // fixed64
+ JAVA_TYPE_INT, // fixed32
+ JAVA_TYPE_BOOLEAN, // bool
+ JAVA_TYPE_STRING, // string
+ JAVA_TYPE_INT, // uint32
+ JAVA_TYPE_INT, // AnEnum
+ JAVA_TYPE_INT, // sfixed32
+ JAVA_TYPE_LONG, // sfixed64
+ JAVA_TYPE_INT, // sint32
+ JAVA_TYPE_LONG // sint64
);
- set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- EXPECT_EQ(1, atom->code);
- EXPECT_EQ("int_atom", atom->name);
- EXPECT_EQ("IntAtom", atom->message);
- EXPECT_NO_ENUM_FIELD(atom);
- atom++;
-
- EXPECT_EQ(2, atom->code);
- EXPECT_EQ("out_of_order_atom", atom->name);
- EXPECT_EQ("OutOfOrderAtom", atom->message);
- EXPECT_NO_ENUM_FIELD(atom);
- atom++;
-
- EXPECT_EQ(3, atom->code);
- EXPECT_EQ("another_int_atom", atom->name);
- EXPECT_EQ("AnotherIntAtom", atom->message);
- EXPECT_NO_ENUM_FIELD(atom);
- atom++;
-
- EXPECT_EQ(4, atom->code);
- EXPECT_EQ("all_types_atom", atom->name);
- EXPECT_EQ("AllTypesAtom", atom->message);
+ EXPECT_EQ(4ul, atoms.decls.size());
+
+ AtomDeclSet::const_iterator atomIt = atoms.decls.begin();
+ EXPECT_EQ(1, (*atomIt)->code);
+ EXPECT_EQ("int_atom", (*atomIt)->name);
+ EXPECT_EQ("IntAtom", (*atomIt)->message);
+ EXPECT_NO_ENUM_FIELD((*atomIt));
+ atomIt++;
+
+ EXPECT_EQ(2, (*atomIt)->code);
+ EXPECT_EQ("out_of_order_atom", (*atomIt)->name);
+ EXPECT_EQ("OutOfOrderAtom", (*atomIt)->message);
+ EXPECT_NO_ENUM_FIELD((*atomIt));
+ atomIt++;
+
+ EXPECT_EQ(3, (*atomIt)->code);
+ EXPECT_EQ("another_int_atom", (*atomIt)->name);
+ EXPECT_EQ("AnotherIntAtom", (*atomIt)->message);
+ EXPECT_NO_ENUM_FIELD((*atomIt));
+ atomIt++;
+
+ EXPECT_EQ(4, (*atomIt)->code);
+ EXPECT_EQ("all_types_atom", (*atomIt)->name);
+ EXPECT_EQ("AllTypesAtom", (*atomIt)->message);
map<int, string> enumValues;
enumValues[0] = "VALUE0";
enumValues[1] = "VALUE1";
- EXPECT_HAS_ENUM_FIELD(atom, "enum_field", enumValues);
- atom++;
+ EXPECT_HAS_ENUM_FIELD((*atomIt), "enum_field", enumValues);
+ atomIt++;
- EXPECT_TRUE(atom == atoms.decls.end());
+ EXPECT_EQ(atoms.decls.end(), atomIt);
}
/**
@@ -151,19 +147,20 @@ TEST(CollationTest, CollateStats) {
*/
TEST(CollationTest, NonMessageTypeFails) {
Atoms atoms;
- int errorCount = collate_atoms(IntAtom::descriptor(), &atoms);
+ int errorCount = collate_atoms(IntAtom::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_EQ(1, errorCount);
}
/**
- * Test that atoms that have non-primitive types are rejected.
+ * Test that atoms that have non-primitive types or repeated fields are
+ * rejected.
*/
TEST(CollationTest, FailOnBadTypes) {
Atoms atoms;
- int errorCount = collate_atoms(BadTypesEvent::descriptor(), &atoms);
+ int errorCount = collate_atoms(BadTypesEvent::descriptor(), DEFAULT_MODULE_NAME, &atoms);
- EXPECT_EQ(2, errorCount);
+ EXPECT_EQ(4, errorCount);
}
/**
@@ -171,18 +168,20 @@ TEST(CollationTest, FailOnBadTypes) {
*/
TEST(CollationTest, FailOnSkippedFieldsSingle) {
Atoms atoms;
- int errorCount = collate_atoms(BadSkippedFieldSingle::descriptor(), &atoms);
+ int errorCount =
+ collate_atoms(BadSkippedFieldSingle::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_EQ(1, errorCount);
}
/**
- * Test that atoms that skip field numbers (not in the first position, and multiple
- * times) are rejected.
+ * Test that atoms that skip field numbers (not in the first position, and
+ * multiple times) are rejected.
*/
TEST(CollationTest, FailOnSkippedFieldsMultiple) {
Atoms atoms;
- int errorCount = collate_atoms(BadSkippedFieldMultiple::descriptor(), &atoms);
+ int errorCount =
+ collate_atoms(BadSkippedFieldMultiple::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_EQ(2, errorCount);
}
@@ -192,99 +191,198 @@ TEST(CollationTest, FailOnSkippedFieldsMultiple) {
* rejected.
*/
TEST(CollationTest, FailBadAttributionNodePosition) {
- Atoms atoms;
- int errorCount =
- collate_atoms(BadAttributionNodePosition::descriptor(), &atoms);
+ Atoms atoms;
+ int errorCount =
+ collate_atoms(BadAttributionNodePosition::descriptor(), DEFAULT_MODULE_NAME, &atoms);
- EXPECT_EQ(1, errorCount);
+ EXPECT_EQ(1, errorCount);
}
TEST(CollationTest, FailOnBadStateAtomOptions) {
Atoms atoms;
- int errorCount = collate_atoms(BadStateAtoms::descriptor(), &atoms);
+ int errorCount = collate_atoms(BadStateAtoms::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_EQ(3, errorCount);
}
TEST(CollationTest, PassOnGoodStateAtomOptions) {
Atoms atoms;
- int errorCount = collate_atoms(GoodStateAtoms::descriptor(), &atoms);
+ int errorCount = collate_atoms(GoodStateAtoms::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_EQ(0, errorCount);
}
TEST(CollationTest, PassOnGoodBinaryFieldAtom) {
Atoms atoms;
int errorCount =
- collate_atoms(GoodEventWithBinaryFieldAtom::descriptor(), &atoms);
+ collate_atoms(GoodEventWithBinaryFieldAtom::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_EQ(0, errorCount);
}
TEST(CollationTest, FailOnBadBinaryFieldAtom) {
Atoms atoms;
int errorCount =
- collate_atoms(BadEventWithBinaryFieldAtom::descriptor(), &atoms);
+ collate_atoms(BadEventWithBinaryFieldAtom::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_TRUE(errorCount > 0);
}
TEST(CollationTest, PassOnWhitelistedAtom) {
Atoms atoms;
- int errorCount = collate_atoms(ListedAtoms::descriptor(), &atoms);
+ int errorCount = collate_atoms(ListedAtoms::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_EQ(errorCount, 0);
EXPECT_EQ(atoms.decls.size(), 2ul);
}
TEST(CollationTest, RecogniseWhitelistedAtom) {
Atoms atoms;
- collate_atoms(ListedAtoms::descriptor(), &atoms);
+ collate_atoms(ListedAtoms::descriptor(), DEFAULT_MODULE_NAME, &atoms);
for (const auto& atomDecl : atoms.decls) {
- if (atomDecl.code == 1) {
- EXPECT_TRUE(atomDecl.whitelisted);
+ if (atomDecl->code == 1) {
+ EXPECT_TRUE(atomDecl->whitelisted);
} else {
- EXPECT_FALSE(atomDecl.whitelisted);
+ EXPECT_FALSE(atomDecl->whitelisted);
}
}
}
TEST(CollationTest, PassOnLogFromModuleAtom) {
Atoms atoms;
- int errorCount = collate_atoms(ModuleAtoms::descriptor(), &atoms);
+ int errorCount = collate_atoms(ModuleAtoms::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_EQ(errorCount, 0);
- EXPECT_EQ(atoms.decls.size(), 3ul);
+ EXPECT_EQ(atoms.decls.size(), 4ul);
}
TEST(CollationTest, RecognizeModuleAtom) {
Atoms atoms;
- int errorCount = collate_atoms(ModuleAtoms::descriptor(), &atoms);
+ int errorCount = collate_atoms(ModuleAtoms::descriptor(), DEFAULT_MODULE_NAME, &atoms);
EXPECT_EQ(errorCount, 0);
- EXPECT_EQ(atoms.decls.size(), 3ul);
- for (const auto& atomDecl: atoms.decls) {
- if (atomDecl.code == 1) {
- EXPECT_TRUE(atomDecl.hasModule);
- EXPECT_EQ(atomDecl.moduleName, "module1");
- } else if (atomDecl.code == 2) {
- EXPECT_TRUE(atomDecl.hasModule);
- EXPECT_EQ(atomDecl.moduleName, "module2");
- } else {
- EXPECT_FALSE(atomDecl.hasModule);
- }
- }
+ EXPECT_EQ(atoms.decls.size(), 4ul);
+ EXPECT_EQ(atoms.signatureInfoMap.size(), 2u);
+ EXPECT_MAP_CONTAINS_SIGNATURE(atoms.signatureInfoMap, JAVA_TYPE_INT);
+ EXPECT_MAP_CONTAINS_SIGNATURE(atoms.signatureInfoMap, JAVA_TYPE_STRING);
+
+ SignatureInfoMap::const_iterator signatureInfoMapIt;
+ const vector<java_type_t>* signature;
+ const FieldNumberToAtomDeclSet* fieldNumberToAtomDeclSet;
+ FieldNumberToAtomDeclSet::const_iterator fieldNumberToAtomDeclSetIt;
+ const AtomDeclSet* atomDeclSet;
+ AtomDeclSet::const_iterator atomDeclSetIt;
+ AtomDecl* atomDecl;
+ FieldNumberToAnnotations* fieldNumberToAnnotations;
+ FieldNumberToAnnotations::const_iterator fieldNumberToAnnotationsIt;
+ const AnnotationSet* annotationSet;
+ AnnotationSet::const_iterator annotationSetIt;
+ Annotation* annotation;
+
+ signatureInfoMapIt = atoms.signatureInfoMap.begin();
+ signature = &(signatureInfoMapIt->first);
+ fieldNumberToAtomDeclSet = &signatureInfoMapIt->second;
+ EXPECT_EQ(1ul, signature->size());
+ EXPECT_EQ(JAVA_TYPE_INT, signature->at(0));
+ EXPECT_EQ(1ul, fieldNumberToAtomDeclSet->size());
+ fieldNumberToAtomDeclSetIt = fieldNumberToAtomDeclSet->begin();
+ EXPECT_EQ(1, fieldNumberToAtomDeclSetIt->first);
+ atomDeclSet = &fieldNumberToAtomDeclSetIt->second;
+ EXPECT_EQ(2ul, atomDeclSet->size());
+ atomDeclSetIt = atomDeclSet->begin();
+ atomDecl = atomDeclSetIt->get();
+ EXPECT_EQ(1, atomDecl->code);
+ fieldNumberToAnnotations = &atomDecl->fieldNumberToAnnotations;
+ fieldNumberToAnnotationsIt = fieldNumberToAnnotations->find(1);
+ EXPECT_NE(fieldNumberToAnnotations->end(), fieldNumberToAnnotationsIt);
+ annotationSet = &fieldNumberToAnnotationsIt->second;
+ EXPECT_EQ(1ul, annotationSet->size());
+ annotationSetIt = annotationSet->begin();
+ annotation = annotationSetIt->get();
+ EXPECT_EQ(ANNOTATION_ID_IS_UID, annotation->annotationId);
+ EXPECT_EQ(1, annotation->atomId);
+ EXPECT_EQ(ANNOTATION_TYPE_BOOL, annotation->type);
+ EXPECT_TRUE(annotation->value.boolValue);
+
+ atomDeclSetIt++;
+ atomDecl = atomDeclSetIt->get();
+ EXPECT_EQ(3, atomDecl->code);
+ fieldNumberToAnnotations = &atomDecl->fieldNumberToAnnotations;
+ fieldNumberToAnnotationsIt = fieldNumberToAnnotations->find(1);
+ EXPECT_NE(fieldNumberToAnnotations->end(), fieldNumberToAnnotationsIt);
+ annotationSet = &fieldNumberToAnnotationsIt->second;
+ EXPECT_EQ(1ul, annotationSet->size());
+ annotationSetIt = annotationSet->begin();
+ annotation = annotationSetIt->get();
+ EXPECT_EQ(ANNOTATION_ID_EXCLUSIVE_STATE, annotation->annotationId);
+ EXPECT_EQ(3, annotation->atomId);
+ EXPECT_EQ(ANNOTATION_TYPE_BOOL, annotation->type);
+ EXPECT_TRUE(annotation->value.boolValue);
+
+ signatureInfoMapIt++;
+ signature = &signatureInfoMapIt->first;
+ fieldNumberToAtomDeclSet = &signatureInfoMapIt->second;
+ EXPECT_EQ(1ul, signature->size());
+ EXPECT_EQ(JAVA_TYPE_STRING, signature->at(0));
+ EXPECT_EQ(0ul, fieldNumberToAtomDeclSet->size());
+}
- EXPECT_EQ(atoms.signatures_to_modules.size(), 2u);
- EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures_to_modules, JAVA_TYPE_INT);
- EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures_to_modules, JAVA_TYPE_STRING);
- for (auto signature_to_modules_it : atoms.signatures_to_modules) {
- vector<java_type_t> signature = signature_to_modules_it.first;
- if (signature[0] == JAVA_TYPE_STRING) {
- EXPECT_EQ(signature_to_modules_it.second.size(), 0u);
- } else if (signature[0] == JAVA_TYPE_INT) {
- set<string> modules = signature_to_modules_it.second;
- EXPECT_EQ(modules.size(), 2u);
- // Assert that the set contains "module1" and "module2".
- EXPECT_NE(modules.find("module1"), modules.end());
- EXPECT_NE(modules.find("module2"), modules.end());
- }
- }
+TEST(CollationTest, RecognizeModule1Atom) {
+ Atoms atoms;
+ const string moduleName = "module1";
+ int errorCount = collate_atoms(ModuleAtoms::descriptor(), moduleName, &atoms);
+ EXPECT_EQ(errorCount, 0);
+ EXPECT_EQ(atoms.decls.size(), 2ul);
+ EXPECT_EQ(atoms.signatureInfoMap.size(), 1u);
+ EXPECT_MAP_CONTAINS_SIGNATURE(atoms.signatureInfoMap, JAVA_TYPE_INT);
+
+ SignatureInfoMap::const_iterator signatureInfoMapIt;
+ const vector<java_type_t>* signature;
+ const FieldNumberToAtomDeclSet* fieldNumberToAtomDeclSet;
+ FieldNumberToAtomDeclSet::const_iterator fieldNumberToAtomDeclSetIt;
+ const AtomDeclSet* atomDeclSet;
+ AtomDeclSet::const_iterator atomDeclSetIt;
+ AtomDecl* atomDecl;
+ FieldNumberToAnnotations* fieldNumberToAnnotations;
+ FieldNumberToAnnotations::const_iterator fieldNumberToAnnotationsIt;
+ const AnnotationSet* annotationSet;
+ AnnotationSet::const_iterator annotationSetIt;
+ Annotation* annotation;
+
+ signatureInfoMapIt = atoms.signatureInfoMap.begin();
+ signature = &(signatureInfoMapIt->first);
+ fieldNumberToAtomDeclSet = &signatureInfoMapIt->second;
+ EXPECT_EQ(1ul, signature->size());
+ EXPECT_EQ(JAVA_TYPE_INT, signature->at(0));
+ EXPECT_EQ(1ul, fieldNumberToAtomDeclSet->size());
+ fieldNumberToAtomDeclSetIt = fieldNumberToAtomDeclSet->begin();
+ EXPECT_EQ(1, fieldNumberToAtomDeclSetIt->first);
+ atomDeclSet = &fieldNumberToAtomDeclSetIt->second;
+ EXPECT_EQ(2ul, atomDeclSet->size());
+ atomDeclSetIt = atomDeclSet->begin();
+ atomDecl = atomDeclSetIt->get();
+ EXPECT_EQ(1, atomDecl->code);
+ fieldNumberToAnnotations = &atomDecl->fieldNumberToAnnotations;
+ fieldNumberToAnnotationsIt = fieldNumberToAnnotations->find(1);
+ EXPECT_NE(fieldNumberToAnnotations->end(), fieldNumberToAnnotationsIt);
+ annotationSet = &fieldNumberToAnnotationsIt->second;
+ EXPECT_EQ(1ul, annotationSet->size());
+ annotationSetIt = annotationSet->begin();
+ annotation = annotationSetIt->get();
+ EXPECT_EQ(ANNOTATION_ID_IS_UID, annotation->annotationId);
+ EXPECT_EQ(1, annotation->atomId);
+ EXPECT_EQ(ANNOTATION_TYPE_BOOL, annotation->type);
+ EXPECT_TRUE(annotation->value.boolValue);
+
+ atomDeclSetIt++;
+ atomDecl = atomDeclSetIt->get();
+ EXPECT_EQ(3, atomDecl->code);
+ fieldNumberToAnnotations = &atomDecl->fieldNumberToAnnotations;
+ fieldNumberToAnnotationsIt = fieldNumberToAnnotations->find(1);
+ EXPECT_NE(fieldNumberToAnnotations->end(), fieldNumberToAnnotationsIt);
+ annotationSet = &fieldNumberToAnnotationsIt->second;
+ EXPECT_EQ(1ul, annotationSet->size());
+ annotationSetIt = annotationSet->begin();
+ annotation = annotationSetIt->get();
+ EXPECT_EQ(ANNOTATION_ID_EXCLUSIVE_STATE, annotation->annotationId);
+ EXPECT_EQ(3, annotation->atomId);
+ EXPECT_EQ(ANNOTATION_TYPE_BOOL, annotation->type);
+ EXPECT_TRUE(annotation->value.boolValue);
}
} // namespace stats_log_api_gen
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/tools/stats_log_api_gen/utils.cpp b/tools/stats_log_api_gen/utils.cpp
index 641404280093..abb89133e58e 100644
--- a/tools/stats_log_api_gen/utils.cpp
+++ b/tools/stats_log_api_gen/utils.cpp
@@ -22,10 +22,10 @@ namespace android {
namespace stats_log_api_gen {
static void build_non_chained_decl_map(const Atoms& atoms,
- std::map<int, set<AtomDecl>::const_iterator>* decl_map) {
- for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
- atom != atoms.non_chained_decls.end(); atom++) {
- decl_map->insert(std::make_pair(atom->code, atom));
+ std::map<int, AtomDeclSet::const_iterator>* decl_map) {
+ for (AtomDeclSet::const_iterator atomIt = atoms.non_chained_decls.begin();
+ atomIt != atoms.non_chained_decls.end(); atomIt++) {
+ decl_map->insert(std::make_pair((*atomIt)->code, atomIt));
}
}
@@ -36,7 +36,7 @@ string make_constant_name(const string& str) {
string result;
const int N = str.size();
bool underscore_next = false;
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
char c = str[i];
if (c >= 'A' && c <= 'Z') {
if (underscore_next) {
@@ -98,22 +98,9 @@ const char* java_type_name(java_type_t type) {
}
}
-bool atom_needed_for_module(const AtomDecl& atomDecl, const string& moduleName) {
- if (moduleName == DEFAULT_MODULE_NAME) {
- return true;
- }
- return atomDecl.hasModule && (moduleName == atomDecl.moduleName);
-}
-
-bool signature_needed_for_module(const set<string>& modules, const string& moduleName) {
- if (moduleName == DEFAULT_MODULE_NAME) {
- return true;
- }
- return modules.find(moduleName) != modules.end();
-}
-
// Native
-// Writes namespaces for the cpp and header files, returning the number of namespaces written.
+// 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) {
@@ -129,35 +116,31 @@ void write_closing_namespace(FILE* out, const string& cppNamespaces) {
}
}
-static void write_cpp_usage(
- FILE* out, const string& method_name, const string& atom_code_name,
- const AtomDecl& atom, const AtomDecl &attributionDecl) {
- fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(),
- atom_code_name.c_str());
+static void write_cpp_usage(FILE* out, const string& method_name, const string& atom_code_name,
+ const shared_ptr<AtomDecl> atom, const AtomDecl& attributionDecl) {
+ fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str());
- for (vector<AtomField>::const_iterator field = atom.fields.begin();
- field != atom.fields.end(); field++) {
+ 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) {
if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, ", const std::vector<%s>& %s",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str());
+ fprintf(out, ", const std::vector<%s>& %s", cpp_type_name(chainField.javaType),
+ chainField.name.c_str());
} else {
fprintf(out, ", const %s* %s, size_t %s_length",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str(), chainField.name.c_str());
+ cpp_type_name(chainField.javaType), chainField.name.c_str(),
+ chainField.name.c_str());
}
}
} else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
- fprintf(out, ", const std::map<int, int32_t>& %s_int"
- ", const std::map<int, int64_t>& %s_long"
- ", const std::map<int, char const*>& %s_str"
- ", const std::map<int, float>& %s_float",
- field->name.c_str(),
- field->name.c_str(),
- field->name.c_str(),
- field->name.c_str());
+ fprintf(out,
+ ", const std::map<int, int32_t>& %s_int"
+ ", const std::map<int, int64_t>& %s_long"
+ ", const std::map<int, char const*>& %s_str"
+ ", const std::map<int, float>& %s_float",
+ field->name.c_str(), field->name.c_str(), field->name.c_str(),
+ field->name.c_str());
} else {
fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
}
@@ -165,38 +148,33 @@ static void write_cpp_usage(
fprintf(out, ");\n");
}
-void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
- const string& moduleName) {
+void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl) {
fprintf(out, "/**\n");
fprintf(out, " * Constants for atom codes.\n");
fprintf(out, " */\n");
fprintf(out, "enum {\n");
- std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+ std::map<int, AtomDeclSet::const_iterator> atom_code_to_non_chained_decl_map;
build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
size_t i = 0;
// Print atom constants
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- // Skip if the atom is not needed for the module.
- if (!atom_needed_for_module(*atom, moduleName)) {
- continue;
- }
- string constant = make_constant_name(atom->name);
+ for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end();
+ atomIt++) {
+ string constant = make_constant_name((*atomIt)->name);
fprintf(out, "\n");
fprintf(out, " /**\n");
- fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str());
- write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
+ fprintf(out, " * %s %s\n", (*atomIt)->message.c_str(), (*atomIt)->name.c_str());
+ write_cpp_usage(out, "stats_write", constant, *atomIt, attributionDecl);
- auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+ auto non_chained_decl = atom_code_to_non_chained_decl_map.find((*atomIt)->code);
if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
- attributionDecl);
+ attributionDecl);
}
fprintf(out, " */\n");
char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
- fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma);
+ fprintf(out, " %s = %d%s\n", constant.c_str(), (*atomIt)->code, comma);
i++;
}
fprintf(out, "\n");
@@ -205,30 +183,30 @@ void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl&
}
void write_native_method_signature(FILE* out, const string& methodName,
- const vector<java_type_t>& signature, const AtomDecl& attributionDecl,
- const string& closer) {
+ const vector<java_type_t>& signature,
+ const AtomDecl& attributionDecl, const string& closer) {
fprintf(out, "%s(int32_t code", methodName.c_str());
int argIndex = 1;
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
+ 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) {
if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, ", const std::vector<%s>& %s",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str());
+ fprintf(out, ", const std::vector<%s>& %s", cpp_type_name(chainField.javaType),
+ chainField.name.c_str());
} else {
- fprintf(out, ", const %s* %s, size_t %s_length",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str(), chainField.name.c_str());
+ fprintf(out, ", const %s* %s, size_t %s_length",
+ cpp_type_name(chainField.javaType), chainField.name.c_str(),
+ chainField.name.c_str());
}
}
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
- fprintf(out, ", const std::map<int, int32_t>& arg%d_1, "
- "const std::map<int, int64_t>& arg%d_2, "
- "const std::map<int, char const*>& arg%d_3, "
- "const std::map<int, float>& arg%d_4",
- argIndex, argIndex, argIndex, argIndex);
+ fprintf(out,
+ ", const std::map<int, int32_t>& arg%d_1, "
+ "const std::map<int, int64_t>& arg%d_2, "
+ "const std::map<int, char const*>& arg%d_3, "
+ "const std::map<int, float>& arg%d_4",
+ argIndex, argIndex, argIndex, argIndex);
} else {
fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
}
@@ -238,86 +216,71 @@ void write_native_method_signature(FILE* out, const string& methodName,
}
void write_native_method_call(FILE* out, const string& methodName,
- const vector<java_type_t>& signature, const AtomDecl& attributionDecl, int argIndex) {
+ const vector<java_type_t>& signature, const AtomDecl& attributionDecl,
+ int argIndex) {
fprintf(out, "%s(code", methodName.c_str());
- 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) {
- if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, ", %s",
+ 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) {
+ if (chainField.javaType == JAVA_TYPE_STRING) {
+ fprintf(out, ", %s", chainField.name.c_str());
+ } else {
+ fprintf(out, ", %s, %s_length", chainField.name.c_str(),
chainField.name.c_str());
- } else {
- fprintf(out, ", %s, %s_length",
- chainField.name.c_str(), chainField.name.c_str());
- }
- }
- } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
- fprintf(out, ", arg%d_1, arg%d_2, arg%d_3, arg%d_4", argIndex,
- argIndex, argIndex, argIndex);
- } else {
- fprintf(out, ", arg%d", argIndex);
- }
- argIndex++;
+ }
+ }
+ } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
+ fprintf(out, ", arg%d_1, arg%d_2, arg%d_3, arg%d_4", argIndex, argIndex, argIndex,
+ argIndex);
+ } else {
+ fprintf(out, ", arg%d", argIndex);
+ }
+ argIndex++;
}
fprintf(out, ");\n");
}
// Java
-void write_java_atom_codes(FILE* out, const Atoms& atoms, const string& moduleName) {
+void write_java_atom_codes(FILE* out, const Atoms& atoms) {
fprintf(out, " // Constants for atom codes.\n");
- std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+ std::map<int, AtomDeclSet::const_iterator> atom_code_to_non_chained_decl_map;
build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
// Print constants for the atom codes.
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- // Skip if the atom is not needed for the module.
- if (!atom_needed_for_module(*atom, moduleName)) {
- continue;
- }
- string constant = make_constant_name(atom->name);
+ for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end();
+ atomIt++) {
+ string constant = make_constant_name((*atomIt)->name);
fprintf(out, "\n");
fprintf(out, " /**\n");
- fprintf(out, " * %s %s<br>\n", atom->message.c_str(), atom->name.c_str());
- write_java_usage(out, "write", constant, *atom);
- auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+ fprintf(out, " * %s %s<br>\n", (*atomIt)->message.c_str(), (*atomIt)->name.c_str());
+ write_java_usage(out, "write", constant, **atomIt);
+ auto non_chained_decl = atom_code_to_non_chained_decl_map.find((*atomIt)->code);
if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
- write_java_usage(out, "write_non_chained", constant, *non_chained_decl->second);
- }
- if (moduleName == DEFAULT_MODULE_NAME) {
- fprintf(out, " * @hide\n");
+ write_java_usage(out, "write_non_chained", constant, **(non_chained_decl->second));
}
fprintf(out, " */\n");
- fprintf(out, " public static final int %s = %d;\n", constant.c_str(), atom->code);
+ fprintf(out, " public static final int %s = %d;\n", constant.c_str(), (*atomIt)->code);
}
fprintf(out, "\n");
}
-void write_java_enum_values(FILE* out, const Atoms& atoms, const string& moduleName) {
+void write_java_enum_values(FILE* out, const Atoms& atoms) {
fprintf(out, " // Constants for enum values.\n\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- // Skip if the atom is not needed for the module.
- if (!atom_needed_for_module(*atom, moduleName)) {
- continue;
- }
- for (vector<AtomField>::const_iterator field = atom->fields.begin();
- field != atom->fields.end(); field++) {
+ for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end();
+ atomIt++) {
+ for (vector<AtomField>::const_iterator field = (*atomIt)->fields.begin();
+ field != (*atomIt)->fields.end(); field++) {
if (field->javaType == JAVA_TYPE_ENUM) {
- fprintf(out, " // Values for %s.%s\n", atom->message.c_str(),
- field->name.c_str());
+ fprintf(out, " // Values for %s.%s\n", (*atomIt)->message.c_str(),
+ field->name.c_str());
for (map<int, string>::const_iterator value = field->enumValues.begin();
- value != field->enumValues.end(); value++) {
- if (moduleName == DEFAULT_MODULE_NAME) {
- fprintf(out, " /** @hide */\n");
- }
+ value != field->enumValues.end(); value++) {
fprintf(out, " public static final int %s__%s__%s = %d;\n",
- make_constant_name(atom->message).c_str(),
- make_constant_name(field->name).c_str(),
- make_constant_name(value->second).c_str(),
- value->first);
+ make_constant_name((*atomIt)->message).c_str(),
+ make_constant_name(field->name).c_str(),
+ make_constant_name(value->second).c_str(), value->first);
}
fprintf(out, "\n");
}
@@ -326,11 +289,11 @@ void write_java_enum_values(FILE* out, const Atoms& atoms, const string& moduleN
}
void write_java_usage(FILE* out, const string& method_name, const string& atom_code_name,
- const AtomDecl& atom) {
- fprintf(out, " * Usage: StatsLog.%s(StatsLog.%s",
- method_name.c_str(), atom_code_name.c_str());
- for (vector<AtomField>::const_iterator field = atom.fields.begin();
- field != atom.fields.end(); field++) {
+ const AtomDecl& atom) {
+ fprintf(out, " * Usage: StatsLog.%s(StatsLog.%s", method_name.c_str(),
+ atom_code_name.c_str());
+ for (vector<AtomField>::const_iterator field = atom.fields.begin(); field != atom.fields.end();
+ field++) {
if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
fprintf(out, ", android.os.WorkSource workSource");
} else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
@@ -344,32 +307,20 @@ void write_java_usage(FILE* out, const string& method_name, const string& atom_c
fprintf(out, ");<br>\n");
}
-int write_java_non_chained_methods(
- FILE* out,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const string& moduleName
- ) {
- for (auto signature_to_modules_it = signatures_to_modules.begin();
- signature_to_modules_it != signatures_to_modules.end(); signature_to_modules_it++) {
- // Skip if this signature is not needed for the module.
- if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) {
- continue;
- }
-
+int write_java_non_chained_methods(FILE* out, const SignatureInfoMap& signatureInfoMap) {
+ for (auto signatureInfoMapIt = signatureInfoMap.begin();
+ signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) {
// Print method signature.
- if (DEFAULT_MODULE_NAME == moduleName) {
- fprintf(out, " /** @hide */\n");
- }
fprintf(out, " public static void write_non_chained(int code");
- vector<java_type_t> signature = signature_to_modules_it->first;
+ vector<java_type_t> signature = signatureInfoMapIt->first;
int argIndex = 1;
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
+ for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end();
+ arg++) {
if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- // Non chained signatures should not have attribution chains.
+ fprintf(stderr, "Non chained signatures should not have attribution chains.\n");
return 1;
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
- // Module logging does not yet support key value pair.
+ fprintf(stderr, "Module logging does not yet support key value pair.\n");
return 1;
} else {
fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
@@ -380,8 +331,8 @@ int write_java_non_chained_methods(
fprintf(out, " write(code");
argIndex = 1;
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
+ for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end();
+ arg++) {
// First two args are uid and tag of attribution chain.
if (argIndex == 1) {
fprintf(out, ", new int[] {arg%d}", argIndex);
@@ -399,30 +350,24 @@ int write_java_non_chained_methods(
return 0;
}
-int write_java_work_source_methods(
- FILE* out,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const string& moduleName
- ) {
+int write_java_work_source_methods(FILE* out, const SignatureInfoMap& signatureInfoMap) {
fprintf(out, " // WorkSource methods.\n");
- for (auto signature_to_modules_it = signatures_to_modules.begin();
- signature_to_modules_it != signatures_to_modules.end(); signature_to_modules_it++) {
- // Skip if this signature is not needed for the module.
- if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) {
- continue;
- }
- vector<java_type_t> signature = signature_to_modules_it->first;
+ for (auto signatureInfoMapIt = signatureInfoMap.begin();
+ signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) {
+ vector<java_type_t> signature = signatureInfoMapIt->first;
// Determine if there is Attribution in this signature.
int attributionArg = -1;
int argIndexMax = 0;
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
+ for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end();
+ arg++) {
argIndexMax++;
if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
if (attributionArg > -1) {
fprintf(stderr, "An atom contains multiple AttributionNode fields.\n");
fprintf(stderr, "This is not supported. Aborting WorkSource method writing.\n");
- fprintf(out, "\n// Invalid for WorkSource: more than one attribution chain.\n");
+ fprintf(out,
+ "\n// Invalid for WorkSource: more than one attribution "
+ "chain.\n");
return 1;
}
attributionArg = argIndexMax;
@@ -434,13 +379,10 @@ int write_java_work_source_methods(
fprintf(out, "\n");
// Method header (signature)
- if (DEFAULT_MODULE_NAME == moduleName) {
- fprintf(out, " /** @hide */\n");
- }
fprintf(out, " public static void write(int code");
int argIndex = 1;
- for (vector<java_type_t>::const_iterator arg = signature.begin();
- arg != signature.end(); arg++) {
+ for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end();
+ arg++) {
if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
fprintf(out, ", android.os.WorkSource ws");
} else {
@@ -450,36 +392,40 @@ int write_java_work_source_methods(
}
fprintf(out, ") {\n");
- // write_non_chained() component. TODO: Remove when flat uids are no longer needed.
+ // write_non_chained() component. TODO: Remove when flat uids are no longer
+ // needed.
fprintf(out, " for (int i = 0; i < ws.size(); ++i) {\n");
fprintf(out, " write_non_chained(code");
for (int argIndex = 1; argIndex <= argIndexMax; argIndex++) {
if (argIndex == attributionArg) {
- fprintf(out, ", ws.get(i), ws.getName(i)");
+ fprintf(out, ", ws.getUid(i), ws.getPackageName(i)");
} else {
- fprintf(out, ", arg%d", argIndex);
+ fprintf(out, ", arg%d", argIndex);
}
}
fprintf(out, ");\n");
- fprintf(out, " }\n"); // close for-loop
+ fprintf(out, " }\n"); // close for-loop
// write() component.
- fprintf(out, " java.util.ArrayList<android.os.WorkSource.WorkChain> workChains = "
+ fprintf(out,
+ " java.util.List<android.os.WorkSource.WorkChain> workChains = "
"ws.getWorkChains();\n");
fprintf(out, " if (workChains != null) {\n");
- fprintf(out, " for (android.os.WorkSource.WorkChain wc : workChains) {\n");
+ fprintf(out,
+ " for (android.os.WorkSource.WorkChain wc : workChains) "
+ "{\n");
fprintf(out, " write(code");
for (int argIndex = 1; argIndex <= argIndexMax; argIndex++) {
if (argIndex == attributionArg) {
fprintf(out, ", wc.getUids(), wc.getTags()");
} else {
- fprintf(out, ", arg%d", argIndex);
+ fprintf(out, ", arg%d", argIndex);
}
}
fprintf(out, ");\n");
- fprintf(out, " }\n"); // close for-loop
- fprintf(out, " }\n"); // close if
- fprintf(out, " }\n"); // close method
+ fprintf(out, " }\n"); // close for-loop
+ fprintf(out, " }\n"); // close if
+ fprintf(out, " }\n"); // close method
}
return 0;
}
diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h
index 50737a68bf89..7d6d08ebbcbe 100644
--- a/tools/stats_log_api_gen/utils.h
+++ b/tools/stats_log_api_gen/utils.h
@@ -16,29 +16,36 @@
#pragma once
-#include "Collation.h"
+#include <stdio.h>
+#include <string.h>
#include <map>
#include <set>
#include <vector>
-#include <stdio.h>
-#include <string.h>
+#include "Collation.h"
namespace android {
namespace stats_log_api_gen {
using namespace std;
-const string DEFAULT_MODULE_NAME = "DEFAULT";
const string DEFAULT_CPP_NAMESPACE = "android,util";
const string DEFAULT_CPP_HEADER_IMPORT = "statslog.h";
const string DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT = "atoms_info.h";
-const string DEFAULT_JAVA_PACKAGE = "android.util";
-const string DEFAULT_JAVA_CLASS = "StatsLogInternal";
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"}};
string make_constant_name(const string& str);
@@ -46,41 +53,32 @@ const char* cpp_type_name(java_type_t type);
const char* java_type_name(java_type_t type);
-bool atom_needed_for_module(const AtomDecl& atomDecl, const string& moduleName);
-
-bool signature_needed_for_module(const set<string>& modules, const string& moduleName);
-
// Common Native helpers
void write_namespace(FILE* out, const string& cppNamespaces);
void write_closing_namespace(FILE* out, const string& cppNamespaces);
-void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
- const string& moduleName);
+void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl);
void write_native_method_signature(FILE* out, const string& methodName,
- const vector<java_type_t>& signature, const AtomDecl& attributionDecl,
- const string& closer);
+ const vector<java_type_t>& signature,
+ const AtomDecl& attributionDecl, const string& closer);
void write_native_method_call(FILE* out, const string& methodName,
- const vector<java_type_t>& signature, const AtomDecl& attributionDecl, int argIndex = 1);
+ const vector<java_type_t>& signature, const AtomDecl& attributionDecl,
+ int argIndex = 1);
// Common Java helpers.
-void write_java_atom_codes(FILE* out, const Atoms& atoms, const string& moduleName);
+void write_java_atom_codes(FILE* out, const Atoms& atoms);
-void write_java_enum_values(FILE* out, const Atoms& atoms, const string& moduleName);
+void write_java_enum_values(FILE* out, const Atoms& atoms);
void write_java_usage(FILE* out, const string& method_name, const string& atom_code_name,
- const AtomDecl& atom);
+ const AtomDecl& atom);
-int write_java_non_chained_methods(FILE* out, const map<vector<java_type_t>,
- set<string>>& signatures_to_modules,
- const string& moduleName);
+int write_java_non_chained_methods(FILE* out, const SignatureInfoMap& signatureInfoMap);
-int write_java_work_source_methods(
- FILE* out,
- const map<vector<java_type_t>, set<string>>& signatures_to_modules,
- const string& moduleName);
+int write_java_work_source_methods(FILE* out, const SignatureInfoMap& signatureInfoMap);
} // namespace stats_log_api_gen
} // namespace android
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index d6b9d81137ac..fe9a438d81d7 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -33,13 +33,13 @@ write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& ind
if (GENERATE_MAPPING) {
string name = make_constant_name(enu.name());
string prefix = name + "_";
- text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
- text << indent << "const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl;
+ text << indent << "static const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
+ text << indent << "static const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl;
for (int i=0; i<N; i++) {
text << indent << INDENT << "\"" << stripPrefix(enu.value(i).name(), prefix) << "\"," << endl;
}
text << indent << "};" << endl;
- text << indent << "const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl;
+ text << indent << "static const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl;
for (int i=0; i<N; i++) {
text << indent << INDENT << make_constant_name(enu.value(i).name()) << "," << endl;
}
@@ -102,13 +102,13 @@ write_message(stringstream& text, const DescriptorProto& message, const string&
if (GENERATE_MAPPING) {
N = message.field_size();
- text << indented << "const int _FIELD_COUNT = " << N << ";" << endl;
- text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl;
+ text << indented << "static const int _FIELD_COUNT = " << N << ";" << endl;
+ text << indented << "static const char* _FIELD_NAMES[" << N << "] = {" << endl;
for (int i=0; i<N; i++) {
text << indented << INDENT << "\"" << message.field(i).name() << "\"," << endl;
}
text << indented << "};" << endl;
- text << indented << "const uint64_t _FIELD_IDS[" << N << "] = {" << endl;
+ text << indented << "static const uint64_t _FIELD_IDS[" << N << "] = {" << endl;
for (int i=0; i<N; i++) {
text << indented << INDENT << make_constant_name(message.field(i).name()) << "," << endl;
}
@@ -152,7 +152,7 @@ write_header_file(CodeGeneratorResponse* response, const FileDescriptorProto& fi
write_message(text, file_descriptor.message_type(i), "");
}
- for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) {
+ for (vector<string>::reverse_iterator it = namespaces.rbegin(); it != namespaces.rend(); it++) {
text << "} // " << *it << endl;
}
diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp
index 56a242f1daaf..5ac9dfd2a557 100644
--- a/tools/validatekeymaps/Main.cpp
+++ b/tools/validatekeymaps/Main.cpp
@@ -137,7 +137,6 @@ static bool validateFile(const char* filename) {
}
}
- log("No errors.\n\n");
return true;
}
diff --git a/tools/validatekeymaps/OWNERS b/tools/validatekeymaps/OWNERS
new file mode 100644
index 000000000000..0313a40f7270
--- /dev/null
+++ b/tools/validatekeymaps/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com