AAPT2: Add Manifest fixing/validation

Change-Id: I7f6d8b74d1c590adc356b4da55cb6cb777cdf1da
This commit is contained in:
Adam Lesinski
2015-11-02 16:10:55 -08:00
parent 3b4cd94034
commit 2ae4a877d1
13 changed files with 373 additions and 300 deletions

View File

@ -32,6 +32,7 @@ sources := \
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 @@ testSources := \
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 @@ else
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

View File

@ -16,6 +16,7 @@
#include "Flags.h"
#include "util/StringPiece.h"
#include "util/Util.h"
#include <iomanip>
#include <iostream>
@ -94,7 +95,14 @@ void Flags::usage(const StringPiece& command, std::ostream* out) {
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();

View File

@ -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

View File

@ -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

View File

@ -74,9 +74,11 @@ bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool*
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 @@ bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool*
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()) {

View File

@ -48,6 +48,11 @@ void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
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.

View File

@ -34,6 +34,8 @@
namespace aapt {
namespace xml {
constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
struct RawVisitor;
/**

View File

@ -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 Maybe<StringPiece16> extractJavaIdentifier(IDiagnostics* diag, const Sour
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;

View File

@ -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 @@ struct LayoutVisitor : public BaseVisitor {
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 @@ struct LayoutVisitor : public BaseVisitor {
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 @@ struct XmlResourceVisitor : public BaseVisitor {
}
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 @@ struct ManifestVisitor : public BaseVisitor {
}
} 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 @@ struct ManifestVisitor : public BaseVisitor {
}
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);

View File

@ -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 @@ struct LinkOptions {
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 @@ struct LinkCommand {
}
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 @@ struct LinkCommand {
}
{
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 @@ struct LinkCommand {
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 @@ int link(const std::vector<StringPiece>& args) {
.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 @@ int link(const std::vector<StringPiece>& args) {
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());
}

View File

@ -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

View File

@ -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 */

View File

@ -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