/*
* 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 "Diagnostics.h"
#include "LoadedApk.h"
#include "android-base/file.h"
#include "android-base/stringprintf.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"( )",
compiled_files_dir, &diag));
const std::string out_apk = GetTestPath("out.apk");
std::vector 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 apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
ASSERT_THAT(apk, Ne(nullptr));
std::unique_ptr 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"( )",
compiled_files_dir, &diag));
const std::string out_apk = GetTestPath("out.apk");
std::vector 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 apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
ASSERT_THAT(apk, Ne(nullptr));
std::unique_ptr 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(android::util::GetString(tree.getStrings(), static_cast(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 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 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 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 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"(
)",
compiled_files_dir, &diag));
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"),
R"(
)",
override_files_dir, &diag));
const std::string out_apk = GetTestPath("out.apk");
std::vector 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 apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
ASSERT_THAT(apk, Ne(nullptr));
const Style* actual_style = test::GetValue
)",
compiled_files_dir, &diag));
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"),
R"(
)",
override_files_dir, &diag));
const std::string out_apk = GetTestPath("out.apk");
std::vector 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 apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
ASSERT_THAT(apk, Ne(nullptr));
const Style* actual_style = test::GetValue
)";
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=0x01fc0050; }"));
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}));
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(0x01fc0050));
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, app_against_non_final}));
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, app_against_final}));
{
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"(
true
@macro/is_enabled
?is_enabled_attr
Hello World!
@an:color/primary_text_dark
@macro/is_enabled
@macro/deep_is_enabled
- @macro/is_enabled
)";
const std::string xml_values =
R"(
)";
// 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(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(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(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(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