| /* |
| * 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 "Diagnostics.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(android::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, |
| android::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, |
| android::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, android::IDiagnostics* diag) { |
| const std::string android_values = |
| R"(<resources> |
| <public type="attr" name="finalized_res" id="0x01010001"/> |
| |
| <!-- S staged attributes (Not support staged resources in the same type id) --> |
| <staging-public-group type="attr" first-id="0x01fc0050"> |
| <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, android::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 (Not support staged resources in the same type id) --> |
| <staging-public-group-final type="attr" first-id="0x01fc0050"> |
| <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, |
| android::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=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"(<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!")); |
| } |
| |
| TEST_F(LinkTest, LocaleConfigVerification) { |
| StdErrDiagnostics diag; |
| const std::string compiled_files_dir = GetTestPath("compiled"); |
| |
| // Normal case |
| ASSERT_TRUE(CompileFile(GetTestPath("res/xml/locales_config.xml"), R"( |
| <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> |
| <locale android:name="en-US"/> |
| <locale android:name="pt"/> |
| <locale android:name="es-419"/> |
| <locale android:name="zh-Hans-SG"/> |
| </locale-config>)", |
| compiled_files_dir, &diag)); |
| |
| const std::string localeconfig_manifest = GetTestPath("localeconfig_manifest.xml"); |
| WriteFile(localeconfig_manifest, android::base::StringPrintf(R"( |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="com.aapt2.app"> |
| |
| <application |
| android:localeConfig="@xml/locales_config"> |
| </application> |
| </manifest>)")); |
| |
| const std::string out_apk = GetTestPath("out.apk"); |
| |
| auto link_args = LinkCommandBuilder(this) |
| .SetManifestFile(localeconfig_manifest) |
| .AddCompiledResDir(compiled_files_dir, &diag) |
| .Build(out_apk); |
| ASSERT_TRUE(Link(link_args, &diag)); |
| |
| // Empty locale list |
| ASSERT_TRUE(CompileFile(GetTestPath("res/xml/empty_locales_config.xml"), R"( |
| <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> |
| </locale-config>)", |
| compiled_files_dir, &diag)); |
| |
| const std::string empty_localeconfig_manifest = GetTestPath("empty_localeconfig_manifest.xml"); |
| WriteFile(empty_localeconfig_manifest, android::base::StringPrintf(R"( |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="com.aapt2.app"> |
| |
| <application |
| android:localeConfig="@xml/empty_locales_config"> |
| </application> |
| </manifest>)")); |
| |
| auto link1_args = LinkCommandBuilder(this) |
| .SetManifestFile(empty_localeconfig_manifest) |
| .AddCompiledResDir(compiled_files_dir, &diag) |
| .Build(out_apk); |
| ASSERT_TRUE(Link(link1_args, &diag)); |
| } |
| |
| TEST_F(LinkTest, LocaleConfigVerificationExternalSymbol) { |
| StdErrDiagnostics diag; |
| const std::string base_files_dir = GetTestPath("base"); |
| ASSERT_TRUE(CompileFile(GetTestPath("res/xml/locales_config.xml"), R"( |
| <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> |
| <locale android:name="en-US"/> |
| <locale android:name="pt"/> |
| <locale android:name="es-419"/> |
| <locale android:name="zh-Hans-SG"/> |
| </locale-config>)", |
| 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 localeconfig_manifest = GetTestPath("localeconfig_manifest.xml"); |
| const std::string out_apk = GetTestPath("out.apk"); |
| WriteFile(localeconfig_manifest, android::base::StringPrintf(R"( |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="com.aapt2.app"> |
| |
| <application |
| android:localeConfig="@xml/locales_config"> |
| </application> |
| </manifest>)")); |
| link_args = LinkCommandBuilder(this) |
| .SetManifestFile(localeconfig_manifest) |
| .AddParameter("-I", base_apk) |
| .Build(out_apk); |
| ASSERT_TRUE(Link(link_args, &diag)); |
| } |
| |
| TEST_F(LinkTest, LocaleConfigWrongTag) { |
| StdErrDiagnostics diag; |
| const std::string compiled_files_dir = GetTestPath("compiled"); |
| |
| // Invalid element: locale1-config |
| ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_locale_config.xml"), R"( |
| <locale1-config xmlns:android="http://schemas.android.com/apk/res/android"> |
| <locale android:name="en-US"/> |
| <locale android:name="pt"/> |
| <locale android:name="es-419"/> |
| <locale android:name="zh-Hans-SG"/> |
| </locale1-config>)", |
| compiled_files_dir, &diag)); |
| |
| const std::string locale1config_manifest = GetTestPath("locale1config_manifest.xml"); |
| WriteFile(locale1config_manifest, android::base::StringPrintf(R"( |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="com.aapt2.app"> |
| |
| <application |
| android:localeConfig="@xml/wrong_locale_config"> |
| </application> |
| </manifest>)")); |
| |
| const std::string out_apk = GetTestPath("out.apk"); |
| auto link_args = LinkCommandBuilder(this) |
| .SetManifestFile(locale1config_manifest) |
| .AddCompiledResDir(compiled_files_dir, &diag) |
| .Build(out_apk); |
| ASSERT_FALSE(Link(link_args, &diag)); |
| |
| // Invalid element: locale1 |
| ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_locale.xml"), R"( |
| <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> |
| <locale1 android:name="en-US"/> |
| <locale android:name="pt"/> |
| <locale android:name="es-419"/> |
| <locale android:name="zh-Hans-SG"/> |
| </locale-config>)", |
| compiled_files_dir, &diag)); |
| |
| const std::string locale1_manifest = GetTestPath("locale1_manifest.xml"); |
| WriteFile(locale1_manifest, android::base::StringPrintf(R"( |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="com.aapt2.app"> |
| |
| <application |
| android:localeConfig="@xml/wrong_locale"> |
| </application> |
| </manifest>)")); |
| |
| auto link1_args = LinkCommandBuilder(this) |
| .SetManifestFile(locale1_manifest) |
| .AddCompiledResDir(compiled_files_dir, &diag) |
| .Build(out_apk); |
| ASSERT_FALSE(Link(link1_args, &diag)); |
| |
| // Invalid attribute: android:name1 |
| ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_attribute.xml"), R"( |
| <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> |
| <locale android:name1="en-US"/> |
| <locale android:name="pt"/> |
| <locale android:name="es-419"/> |
| <locale android:name="zh-Hans-SG"/> |
| </locale-config>)", |
| compiled_files_dir, &diag)); |
| |
| const std::string wrong_attribute_manifest = GetTestPath("wrong_attribute_manifest.xml"); |
| WriteFile(wrong_attribute_manifest, android::base::StringPrintf(R"( |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="com.aapt2.app"> |
| |
| <application |
| android:localeConfig="@xml/wrong_attribute"> |
| </application> |
| </manifest>)")); |
| |
| auto link2_args = LinkCommandBuilder(this) |
| .SetManifestFile(wrong_attribute_manifest) |
| .AddCompiledResDir(compiled_files_dir, &diag) |
| .Build(out_apk); |
| ASSERT_FALSE(Link(link2_args, &diag)); |
| } |
| |
| TEST_F(LinkTest, LocaleConfigWrongLocaleFormat) { |
| StdErrDiagnostics diag; |
| const std::string compiled_files_dir = GetTestPath("compiled"); |
| |
| // Invalid locale: en-U |
| ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_locale.xml"), R"( |
| <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> |
| <locale android:name="en-U"/> |
| <locale android:name="pt"/> |
| <locale android:name="es-419"/> |
| <locale android:name="zh-Hans-SG"/> |
| </locale-config>)", |
| compiled_files_dir, &diag)); |
| |
| const std::string wrong_locale_manifest = GetTestPath("wrong_locale_manifest.xml"); |
| WriteFile(wrong_locale_manifest, android::base::StringPrintf(R"( |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="com.aapt2.app"> |
| |
| <application |
| android:localeConfig="@xml/wrong_locale"> |
| </application> |
| </manifest>)")); |
| |
| const std::string out_apk = GetTestPath("out.apk"); |
| auto link_args = LinkCommandBuilder(this) |
| .SetManifestFile(wrong_locale_manifest) |
| .AddCompiledResDir(compiled_files_dir, &diag) |
| .Build(out_apk); |
| ASSERT_FALSE(Link(link_args, &diag)); |
| } |
| |
| } // namespace aapt |