AAPT2: Add Manifest fixing/validation

Change-Id: I7f6d8b74d1c590adc356b4da55cb6cb777cdf1da
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