diff options
Diffstat (limited to 'tools/aapt2/java')
-rw-r--r-- | tools/aapt2/java/AnnotationProcessor.cpp | 81 | ||||
-rw-r--r-- | tools/aapt2/java/AnnotationProcessor.h | 85 | ||||
-rw-r--r-- | tools/aapt2/java/AnnotationProcessor_test.cpp | 80 | ||||
-rw-r--r-- | tools/aapt2/java/ClassDefinitionWriter.h | 141 | ||||
-rw-r--r-- | tools/aapt2/java/JavaClassGenerator.cpp | 343 | ||||
-rw-r--r-- | tools/aapt2/java/JavaClassGenerator.h | 96 | ||||
-rw-r--r-- | tools/aapt2/java/JavaClassGenerator_test.cpp | 233 | ||||
-rw-r--r-- | tools/aapt2/java/ManifestClassGenerator.cpp | 126 | ||||
-rw-r--r-- | tools/aapt2/java/ManifestClassGenerator.h | 35 | ||||
-rw-r--r-- | tools/aapt2/java/ManifestClassGenerator_test.cpp | 121 | ||||
-rw-r--r-- | tools/aapt2/java/ProguardRules.cpp | 245 | ||||
-rw-r--r-- | tools/aapt2/java/ProguardRules.h | 58 |
12 files changed, 1644 insertions, 0 deletions
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp new file mode 100644 index 000000000000..9c25d4e35975 --- /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 std::string& comment) { + static const std::string sDeprecated = "@deprecated"; + static const std::string sSystemApi = "@SystemApi"; + + if (comment.find(sDeprecated) != std::string::npos) { + mAnnotationBitMask |= kDeprecated; + } + + if (comment.find(sSystemApi) != std::string::npos) { + mAnnotationBitMask |= kSystemApi; + } + + if (!mHasComments) { + mHasComments = true; + mComment << "/**"; + } + + mComment << "\n" << " * " << 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')) { + line = util::trimWhitespace(line); + if (!line.empty()) { + appendCommentLine(util::utf16ToUtf8(line)); + } + } +} + +void AnnotationProcessor::appendComment(const StringPiece& comment) { + for (StringPiece line : util::tokenize(comment, '\n')) { + line = util::trimWhitespace(line); + if (!line.empty()) { + appendCommentLine(line.toString()); + } + } +} + +void AnnotationProcessor::writeToStream(std::ostream* out, const StringPiece& prefix) { + if (mHasComments) { + std::string result = mComment.str(); + for (StringPiece line : util::tokenize<char>(result, '\n')) { + *out << prefix << line << "\n"; + } + *out << prefix << " */" << "\n"; + } + + if (mAnnotationBitMask & kDeprecated) { + *out << prefix << "@Deprecated\n"; + } + + if (mAnnotationBitMask & kSystemApi) { + *out << prefix << "@android.annotation.SystemApi\n"; + } +} + +} // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h new file mode 100644 index 000000000000..e7f2be0dc12b --- /dev/null +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -0,0 +1,85 @@ +/* + * 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 <sstream> +#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: + /** + * Adds more comments. Since resources can have various values with different configurations, + * we need to collect all the comments. + */ + void appendComment(const StringPiece16& comment); + void appendComment(const StringPiece& comment); + + /** + * Writes the comments and annotations to the stream, with the given prefix before each line. + */ + void writeToStream(std::ostream* out, const StringPiece& prefix); + +private: + enum : uint32_t { + kDeprecated = 0x01, + kSystemApi = 0x02, + }; + + std::stringstream mComment; + std::stringstream mAnnotations; + bool mHasComments = false; + uint32_t mAnnotationBitMask = 0; + + void appendCommentLine(const std::string& line); +}; + +} // namespace aapt + +#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */ diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp new file mode 100644 index 000000000000..d5a2b38bcbc4 --- /dev/null +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -0,0 +1,80 @@ +/* + * 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 "ResourceParser.h" +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "XmlPullParser.h" + +#include "java/AnnotationProcessor.h" + +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +namespace aapt { + +struct AnnotationProcessorTest : public ::testing::Test { + std::unique_ptr<IAaptContext> mContext; + ResourceTable mTable; + + void SetUp() override { + mContext = test::ContextBuilder().build(); + } + + ::testing::AssertionResult parse(const StringPiece& str) { + ResourceParserOptions options; + ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{}, ConfigDescription{}, + options); + std::stringstream in; + in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; + XmlPullParser xmlParser(in); + if (parser.parse(&xmlParser)) { + return ::testing::AssertionSuccess(); + } + return ::testing::AssertionFailure(); + } +}; + +TEST_F(AnnotationProcessorTest, EmitsDeprecated) { + ASSERT_TRUE(parse(R"EOF( + <resources> + <declare-styleable name="foo"> + <!-- Some comment, and it should contain + a marker word, something that marks + this resource as nor needed. + {@deprecated That's the marker! } --> + <attr name="autoText" format="boolean" /> + </declare-styleable> + </resources>)EOF")); + + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/autoText"); + ASSERT_NE(nullptr, attr); + + AnnotationProcessor processor; + processor.appendComment(attr->getComment()); + + std::stringstream result; + processor.writeToStream(&result, ""); + std::string annotations = result.str(); + + EXPECT_NE(std::string::npos, annotations.find("@Deprecated")); +} + +} // namespace aapt + + diff --git a/tools/aapt2/java/ClassDefinitionWriter.h b/tools/aapt2/java/ClassDefinitionWriter.h new file mode 100644 index 000000000000..b8886f90c6c5 --- /dev/null +++ b/tools/aapt2/java/ClassDefinitionWriter.h @@ -0,0 +1,141 @@ +/* + * 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_CLASSDEFINITION_H +#define AAPT_JAVA_CLASSDEFINITION_H + +#include "java/AnnotationProcessor.h" +#include "util/StringPiece.h" +#include "util/Util.h" + +#include <sstream> +#include <string> + +namespace aapt { + +struct ClassDefinitionWriterOptions { + bool useFinalQualifier = false; + bool forceCreationIfEmpty = false; +}; + +/** + * Writes a class for use in R.java or Manifest.java. + */ +class ClassDefinitionWriter { +public: + ClassDefinitionWriter(const StringPiece& name, const ClassDefinitionWriterOptions& options) : + mName(name.toString()), mOptions(options), mStarted(false) { + } + + ClassDefinitionWriter(const StringPiece16& name, const ClassDefinitionWriterOptions& options) : + mName(util::utf16ToUtf8(name)), mOptions(options), mStarted(false) { + } + + void addIntMember(const StringPiece& name, AnnotationProcessor* processor, + const uint32_t val) { + ensureClassDeclaration(); + if (processor) { + processor->writeToStream(&mOut, kIndent); + } + mOut << kIndent << "public static " << (mOptions.useFinalQualifier ? "final " : "") + << "int " << name << "=" << val << ";\n"; + } + + void addStringMember(const StringPiece16& name, AnnotationProcessor* processor, + const StringPiece16& val) { + ensureClassDeclaration(); + if (processor) { + processor->writeToStream(&mOut, kIndent); + } + mOut << kIndent << "public static " << (mOptions.useFinalQualifier ? "final " : "") + << "String " << name << "=\"" << val << "\";\n"; + } + + void addResourceMember(const StringPiece16& name, AnnotationProcessor* processor, + const ResourceId id) { + ensureClassDeclaration(); + if (processor) { + processor->writeToStream(&mOut, kIndent); + } + mOut << kIndent << "public static " << (mOptions.useFinalQualifier ? "final " : "") + << "int " << name << "=" << id <<";\n"; + } + + template <typename Iterator, typename FieldAccessorFunc> + void addArrayMember(const StringPiece16& name, AnnotationProcessor* processor, + const Iterator begin, const Iterator end, FieldAccessorFunc f) { + ensureClassDeclaration(); + if (processor) { + processor->writeToStream(&mOut, kIndent); + } + mOut << kIndent << "public static final int[] " << name << "={"; + + for (Iterator current = begin; current != end; ++current) { + if (std::distance(begin, current) % kAttribsPerLine == 0) { + mOut << "\n" << kIndent << kIndent; + } + + mOut << f(*current); + if (std::distance(current, end) > 1) { + mOut << ", "; + } + } + mOut << "\n" << kIndent <<"};\n"; + } + + void writeToStream(std::ostream* out, const StringPiece& prefix, + AnnotationProcessor* processor=nullptr) { + if (mOptions.forceCreationIfEmpty) { + ensureClassDeclaration(); + } + + if (!mStarted) { + return; + } + + if (processor) { + processor->writeToStream(out, prefix); + } + + std::string result = mOut.str(); + for (StringPiece line : util::tokenize<char>(result, '\n')) { + *out << prefix << line << "\n"; + } + *out << prefix << "}\n"; + } + +private: + constexpr static const char* kIndent = " "; + + // The number of attributes to emit per line in a Styleable array. + constexpr static size_t kAttribsPerLine = 4; + + void ensureClassDeclaration() { + if (!mStarted) { + mStarted = true; + mOut << "public static final class " << mName << " {\n"; + } + } + + std::stringstream mOut; + std::string mName; + ClassDefinitionWriterOptions mOptions; + bool mStarted; +}; + +} // namespace aapt + +#endif /* AAPT_JAVA_CLASSDEFINITION_H */ diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp new file mode 100644 index 000000000000..7280f3a968a0 --- /dev/null +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -0,0 +1,343 @@ +/* + * 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 "ValueVisitor.h" + +#include "java/AnnotationProcessor.h" +#include "java/ClassDefinitionWriter.h" +#include "java/JavaClassGenerator.h" +#include "util/Comparators.h" +#include "util/StringPiece.h" + +#include <algorithm> +#include <ostream> +#include <set> +#include <sstream> +#include <tuple> + +namespace aapt { + +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::writeStyleableEntryForClass(ClassDefinitionWriter* outClassDef, + AnnotationProcessor* processor, + const StringPiece16& packageNameToGenerate, + const std::u16string& entryName, + const Styleable* styleable) { + // 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()); + + auto accessorFunc = [](const std::pair<ResourceId, ResourceNameRef>& a) -> ResourceId { + return a.first; + }; + + // First we emit the array containing the IDs of each attribute. + outClassDef->addArrayMember(transform(entryName), processor, + sortedAttributes.begin(), + sortedAttributes.end(), + accessorFunc); + + // Now we emit the indices into the array. + size_t attrCount = sortedAttributes.size(); + for (size_t i = 0; i < attrCount; i++) { + std::stringstream name; + name << 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) { + name << "_" << transform(itemName.package); + } + name << "_" << transform(itemName.entry); + + outClassDef->addIntMember(name.str(), nullptr, i); + } +} + +static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) { + const uint32_t typeMask = attr->typeMask; + if (typeMask & android::ResTable_map::TYPE_REFERENCE) { + processor->appendComment( + "<p>May be a reference to another resource, in the form\n" + "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n" + "attribute in the form\n" + "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\"."); + } + + if (typeMask & android::ResTable_map::TYPE_STRING) { + processor->appendComment( + "<p>May be a string value, using '\\\\;' to escape characters such as\n" + "'\\\\n' or '\\\\uxxxx' for a unicode character;"); + } + + if (typeMask & android::ResTable_map::TYPE_INTEGER) { + processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\"."); + } + + if (typeMask & android::ResTable_map::TYPE_BOOLEAN) { + processor->appendComment( + "<p>May be a boolean value, such as \"<code>true</code>\" or\n" + "\"<code>false</code>\"."); + } + + if (typeMask & android::ResTable_map::TYPE_COLOR) { + processor->appendComment( + "<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n" + "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n" + "\"<code>#<i>aarrggbb</i></code>\"."); + } + + if (typeMask & android::ResTable_map::TYPE_FLOAT) { + processor->appendComment( + "<p>May be a floating point value, such as \"<code>1.2</code>\"."); + } + + if (typeMask & android::ResTable_map::TYPE_DIMENSION) { + processor->appendComment( + "<p>May be a dimension value, which is a floating point number appended with a\n" + "unit such as \"<code>14.5sp</code>\".\n" + "Available units are: px (pixels), dp (density-independent pixels),\n" + "sp (scaled pixels based on preferred font size), in (inches), and\n" + "mm (millimeters)."); + } + + if (typeMask & android::ResTable_map::TYPE_FRACTION) { + processor->appendComment( + "<p>May be a fractional value, which is a floating point number appended with\n" + "either % or %p, such as \"<code>14.5%</code>\".\n" + "The % suffix always means a percentage of the base size;\n" + "the optional %p suffix provides a size relative to some parent container."); + } + + if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) { + if (typeMask & android::ResTable_map::TYPE_FLAGS) { + processor->appendComment( + "<p>Must be one or more (separated by '|') of the following " + "constant values.</p>"); + } else { + processor->appendComment("<p>Must be one of the following constant values.</p>"); + } + + processor->appendComment("<table>\n<colgroup align=\"left\" />\n" + "<colgroup align=\"left\" />\n" + "<colgroup align=\"left\" />\n" + "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n"); + for (const Attribute::Symbol& symbol : attr->symbols) { + std::stringstream line; + line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>" + << "<td>" << std::hex << symbol.value << std::dec << "</td>" + << "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>"; + processor->appendComment(line.str()); + } + processor->appendComment("</table>"); + } +} + +bool JavaClassGenerator::writeEntriesForClass(ClassDefinitionWriter* outClassDef, + const StringPiece16& packageNameToGenerate, + const ResourceTablePackage* package, + const ResourceTableType* type) { + 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()); + + std::u16string unmangledPackage; + std::u16string 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; + } + + // Build the comments and annotations for this entry. + + AnnotationProcessor processor; + if (entry->symbolStatus.state != SymbolState::kUndefined) { + processor.appendComment(entry->symbolStatus.comment); + } + + for (const auto& configValue : entry->values) { + processor.appendComment(configValue.value->getComment()); + } + + // If this is an Attribute, append the format Javadoc. + if (!entry->values.empty()) { + if (Attribute* attr = valueCast<Attribute>(entry->values.front().value.get())) { + // We list out the available values for the given attribute. + addAttributeFormatDoc(&processor, attr); + } + } + + if (type->type == ResourceType::kStyleable) { + assert(!entry->values.empty()); + const Styleable* styleable = static_cast<const Styleable*>( + entry->values.front().value.get()); + writeStyleableEntryForClass(outClassDef, &processor, packageNameToGenerate, + unmangledName, styleable); + } else { + outClassDef->addResourceMember(transform(unmangledName), &processor, id); + } + } + 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) { + if (type->type == ResourceType::kAttrPrivate) { + continue; + } + + ClassDefinitionWriterOptions classOptions; + classOptions.useFinalQualifier = mOptions.useFinal; + classOptions.forceCreationIfEmpty = + (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic); + ClassDefinitionWriter classDef(toString(type->type), classOptions); + bool result = writeEntriesForClass(&classDef, packageNameToGenerate, + package.get(), type.get()); + if (!result) { + return false; + } + + if (type->type == ResourceType::kAttr) { + // Also include private attributes in this same class. + auto iter = std::lower_bound(package->types.begin(), package->types.end(), + ResourceType::kAttrPrivate, cmp::lessThanType); + if (iter != package->types.end() && (*iter)->type == ResourceType::kAttrPrivate) { + result = writeEntriesForClass(&classDef, packageNameToGenerate, + package.get(), iter->get()); + if (!result) { + return false; + } + } + } + + AnnotationProcessor processor; + if (type->type == ResourceType::kStyleable && + mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) { + // When generating a public R class, we don't want Styleable to be part of the API. + // It is only emitted for documentation purposes. + processor.appendComment("@doconly"); + } + classDef.writeToStream(out, " ", &processor); + } + } + + *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 000000000000..023d6d635f8c --- /dev/null +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -0,0 +1,96 @@ +/* + * 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 { + +class AnnotationProcessor; +class ClassDefinitionWriter; + +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 writeEntriesForClass(ClassDefinitionWriter* outClassDef, + const StringPiece16& packageNameToGenerate, + const ResourceTablePackage* package, + const ResourceTableType* type); + + void writeStyleableEntryForClass(ClassDefinitionWriter* outClassDef, + AnnotationProcessor* processor, + const StringPiece16& packageNameToGenerate, + const std::u16string& entryName, + const Styleable* styleable); + + 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 000000000000..e9e788167966 --- /dev/null +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -0,0 +1,233 @@ +/* + * 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/two", ResourceId(0x01010001)) + .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=")); +} + +TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"android", 0x01) + .addSimple(u"@android:id/foo", ResourceId(0x01010000)) + .build(); + test::getValue<Id>(table.get(), u"@android:id/foo") + ->setComment(std::u16string(u"This is a comment\n@deprecated")); + + JavaClassGenerator generator(table.get(), {}); + + std::stringstream out; + ASSERT_TRUE(generator.generate(u"android", &out)); + std::string actual = out.str(); + + EXPECT_NE(std::string::npos, actual.find( + R"EOF(/** + * This is a comment + * @deprecated + */ + @Deprecated + public static final int foo=0x01010000;)EOF")); +} + +TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) { + +} + +TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) { + +} + +} // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp new file mode 100644 index 000000000000..d963d8948f8d --- /dev/null +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -0,0 +1,126 @@ +/* + * 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/ClassDefinitionWriter.h" +#include "java/ManifestClassGenerator.h" +#include "util/Maybe.h" + +#include <algorithm> + +namespace aapt { + +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, ClassDefinitionWriter* outClassDef, const Source& source, + xml::Element* el) { + xml::Attribute* attr = el->findAttribute(xml::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; + } + + AnnotationProcessor processor; + processor.appendComment(el->comment); + outClassDef->addStringMember(result.value(), &processor, attr->value); + 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 final class Manifest {\n"; + + bool error = false; + std::vector<xml::Element*> children = el->getChildElements(); + + ClassDefinitionWriterOptions classOptions; + classOptions.useFinalQualifier = true; + classOptions.forceCreationIfEmpty = false; + + // First write out permissions. + ClassDefinitionWriter classDef("permission", classOptions); + for (xml::Element* childEl : children) { + if (childEl->namespaceUri.empty() && childEl->name == u"permission") { + error |= !writeSymbol(diag, &classDef, res->file.source, childEl); + } + } + classDef.writeToStream(out, " "); + + // Next write out permission groups. + classDef = ClassDefinitionWriter("permission_group", classOptions); + for (xml::Element* childEl : children) { + if (childEl->namespaceUri.empty() && childEl->name == u"permission-group") { + error |= !writeSymbol(diag, &classDef, res->file.source, childEl); + } + } + classDef.writeToStream(out, " "); + + *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 000000000000..0f0998f8e2ba --- /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 000000000000..4081287a1852 --- /dev/null +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -0,0 +1,121 @@ +/* + * 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 final class permission {"); + const size_t permissionGroupClassPos = + actual.find("public static final 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.annotation.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 000000000000..44314772fbd4 --- /dev/null +++ b/tools/aapt2/java/ProguardRules.cpp @@ -0,0 +1,245 @@ +/* + * 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 { + +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 == xml::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 == xml::kSchemaAndroid && + attr.name == u"name" && util::isJavaClassName(attr.value)) { + addClass(node->lineNumber, attr.value); + } else if (attr.namespaceUri == xml::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(xml::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(xml::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(xml::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 000000000000..be61eb9095c2 --- /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 |