You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
787 lines
31 KiB
787 lines
31 KiB
/*
|
|
* Copyright (C) 2018 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.h"
|
|
|
|
#include <android-base/file.h>
|
|
|
|
#include "AppInfo.h"
|
|
#include "LoadedApk.h"
|
|
#include "test/Test.h"
|
|
|
|
using testing::Eq;
|
|
using testing::HasSubstr;
|
|
using testing::IsNull;
|
|
using testing::Ne;
|
|
using testing::NotNull;
|
|
|
|
namespace aapt {
|
|
|
|
using LinkTest = CommandTestFixture;
|
|
|
|
TEST_F(LinkTest, RemoveRawXmlStrings) {
|
|
StdErrDiagnostics diag;
|
|
const std::string compiled_files_dir = GetTestPath("compiled");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/xml/test.xml"), R"(<Item AgentCode="007"/>)",
|
|
compiled_files_dir, &diag));
|
|
|
|
const std::string out_apk = GetTestPath("out.apk");
|
|
std::vector<std::string> link_args = {
|
|
"--manifest", GetDefaultManifest(),
|
|
"-o", out_apk,
|
|
};
|
|
|
|
ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
|
|
|
|
// Load the binary xml tree
|
|
android::ResXMLTree tree;
|
|
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
|
|
ASSERT_THAT(apk, Ne(nullptr));
|
|
|
|
std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
|
|
ASSERT_THAT(data, Ne(nullptr));
|
|
AssertLoadXml(apk.get(), data.get(), &tree);
|
|
|
|
// Check that the raw string index has not been assigned
|
|
EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1));
|
|
}
|
|
|
|
TEST_F(LinkTest, KeepRawXmlStrings) {
|
|
StdErrDiagnostics diag;
|
|
const std::string compiled_files_dir = GetTestPath("compiled");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/xml/test.xml"), R"(<Item AgentCode="007"/>)",
|
|
compiled_files_dir, &diag));
|
|
|
|
const std::string out_apk = GetTestPath("out.apk");
|
|
std::vector<std::string> link_args = {
|
|
"--manifest", GetDefaultManifest(),
|
|
"-o", out_apk,
|
|
"--keep-raw-values"
|
|
};
|
|
|
|
ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
|
|
|
|
// Load the binary xml tree
|
|
android::ResXMLTree tree;
|
|
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
|
|
ASSERT_THAT(apk, Ne(nullptr));
|
|
|
|
std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
|
|
ASSERT_THAT(data, Ne(nullptr));
|
|
AssertLoadXml(apk.get(), data.get(), &tree);
|
|
|
|
// Check that the raw string index has been set to the correct string pool entry
|
|
int32_t raw_index = tree.getAttributeValueStringID(0);
|
|
ASSERT_THAT(raw_index, Ne(-1));
|
|
EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007"));
|
|
}
|
|
|
|
TEST_F(LinkTest, NoCompressAssets) {
|
|
StdErrDiagnostics diag;
|
|
std::string content(500, 'a');
|
|
WriteFile(GetTestPath("assets/testtxt"), content);
|
|
WriteFile(GetTestPath("assets/testtxt2"), content);
|
|
WriteFile(GetTestPath("assets/test.txt"), content);
|
|
WriteFile(GetTestPath("assets/test.hello.txt"), content);
|
|
WriteFile(GetTestPath("assets/test.hello.xml"), content);
|
|
|
|
const std::string out_apk = GetTestPath("out.apk");
|
|
std::vector<std::string> link_args = {
|
|
"--manifest", GetDefaultManifest(),
|
|
"-o", out_apk,
|
|
"-0", ".txt",
|
|
"-0", "txt2",
|
|
"-0", ".hello.txt",
|
|
"-0", "hello.xml",
|
|
"-A", GetTestPath("assets")
|
|
};
|
|
|
|
ASSERT_TRUE(Link(link_args, &diag));
|
|
|
|
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
|
|
ASSERT_THAT(apk, Ne(nullptr));
|
|
io::IFileCollection* zip = apk->GetFileCollection();
|
|
ASSERT_THAT(zip, Ne(nullptr));
|
|
|
|
auto file = zip->FindFile("assets/testtxt");
|
|
ASSERT_THAT(file, Ne(nullptr));
|
|
EXPECT_TRUE(file->WasCompressed());
|
|
|
|
file = zip->FindFile("assets/testtxt2");
|
|
ASSERT_THAT(file, Ne(nullptr));
|
|
EXPECT_FALSE(file->WasCompressed());
|
|
|
|
file = zip->FindFile("assets/test.txt");
|
|
ASSERT_THAT(file, Ne(nullptr));
|
|
EXPECT_FALSE(file->WasCompressed());
|
|
|
|
file = zip->FindFile("assets/test.hello.txt");
|
|
ASSERT_THAT(file, Ne(nullptr));
|
|
EXPECT_FALSE(file->WasCompressed());
|
|
|
|
file = zip->FindFile("assets/test.hello.xml");
|
|
ASSERT_THAT(file, Ne(nullptr));
|
|
EXPECT_FALSE(file->WasCompressed());
|
|
}
|
|
|
|
TEST_F(LinkTest, NoCompressResources) {
|
|
StdErrDiagnostics diag;
|
|
std::string content(500, 'a');
|
|
const std::string compiled_files_dir = GetTestPath("compiled");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/raw/testtxt"), content, compiled_files_dir, &diag));
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test.txt"), content, compiled_files_dir, &diag));
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test1.hello.txt"), content, compiled_files_dir,
|
|
&diag));
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test2.goodbye.xml"), content, compiled_files_dir,
|
|
&diag));
|
|
|
|
const std::string out_apk = GetTestPath("out.apk");
|
|
std::vector<std::string> link_args = {
|
|
"--manifest", GetDefaultManifest(),
|
|
"-o", out_apk,
|
|
"-0", ".txt",
|
|
"-0", ".hello.txt",
|
|
"-0", "goodbye.xml",
|
|
};
|
|
|
|
ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
|
|
|
|
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
|
|
ASSERT_THAT(apk, Ne(nullptr));
|
|
io::IFileCollection* zip = apk->GetFileCollection();
|
|
ASSERT_THAT(zip, Ne(nullptr));
|
|
|
|
auto file = zip->FindFile("res/raw/testtxt");
|
|
ASSERT_THAT(file, Ne(nullptr));
|
|
EXPECT_TRUE(file->WasCompressed());
|
|
|
|
file = zip->FindFile("res/raw/test.txt");
|
|
ASSERT_THAT(file, Ne(nullptr));
|
|
EXPECT_FALSE(file->WasCompressed());
|
|
|
|
file = zip->FindFile("res/raw/test1.hello.hello.txt");
|
|
ASSERT_THAT(file, Ne(nullptr));
|
|
EXPECT_FALSE(file->WasCompressed());
|
|
|
|
file = zip->FindFile("res/raw/test2.goodbye.goodbye.xml");
|
|
ASSERT_THAT(file, Ne(nullptr));
|
|
EXPECT_FALSE(file->WasCompressed());
|
|
}
|
|
|
|
TEST_F(LinkTest, OverlayStyles) {
|
|
StdErrDiagnostics diag;
|
|
const std::string compiled_files_dir = GetTestPath("compiled");
|
|
const std::string override_files_dir = GetTestPath("compiled-override");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
|
|
R"(<resources>
|
|
<style name="MyStyle">
|
|
<item name="android:textColor">#123</item>
|
|
</style>
|
|
</resources>)",
|
|
compiled_files_dir, &diag));
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"),
|
|
R"(<resources>
|
|
<style name="MyStyle">
|
|
<item name="android:background">#456</item>
|
|
</style>
|
|
</resources>)",
|
|
override_files_dir, &diag));
|
|
|
|
|
|
const std::string out_apk = GetTestPath("out.apk");
|
|
std::vector<std::string> link_args = {
|
|
"--manifest", GetDefaultManifest(kDefaultPackageName),
|
|
"-o", out_apk,
|
|
};
|
|
const auto override_files = file::FindFiles(override_files_dir, &diag);
|
|
for (const auto &override_file : override_files.value()) {
|
|
link_args.push_back("-R");
|
|
link_args.push_back(file::BuildPath({override_files_dir, override_file}));
|
|
}
|
|
ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
|
|
|
|
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
|
|
ASSERT_THAT(apk, Ne(nullptr));
|
|
|
|
const Style* actual_style = test::GetValue<Style>(
|
|
apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle");
|
|
ASSERT_NE(actual_style, nullptr);
|
|
ASSERT_EQ(actual_style->entries.size(), 2);
|
|
EXPECT_EQ(actual_style->entries[0].key.id, 0x01010098); // android:textColor
|
|
EXPECT_EQ(actual_style->entries[1].key.id, 0x010100d4); // android:background
|
|
}
|
|
|
|
TEST_F(LinkTest, OverrideStylesInsteadOfOverlaying) {
|
|
StdErrDiagnostics diag;
|
|
const std::string compiled_files_dir = GetTestPath("compiled");
|
|
const std::string override_files_dir = GetTestPath("compiled-override");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
|
|
R"(<resources>
|
|
<style name="MyStyle">
|
|
<item name="android:textColor">#123</item>
|
|
</style>
|
|
</resources>)",
|
|
compiled_files_dir, &diag));
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"),
|
|
R"(<resources>
|
|
<style name="MyStyle">
|
|
<item name="android:background">#456</item>
|
|
</style>
|
|
</resources>)",
|
|
override_files_dir, &diag));
|
|
|
|
|
|
const std::string out_apk = GetTestPath("out.apk");
|
|
std::vector<std::string> link_args = {
|
|
"--manifest", GetDefaultManifest(kDefaultPackageName),
|
|
"--override-styles-instead-of-overlaying",
|
|
"-o", out_apk,
|
|
};
|
|
const auto override_files = file::FindFiles(override_files_dir, &diag);
|
|
for (const auto &override_file : override_files.value()) {
|
|
link_args.push_back("-R");
|
|
link_args.push_back(file::BuildPath({override_files_dir, override_file}));
|
|
}
|
|
ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
|
|
|
|
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
|
|
ASSERT_THAT(apk, Ne(nullptr));
|
|
|
|
const Style* actual_style = test::GetValue<Style>(
|
|
apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle");
|
|
ASSERT_NE(actual_style, nullptr);
|
|
ASSERT_EQ(actual_style->entries.size(), 1);
|
|
EXPECT_EQ(actual_style->entries[0].key.id, 0x010100d4); // android:background
|
|
}
|
|
|
|
TEST_F(LinkTest, AppInfoWithUsesSplit) {
|
|
StdErrDiagnostics diag;
|
|
const std::string base_files_dir = GetTestPath("base");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
|
|
R"(<resources>
|
|
<string name="bar">bar</string>
|
|
</resources>)",
|
|
base_files_dir, &diag));
|
|
const std::string base_apk = GetTestPath("base.apk");
|
|
std::vector<std::string> link_args = {
|
|
"--manifest", GetDefaultManifest("com.aapt2.app"),
|
|
"-o", base_apk,
|
|
};
|
|
ASSERT_TRUE(Link(link_args, base_files_dir, &diag));
|
|
|
|
const std::string feature_manifest = GetTestPath("feature_manifest.xml");
|
|
WriteFile(feature_manifest, android::base::StringPrintf(R"(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="com.aapt2.app" split="feature1">
|
|
</manifest>)"));
|
|
const std::string feature_files_dir = GetTestPath("feature");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
|
|
R"(<resources>
|
|
<string name="foo">foo</string>
|
|
</resources>)",
|
|
feature_files_dir, &diag));
|
|
const std::string feature_apk = GetTestPath("feature.apk");
|
|
const std::string feature_package_id = "0x80";
|
|
link_args = {
|
|
"--manifest", feature_manifest,
|
|
"-I", base_apk,
|
|
"--package-id", feature_package_id,
|
|
"-o", feature_apk,
|
|
};
|
|
ASSERT_TRUE(Link(link_args, feature_files_dir, &diag));
|
|
|
|
const std::string feature2_manifest = GetTestPath("feature2_manifest.xml");
|
|
WriteFile(feature2_manifest, android::base::StringPrintf(R"(
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
package="com.aapt2.app" split="feature2">
|
|
<uses-split android:name="feature1"/>
|
|
</manifest>)"));
|
|
const std::string feature2_files_dir = GetTestPath("feature2");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
|
|
R"(<resources>
|
|
<string-array name="string_array">
|
|
<item>@string/bar</item>
|
|
<item>@string/foo</item>
|
|
</string-array>
|
|
</resources>)",
|
|
feature2_files_dir, &diag));
|
|
const std::string feature2_apk = GetTestPath("feature2.apk");
|
|
const std::string feature2_package_id = "0x81";
|
|
link_args = {
|
|
"--manifest", feature2_manifest,
|
|
"-I", base_apk,
|
|
"-I", feature_apk,
|
|
"--package-id", feature2_package_id,
|
|
"-o", feature2_apk,
|
|
};
|
|
ASSERT_TRUE(Link(link_args, feature2_files_dir, &diag));
|
|
}
|
|
|
|
TEST_F(LinkTest, SharedLibraryAttributeRJava) {
|
|
StdErrDiagnostics diag;
|
|
const std::string lib_values =
|
|
R"(<resources>
|
|
<attr name="foo"/>
|
|
<public type="attr" name="foo" id="0x00010001"/>
|
|
<declare-styleable name="LibraryStyleable">
|
|
<attr name="foo" />
|
|
</declare-styleable>
|
|
</resources>)";
|
|
|
|
const std::string client_values =
|
|
R"(<resources>
|
|
<attr name="bar" />
|
|
<declare-styleable name="ClientStyleable">
|
|
<attr name="com.example.lib:foo" />
|
|
<attr name="bar" />
|
|
</declare-styleable>
|
|
</resources>)";
|
|
|
|
// Build a library with a public attribute
|
|
const std::string lib_res = GetTestPath("library-res");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), lib_values, lib_res, &diag));
|
|
|
|
const std::string lib_apk = GetTestPath("library.apk");
|
|
const std::string lib_java = GetTestPath("library_java");
|
|
// clang-format off
|
|
auto lib_manifest = ManifestBuilder(this)
|
|
.SetPackageName("com.example.lib")
|
|
.Build();
|
|
|
|
auto lib_link_args = LinkCommandBuilder(this)
|
|
.SetManifestFile(lib_manifest)
|
|
.AddFlag("--shared-lib")
|
|
.AddParameter("--java", lib_java)
|
|
.AddCompiledResDir(lib_res, &diag)
|
|
.Build(lib_apk);
|
|
// clang-format on
|
|
ASSERT_TRUE(Link(lib_link_args, &diag));
|
|
|
|
const std::string lib_r_java = lib_java + "/com/example/lib/R.java";
|
|
std::string lib_r_contents;
|
|
ASSERT_TRUE(android::base::ReadFileToString(lib_r_java, &lib_r_contents));
|
|
EXPECT_THAT(lib_r_contents, HasSubstr(" public static int foo=0x00010001;"));
|
|
EXPECT_THAT(lib_r_contents, HasSubstr(" com.example.lib.R.attr.foo"));
|
|
|
|
// Build a client that uses the library attribute in a declare-styleable
|
|
const std::string client_res = GetTestPath("client-res");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), client_values, client_res, &diag));
|
|
|
|
const std::string client_apk = GetTestPath("client.apk");
|
|
const std::string client_java = GetTestPath("client_java");
|
|
// clang-format off
|
|
auto client_manifest = ManifestBuilder(this)
|
|
.SetPackageName("com.example.client")
|
|
.Build();
|
|
|
|
auto client_link_args = LinkCommandBuilder(this)
|
|
.SetManifestFile(client_manifest)
|
|
.AddParameter("--java", client_java)
|
|
.AddParameter("-I", lib_apk)
|
|
.AddCompiledResDir(client_res, &diag)
|
|
.Build(client_apk);
|
|
// clang-format on
|
|
ASSERT_TRUE(Link(client_link_args, &diag));
|
|
|
|
const std::string client_r_java = client_java + "/com/example/client/R.java";
|
|
std::string client_r_contents;
|
|
ASSERT_TRUE(android::base::ReadFileToString(client_r_java, &client_r_contents));
|
|
EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000"));
|
|
}
|
|
|
|
struct SourceXML {
|
|
std::string res_file_path;
|
|
std::string file_contents;
|
|
};
|
|
|
|
static void BuildApk(const std::vector<SourceXML>& source_files, const std::string& apk_path,
|
|
LinkCommandBuilder&& link_args, CommandTestFixture* fixture,
|
|
IDiagnostics* diag) {
|
|
TemporaryDir res_dir;
|
|
TemporaryDir compiled_res_dir;
|
|
for (auto& source_file : source_files) {
|
|
ASSERT_TRUE(fixture->CompileFile(res_dir.path + source_file.res_file_path,
|
|
source_file.file_contents, compiled_res_dir.path, diag));
|
|
}
|
|
ASSERT_TRUE(fixture->Link(
|
|
link_args.AddCompiledResDir(compiled_res_dir.path, diag).Build(apk_path), diag));
|
|
}
|
|
|
|
static void BuildSDK(const std::vector<SourceXML>& source_files, const std::string& apk_path,
|
|
const std::string& java_root_path, CommandTestFixture* fixture,
|
|
IDiagnostics* diag) {
|
|
auto android_manifest = ManifestBuilder(fixture).SetPackageName("android").Build();
|
|
|
|
auto android_link_args = LinkCommandBuilder(fixture)
|
|
.SetManifestFile(android_manifest)
|
|
.AddParameter("--private-symbols", "com.android.internal")
|
|
.AddParameter("--java", java_root_path);
|
|
|
|
BuildApk(source_files, apk_path, std::move(android_link_args), fixture, diag);
|
|
}
|
|
|
|
static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& java_path,
|
|
CommandTestFixture* fixture, IDiagnostics* diag) {
|
|
const std::string android_values =
|
|
R"(<resources>
|
|
<public type="attr" name="finalized_res" id="0x01010001"/>
|
|
|
|
<!-- S staged attributes (support staged resources in the same type id) -->
|
|
<staging-public-group type="attr" first-id="0x01010050">
|
|
<public name="staged_s_res" />
|
|
</staging-public-group>
|
|
|
|
<staging-public-group type="string" first-id="0x01fd0080">
|
|
<public name="staged_s_string" />
|
|
</staging-public-group>
|
|
|
|
<!-- SV2 staged attributes (support staged resources in a separate type id) -->
|
|
<staging-public-group type="attr" first-id="0x01ff0049">
|
|
<public name="staged_s2_res" />
|
|
</staging-public-group>
|
|
|
|
<!-- T staged attributes (support staged resources in multiple separate type ids) -->
|
|
<staging-public-group type="attr" first-id="0x01fe0063">
|
|
<public name="staged_t_res" />
|
|
</staging-public-group>
|
|
|
|
<attr name="finalized_res" />
|
|
<attr name="staged_s_res" />
|
|
<attr name="staged_s2_res" />
|
|
<attr name="staged_t_res" />
|
|
<string name="staged_s_string">Hello</string>
|
|
</resources>)";
|
|
|
|
SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
|
|
BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
|
|
}
|
|
|
|
static void BuildFinalizedSDK(const std::string& apk_path, const std::string& java_path,
|
|
CommandTestFixture* fixture, IDiagnostics* diag) {
|
|
const std::string android_values =
|
|
R"(<resources>
|
|
<public type="attr" name="finalized_res" id="0x01010001"/>
|
|
<public type="attr" name="staged_s_res" id="0x01010002"/>
|
|
<public type="attr" name="staged_s2_res" id="0x01010003"/>
|
|
<public type="string" name="staged_s_string" id="0x01020000"/>
|
|
|
|
<!-- S staged attributes (support staged resources in the same type id) -->
|
|
<staging-public-group-final type="attr" first-id="0x01010050">
|
|
<public name="staged_s_res" />
|
|
</staging-public-group-final>
|
|
|
|
<staging-public-group-final type="string" first-id="0x01fd0080">
|
|
<public name="staged_s_string" />
|
|
</staging-public-group-final>
|
|
|
|
<!-- SV2 staged attributes (support staged resources in a separate type id) -->
|
|
<staging-public-group-final type="attr" first-id="0x01ff0049">
|
|
<public name="staged_s2_res" />
|
|
</staging-public-group-final>
|
|
|
|
<!-- T staged attributes (support staged resources in multiple separate type ids) -->
|
|
<staging-public-group type="attr" first-id="0x01fe0063">
|
|
<public name="staged_t_res" />
|
|
</staging-public-group>
|
|
|
|
<attr name="finalized_res" />
|
|
<attr name="staged_s_res" />
|
|
<attr name="staged_s2_res" />
|
|
<attr name="staged_t_res" />
|
|
<string name="staged_s_string">Hello</string>
|
|
</resources>)";
|
|
|
|
SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
|
|
BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
|
|
}
|
|
|
|
static void BuildAppAgainstSDK(const std::string& apk_path, const std::string& java_path,
|
|
const std::string& sdk_path, CommandTestFixture* fixture,
|
|
IDiagnostics* diag) {
|
|
const std::string app_values =
|
|
R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
|
<attr name="bar" />
|
|
<style name="MyStyle">
|
|
<item name="android:staged_s_res">@android:string/staged_s_string</item>
|
|
</style>
|
|
<declare-styleable name="ClientStyleable">
|
|
<attr name="android:finalized_res" />
|
|
<attr name="android:staged_s_res" />
|
|
<attr name="bar" />
|
|
</declare-styleable>
|
|
<public name="MyStyle" type="style" id="0x7f020000" />
|
|
</resources>)";
|
|
|
|
SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = app_values};
|
|
|
|
auto app_manifest = ManifestBuilder(fixture).SetPackageName("com.example.app").Build();
|
|
|
|
auto app_link_args = LinkCommandBuilder(fixture)
|
|
.SetManifestFile(app_manifest)
|
|
.AddParameter("--java", java_path)
|
|
.AddParameter("-I", sdk_path);
|
|
|
|
BuildApk({source_xml}, apk_path, std::move(app_link_args), fixture, diag);
|
|
}
|
|
|
|
TEST_F(LinkTest, StagedAndroidApi) {
|
|
StdErrDiagnostics diag;
|
|
const std::string android_apk = GetTestPath("android.apk");
|
|
const std::string android_java = GetTestPath("android-java");
|
|
BuildNonFinalizedSDK(android_apk, android_java, this, &diag);
|
|
|
|
const std::string android_r_java = android_java + "/android/R.java";
|
|
std::string android_r_contents;
|
|
ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents));
|
|
EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
|
|
EXPECT_THAT(
|
|
android_r_contents,
|
|
HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }"));
|
|
EXPECT_THAT(
|
|
android_r_contents,
|
|
HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }"));
|
|
EXPECT_THAT(
|
|
android_r_contents,
|
|
HasSubstr("public static final int staged_s2_res; static { staged_s2_res=0x01ff0049; }"));
|
|
EXPECT_THAT(
|
|
android_r_contents,
|
|
HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
|
|
|
|
const std::string app_apk = GetTestPath("app.apk");
|
|
const std::string app_java = GetTestPath("app-java");
|
|
BuildAppAgainstSDK(app_apk, app_java, android_apk, this, &diag);
|
|
|
|
const std::string client_r_java = app_java + "/com/example/app/R.java";
|
|
std::string client_r_contents;
|
|
ASSERT_TRUE(android::base::ReadFileToString(client_r_java, &client_r_contents));
|
|
EXPECT_THAT(client_r_contents, HasSubstr(" 0x01010001, android.R.attr.staged_s_res, 0x7f010000"));
|
|
|
|
// Test that the resource ids of staged and non-staged resource can be retrieved
|
|
android::AssetManager2 am;
|
|
auto android_asset = android::ApkAssets::Load(android_apk);
|
|
ASSERT_THAT(android_asset, NotNull());
|
|
ASSERT_TRUE(am.SetApkAssets({android_asset.get()}));
|
|
|
|
auto result = am.GetResourceId("android:attr/finalized_res");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_THAT(*result, Eq(0x01010001));
|
|
|
|
result = am.GetResourceId("android:attr/staged_s_res");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_THAT(*result, Eq(0x01010050));
|
|
|
|
result = am.GetResourceId("android:string/staged_s_string");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_THAT(*result, Eq(0x01fd0080));
|
|
|
|
result = am.GetResourceId("android:attr/staged_s2_res");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_THAT(*result, Eq(0x01ff0049));
|
|
|
|
result = am.GetResourceId("android:attr/staged_t_res");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_THAT(*result, Eq(0x01fe0063));
|
|
}
|
|
|
|
TEST_F(LinkTest, FinalizedAndroidApi) {
|
|
StdErrDiagnostics diag;
|
|
const std::string android_apk = GetTestPath("android.apk");
|
|
const std::string android_java = GetTestPath("android-java");
|
|
BuildFinalizedSDK(android_apk, android_java, this, &diag);
|
|
|
|
const std::string android_r_java = android_java + "/android/R.java";
|
|
std::string android_r_contents;
|
|
ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents));
|
|
EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
|
|
EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_res=0x01010002;"));
|
|
EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_string=0x01020000;"));
|
|
EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s2_res=0x01010003;"));
|
|
EXPECT_THAT(
|
|
android_r_contents,
|
|
HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
|
|
;
|
|
|
|
// Build an application against the non-finalized SDK and then load it into an AssetManager with
|
|
// the finalized SDK.
|
|
const std::string non_finalized_android_apk = GetTestPath("non-finalized-android.apk");
|
|
const std::string non_finalized_android_java = GetTestPath("non-finalized-android-java");
|
|
BuildNonFinalizedSDK(non_finalized_android_apk, non_finalized_android_java, this, &diag);
|
|
|
|
const std::string app_apk = GetTestPath("app.apk");
|
|
const std::string app_java = GetTestPath("app-java");
|
|
BuildAppAgainstSDK(app_apk, app_java, non_finalized_android_apk, this, &diag);
|
|
|
|
android::AssetManager2 am;
|
|
auto android_asset = android::ApkAssets::Load(android_apk);
|
|
auto app_against_non_final = android::ApkAssets::Load(app_apk);
|
|
ASSERT_THAT(android_asset, NotNull());
|
|
ASSERT_THAT(app_against_non_final, NotNull());
|
|
ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()}));
|
|
|
|
auto result = am.GetResourceId("android:attr/finalized_res");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_THAT(*result, Eq(0x01010001));
|
|
|
|
result = am.GetResourceId("android:attr/staged_s_res");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_THAT(*result, Eq(0x01010002));
|
|
|
|
result = am.GetResourceId("android:string/staged_s_string");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_THAT(*result, Eq(0x01020000));
|
|
|
|
result = am.GetResourceId("android:attr/staged_s2_res");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_THAT(*result, Eq(0x01010003));
|
|
|
|
{
|
|
auto style = am.GetBag(0x7f020000);
|
|
ASSERT_TRUE(style.has_value());
|
|
|
|
auto& entry = (*style)->entries[0];
|
|
EXPECT_THAT(entry.key, Eq(0x01010002));
|
|
EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
|
|
EXPECT_THAT(entry.value.data, Eq(0x01020000));
|
|
}
|
|
|
|
// Re-compile the application against the finalized SDK and then load it into an AssetManager with
|
|
// the finalized SDK.
|
|
const std::string app_apk_respin = GetTestPath("app-respin.apk");
|
|
const std::string app_java_respin = GetTestPath("app-respin-java");
|
|
BuildAppAgainstSDK(app_apk_respin, app_java_respin, android_apk, this, &diag);
|
|
|
|
auto app_against_final = android::ApkAssets::Load(app_apk_respin);
|
|
ASSERT_THAT(app_against_final, NotNull());
|
|
ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()}));
|
|
|
|
{
|
|
auto style = am.GetBag(0x7f020000);
|
|
ASSERT_TRUE(style.has_value());
|
|
|
|
auto& entry = (*style)->entries[0];
|
|
EXPECT_THAT(entry.key, Eq(0x01010002));
|
|
EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
|
|
EXPECT_THAT(entry.value.data, Eq(0x01020000));
|
|
}
|
|
}
|
|
|
|
TEST_F(LinkTest, MacroSubstitution) {
|
|
StdErrDiagnostics diag;
|
|
const std::string values =
|
|
R"(<resources xmlns:an="http://schemas.android.com/apk/res/android">
|
|
<macro name="is_enabled">true</macro>
|
|
<macro name="deep_is_enabled">@macro/is_enabled</macro>
|
|
<macro name="attr_ref">?is_enabled_attr</macro>
|
|
<macro name="raw_string">Hello World!</macro>
|
|
<macro name="android_ref">@an:color/primary_text_dark</macro>
|
|
|
|
<attr name="is_enabled_attr" />
|
|
<public type="attr" name="is_enabled_attr" id="0x7f010000"/>
|
|
|
|
<string name="is_enabled_str">@macro/is_enabled</string>
|
|
<bool name="is_enabled_bool">@macro/deep_is_enabled</bool>
|
|
|
|
<array name="my_array">
|
|
<item>@macro/is_enabled</item>
|
|
</array>
|
|
|
|
<style name="MyStyle">
|
|
<item name="android:background">@macro/attr_ref</item>
|
|
<item name="android:fontFamily">@macro/raw_string</item>
|
|
</style>
|
|
</resources>)";
|
|
|
|
const std::string xml_values =
|
|
R"(<SomeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
android:background="@macro/android_ref"
|
|
android:fontFamily="@macro/raw_string">
|
|
</SomeLayout>)";
|
|
|
|
// Build a library with a public attribute
|
|
const std::string lib_res = GetTestPath("test-res");
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), values, lib_res, &diag));
|
|
ASSERT_TRUE(CompileFile(GetTestPath("res/layout/layout.xml"), xml_values, lib_res, &diag));
|
|
|
|
const std::string lib_apk = GetTestPath("test.apk");
|
|
// clang-format off
|
|
auto lib_link_args = LinkCommandBuilder(this)
|
|
.SetManifestFile(ManifestBuilder(this).SetPackageName("com.test").Build())
|
|
.AddCompiledResDir(lib_res, &diag)
|
|
.AddFlag("--no-auto-version")
|
|
.Build(lib_apk);
|
|
// clang-format on
|
|
ASSERT_TRUE(Link(lib_link_args, &diag));
|
|
|
|
auto apk = LoadedApk::LoadApkFromPath(lib_apk, &diag);
|
|
ASSERT_THAT(apk, NotNull());
|
|
|
|
// Test that the type flags determines the value type
|
|
auto actual_bool =
|
|
test::GetValue<BinaryPrimitive>(apk->GetResourceTable(), "com.test:bool/is_enabled_bool");
|
|
ASSERT_THAT(actual_bool, NotNull());
|
|
EXPECT_EQ(android::Res_value::TYPE_INT_BOOLEAN, actual_bool->value.dataType);
|
|
EXPECT_EQ(0xffffffffu, actual_bool->value.data);
|
|
|
|
auto actual_str =
|
|
test::GetValue<String>(apk->GetResourceTable(), "com.test:string/is_enabled_str");
|
|
ASSERT_THAT(actual_str, NotNull());
|
|
EXPECT_EQ(*actual_str->value, "true");
|
|
|
|
// Test nested data structures
|
|
auto actual_array = test::GetValue<Array>(apk->GetResourceTable(), "com.test:array/my_array");
|
|
ASSERT_THAT(actual_array, NotNull());
|
|
EXPECT_THAT(actual_array->elements.size(), Eq(1));
|
|
|
|
auto array_el_ref = ValueCast<BinaryPrimitive>(actual_array->elements[0].get());
|
|
ASSERT_THAT(array_el_ref, NotNull());
|
|
EXPECT_THAT(array_el_ref->value.dataType, Eq(android::Res_value::TYPE_INT_BOOLEAN));
|
|
EXPECT_THAT(array_el_ref->value.data, Eq(0xffffffffu));
|
|
|
|
auto actual_style = test::GetValue<Style>(apk->GetResourceTable(), "com.test:style/MyStyle");
|
|
ASSERT_THAT(actual_style, NotNull());
|
|
EXPECT_THAT(actual_style->entries.size(), Eq(2));
|
|
|
|
{
|
|
auto style_el = ValueCast<Reference>(actual_style->entries[0].value.get());
|
|
ASSERT_THAT(style_el, NotNull());
|
|
EXPECT_THAT(style_el->reference_type, Eq(Reference::Type::kAttribute));
|
|
EXPECT_THAT(style_el->id, Eq(0x7f010000));
|
|
}
|
|
|
|
{
|
|
auto style_el = ValueCast<String>(actual_style->entries[1].value.get());
|
|
ASSERT_THAT(style_el, NotNull());
|
|
EXPECT_THAT(*style_el->value, Eq("Hello World!"));
|
|
}
|
|
|
|
// Test substitution in compiled xml files
|
|
auto xml = apk->LoadXml("res/layout/layout.xml", &diag);
|
|
ASSERT_THAT(xml, NotNull());
|
|
|
|
auto& xml_attrs = xml->root->attributes;
|
|
ASSERT_THAT(xml_attrs.size(), Eq(2));
|
|
|
|
auto attr_value = ValueCast<Reference>(xml_attrs[0].compiled_value.get());
|
|
ASSERT_THAT(attr_value, NotNull());
|
|
EXPECT_THAT(attr_value->reference_type, Eq(Reference::Type::kResource));
|
|
EXPECT_THAT(attr_value->id, Eq(0x01060001));
|
|
|
|
EXPECT_THAT(xml_attrs[1].compiled_value.get(), IsNull());
|
|
EXPECT_THAT(xml_attrs[1].value, Eq("Hello World!"));
|
|
}
|
|
|
|
} // namespace aapt
|