AAPT2: Add Manifest fixing/validation
Change-Id: I7f6d8b74d1c590adc356b4da55cb6cb777cdf1da
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index a41d2d7..ceed21e 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -32,6 +32,7 @@
flatten/TableFlattener.cpp \
flatten/XmlFlattener.cpp \
link/AutoVersioner.cpp \
+ link/ManifestFixer.cpp \
link/PrivateAttributeMover.cpp \
link/ReferenceLinker.cpp \
link/TableMerger.cpp \
@@ -67,6 +68,7 @@
flatten/TableFlattener_test.cpp \
flatten/XmlFlattener_test.cpp \
link/AutoVersioner_test.cpp \
+ link/ManifestFixer_test.cpp \
link/PrivateAttributeMover_test.cpp \
link/ReferenceLinker_test.cpp \
link/TableMerger_test.cpp \
@@ -113,7 +115,7 @@
endif
cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
-cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions
+cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti
# ==========================================================
# Build the host static library: libaapt2
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
index 6ae5af7..9435396 100644
--- a/tools/aapt2/Flags.cpp
+++ b/tools/aapt2/Flags.cpp
@@ -16,6 +16,7 @@
#include "Flags.h"
#include "util/StringPiece.h"
+#include "util/Util.h"
#include <iomanip>
#include <iostream>
@@ -94,7 +95,14 @@
if (flag.numArgs > 0) {
argLine += " arg";
}
- *out << " " << std::setw(30) << std::left << argLine << flag.description << "\n";
+
+ // Split the description by newlines and write out the argument (which is empty after
+ // the first line) followed by the description line. This will make sure that multiline
+ // descriptions are still right justified and aligned.
+ for (StringPiece line : util::tokenize<char>(flag.description, '\n')) {
+ *out << " " << std::setw(30) << std::left << argLine << line << "\n";
+ argLine = " ";
+ }
}
*out << " " << std::setw(30) << std::left << "-h" << "Displays this help menu\n";
out->flush();
diff --git a/tools/aapt2/ManifestValidator.cpp b/tools/aapt2/ManifestValidator.cpp
deleted file mode 100644
index 9f971fb..0000000
--- a/tools/aapt2/ManifestValidator.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * 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 "Logger.h"
-#include "ManifestValidator.h"
-#include "util/Maybe.h"
-#include "Source.h"
-#include "util/Util.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-ManifestValidator::ManifestValidator(const android::ResTable& table)
-: mTable(table) {
-}
-
-bool ManifestValidator::validate(const Source& source, android::ResXMLParser* parser) {
- SourceLogger logger(source);
-
- android::ResXMLParser::event_code_t code;
- while ((code = parser->next()) != android::ResXMLParser::END_DOCUMENT &&
- code != android::ResXMLParser::BAD_DOCUMENT) {
- if (code != android::ResXMLParser::START_TAG) {
- continue;
- }
-
- size_t len = 0;
- const StringPiece16 namespaceUri(parser->getElementNamespace(&len), len);
- if (!namespaceUri.empty()) {
- continue;
- }
-
- const StringPiece16 name(parser->getElementName(&len), len);
- if (name.empty()) {
- logger.error(parser->getLineNumber())
- << "failed to get the element name."
- << std::endl;
- return false;
- }
-
- if (name == u"manifest") {
- if (!validateManifest(source, parser)) {
- return false;
- }
- }
- }
- return true;
-}
-
-Maybe<StringPiece16> ManifestValidator::getAttributeValue(android::ResXMLParser* parser,
- size_t idx) {
- android::Res_value value;
- if (parser->getAttributeValue(idx, &value) < 0) {
- return StringPiece16();
- }
-
- const android::ResStringPool* pool = &parser->getStrings();
- if (value.dataType == android::Res_value::TYPE_REFERENCE) {
- ssize_t strIdx = mTable.resolveReference(&value, 0x10000000u);
- if (strIdx < 0) {
- return {};
- }
- pool = mTable.getTableStringBlock(strIdx);
- }
-
- if (value.dataType != android::Res_value::TYPE_STRING || !pool) {
- return {};
- }
- return util::getString(*pool, value.data);
-}
-
-Maybe<StringPiece16> ManifestValidator::getAttributeInlineValue(android::ResXMLParser* parser,
- size_t idx) {
- android::Res_value value;
- if (parser->getAttributeValue(idx, &value) < 0) {
- return StringPiece16();
- }
-
- if (value.dataType != android::Res_value::TYPE_STRING) {
- return {};
- }
- return util::getString(parser->getStrings(), value.data);
-}
-
-bool ManifestValidator::validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
- SourceLogger& logger,
- const StringPiece16& charSet) {
- size_t len = 0;
- StringPiece16 element(parser->getElementName(&len), len);
- StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
- Maybe<StringPiece16> result = getAttributeInlineValue(parser, idx);
- if (!result) {
- logger.error(parser->getLineNumber())
- << "<"
- << element
- << "> must have a '"
- << attributeName
- << "' attribute with a string literal value."
- << std::endl;
- return false;
- }
- return validateAttributeImpl(element, attributeName, result.value(), charSet,
- parser->getLineNumber(), logger);
-}
-
-bool ManifestValidator::validateAttribute(android::ResXMLParser* parser, size_t idx,
- SourceLogger& logger, const StringPiece16& charSet) {
- size_t len = 0;
- StringPiece16 element(parser->getElementName(&len), len);
- StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
- Maybe<StringPiece16> result = getAttributeValue(parser, idx);
- if (!result) {
- logger.error(parser->getLineNumber())
- << "<"
- << element
- << "> must have a '"
- << attributeName
- << "' attribute that points to a string."
- << std::endl;
- return false;
- }
- return validateAttributeImpl(element, attributeName, result.value(), charSet,
- parser->getLineNumber(), logger);
-}
-
-bool ManifestValidator::validateAttributeImpl(const StringPiece16& element,
- const StringPiece16& attributeName,
- const StringPiece16& attributeValue,
- const StringPiece16& charSet, size_t lineNumber,
- SourceLogger& logger) {
- StringPiece16::const_iterator badIter =
- util::findNonAlphaNumericAndNotInSet(attributeValue, charSet);
- if (badIter != attributeValue.end()) {
- logger.error(lineNumber)
- << "tag <"
- << element
- << "> attribute '"
- << attributeName
- << "' has invalid character '"
- << StringPiece16(badIter, 1)
- << "'."
- << std::endl;
- return false;
- }
-
- if (!attributeValue.empty()) {
- StringPiece16 trimmed = util::trimWhitespace(attributeValue);
- if (attributeValue.begin() != trimmed.begin()) {
- logger.error(lineNumber)
- << "tag <"
- << element
- << "> attribute '"
- << attributeName
- << "' can not start with whitespace."
- << std::endl;
- return false;
- }
-
- if (attributeValue.end() != trimmed.end()) {
- logger.error(lineNumber)
- << "tag <"
- << element
- << "> attribute '"
- << attributeName
- << "' can not end with whitespace."
- << std::endl;
- return false;
- }
- }
- return true;
-}
-
-constexpr const char16_t* kPackageIdentSet = u"._";
-
-bool ManifestValidator::validateManifest(const Source& source, android::ResXMLParser* parser) {
- bool error = false;
- SourceLogger logger(source);
-
- const StringPiece16 kAndroid = u"android";
- const StringPiece16 kPackage = u"package";
- const StringPiece16 kSharedUserId = u"sharedUserId";
-
- ssize_t idx;
-
- idx = parser->indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
- if (idx < 0) {
- logger.error(parser->getLineNumber())
- << "missing package attribute."
- << std::endl;
- error = true;
- } else {
- error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet);
- }
-
- idx = parser->indexOfAttribute(kAndroid.data(), kAndroid.size(),
- kSharedUserId.data(), kSharedUserId.size());
- if (idx >= 0) {
- error |= !validateInlineAttribute(parser, idx, logger, kPackageIdentSet);
- }
- return !error;
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/ManifestValidator.h b/tools/aapt2/ManifestValidator.h
deleted file mode 100644
index 1a7f48e..0000000
--- a/tools/aapt2/ManifestValidator.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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_MANIFEST_VALIDATOR_H
-#define AAPT_MANIFEST_VALIDATOR_H
-
-#include "Logger.h"
-#include "util/Maybe.h"
-#include "Source.h"
-#include "util/StringPiece.h"
-
-#include <androidfw/ResourceTypes.h>
-
-namespace aapt {
-
-class ManifestValidator {
-public:
- ManifestValidator(const android::ResTable& table);
- ManifestValidator(const ManifestValidator&) = delete;
-
- bool validate(const Source& source, android::ResXMLParser* parser);
-
-private:
- bool validateManifest(const Source& source, android::ResXMLParser* parser);
-
- Maybe<StringPiece16> getAttributeInlineValue(android::ResXMLParser* parser, size_t idx);
- Maybe<StringPiece16> getAttributeValue(android::ResXMLParser* parser, size_t idx);
-
- bool validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
- SourceLogger& logger, const StringPiece16& charSet);
- bool validateAttribute(android::ResXMLParser* parser, size_t idx, SourceLogger& logger,
- const StringPiece16& charSet);
- bool validateAttributeImpl(const StringPiece16& element, const StringPiece16& attributeName,
- const StringPiece16& attributeValue, const StringPiece16& charSet,
- size_t lineNumber, SourceLogger& logger);
-
- const android::ResTable& mTable;
-};
-
-} // namespace aapt
-
-#endif // AAPT_MANIFEST_VALIDATOR_H
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 0db1c37..ae3b4ff 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -74,9 +74,11 @@
return false;
}
- outRef->package = package;
- outRef->type = *parsedType;
- outRef->entry = entry;
+ if (outRef != nullptr) {
+ outRef->package = package;
+ outRef->type = *parsedType;
+ outRef->entry = entry;
+ }
if (outCreate) {
*outCreate = create;
}
@@ -88,6 +90,10 @@
return false;
}
+bool isReference(const StringPiece16& str) {
+ return tryParseReference(str, nullptr, nullptr, nullptr);
+}
+
bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
StringPiece16 trimmedStr(util::trimWhitespace(str));
if (trimmedStr.empty()) {
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 118a2ee..3c532de 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -49,6 +49,11 @@
bool* outCreate = nullptr, bool* outPrivate = nullptr);
/*
+ * Returns true if the string is in the form of a resource reference (@[+][package:]type/name).
+ */
+bool isReference(const StringPiece16& str);
+
+/*
* Returns true if the string was parsed as an attribute reference (?[package:]type/name),
* with `outReference` set to the parsed reference.
*/
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
index b1987f1..9a46bcb 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/XmlDom.h
@@ -34,6 +34,8 @@
namespace aapt {
namespace xml {
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
struct RawVisitor;
/**
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
index c12da64..901a344 100644
--- a/tools/aapt2/java/ManifestClassGenerator.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -25,8 +25,6 @@
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".";
@@ -62,7 +60,7 @@
static bool writeSymbol(IDiagnostics* diag, const Source& source, xml::Element* el,
std::ostream* out) {
- xml::Attribute* attr = el->findAttribute(kSchemaAndroid, u"name");
+ xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name");
if (!attr) {
diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'");
return false;
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 7683f27..4431477 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -25,8 +25,6 @@
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) {
@@ -83,7 +81,7 @@
bool checkName = false;
if (node->namespaceUri.empty()) {
checkClass = node->name == u"view" || node->name == u"fragment";
- } else if (node->namespaceUri == kSchemaAndroid) {
+ } else if (node->namespaceUri == xml::kSchemaAndroid) {
checkName = node->name == u"fragment";
}
@@ -91,10 +89,10 @@
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)) {
+ } else if (checkName && attr.namespaceUri == xml::kSchemaAndroid &&
+ attr.name == u"name" && util::isJavaClassName(attr.value)) {
addClass(node->lineNumber, attr.value);
- } else if (attr.namespaceUri == kSchemaAndroid && attr.name == u"onClick") {
+ } else if (attr.namespaceUri == xml::kSchemaAndroid && attr.name == u"onClick") {
addMethod(node->lineNumber, attr.value);
}
}
@@ -114,7 +112,7 @@
}
if (checkFragment) {
- xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"fragment");
+ xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"fragment");
if (attr && util::isJavaClassName(attr->value)) {
addClass(node->lineNumber, attr->value);
}
@@ -156,7 +154,7 @@
}
} else if (node->name == u"application") {
getName = true;
- xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"backupAgent");
+ xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"backupAgent");
if (attr) {
Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
attr->value);
@@ -171,7 +169,7 @@
}
if (getName) {
- xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"name");
+ xml::Attribute* attr = node->findAttribute(xml::kSchemaAndroid, u"name");
if (attr) {
Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
attr->value);
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 77918ac..0236e98 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -28,6 +28,7 @@
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
#include "link/Linkers.h"
+#include "link/ManifestFixer.h"
#include "link/TableMerger.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
@@ -54,6 +55,8 @@
bool verbose = false;
bool outputToDirectory = false;
Maybe<std::u16string> privateSymbols;
+ Maybe<std::u16string> minSdkVersionDefault;
+ Maybe<std::u16string> targetSdkVersionDefault;
};
struct LinkContext : public IAaptContext {
@@ -240,15 +243,8 @@
}
Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
- xml::Node* node = xmlRes->root.get();
-
- // Find the first xml::Element.
- while (node && !xml::nodeCast<xml::Element>(node)) {
- node = !node->children.empty() ? node->children.front().get() : nullptr;
- }
-
// Make sure the first element is <manifest> with package attribute.
- if (xml::Element* manifestEl = xml::nodeCast<xml::Element>(node)) {
+ if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
return AppInfo{ packageAttr->value };
@@ -570,9 +566,16 @@
}
{
+ ManifestFixerOptions manifestFixerOptions;
+ manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
+ manifestFixerOptions.targetSdkVersionDefault = mOptions.targetSdkVersionDefault;
+ ManifestFixer manifestFixer(manifestFixerOptions);
+ if (!manifestFixer.consume(&mContext, manifestXml.get())) {
+ error = true;
+ }
+
XmlReferenceLinker manifestLinker;
if (manifestLinker.consume(&mContext, manifestXml.get())) {
-
if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
manifestXml.get(),
&proguardKeepSet)) {
@@ -742,6 +745,7 @@
int link(const std::vector<StringPiece>& args) {
LinkOptions options;
Maybe<std::string> privateSymbolsPackage;
+ Maybe<std::string> minSdkVersion, targetSdkVersion;
Flags flags = Flags()
.requiredFlag("-o", "Output path", &options.outputPath)
.requiredFlag("--manifest", "Path to the Android manifest to build",
@@ -757,10 +761,15 @@
.optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
"by -o",
&options.outputToDirectory)
+ .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
+ "AndroidManifest.xml", &minSdkVersion)
+ .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
+ "AndroidManifest.xml", &targetSdkVersion)
.optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
.optionalFlag("--private-symbols", "Package name to use when generating R.java for "
- "private symbols. If not specified, public and private symbols will "
- "use the application's package name", &privateSymbolsPackage)
+ "private symbols.\n"
+ "If not specified, public and private symbols will use the application's "
+ "package name", &privateSymbolsPackage)
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
if (!flags.parse("aapt2 link", args, &std::cerr)) {
@@ -771,6 +780,14 @@
options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
}
+ if (minSdkVersion) {
+ options.minSdkVersionDefault = util::utf8ToUtf16(minSdkVersion.value());
+ }
+
+ if (targetSdkVersion) {
+ options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
+ }
+
LinkCommand cmd = { options };
return cmd.run(flags.getArgs());
}
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
new file mode 100644
index 0000000..52d9426
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -0,0 +1,100 @@
+/*
+ * 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 "ResourceUtils.h"
+#include "XmlDom.h"
+
+#include "link/ManifestFixer.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+static bool verifyManifest(IAaptContext* context, const Source& source, xml::Element* manifestEl) {
+ bool error = false;
+
+ xml::Attribute* attr = manifestEl->findAttribute({}, u"package");
+ if (!attr) {
+ context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
+ << "missing 'package' attribute");
+ error = true;
+ } else if (ResourceUtils::isReference(attr->value)) {
+ context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
+ << "value for attribute 'package' must not be a "
+ "reference");
+ error = true;
+ } else if (!util::isJavaPackageName(attr->value)) {
+ context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
+ << "invalid package name '" << attr->value << "'");
+ error = true;
+ }
+
+ return !error;
+}
+
+static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element* el,
+ const ManifestFixerOptions& options) {
+ if (options.minSdkVersionDefault &&
+ el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) {
+ // There was no minSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, u"minSdkVersion", options.minSdkVersionDefault.value() });
+ }
+
+ if (options.targetSdkVersionDefault &&
+ el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) {
+ // There was no targetSdkVersion defined and we have a default to assign.
+ el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, u"targetSdkVersion",
+ options.targetSdkVersionDefault.value() });
+ }
+ return true;
+}
+
+bool ManifestFixer::consume(IAaptContext* context, XmlResource* doc) {
+ xml::Element* root = xml::findRootElement(doc->root.get());
+ if (!root || !root->namespaceUri.empty() || root->name != u"manifest") {
+ context->getDiagnostics()->error(DiagMessage(doc->file.source)
+ << "root tag must be <manifest>");
+ return false;
+ }
+
+ if (!verifyManifest(context, doc->file.source, root)) {
+ return false;
+ }
+
+ bool foundUsesSdk = false;
+ for (xml::Element* el : root->getChildElements()) {
+ if (!el->namespaceUri.empty()) {
+ continue;
+ }
+
+ if (el->name == u"uses-sdk") {
+ foundUsesSdk = true;
+ fixUsesSdk(context, doc->file.source, el, mOptions);
+ }
+ }
+
+ if (!foundUsesSdk && (mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)) {
+ std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>();
+ usesSdk->name = u"uses-sdk";
+ fixUsesSdk(context, doc->file.source, usesSdk.get(), mOptions);
+ root->addChild(std::move(usesSdk));
+ }
+
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
new file mode 100644
index 0000000..16e161d
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -0,0 +1,44 @@
+/*
+ * 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_LINK_MANIFESTFIXER_H
+#define AAPT_LINK_MANIFESTFIXER_H
+
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+struct ManifestFixerOptions {
+ Maybe<std::u16string> minSdkVersionDefault;
+ Maybe<std::u16string> targetSdkVersionDefault;
+};
+
+/**
+ * Verifies that the manifest is correctly formed and inserts defaults
+ * where specified with ManifestFixerOptions.
+ */
+struct ManifestFixer : public IXmlResourceConsumer {
+ ManifestFixerOptions mOptions;
+
+ ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
+ }
+
+ bool consume(IAaptContext* context, XmlResource* doc) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINK_MANIFESTFIXER_H */
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
new file mode 100644
index 0000000..5c5d8af
--- /dev/null
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -0,0 +1,165 @@
+/*
+ * 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 "link/ManifestFixer.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+struct ManifestFixerTest : public ::testing::Test {
+ std::unique_ptr<IAaptContext> mContext;
+
+ void SetUp() override {
+ mContext = test::ContextBuilder()
+ .setCompilationPackage(u"android")
+ .setPackageId(0x01)
+ .setNameManglerPolicy(NameManglerPolicy{ u"android" })
+ .setSymbolTable(test::StaticSymbolTableBuilder()
+ .addSymbol(u"@android:attr/package", ResourceId(0x01010000),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING)
+ .build())
+ .addSymbol(u"@android:attr/minSdkVersion", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .build())
+ .addSymbol(u"@android:attr/targetSdkVersion", ResourceId(0x01010002),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_STRING |
+ android::ResTable_map::TYPE_INTEGER)
+ .build())
+ .addSymbol(u"@android:string/str", ResourceId(0x01060000))
+ .build())
+ .build();
+ }
+
+ std::unique_ptr<XmlResource> verify(const StringPiece& str) {
+ return verifyWithOptions(str, {});
+ }
+
+ std::unique_ptr<XmlResource> verifyWithOptions(const StringPiece& str,
+ const ManifestFixerOptions& options) {
+ std::unique_ptr<XmlResource> doc = test::buildXmlDom(str);
+ ManifestFixer fixer(options);
+ if (fixer.consume(mContext.get(), doc.get())) {
+ return doc;
+ }
+ return {};
+ }
+};
+
+TEST_F(ManifestFixerTest, EnsureManifestIsRootTag) {
+ EXPECT_EQ(nullptr, verify("<other-tag />"));
+ EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>"));
+}
+
+TEST_F(ManifestFixerTest, EnsureManifestHasPackage) {
+ EXPECT_NE(nullptr, verify("<manifest package=\"android\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />"));
+ EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />"));
+ EXPECT_EQ(nullptr, verify("<manifest package=\"com.android.google.Class$1\" />"));
+ EXPECT_EQ(nullptr,
+ verify("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
+ "android:package=\"com.android\" />"));
+ EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />"));
+}
+
+
+
+TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
+ ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") };
+
+ std::unique_ptr<XmlResource> doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* el;
+ xml::Attribute* attr;
+
+ el = xml::findRootElement(doc->root.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"7", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"21", attr->value);
+
+ doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <uses-sdk android:targetSdkVersion="21" />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::findRootElement(doc->root.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"21", attr->value);
+
+ doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <uses-sdk />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::findRootElement(doc->root.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"22", attr->value);
+
+ doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android" />)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ el = xml::findRootElement(doc->root.get());
+ ASSERT_NE(nullptr, el);
+ el = el->findChild({}, u"uses-sdk");
+ ASSERT_NE(nullptr, el);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"8", attr->value);
+ attr = el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(u"22", attr->value);
+}
+
+} // namespace aapt