AAPT2: Support generating Manifest.java
This includes comments from AndroidManifest.xml.
Change-Id: I412d9ecb12bad20a49a683d6b3bea4a0be1235ae
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
new file mode 100644
index 0000000..16440bc
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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 "java/AnnotationProcessor.h"
+#include "util/Util.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+void AnnotationProcessor::appendCommentLine(const StringPiece16& line) {
+ static const std::string sDeprecated = "@deprecated";
+ static const std::string sSystemApi = "@SystemApi";
+
+ if (line.empty()) {
+ return;
+ }
+
+ std::string comment = util::utf16ToUtf8(line);
+
+ if (comment.find(sDeprecated) != std::string::npos && !mDeprecated) {
+ mDeprecated = true;
+ if (!mAnnotations.empty()) {
+ mAnnotations += "\n";
+ }
+ mAnnotations += mPrefix;
+ mAnnotations += "@Deprecated";
+ }
+
+ if (comment.find(sSystemApi) != std::string::npos && !mSystemApi) {
+ mSystemApi = true;
+ if (!mAnnotations.empty()) {
+ mAnnotations += "\n";
+ }
+ mAnnotations += mPrefix;
+ mAnnotations += "@android.annotations.SystemApi";
+ }
+
+ if (mComment.empty()) {
+ mComment += mPrefix;
+ mComment += "/**";
+ }
+
+ mComment += "\n";
+ mComment += mPrefix;
+ mComment += " * ";
+ mComment += std::move(comment);
+}
+
+void AnnotationProcessor::appendComment(const StringPiece16& comment) {
+ // We need to process line by line to clean-up whitespace and append prefixes.
+ for (StringPiece16 line : util::tokenize(comment, u'\n')) {
+ appendCommentLine(util::trimWhitespace(line));
+ }
+}
+
+std::string AnnotationProcessor::buildComment() {
+ mComment += "\n";
+ mComment += mPrefix;
+ mComment += " */";
+ return std::move(mComment);
+}
+
+std::string AnnotationProcessor::buildAnnotations() {
+ return std::move(mAnnotations);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
new file mode 100644
index 0000000..b472109
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_JAVA_ANNOTATIONPROCESSOR_H
+#define AAPT_JAVA_ANNOTATIONPROCESSOR_H
+
+#include "util/StringPiece.h"
+
+#include <string>
+
+namespace aapt {
+
+/**
+ * Builds a JavaDoc comment from a set of XML comments.
+ * This will also look for instances of @SystemApi and convert them to
+ * actual Java annotations.
+ *
+ * Example:
+ *
+ * Input XML:
+ *
+ * <!-- This is meant to be hidden because
+ * It is system api. Also it is @deprecated
+ * @SystemApi
+ * -->
+ *
+ * Output JavaDoc:
+ *
+ * /\*
+ * * This is meant to be hidden because
+ * * It is system api. Also it is @deprecated
+ * * @SystemApi
+ * *\/
+ *
+ * Output Annotations:
+ *
+ * @Deprecated
+ * @android.annotation.SystemApi
+ *
+ */
+class AnnotationProcessor {
+public:
+ /**
+ * Creates an AnnotationProcessor with a given prefix for each line generated.
+ * This is usually a set of spaces for indentation.
+ */
+ AnnotationProcessor(const StringPiece& prefix) : mPrefix(prefix.toString()) {
+ }
+
+ /**
+ * Adds more comments. Since resources can have various values with different configurations,
+ * we need to collect all the comments.
+ */
+ void appendComment(const StringPiece16& comment);
+
+ /**
+ * Finishes the comment and moves it to the caller. Subsequent calls to buildComment() have
+ * undefined results.
+ */
+ std::string buildComment();
+
+ /**
+ * Finishes the annotation and moves it to the caller. Subsequent calls to buildAnnotations()
+ * have undefined results.
+ */
+ std::string buildAnnotations();
+
+private:
+ std::string mPrefix;
+ std::string mComment;
+ std::string mAnnotations;
+ bool mDeprecated = false;
+ bool mSystemApi = false;
+
+ void appendCommentLine(const StringPiece16& line);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
new file mode 100644
index 0000000..0175489
--- /dev/null
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2015 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 "NameMangler.h"
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "java/AnnotationProcessor.h"
+#include "java/JavaClassGenerator.h"
+#include "util/StringPiece.h"
+
+#include <algorithm>
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <tuple>
+
+namespace aapt {
+
+// The number of attributes to emit per line in a Styleable array.
+constexpr size_t kAttribsPerLine = 4;
+
+JavaClassGenerator::JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options) :
+ mTable(table), mOptions(options) {
+}
+
+static void generateHeader(const StringPiece16& packageNameToGenerate, std::ostream* out) {
+ *out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
+ " *\n"
+ " * This class was automatically generated by the\n"
+ " * aapt tool from the resource data it found. It\n"
+ " * should not be modified by hand.\n"
+ " */\n\n"
+ "package " << packageNameToGenerate << ";\n\n";
+}
+
+static const std::set<StringPiece16> sJavaIdentifiers = {
+ u"abstract", u"assert", u"boolean", u"break", u"byte",
+ u"case", u"catch", u"char", u"class", u"const", u"continue",
+ u"default", u"do", u"double", u"else", u"enum", u"extends",
+ u"final", u"finally", u"float", u"for", u"goto", u"if",
+ u"implements", u"import", u"instanceof", u"int", u"interface",
+ u"long", u"native", u"new", u"package", u"private", u"protected",
+ u"public", u"return", u"short", u"static", u"strictfp", u"super",
+ u"switch", u"synchronized", u"this", u"throw", u"throws",
+ u"transient", u"try", u"void", u"volatile", u"while", u"true",
+ u"false", u"null"
+};
+
+static bool isValidSymbol(const StringPiece16& symbol) {
+ return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
+}
+
+/*
+ * Java symbols can not contain . or -, but those are valid in a resource name.
+ * Replace those with '_'.
+ */
+static std::u16string transform(const StringPiece16& symbol) {
+ std::u16string output = symbol.toString();
+ for (char16_t& c : output) {
+ if (c == u'.' || c == u'-') {
+ c = u'_';
+ }
+ }
+ return output;
+}
+
+bool JavaClassGenerator::skipSymbol(SymbolState state) {
+ switch (mOptions.types) {
+ case JavaClassGeneratorOptions::SymbolTypes::kAll:
+ return false;
+ case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
+ return state == SymbolState::kUndefined;
+ case JavaClassGeneratorOptions::SymbolTypes::kPublic:
+ return state != SymbolState::kPublic;
+ }
+ return true;
+}
+
+void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGenerate,
+ const std::u16string& entryName,
+ const Styleable* styleable,
+ std::ostream* out) {
+ const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
+
+ // This must be sorted by resource ID.
+ std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes;
+ sortedAttributes.reserve(styleable->entries.size());
+ for (const auto& attr : styleable->entries) {
+ // If we are not encoding final attributes, the styleable entry may have no ID
+ // if we are building a static library.
+ assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry");
+ assert(attr.name && "no name set for Styleable entry");
+ sortedAttributes.emplace_back(attr.id ? attr.id.value() : ResourceId(0), attr.name.value());
+ }
+ std::sort(sortedAttributes.begin(), sortedAttributes.end());
+
+ // First we emit the array containing the IDs of each attribute.
+ *out << " "
+ << "public static final int[] " << transform(entryName) << " = {";
+
+ const size_t attrCount = sortedAttributes.size();
+ for (size_t i = 0; i < attrCount; i++) {
+ if (i % kAttribsPerLine == 0) {
+ *out << "\n ";
+ }
+
+ *out << sortedAttributes[i].first;
+ if (i != attrCount - 1) {
+ *out << ", ";
+ }
+ }
+ *out << "\n };\n";
+
+ // Now we emit the indices into the array.
+ for (size_t i = 0; i < attrCount; i++) {
+ *out << " "
+ << "public static" << finalModifier
+ << " int " << transform(entryName);
+
+ // We may reference IDs from other packages, so prefix the entry name with
+ // the package.
+ const ResourceNameRef& itemName = sortedAttributes[i].second;
+ if (!itemName.package.empty() && packageNameToGenerate != itemName.package) {
+ *out << "_" << transform(itemName.package);
+ }
+ *out << "_" << transform(itemName.entry) << " = " << i << ";\n";
+ }
+}
+
+bool JavaClassGenerator::generateType(const StringPiece16& packageNameToGenerate,
+ const ResourceTablePackage* package,
+ const ResourceTableType* type,
+ std::ostream* out) {
+ const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
+
+ std::u16string unmangledPackage;
+ std::u16string unmangledName;
+ for (const auto& entry : type->entries) {
+ if (skipSymbol(entry->symbolStatus.state)) {
+ continue;
+ }
+
+ ResourceId id(package->id.value(), type->id.value(), entry->id.value());
+ assert(id.isValid());
+
+ unmangledName = entry->name;
+ if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
+ // The entry name was mangled, and we successfully unmangled it.
+ // Check that we want to emit this symbol.
+ if (package->name != unmangledPackage) {
+ // Skip the entry if it doesn't belong to the package we're writing.
+ continue;
+ }
+ } else {
+ if (packageNameToGenerate != package->name) {
+ // We are processing a mangled package name,
+ // but this is a non-mangled resource.
+ continue;
+ }
+ }
+
+ if (!isValidSymbol(unmangledName)) {
+ ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName);
+ std::stringstream err;
+ err << "invalid symbol name '" << resourceName << "'";
+ mError = err.str();
+ return false;
+ }
+
+ if (type->type == ResourceType::kStyleable) {
+ assert(!entry->values.empty());
+ generateStyleable(packageNameToGenerate, unmangledName, static_cast<const Styleable*>(
+ entry->values.front().value.get()), out);
+ } else {
+ *out << " " << "public static" << finalModifier
+ << " int " << transform(unmangledName) << " = " << id << ";\n";
+ }
+ }
+ return true;
+}
+
+bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) {
+ return generate(packageNameToGenerate, packageNameToGenerate, out);
+}
+
+bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate,
+ const StringPiece16& outPackageName, std::ostream* out) {
+ generateHeader(outPackageName, out);
+
+ *out << "public final class R {\n";
+
+ for (const auto& package : mTable->packages) {
+ for (const auto& type : package->types) {
+ StringPiece16 typeStr;
+ if (type->type == ResourceType::kAttrPrivate) {
+ typeStr = toString(ResourceType::kAttr);
+ } else {
+ typeStr = toString(type->type);
+ }
+ *out << " public static final class " << typeStr << " {\n";
+ if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) {
+ return false;
+ }
+ *out << " }\n";
+ }
+ }
+
+ *out << "}\n";
+ out->flush();
+ return true;
+}
+
+
+
+} // namespace aapt
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
new file mode 100644
index 0000000..e53a765
--- /dev/null
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_JAVA_CLASS_GENERATOR_H
+#define AAPT_JAVA_CLASS_GENERATOR_H
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+
+#include "util/StringPiece.h"
+
+#include <ostream>
+#include <string>
+
+namespace aapt {
+
+struct JavaClassGeneratorOptions {
+ /*
+ * Specifies whether to use the 'final' modifier
+ * on resource entries. Default is true.
+ */
+ bool useFinal = true;
+
+ enum class SymbolTypes {
+ kAll,
+ kPublicPrivate,
+ kPublic,
+ };
+
+ /*
+ *
+ */
+ SymbolTypes types = SymbolTypes::kAll;
+};
+
+/*
+ * Generates the R.java file for a resource table.
+ */
+class JavaClassGenerator {
+public:
+ JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options);
+
+ /*
+ * Writes the R.java file to `out`. Only symbols belonging to `package` are written.
+ * All symbols technically belong to a single package, but linked libraries will
+ * have their names mangled, denoting that they came from a different package.
+ * We need to generate these symbols in a separate file.
+ * Returns true on success.
+ */
+ bool generate(const StringPiece16& packageNameToGenerate, std::ostream* out);
+
+ bool generate(const StringPiece16& packageNameToGenerate,
+ const StringPiece16& outputPackageName,
+ std::ostream* out);
+
+ const std::string& getError() const;
+
+private:
+ bool generateType(const StringPiece16& packageNameToGenerate,
+ const ResourceTablePackage* package,
+ const ResourceTableType* type,
+ std::ostream* out);
+
+ void generateStyleable(const StringPiece16& packageNameToGenerate,
+ const std::u16string& entryName,
+ const Styleable* styleable,
+ std::ostream* out);
+
+ bool skipSymbol(SymbolState state);
+
+ ResourceTable* mTable;
+ JavaClassGeneratorOptions mOptions;
+ std::string mError;
+};
+
+inline const std::string& JavaClassGenerator::getError() const {
+ return mError;
+}
+
+} // namespace aapt
+
+#endif // AAPT_JAVA_CLASS_GENERATOR_H
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
new file mode 100644
index 0000000..3625f9c
--- /dev/null
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2015 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 "java/JavaClassGenerator.h"
+#include "util/Util.h"
+
+#include "test/Builders.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:id/class", ResourceId(0x01020000))
+ .build();
+
+ JavaClassGenerator generator(table.get(), {});
+
+ std::stringstream out;
+ EXPECT_FALSE(generator.generate(u"android", &out));
+}
+
+TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:id/hey-man", ResourceId(0x01020000))
+ .addSimple(u"@android:attr/cool.attr", ResourceId(0x01010000))
+ .addValue(u"@android:styleable/hey.dude", ResourceId(0x01030000),
+ test::StyleableBuilder()
+ .addItem(u"@android:attr/cool.attr", ResourceId(0x01010000))
+ .build())
+ .build();
+
+ JavaClassGenerator generator(table.get(), {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(u"android", &out));
+
+ std::string output = out.str();
+
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int hey_man = 0x01020000;"));
+
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int[] hey_dude = {"));
+
+ EXPECT_NE(std::string::npos,
+ output.find("public static final int hey_dude_cool_attr = 0;"));
+}
+
+TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:id/one", ResourceId(0x01020000))
+ .addSimple(u"@android:id/com.foo$two", ResourceId(0x01020001))
+ .build();
+
+ JavaClassGenerator generator(table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", u"com.android.internal", &out));
+
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("package com.android.internal;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+ EXPECT_EQ(std::string::npos, output.find("two"));
+ EXPECT_EQ(std::string::npos, output.find("com_foo$two"));
+}
+
+TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:^attr-private/one", ResourceId(0x01010000))
+ .build();
+
+ JavaClassGenerator generator(table.get(), {});
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("public static final class attr"));
+ EXPECT_EQ(std::string::npos, output.find("public static final class ^attr-private"));
+}
+
+TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
+ StdErrDiagnostics diag;
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addSimple(u"@android:id/one", ResourceId(0x01020000))
+ .addSimple(u"@android:id/two", ResourceId(0x01020001))
+ .addSimple(u"@android:id/three", ResourceId(0x01020002))
+ .setSymbolState(u"@android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
+ .setSymbolState(u"@android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
+ .build();
+
+ JavaClassGeneratorOptions options;
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+ {
+ JavaClassGenerator generator(table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+ EXPECT_EQ(std::string::npos, output.find("two"));
+ EXPECT_EQ(std::string::npos, output.find("three"));
+ }
+
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+ {
+ JavaClassGenerator generator(table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int two = 0x01020001;"));
+ EXPECT_EQ(std::string::npos, output.find("three"));
+ }
+
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+ {
+ JavaClassGenerator generator(table.get(), options);
+ std::stringstream out;
+ ASSERT_TRUE(generator.generate(u"android", &out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int two = 0x01020001;"));
+ EXPECT_NE(std::string::npos, output.find("public static final int three = 0x01020002;"));
+ }
+}
+
+/*
+ * TODO(adamlesinski): Re-enable this once we get merging working again.
+ * TEST(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
+ ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" },
+ ResourceId{ 0x01, 0x02, 0x0000 }));
+ ResourceTable table;
+ table.setPackage(u"com.lib");
+ ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {},
+ Source{ "lib.xml", 33 }, util::make_unique<Id>()));
+ ASSERT_TRUE(mTable->merge(std::move(table)));
+
+ Linker linker(mTable,
+ std::make_shared<MockResolver>(mTable, std::map<ResourceName, ResourceId>()),
+ {});
+ ASSERT_TRUE(linker.linkAndValidate());
+
+ JavaClassGenerator generator(mTable, {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int foo ="));
+ EXPECT_EQ(std::string::npos, output.find("int test ="));
+
+ out.str("");
+ EXPECT_TRUE(generator.generate(u"com.lib", out));
+ output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int test ="));
+ EXPECT_EQ(std::string::npos, output.find("int foo ="));
+}*/
+
+TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .setPackageId(u"com.lib", 0x02)
+ .addSimple(u"@android:attr/bar", ResourceId(0x01010000))
+ .addSimple(u"@com.lib:attr/bar", ResourceId(0x02010000))
+ .addValue(u"@android:styleable/foo", ResourceId(0x01030000),
+ test::StyleableBuilder()
+ .addItem(u"@android:attr/bar", ResourceId(0x01010000))
+ .addItem(u"@com.lib:attr/bar", ResourceId(0x02010000))
+ .build())
+ .build();
+
+ JavaClassGenerator generator(table.get(), {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(u"android", &out));
+
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int foo_bar ="));
+ EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar ="));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
new file mode 100644
index 0000000..c12da64
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 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 "Source.h"
+#include "XmlDom.h"
+
+#include "java/AnnotationProcessor.h"
+#include "java/ManifestClassGenerator.h"
+#include "util/Maybe.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+static Maybe<StringPiece16> extractJavaIdentifier(IDiagnostics* diag, const Source& source,
+ const StringPiece16& value) {
+ const StringPiece16 sep = u".";
+ auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end());
+
+ StringPiece16 result;
+ if (iter != value.end()) {
+ result.assign(iter + sep.size(), value.end() - (iter + sep.size()));
+ } else {
+ result = value;
+ }
+
+ if (result.empty()) {
+ diag->error(DiagMessage(source) << "empty symbol");
+ return {};
+ }
+
+ iter = util::findNonAlphaNumericAndNotInSet(result, u"_");
+ if (iter != result.end()) {
+ diag->error(DiagMessage(source)
+ << "invalid character '" << StringPiece16(iter, 1)
+ << "' in '" << result << "'");
+ return {};
+ }
+
+ if (*result.begin() >= u'0' && *result.begin() <= u'9') {
+ diag->error(DiagMessage(source) << "symbol can not start with a digit");
+ return {};
+ }
+
+ return result;
+}
+
+static bool writeSymbol(IDiagnostics* diag, const Source& source, xml::Element* el,
+ std::ostream* out) {
+ xml::Attribute* attr = el->findAttribute(kSchemaAndroid, u"name");
+ if (!attr) {
+ diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'");
+ return false;
+ }
+
+ Maybe<StringPiece16> result = extractJavaIdentifier(diag, source.withLine(el->lineNumber),
+ attr->value);
+ if (!result) {
+ return false;
+ }
+
+ *out << "\n";
+
+ if (!util::trimWhitespace(el->comment).empty()) {
+ AnnotationProcessor processor(" ");
+ processor.appendComment(el->comment);
+ *out << processor.buildComment() << "\n";
+ std::string annotations = processor.buildAnnotations();
+ if (!annotations.empty()) {
+ *out << annotations << "\n";
+ }
+ }
+ *out << " public static final String " << result.value() << "=\"" << attr->value << "\";\n";
+ return true;
+}
+
+bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package,
+ XmlResource* res, std::ostream* out) {
+ xml::Element* el = xml::findRootElement(res->root.get());
+ if (!el) {
+ return false;
+ }
+
+ if (el->name != u"manifest" && !el->namespaceUri.empty()) {
+ diag->error(DiagMessage(res->file.source) << "no <manifest> root tag defined");
+ return false;
+ }
+
+ *out << "package " << package << ";\n\n"
+ << "public class Manifest {\n";
+
+ bool error = false;
+ std::vector<xml::Element*> children = el->getChildElements();
+
+
+ // First write out permissions.
+ *out << " public static class permission {\n";
+ for (xml::Element* childEl : children) {
+ if (childEl->namespaceUri.empty() && childEl->name == u"permission") {
+ error |= !writeSymbol(diag, res->file.source, childEl, out);
+ }
+ }
+ *out << " }\n";
+
+ // Next write out permission groups.
+ *out << " public static class permission_group {\n";
+ for (xml::Element* childEl : children) {
+ if (childEl->namespaceUri.empty() && childEl->name == u"permission-group") {
+ error |= !writeSymbol(diag, res->file.source, childEl, out);
+ }
+ }
+ *out << " }\n";
+
+ *out << "}\n";
+ return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
new file mode 100644
index 0000000..0f0998f
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_JAVA_MANIFESTCLASSGENERATOR_H
+#define AAPT_JAVA_MANIFESTCLASSGENERATOR_H
+
+#include "Diagnostics.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/StringPiece.h"
+
+#include <iostream>
+
+namespace aapt {
+
+struct ManifestClassGenerator {
+ bool generate(IDiagnostics* diag, const StringPiece16& package, XmlResource* res,
+ std::ostream* out);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
new file mode 100644
index 0000000..1b5bc05
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 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 "java/ManifestClassGenerator.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <permission android:name="android.permission.ACCESS_INTERNET" />
+ <permission android:name="android.DO_DANGEROUS_THINGS" />
+ <permission android:name="com.test.sample.permission.HUH" />
+ <permission-group android:name="foo.bar.PERMISSION" />
+ </manifest>)EOF");
+
+ std::stringstream out;
+ ManifestClassGenerator generator;
+ ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out));
+
+ std::string actual = out.str();
+
+ const size_t permissionClassPos = actual.find("public static class permission {");
+ const size_t permissionGroupClassPos = actual.find("public static class permission_group {");
+ ASSERT_NE(std::string::npos, permissionClassPos);
+ ASSERT_NE(std::string::npos, permissionGroupClassPos);
+
+ //
+ // Make sure these permissions are in the permission class.
+ //
+
+ size_t pos = actual.find("public static final String ACCESS_INTERNET="
+ "\"android.permission.ACCESS_INTERNET\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ pos = actual.find("public static final String DO_DANGEROUS_THINGS="
+ "\"android.DO_DANGEROUS_THINGS\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ pos = actual.find("public static final String HUH=\"com.test.sample.permission.HUH\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ //
+ // Make sure these permissions are in the permission_group class
+ //
+
+ pos = actual.find("public static final String PERMISSION="
+ "\"foo.bar.PERMISSION\";");
+ EXPECT_GT(pos, permissionGroupClassPos);
+ EXPECT_LT(pos, std::string::npos);
+}
+
+TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Required to access the internet.
+ Added in API 1. -->
+ <permission android:name="android.permission.ACCESS_INTERNET" />
+ <!-- @deprecated This permission is for playing outside. -->
+ <permission android:name="android.permission.PLAY_OUTSIDE" />
+ <!-- This is a private permission for system only!
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.SECRET" />
+ </manifest>)EOF");
+
+ std::stringstream out;
+ ManifestClassGenerator generator;
+ ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out));
+
+ std::string actual = out.str();
+
+ EXPECT_NE(std::string::npos, actual.find(
+R"EOF( /**
+ * Required to access the internet.
+ * Added in API 1.
+ */
+ public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF"));
+
+ EXPECT_NE(std::string::npos, actual.find(
+R"EOF( /**
+ * @deprecated This permission is for playing outside.
+ */
+ @Deprecated
+ public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF"));
+
+ EXPECT_NE(std::string::npos, actual.find(
+R"EOF( /**
+ * This is a private permission for system only!
+ * @hide
+ * @SystemApi
+ */
+ @android.annotations.SystemApi
+ public static final String SECRET="android.permission.SECRET";)EOF"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
new file mode 100644
index 0000000..7683f27
--- /dev/null
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2015 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 "XmlDom.h"
+
+#include "java/ProguardRules.h"
+#include "util/Util.h"
+
+#include <memory>
+#include <string>
+
+namespace aapt {
+namespace proguard {
+
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+class BaseVisitor : public xml::Visitor {
+public:
+ BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) {
+ }
+
+ virtual void visit(xml::Text*) override {};
+
+ virtual void visit(xml::Namespace* node) override {
+ for (const auto& child : node->children) {
+ child->accept(this);
+ }
+ }
+
+ virtual void visit(xml::Element* node) override {
+ if (!node->namespaceUri.empty()) {
+ Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace(
+ node->namespaceUri);
+ if (maybePackage) {
+ // This is a custom view, let's figure out the class name from this.
+ std::u16string package = maybePackage.value() + u"." + node->name;
+ if (util::isJavaClassName(package)) {
+ addClass(node->lineNumber, package);
+ }
+ }
+ } else if (util::isJavaClassName(node->name)) {
+ addClass(node->lineNumber, node->name);
+ }
+
+ for (const auto& child: node->children) {
+ child->accept(this);
+ }
+ }
+
+protected:
+ void addClass(size_t lineNumber, const std::u16string& className) {
+ mKeepSet->addClass(Source(mSource.path, lineNumber), className);
+ }
+
+ void addMethod(size_t lineNumber, const std::u16string& methodName) {
+ mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName);
+ }
+
+private:
+ Source mSource;
+ KeepSet* mKeepSet;
+};
+
+struct LayoutVisitor : public BaseVisitor {
+ LayoutVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ bool checkClass = false;
+ bool checkName = false;
+ if (node->namespaceUri.empty()) {
+ checkClass = node->name == u"view" || node->name == u"fragment";
+ } else if (node->namespaceUri == kSchemaAndroid) {
+ checkName = node->name == u"fragment";
+ }
+
+ for (const auto& attr : node->attributes) {
+ if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" &&
+ util::isJavaClassName(attr.value)) {
+ addClass(node->lineNumber, attr.value);
+ } else if (checkName && attr.namespaceUri == kSchemaAndroid && attr.name == u"name" &&
+ util::isJavaClassName(attr.value)) {
+ addClass(node->lineNumber, attr.value);
+ } else if (attr.namespaceUri == kSchemaAndroid && attr.name == u"onClick") {
+ addMethod(node->lineNumber, attr.value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct XmlResourceVisitor : public BaseVisitor {
+ XmlResourceVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ bool checkFragment = false;
+ if (node->namespaceUri.empty()) {
+ checkFragment = node->name == u"PreferenceScreen" || node->name == u"header";
+ }
+
+ if (checkFragment) {
+ xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"fragment");
+ if (attr && util::isJavaClassName(attr->value)) {
+ addClass(node->lineNumber, attr->value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct TransitionVisitor : public BaseVisitor {
+ TransitionVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ bool checkClass = node->namespaceUri.empty() &&
+ (node->name == u"transition" || node->name == u"pathMotion");
+ if (checkClass) {
+ xml::Attribute* attr = node->findAttribute({}, u"class");
+ if (attr && util::isJavaClassName(attr->value)) {
+ addClass(node->lineNumber, attr->value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct ManifestVisitor : public BaseVisitor {
+ ManifestVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ if (node->namespaceUri.empty()) {
+ bool getName = false;
+ if (node->name == u"manifest") {
+ xml::Attribute* attr = node->findAttribute({}, u"package");
+ if (attr) {
+ mPackage = attr->value;
+ }
+ } else if (node->name == u"application") {
+ getName = true;
+ xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"backupAgent");
+ if (attr) {
+ Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
+ attr->value);
+ if (result) {
+ addClass(node->lineNumber, result.value());
+ }
+ }
+ } else if (node->name == u"activity" || node->name == u"service" ||
+ node->name == u"receiver" || node->name == u"provider" ||
+ node->name == u"instrumentation") {
+ getName = true;
+ }
+
+ if (getName) {
+ xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"name");
+ if (attr) {
+ Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
+ attr->value);
+ if (result) {
+ addClass(node->lineNumber, result.value());
+ }
+ }
+ }
+ }
+ BaseVisitor::visit(node);
+ }
+
+ std::u16string mPackage;
+};
+
+bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet) {
+ ManifestVisitor visitor(source, keepSet);
+ if (res->root) {
+ res->root->accept(&visitor);
+ return true;
+ }
+ return false;
+}
+
+bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet) {
+ if (!res->root) {
+ return false;
+ }
+
+ switch (res->file.name.type) {
+ case ResourceType::kLayout: {
+ LayoutVisitor visitor(source, keepSet);
+ res->root->accept(&visitor);
+ break;
+ }
+
+ case ResourceType::kXml: {
+ XmlResourceVisitor visitor(source, keepSet);
+ res->root->accept(&visitor);
+ break;
+ }
+
+ case ResourceType::kTransition: {
+ TransitionVisitor visitor(source, keepSet);
+ res->root->accept(&visitor);
+ break;
+ }
+
+ default:
+ break;
+ }
+ return true;
+}
+
+bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
+ for (const auto& entry : keepSet.mKeepSet) {
+ for (const Source& source : entry.second) {
+ *out << "// Referenced at " << source << "\n";
+ }
+ *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+ }
+
+ for (const auto& entry : keepSet.mKeepMethodSet) {
+ for (const Source& source : entry.second) {
+ *out << "// Referenced at " << source << "\n";
+ }
+ *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
+ }
+ return true;
+}
+
+} // namespace proguard
+} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
new file mode 100644
index 0000000..be61eb9
--- /dev/null
+++ b/tools/aapt2/java/ProguardRules.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PROGUARD_RULES_H
+#define AAPT_PROGUARD_RULES_H
+
+#include "Resource.h"
+#include "Source.h"
+
+#include "process/IResourceTableConsumer.h"
+
+#include <map>
+#include <ostream>
+#include <set>
+#include <string>
+
+namespace aapt {
+namespace proguard {
+
+class KeepSet {
+public:
+ inline void addClass(const Source& source, const std::u16string& className) {
+ mKeepSet[className].insert(source);
+ }
+
+ inline void addMethod(const Source& source, const std::u16string& methodName) {
+ mKeepMethodSet[methodName].insert(source);
+ }
+
+private:
+ friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+
+ std::map<std::u16string, std::set<Source>> mKeepSet;
+ std::map<std::u16string, std::set<Source>> mKeepMethodSet;
+};
+
+bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet);
+bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet);
+
+bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+
+} // namespace proguard
+} // namespace aapt
+
+#endif // AAPT_PROGUARD_RULES_H