Add staging-public-group to aapt2
staging-public-group is a tag for putting resources that have been
added during platform development, but have not yet been finalized,
into a separate resource id namespace.
R.java fields of staged resources are non-final, so when the SDK is
finalized, applications using the android R.java will automatically
use the new finalized resource id without having to recompile.
Staged resources can exist either in the same type id as the type's
non-staged counterpart or in a separate type id. Multiple
staging-public-group tags each with a different type id can exist
simultaneously, which allows for multiple versions of the platform
to be developed at once.
Bug: 183411093
Test: aapt2_tests
Change-Id: Ibb6c84c3626751e33c6097f35a03e306bb85616a
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 27cbe88..dfdac6b 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -25,6 +25,7 @@
using testing::Eq;
using testing::HasSubstr;
using testing::Ne;
+using testing::NotNull;
namespace aapt {
@@ -400,4 +401,127 @@
EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000"));
}
+TEST_F(LinkTest, StagedAndroidApi) {
+ StdErrDiagnostics 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>
+
+ <!-- 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>
+
+ <staging-public-group type="string" first-id="0x01fd0072">
+ <public name="staged_t_string" />
+ </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_t_string">Hello</string>
+ </resources>)";
+
+ const std::string app_values =
+ R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <attr name="bar" />
+ <declare-styleable name="ClientStyleable">
+ <attr name="android:finalized_res" />
+ <attr name="android:staged_s_res" />
+ <attr name="bar" />
+ </declare-styleable>
+ </resources>)";
+
+ const std::string android_res = GetTestPath("android-res");
+ ASSERT_TRUE(
+ CompileFile(GetTestPath("res/values/values.xml"), android_values, android_res, &diag));
+
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android_java");
+ // clang-format off
+ auto android_manifest = ManifestBuilder(this)
+ .SetPackageName("android")
+ .Build();
+
+ auto android_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(android_manifest)
+ .AddParameter("--private-symbols", "com.android.internal")
+ .AddParameter("--java", android_java)
+ .AddCompiledResDir(android_res, &diag)
+ .Build(android_apk);
+ // clang-format on
+ ASSERT_TRUE(Link(android_link_args, &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 int staged_s_res=0x01010050;"));
+ EXPECT_THAT(android_r_contents, HasSubstr(" public static int staged_s2_res=0x01ff0049;"));
+ EXPECT_THAT(android_r_contents, HasSubstr(" public static int staged_t_res=0x01fe0063;"));
+ EXPECT_THAT(android_r_contents, HasSubstr(" public static int staged_t_string=0x01fd0072;"));
+
+ // Build an app that uses the framework attribute in a declare-styleable
+ const std::string client_res = GetTestPath("app-res");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), app_values, client_res, &diag));
+
+ const std::string app_apk = GetTestPath("app.apk");
+ const std::string app_java = GetTestPath("app_java");
+ // clang-format off
+ auto app_manifest = ManifestBuilder(this)
+ .SetPackageName("com.example.app")
+ .Build();
+
+ auto app_link_args = LinkCommandBuilder(this)
+ .SetManifestFile(app_manifest)
+ .AddParameter("--java", app_java)
+ .AddParameter("-I", android_apk)
+ .AddCompiledResDir(client_res, &diag)
+ .Build(app_apk);
+ // clang-format on
+ ASSERT_TRUE(Link(app_link_args, &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: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));
+
+ result = am.GetResourceId("android:string/staged_t_string");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01fd0072));
+}
+
} // namespace aapt