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