In preparation for exporting an XML proto format for UAM to consume, this change brings the XML DOM API more in line with other APIs that do not make the Namespace a separate node. Treating Namespace declarations as just properties of an Element node makes the implementation of algorithms much simpler, as the constraints that Namespace nodes have only one child are now built in and traversing to find Element nodes is much simpler. Also made a bunch of quality of life improvements, like formatting and comment style. Test: make aapt2_tests Bug: 65645766 Change-Id: Ib97ff1c4252b7907e2cc1f13a448dc4ca3b809a4 Merged-In: Ib97ff1c4252b7907e2cc1f13a448dc4ca3b809a4
443 lines
16 KiB
C++
443 lines
16 KiB
C++
/*
|
|
* 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/Test.h"
|
|
|
|
using ::android::StringPiece;
|
|
using ::testing::NotNull;
|
|
|
|
namespace aapt {
|
|
|
|
struct ManifestFixerTest : public ::testing::Test {
|
|
std::unique_ptr<IAaptContext> mContext;
|
|
|
|
void SetUp() override {
|
|
mContext =
|
|
test::ContextBuilder()
|
|
.SetCompilationPackage("android")
|
|
.SetPackageId(0x01)
|
|
.SetNameManglerPolicy(NameManglerPolicy{"android"})
|
|
.AddSymbolSource(
|
|
test::StaticSymbolSourceBuilder()
|
|
.AddSymbol(
|
|
"android:attr/package", ResourceId(0x01010000),
|
|
test::AttributeBuilder()
|
|
.SetTypeMask(android::ResTable_map::TYPE_STRING)
|
|
.Build())
|
|
.AddSymbol(
|
|
"android:attr/minSdkVersion", ResourceId(0x01010001),
|
|
test::AttributeBuilder()
|
|
.SetTypeMask(android::ResTable_map::TYPE_STRING |
|
|
android::ResTable_map::TYPE_INTEGER)
|
|
.Build())
|
|
.AddSymbol(
|
|
"android:attr/targetSdkVersion", ResourceId(0x01010002),
|
|
test::AttributeBuilder()
|
|
.SetTypeMask(android::ResTable_map::TYPE_STRING |
|
|
android::ResTable_map::TYPE_INTEGER)
|
|
.Build())
|
|
.AddSymbol("android:string/str", ResourceId(0x01060000))
|
|
.Build())
|
|
.Build();
|
|
}
|
|
|
|
std::unique_ptr<xml::XmlResource> Verify(const StringPiece& str) {
|
|
return VerifyWithOptions(str, {});
|
|
}
|
|
|
|
std::unique_ptr<xml::XmlResource> VerifyWithOptions(
|
|
const StringPiece& str, const ManifestFixerOptions& options) {
|
|
std::unique_ptr<xml::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, AllowMetaData) {
|
|
auto doc = Verify(R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<meta-data />
|
|
<application>
|
|
<meta-data />
|
|
<activity android:name=".Hi"><meta-data /></activity>
|
|
<activity-alias android:name=".Ho"><meta-data /></activity-alias>
|
|
<receiver android:name=".OffTo"><meta-data /></receiver>
|
|
<provider android:name=".Work"><meta-data /></provider>
|
|
<service android:name=".We"><meta-data /></service>
|
|
</application>
|
|
<instrumentation android:name=".Go"><meta-data /></instrumentation>
|
|
</manifest>)EOF");
|
|
ASSERT_NE(nullptr, doc);
|
|
}
|
|
|
|
TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
|
|
ManifestFixerOptions options = {std::string("8"), std::string("22")};
|
|
|
|
std::unique_ptr<xml::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 = doc->root.get();
|
|
ASSERT_NE(nullptr, el);
|
|
el = el->FindChild({}, "uses-sdk");
|
|
ASSERT_NE(nullptr, el);
|
|
attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ("7", attr->value);
|
|
attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ("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 = doc->root.get();
|
|
ASSERT_NE(nullptr, el);
|
|
el = el->FindChild({}, "uses-sdk");
|
|
ASSERT_NE(nullptr, el);
|
|
attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ("8", attr->value);
|
|
attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ("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 = doc->root.get();
|
|
ASSERT_NE(nullptr, el);
|
|
el = el->FindChild({}, "uses-sdk");
|
|
ASSERT_NE(nullptr, el);
|
|
attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ("8", attr->value);
|
|
attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ("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 = doc->root.get();
|
|
ASSERT_NE(nullptr, el);
|
|
el = el->FindChild({}, "uses-sdk");
|
|
ASSERT_NE(nullptr, el);
|
|
attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ("8", attr->value);
|
|
attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ("22", attr->value);
|
|
}
|
|
|
|
TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) {
|
|
ManifestFixerOptions options = {std::string("8"), std::string("22")};
|
|
std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<application android:name=".MainApplication" />
|
|
</manifest>)EOF",
|
|
options);
|
|
ASSERT_NE(nullptr, doc);
|
|
|
|
xml::Element* manifest_el = doc->root.get();
|
|
ASSERT_NE(nullptr, manifest_el);
|
|
ASSERT_EQ("manifest", manifest_el->name);
|
|
|
|
xml::Element* application_el = manifest_el->FindChild("", "application");
|
|
ASSERT_NE(nullptr, application_el);
|
|
|
|
xml::Element* uses_sdk_el = manifest_el->FindChild("", "uses-sdk");
|
|
ASSERT_NE(nullptr, uses_sdk_el);
|
|
|
|
// Check that the uses_sdk_el comes before application_el in the children
|
|
// vector.
|
|
// Since there are no namespaces here, these children are direct descendants
|
|
// of manifest.
|
|
auto uses_sdk_iter =
|
|
std::find_if(manifest_el->children.begin(), manifest_el->children.end(),
|
|
[&](const std::unique_ptr<xml::Node>& child) {
|
|
return child.get() == uses_sdk_el;
|
|
});
|
|
|
|
auto application_iter =
|
|
std::find_if(manifest_el->children.begin(), manifest_el->children.end(),
|
|
[&](const std::unique_ptr<xml::Node>& child) {
|
|
return child.get() == application_el;
|
|
});
|
|
|
|
ASSERT_NE(manifest_el->children.end(), uses_sdk_iter);
|
|
ASSERT_NE(manifest_el->children.end(), application_iter);
|
|
|
|
// The distance should be positive, meaning uses_sdk_iter comes before
|
|
// application_iter.
|
|
EXPECT_GT(std::distance(uses_sdk_iter, application_iter), 0);
|
|
}
|
|
|
|
TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) {
|
|
ManifestFixerOptions options;
|
|
options.rename_manifest_package = std::string("com.android");
|
|
|
|
std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<application android:name=".MainApplication" text="hello">
|
|
<activity android:name=".activity.Start" />
|
|
<receiver android:name="com.google.android.Receiver" />
|
|
</application>
|
|
</manifest>)EOF",
|
|
options);
|
|
ASSERT_NE(nullptr, doc);
|
|
|
|
xml::Element* manifestEl = doc->root.get();
|
|
ASSERT_NE(nullptr, manifestEl);
|
|
|
|
xml::Attribute* attr = nullptr;
|
|
|
|
attr = manifestEl->FindAttribute({}, "package");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ(std::string("com.android"), attr->value);
|
|
|
|
xml::Element* applicationEl = manifestEl->FindChild({}, "application");
|
|
ASSERT_NE(nullptr, applicationEl);
|
|
|
|
attr = applicationEl->FindAttribute(xml::kSchemaAndroid, "name");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ(std::string("android.MainApplication"), attr->value);
|
|
|
|
attr = applicationEl->FindAttribute({}, "text");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ(std::string("hello"), attr->value);
|
|
|
|
xml::Element* el;
|
|
el = applicationEl->FindChild({}, "activity");
|
|
ASSERT_NE(nullptr, el);
|
|
|
|
attr = el->FindAttribute(xml::kSchemaAndroid, "name");
|
|
ASSERT_NE(nullptr, el);
|
|
EXPECT_EQ(std::string("android.activity.Start"), attr->value);
|
|
|
|
el = applicationEl->FindChild({}, "receiver");
|
|
ASSERT_NE(nullptr, el);
|
|
|
|
attr = el->FindAttribute(xml::kSchemaAndroid, "name");
|
|
ASSERT_NE(nullptr, el);
|
|
EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value);
|
|
}
|
|
|
|
TEST_F(ManifestFixerTest,
|
|
RenameManifestInstrumentationPackageAndFullyQualifyTarget) {
|
|
ManifestFixerOptions options;
|
|
options.rename_instrumentation_target_package = std::string("com.android");
|
|
|
|
std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<instrumentation android:name=".TestRunner" android:targetPackage="android" />
|
|
</manifest>)EOF",
|
|
options);
|
|
ASSERT_NE(nullptr, doc);
|
|
|
|
xml::Element* manifest_el = doc->root.get();
|
|
ASSERT_NE(nullptr, manifest_el);
|
|
|
|
xml::Element* instrumentation_el =
|
|
manifest_el->FindChild({}, "instrumentation");
|
|
ASSERT_NE(nullptr, instrumentation_el);
|
|
|
|
xml::Attribute* attr =
|
|
instrumentation_el->FindAttribute(xml::kSchemaAndroid, "targetPackage");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ(std::string("com.android"), attr->value);
|
|
}
|
|
|
|
TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {
|
|
ManifestFixerOptions options;
|
|
options.version_name_default = std::string("Beta");
|
|
options.version_code_default = std::string("0x10000000");
|
|
|
|
std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android" />)EOF",
|
|
options);
|
|
ASSERT_NE(nullptr, doc);
|
|
|
|
xml::Element* manifest_el = doc->root.get();
|
|
ASSERT_NE(nullptr, manifest_el);
|
|
|
|
xml::Attribute* attr =
|
|
manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ(std::string("Beta"), attr->value);
|
|
|
|
attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
|
|
ASSERT_NE(nullptr, attr);
|
|
EXPECT_EQ(std::string("0x10000000"), attr->value);
|
|
}
|
|
|
|
TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) {
|
|
EXPECT_EQ(nullptr,
|
|
Verify("<manifest package=\"android\" coreApp=\"hello\" />"));
|
|
EXPECT_EQ(nullptr,
|
|
Verify("<manifest package=\"android\" coreApp=\"1dp\" />"));
|
|
|
|
std::unique_ptr<xml::XmlResource> doc =
|
|
Verify("<manifest package=\"android\" coreApp=\"true\" />");
|
|
ASSERT_NE(nullptr, doc);
|
|
|
|
xml::Element* el = doc->root.get();
|
|
ASSERT_NE(nullptr, el);
|
|
|
|
EXPECT_EQ("manifest", el->name);
|
|
|
|
xml::Attribute* attr = el->FindAttribute("", "coreApp");
|
|
ASSERT_NE(nullptr, attr);
|
|
|
|
EXPECT_NE(nullptr, attr->compiled_value);
|
|
EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(attr->compiled_value.get()));
|
|
}
|
|
|
|
TEST_F(ManifestFixerTest, UsesFeatureMustHaveNameOrGlEsVersion) {
|
|
std::string input = R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<uses-feature android:name="feature" />
|
|
<uses-feature android:glEsVersion="1" />
|
|
<feature-group />
|
|
<feature-group>
|
|
<uses-feature android:name="feature_in_group" />
|
|
<uses-feature android:glEsVersion="2" />
|
|
</feature-group>
|
|
</manifest>)EOF";
|
|
EXPECT_NE(nullptr, Verify(input));
|
|
|
|
input = R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<uses-feature android:name="feature" android:glEsVersion="1" />
|
|
</manifest>)EOF";
|
|
EXPECT_EQ(nullptr, Verify(input));
|
|
|
|
input = R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<uses-feature />
|
|
</manifest>)EOF";
|
|
EXPECT_EQ(nullptr, Verify(input));
|
|
|
|
input = R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<feature-group>
|
|
<uses-feature android:name="feature" android:glEsVersion="1" />
|
|
</feature-group>
|
|
</manifest>)EOF";
|
|
EXPECT_EQ(nullptr, Verify(input));
|
|
|
|
input = R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<feature-group>
|
|
<uses-feature />
|
|
</feature-group>
|
|
</manifest>)EOF";
|
|
EXPECT_EQ(nullptr, Verify(input));
|
|
}
|
|
|
|
TEST_F(ManifestFixerTest, IgnoreNamespacedElements) {
|
|
std::string input = R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<special:tag whoo="true" xmlns:special="http://google.com" />
|
|
</manifest>)EOF";
|
|
EXPECT_NE(nullptr, Verify(input));
|
|
}
|
|
|
|
TEST_F(ManifestFixerTest, DoNotIgnoreNonNamespacedElements) {
|
|
std::string input = R"EOF(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<tag whoo="true" />
|
|
</manifest>)EOF";
|
|
EXPECT_EQ(nullptr, Verify(input));
|
|
}
|
|
|
|
TEST_F(ManifestFixerTest, SupportKeySets) {
|
|
std::string input = R"(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="android">
|
|
<key-sets>
|
|
<key-set android:name="old-set">
|
|
<public-key android:name="old-key" android:value="some+old+key" />
|
|
</key-set>
|
|
<key-set android:name="new-set">
|
|
<public-key android:name="new-key" android:value="some+new+key" />
|
|
</key-set>
|
|
<upgrade-key-set android:name="old-set" />
|
|
<upgrade-key-set android:name="new-set" />
|
|
</key-sets>
|
|
</manifest>)";
|
|
EXPECT_THAT(Verify(input), NotNull());
|
|
}
|
|
|
|
} // namespace aapt
|