Export monolithic hidden API files from platform_bootclasspath

Makes the monolithic hidden API files accessible from the
platform_bootclasspath so they can be output to the dist build target
and used by other modules, e.g. by doing something like this:
  java_resources: [
    ":platform-bootclasspath{hiddenapi-flags.csv}",
  ],

It makes the paths relative to the out/soong/hiddenapi directory rather
than the out/soong directory to make them easier to use in the
java_resources property without changing the structure of the APK.
Without that attempting to use them in a java_resources property will
result in them being copied to a hiddenapi/ within the APK instead of
being used at the top level as existing APKs like
CtsHiddenApiBlocklistTestApiTestCases expect.

Bug: 177892522
Test: m nothing
Change-Id: I829412fc7d25411e0c2e0713d0d219a18f4af2ee
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index 641e19f..37c394a 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -101,11 +101,15 @@
 // yet been created.
 func hiddenAPISingletonPaths(ctx android.PathContext) hiddenAPISingletonPathsStruct {
 	return ctx.Config().Once(hiddenAPISingletonPathsKey, func() interface{} {
+		// Make the paths relative to the out/soong/hiddenapi directory instead of to the out/soong/
+		// directory. This ensures that if they are used as java_resources they do not end up in a
+		// hiddenapi directory in the resulting APK.
+		hiddenapiDir := android.PathForOutput(ctx, "hiddenapi")
 		return hiddenAPISingletonPathsStruct{
-			flags:     android.PathForOutput(ctx, "hiddenapi", "hiddenapi-flags.csv"),
-			index:     android.PathForOutput(ctx, "hiddenapi", "hiddenapi-index.csv"),
-			metadata:  android.PathForOutput(ctx, "hiddenapi", "hiddenapi-unsupported.csv"),
-			stubFlags: android.PathForOutput(ctx, "hiddenapi", "hiddenapi-stub-flags.txt"),
+			flags:     hiddenapiDir.Join(ctx, "hiddenapi-flags.csv"),
+			index:     hiddenapiDir.Join(ctx, "hiddenapi-index.csv"),
+			metadata:  hiddenapiDir.Join(ctx, "hiddenapi-unsupported.csv"),
+			stubFlags: hiddenapiDir.Join(ctx, "hiddenapi-stub-flags.txt"),
 		}
 	}).(hiddenAPISingletonPathsStruct)
 }
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index e292d80..86ab708 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -15,6 +15,8 @@
 package java
 
 import (
+	"fmt"
+
 	"android/soong/android"
 	"android/soong/dexpreopt"
 	"github.com/google/blueprint"
@@ -69,6 +71,15 @@
 	//
 	// Currently only for testing.
 	fragments []android.Module
+
+	// Path to the monolithic hiddenapi-flags.csv file.
+	hiddenAPIFlagsCSV android.Path
+
+	// Path to the monolithic hiddenapi-index.csv file.
+	hiddenAPIIndexCSV android.Path
+
+	// Path to the monolithic hiddenapi-unsupported.csv file.
+	hiddenAPIMetadataCSV android.Path
 }
 
 // ApexVariantReference specifies a particular apex variant of a module.
@@ -98,6 +109,34 @@
 	return m
 }
 
+var _ android.OutputFileProducer = (*platformBootclasspathModule)(nil)
+
+// A minimal AndroidMkEntries is needed in order to support the dists property.
+func (b *platformBootclasspathModule) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{
+		{
+			Class: "FAKE",
+			// Need at least one output file in order for this to take effect.
+			OutputFile: android.OptionalPathForPath(b.hiddenAPIFlagsCSV),
+			Include:    "$(BUILD_PHONY_PACKAGE)",
+		},
+	}
+}
+
+// Make the hidden API files available from the platform-bootclasspath module.
+func (b *platformBootclasspathModule) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "hiddenapi-flags.csv":
+		return android.Paths{b.hiddenAPIFlagsCSV}, nil
+	case "hiddenapi-index.csv":
+		return android.Paths{b.hiddenAPIIndexCSV}, nil
+	case "hiddenapi-metadata.csv":
+		return android.Paths{b.hiddenAPIMetadataCSV}, nil
+	}
+
+	return nil, fmt.Errorf("unknown tag %s", tag)
+}
+
 func (b *platformBootclasspathModule) DepsMutator(ctx android.BottomUpMutatorContext) {
 	if SkipDexpreoptBootJars(ctx) {
 		return
@@ -222,6 +261,17 @@
 // generateHiddenAPIBuildActions generates all the hidden API related build rules.
 func (b *platformBootclasspathModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, modules []android.Module) {
 
+	// Save the paths to the monolithic files for retrieval via OutputFiles()
+	// Make the paths relative to the out/soong/hiddenapi directory instead of to the out/soong/
+	// directory. This ensures that if they are used as java_resources they do not end up in a
+	// hiddenapi directory in the resulting APK.
+	relToHiddenapiDir := func(path android.OutputPath) android.Path {
+		return path
+	}
+	b.hiddenAPIFlagsCSV = relToHiddenapiDir(hiddenAPISingletonPaths(ctx).flags)
+	b.hiddenAPIIndexCSV = relToHiddenapiDir(hiddenAPISingletonPaths(ctx).index)
+	b.hiddenAPIMetadataCSV = relToHiddenapiDir(hiddenAPISingletonPaths(ctx).metadata)
+
 	moduleSpecificFlagsPaths := android.Paths{}
 	for _, module := range modules {
 		if h, ok := module.(hiddenAPIIntf); ok {
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index ebbe3a5..a0d0501 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -131,3 +131,44 @@
 		})
 	})
 }
+
+func TestPlatformBootclasspath_Dist(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		dexpreopt.FixtureSetBootJars("platform:foo", "platform:bar"),
+		android.PrepareForTestWithAndroidMk,
+		android.FixtureWithRootAndroidBp(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+				dists: [
+					{
+						targets: ["droidcore"],
+						tag: "hiddenapi-flags.csv",
+					},
+				],
+			}
+
+			java_library {
+				name: "bar",
+				srcs: ["a.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+			}
+
+			java_library {
+				name: "foo",
+				srcs: ["a.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+			}
+		`),
+	).RunTest(t)
+
+	platformBootclasspath := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, platformBootclasspath)
+	goals := entries[0].GetDistForGoals(platformBootclasspath)
+	android.AssertStringEquals(t, "platform dist goals phony", ".PHONY: droidcore\n", goals[0])
+	android.AssertStringEquals(t, "platform dist goals call", "$(call dist-for-goals,droidcore,out/soong/hiddenapi/hiddenapi-flags.csv:hiddenapi-flags.csv)\n", android.StringRelativeToTop(result.Config, goals[1]))
+}